feat(isolated-declarations): inferring set accessor parameter type from get accessor return type (#3725)

This commit is contained in:
Dunqing 2024-06-17 15:47:23 +00:00
parent 4bce59df68
commit 81e952632d
5 changed files with 183 additions and 73 deletions

View file

@ -2943,6 +2943,9 @@ impl MethodDefinitionKind {
pub fn is_set(&self) -> bool {
matches!(self, Self::Set)
}
pub fn is_get(&self) -> bool {
matches!(self, Self::Get)
}
pub fn scope_flags(self) -> ScopeFlags {
match self {

View file

@ -2,7 +2,8 @@
use oxc_ast::ast::*;
use oxc_allocator::Box;
use oxc_span::{GetSpan, SPAN};
use oxc_span::{Atom, GetSpan, SPAN};
use rustc_hash::FxHashMap;
use crate::{
diagnostics::{
@ -98,8 +99,10 @@ impl<'a> IsolatedDeclarations<'a> {
&self,
definition: &MethodDefinition<'a>,
params: Box<'a, FormalParameters<'a>>,
return_type: Option<Box<'a, TSTypeAnnotation<'a>>>,
) -> ClassElement<'a> {
let function = &definition.value;
if definition.accessibility.is_some_and(|a| a.is_private()) {
let r#type = match definition.r#type {
MethodDefinitionType::MethodDefinition => {
@ -117,22 +120,6 @@ impl<'a> IsolatedDeclarations<'a> {
);
}
let type_annotation = self.infer_function_return_type(function);
if type_annotation.is_none() {
match definition.kind {
MethodDefinitionKind::Method => {
self.error(method_must_have_explicit_return_type(definition.key.span()));
}
MethodDefinitionKind::Get => {
self.error(accessor_must_have_explicit_return_type(definition.key.span()));
}
MethodDefinitionKind::Constructor | MethodDefinitionKind::Set => {}
}
}
// TODO: Infer the parameter type of the `set` method from the `get` method
let value = self.ast.function(
FunctionType::TSEmptyBodyFunctionExpression,
function.span,
@ -143,9 +130,10 @@ impl<'a> IsolatedDeclarations<'a> {
params,
None,
self.ast.copy(&function.type_parameters),
type_annotation,
return_type,
Modifiers::empty(),
);
self.ast.class_method(
definition.r#type,
definition.span,
@ -234,22 +222,72 @@ impl<'a> IsolatedDeclarations<'a> {
let mut elements = self.ast.new_vec();
let mut has_private_key = false;
let mut accessor_return_types: FxHashMap<&Atom<'a>, Option<Box<'a, TSTypeAnnotation<'a>>>> =
FxHashMap::default();
// Transform get accessor first, and collect return type.
// The return type will be used to infer the type of the set accessor.
for element in &decl.body.body {
match element {
ClassElement::StaticBlock(_) => {}
ClassElement::MethodDefinition(definition) => {
if self.report_property_key(&definition.key, definition.computed) {
if let ClassElement::MethodDefinition(method) = element {
if method.key.is_private_identifier() {
has_private_key = true;
continue;
}
if self.report_property_key(&method.key, method.computed) {
continue;
}
if method.kind.is_get() {
if let PropertyKey::StaticIdentifier(ident) = &method.key {
let function = &method.value;
let params = self.transform_formal_parameters(&function.params);
let return_type = self.infer_function_return_type(function);
if return_type.is_none() {
self.error(accessor_must_have_explicit_return_type(method.key.span()));
}
accessor_return_types.insert(&ident.name, self.ast.copy(&return_type));
let element =
self.transform_class_method_definition(method, params, return_type);
elements.push(element);
continue;
}
if definition.key.is_private_identifier() {
}
}
elements.push(self.ast.copy(element));
}
let mut new_elements = self.ast.new_vec();
for element in elements.drain(..) {
match element {
ClassElement::StaticBlock(_) => {}
ClassElement::MethodDefinition(ref method) => {
// Transformed in the first loop
if method.kind.is_get() {
new_elements.push(element);
continue;
}
if method.key.is_private_identifier() {
has_private_key = true;
continue;
}
if self.report_property_key(&method.key, method.computed) {
continue;
}
let function = &method.value;
let params = if method.kind.is_set() {
if let PropertyKey::StaticIdentifier(ident) = &method.key {
self.transform_set_accessor_params(
&function.params,
accessor_return_types.remove(&ident.name).unwrap_or_default(),
)
} else {
self.transform_formal_parameters(&function.params)
}
} else {
self.transform_formal_parameters(&function.params)
};
let function = &definition.value;
let params = self.transform_formal_parameters(&function.params);
if definition.kind.is_constructor() {
if let MethodDefinitionKind::Constructor = method.kind {
for (index, param) in function.params.items.iter().enumerate() {
if param.accessibility.is_some() {
// transformed params will definitely have type annotation
@ -261,14 +299,30 @@ impl<'a> IsolatedDeclarations<'a> {
type_annotation,
)
{
elements.push(new_element);
new_elements.push(new_element);
}
}
}
}
let new_element = self.transform_class_method_definition(definition, params);
elements.push(new_element);
let return_type = match method.kind {
MethodDefinitionKind::Method => {
let rt = self.infer_function_return_type(function);
if rt.is_none() {
self.error(method_must_have_explicit_return_type(
method.key.span(),
));
}
rt
}
MethodDefinitionKind::Set | MethodDefinitionKind::Constructor => None,
MethodDefinitionKind::Get => {
unreachable!("get accessor should be transformed in the first loop")
}
};
let new_element =
self.transform_class_method_definition(method, params, return_type);
new_elements.push(new_element);
}
ClassElement::PropertyDefinition(property) => {
if self.report_property_key(&property.key, property.computed) {
@ -278,7 +332,7 @@ impl<'a> IsolatedDeclarations<'a> {
if property.key.is_private_identifier() {
has_private_key = true;
} else {
elements.push(self.transform_class_property_definition(property));
new_elements.push(self.transform_class_property_definition(&property));
}
}
ClassElement::AccessorProperty(property) => {
@ -301,9 +355,9 @@ impl<'a> IsolatedDeclarations<'a> {
property.r#static,
self.ast.new_vec(),
);
elements.push(new_element);
new_elements.push(new_element);
}
ClassElement::TSIndexSignature(_) => elements.push(self.ast.copy(element)),
ClassElement::TSIndexSignature(_) => new_elements.push(element),
}
}
@ -316,15 +370,15 @@ impl<'a> IsolatedDeclarations<'a> {
.property_key_private_identifier(PrivateIdentifier::new(SPAN, "private".into()));
let r#type = PropertyDefinitionType::PropertyDefinition;
let decorators = self.ast.new_vec();
let new_element = self.ast.class_property(
let element = self.ast.class_property(
r#type, SPAN, ident, None, false, false, false, false, false, false, false, None,
None, decorators,
);
elements.insert(0, new_element);
new_elements.insert(0, element);
}
let body = self.ast.class_body(decl.body.span, elements);
let body = self.ast.class_body(decl.body.span, new_elements);
let mut modifiers = self.modifiers_declare();
if decl.modifiers.is_contains_abstract() {
@ -344,4 +398,39 @@ impl<'a> IsolatedDeclarations<'a> {
modifiers,
))
}
pub fn transform_set_accessor_params(
&self,
params: &Box<'a, FormalParameters<'a>>,
type_annotation: Option<Box<'a, TSTypeAnnotation<'a>>>,
) -> Box<'a, FormalParameters<'a>> {
let items = &params.items;
if items.first().map_or(true, |item| item.pattern.type_annotation.is_none()) {
let kind = items.first().map_or_else(
|| {
self.ast.binding_pattern_identifier(BindingIdentifier::new(
SPAN,
self.ast.new_atom("value"),
))
},
|item| self.ast.copy(&item.pattern.kind),
);
self.create_formal_parameters(kind, type_annotation)
} else {
self.transform_formal_parameters(params)
}
}
pub fn create_formal_parameters(
&self,
kind: BindingPatternKind<'a>,
type_annotation: Option<Box<'a, TSTypeAnnotation<'a>>>,
) -> Box<'a, FormalParameters<'a>> {
let pattern = BindingPattern { kind, type_annotation, optional: false };
let parameter =
self.ast.formal_parameter(SPAN, pattern, None, false, false, self.ast.new_vec());
let items = self.ast.new_vec_single(parameter);
self.ast.formal_parameters(SPAN, FormalParameterKind::Signature, items, None)
}
}

View file

@ -77,9 +77,21 @@ pub fn inferred_type_of_expression(span: Span) -> OxcDiagnostic {
.with_label(span)
}
// Inference from class expressions is not supported with --isolatedDeclarations.
pub fn inferred_type_of_class_expression(span: Span) -> OxcDiagnostic {
OxcDiagnostic::error("Class expression type can't be inferred with --isolatedDeclarations.")
.with_label(span)
}
pub fn parameter_must_have_explicit_type(span: Span) -> OxcDiagnostic {
OxcDiagnostic::error(
"Parameter must have an explicit type annotation with --isolatedDeclarations.",
)
.with_label(span)
}
pub fn implicitly_adding_undefined_to_type(span: Span) -> OxcDiagnostic {
OxcDiagnostic::error(
"Declaration emit for this parameter requires implicitly adding undefined to it's type. This is not supported with --isolatedDeclarations.",
)
.with_label(span)
}

View file

@ -3,10 +3,15 @@ use oxc_ast::ast::*;
use oxc_allocator::Box;
use oxc_ast::ast::Function;
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::{Span, SPAN};
use crate::{diagnostics::function_must_have_explicit_return_type, IsolatedDeclarations};
use crate::{
diagnostics::{
function_must_have_explicit_return_type, implicitly_adding_undefined_to_type,
parameter_must_have_explicit_type,
},
IsolatedDeclarations,
};
impl<'a> IsolatedDeclarations<'a> {
pub fn transform_function(&mut self, func: &Function<'a>) -> Option<Box<'a, Function<'a>>> {
@ -52,35 +57,40 @@ impl<'a> IsolatedDeclarations<'a> {
next_param.map_or(true, |next_param| next_param.pattern.optional);
let type_annotation = pattern
.type_annotation
.as_ref()
.map(|type_annotation| self.ast.copy(&type_annotation.type_annotation))
.or_else(|| {
// report error for has no type annotation
let new_type = self
.infer_type_from_formal_parameter(param)
.unwrap_or_else(|| self.ast.ts_unknown_keyword(param.span));
Some(new_type)
})
.map(|ts_type| {
// jf next param is not optional and current param is assignment pattern
// we need to add undefined to it's type
if !is_next_param_optional {
if matches!(ts_type, TSType::TSTypeReference(_)) {
self.error(
OxcDiagnostic::error("Declaration emit for this parameter requires implicitly adding undefined to it's type. This is not supported with --isolatedDeclarations.")
.with_label(param.span),
);
} else if !ts_type.is_maybe_undefined() {
// union with undefined
return self.ast.ts_type_annotation(SPAN,
self.ast.ts_union_type(SPAN, self.ast.new_vec_from_iter([ts_type, self.ast.ts_undefined_keyword(SPAN)]))
);
}
}
.type_annotation
.as_ref()
.map(|type_annotation| self.ast.copy(&type_annotation.type_annotation))
.or_else(|| {
// report error for has no type annotation
let new_type = self.infer_type_from_formal_parameter(param);
if new_type.is_none() {
self.error(parameter_must_have_explicit_type(param.span));
}
new_type
})
.map(|ts_type| {
// jf next param is not optional and current param is assignment pattern
// we need to add undefined to it's type
if !is_next_param_optional {
if matches!(ts_type, TSType::TSTypeReference(_)) {
self.error(implicitly_adding_undefined_to_type(param.span));
} else if !ts_type.is_maybe_undefined() {
// union with undefined
return self.ast.ts_type_annotation(
SPAN,
self.ast.ts_union_type(
SPAN,
self.ast.new_vec_from_iter([
ts_type,
self.ast.ts_undefined_keyword(SPAN),
]),
),
);
}
}
self.ast.ts_type_annotation(SPAN, ts_type)
});
self.ast.ts_type_annotation(SPAN, ts_type)
});
pattern = self.ast.binding_pattern(
self.ast.copy(&pattern.kind),
@ -115,9 +125,7 @@ impl<'a> IsolatedDeclarations<'a> {
if let Some(rest) = &params.rest {
if rest.argument.type_annotation.is_none() {
self.error(OxcDiagnostic::error(
"Parameter must have an explicit type annotation with --isolatedDeclarations.",
).with_label(rest.span));
self.error(parameter_must_have_explicit_type(rest.span));
}
}

View file

@ -178,8 +178,6 @@ impl<'a> Visit<'a> for ScopeTree<'a> {
walk_declaration(self, declaration);
}
// // TODO: handle ts infer and ts mapped type
// ==================== TSTypeParameter ====================
/// ```ts