Skip to content

Commit dc7e539

Browse files
committed
feat(isolated-declarations): improve inferring the return type from function
1 parent e73991c commit dc7e539

File tree

5 files changed

+134
-51
lines changed

5 files changed

+134
-51
lines changed

crates/oxc_isolated_declarations/src/class.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ impl<'a> IsolatedDeclarations<'a> {
265265
}
266266
}
267267

268-
let mut inferred_accessor_type: FxHashMap<Atom<'a>, Box<'a, TSTypeAnnotation<'a>>> =
268+
let mut inferred_accessor_types: FxHashMap<Atom<'a>, Box<'a, TSTypeAnnotation<'a>>> =
269269
FxHashMap::default();
270270

271271
// Infer get accessor return type from set accessor
@@ -282,7 +282,7 @@ impl<'a> IsolatedDeclarations<'a> {
282282
continue;
283283
};
284284
let name = self.ast.new_atom(&name);
285-
if inferred_accessor_type.contains_key(&name) {
285+
if inferred_accessor_types.contains_key(&name) {
286286
// We've inferred that accessor type already
287287
continue;
288288
}
@@ -291,7 +291,7 @@ impl<'a> IsolatedDeclarations<'a> {
291291
MethodDefinitionKind::Get => {
292292
let return_type = self.infer_function_return_type(function);
293293
if let Some(return_type) = return_type {
294-
inferred_accessor_type.insert(name, self.ast.copy(&return_type));
294+
inferred_accessor_types.insert(name, self.ast.copy(&return_type));
295295
}
296296
}
297297
MethodDefinitionKind::Set => {
@@ -305,7 +305,7 @@ impl<'a> IsolatedDeclarations<'a> {
305305
|t| Some(self.ast.copy(t)),
306306
);
307307
if let Some(type_annotation) = type_annotation {
308-
inferred_accessor_type.insert(name, type_annotation);
308+
inferred_accessor_types.insert(name, type_annotation);
309309
}
310310
}
311311
}
@@ -338,7 +338,7 @@ impl<'a> IsolatedDeclarations<'a> {
338338
|n| {
339339
self.transform_set_accessor_params(
340340
&function.params,
341-
inferred_accessor_type
341+
inferred_accessor_types
342342
.get(&self.ast.new_atom(&n))
343343
.map(|t| self.ast.copy(t)),
344344
)
@@ -368,7 +368,7 @@ impl<'a> IsolatedDeclarations<'a> {
368368
}
369369
MethodDefinitionKind::Get => {
370370
let rt = method.key.static_name().and_then(|name| {
371-
inferred_accessor_type
371+
inferred_accessor_types
372372
.get(&self.ast.new_atom(&name))
373373
.map(|t| self.ast.copy(t))
374374
});

crates/oxc_isolated_declarations/src/declaration.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,12 @@ impl<'a> IsolatedDeclarations<'a> {
7575
}
7676
if init.is_none() && binding_type.is_none() {
7777
binding_type = Some(self.ast.ts_unknown_keyword(SPAN));
78-
self.error(
79-
OxcDiagnostic::error("Variable must have an explicit type annotation with --isolatedDeclarations.")
80-
.with_label(decl.id.span()),
81-
);
78+
if !decl.init.as_ref().is_some_and(Expression::is_function) {
79+
self.error(
80+
OxcDiagnostic::error("Variable must have an explicit type annotation with --isolatedDeclarations.")
81+
.with_label(decl.id.span()),
82+
);
83+
}
8284
}
8385
}
8486
let id = binding_type.map_or_else(

crates/oxc_isolated_declarations/src/return_type.rs

+81-41
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,38 @@ use oxc_ast::{
55
},
66
AstBuilder, Visit,
77
};
8-
use oxc_span::{Atom, GetSpan};
8+
use oxc_span::{Atom, GetSpan, SPAN};
99
use oxc_syntax::scope::ScopeFlags;
1010

1111
use crate::{diagnostics::type_containing_private_name, IsolatedDeclarations};
1212

13-
/// Infer return type from return statement. Does not support multiple return statements.
13+
/// Infer return type from return statement.
14+
/// ```ts
15+
/// function foo() {
16+
/// return 1;
17+
/// }
18+
/// // inferred type is number
19+
///
20+
/// function bar() {
21+
/// if (true) {
22+
/// return;
23+
/// }
24+
/// return 1;
25+
/// }
26+
/// // inferred type is number | undefined
27+
///
28+
/// function baz() {
29+
/// if (true) {
30+
/// return null;
31+
/// }
32+
/// return 1;
33+
/// }
34+
/// // We can't infer return type if there are multiple return statements with different types
35+
/// ```
36+
#[allow(clippy::option_option)]
1437
pub struct FunctionReturnType<'a> {
1538
ast: AstBuilder<'a>,
16-
return_expression: Option<Expression<'a>>,
39+
return_expression: Option<Option<Expression<'a>>>,
1740
value_bindings: Vec<Atom<'a>>,
1841
type_bindings: Vec<Atom<'a>>,
1942
return_statement_count: u8,
@@ -36,48 +59,57 @@ impl<'a> FunctionReturnType<'a> {
3659

3760
visitor.visit_function_body(body);
3861

39-
if visitor.return_statement_count > 1 {
40-
return None;
41-
}
42-
43-
visitor.return_expression.and_then(|expr| {
44-
let expr_type = transformer.infer_type_from_expression(&expr)?;
62+
let expr = visitor.return_expression??;
63+
let Some(mut expr_type) = transformer.infer_type_from_expression(&expr) else {
64+
// Avoid report error in parent function
65+
return if expr.is_function() {
66+
Some(transformer.ast.ts_unknown_keyword(SPAN))
67+
} else {
68+
None
69+
};
70+
};
4571

46-
if let Some((reference_name, is_value)) = match &expr_type {
47-
TSType::TSTypeReference(type_reference) => {
48-
if let TSTypeName::IdentifierReference(ident) = &type_reference.type_name {
49-
Some((ident.name.clone(), false))
50-
} else {
51-
None
52-
}
53-
}
54-
TSType::TSTypeQuery(query) => {
55-
if let TSTypeQueryExprName::IdentifierReference(ident) = &query.expr_name {
56-
Some((ident.name.clone(), true))
57-
} else {
58-
None
59-
}
72+
if let Some((reference_name, is_value)) = match &expr_type {
73+
TSType::TSTypeReference(type_reference) => {
74+
if let TSTypeName::IdentifierReference(ident) = &type_reference.type_name {
75+
Some((ident.name.clone(), false))
76+
} else {
77+
None
6078
}
61-
_ => None,
62-
} {
63-
let is_defined_in_current_scope = if is_value {
64-
visitor.value_bindings.contains(&reference_name)
79+
}
80+
TSType::TSTypeQuery(query) => {
81+
if let TSTypeQueryExprName::IdentifierReference(ident) = &query.expr_name {
82+
Some((ident.name.clone(), true))
6583
} else {
66-
visitor.type_bindings.contains(&reference_name)
67-
};
68-
69-
if is_defined_in_current_scope {
70-
transformer.error(type_containing_private_name(
71-
&reference_name,
72-
expr_type
73-
.get_identifier_reference()
74-
.map_or_else(|| expr_type.span(), |ident| ident.span),
75-
));
84+
None
7685
}
7786
}
87+
_ => None,
88+
} {
89+
let is_defined_in_current_scope = if is_value {
90+
visitor.value_bindings.contains(&reference_name)
91+
} else {
92+
visitor.type_bindings.contains(&reference_name)
93+
};
7894

79-
Some(expr_type)
80-
})
95+
if is_defined_in_current_scope {
96+
transformer.error(type_containing_private_name(
97+
&reference_name,
98+
expr_type
99+
.get_identifier_reference()
100+
.map_or_else(|| expr_type.span(), |ident| ident.span),
101+
));
102+
}
103+
}
104+
105+
//
106+
if visitor.return_statement_count > 1 {
107+
let types = transformer
108+
.ast
109+
.new_vec_from_iter([expr_type, transformer.ast.ts_undefined_keyword(SPAN)]);
110+
expr_type = transformer.ast.ts_union_type(SPAN, types);
111+
}
112+
Some(expr_type)
81113
}
82114
}
83115

@@ -109,8 +141,16 @@ impl<'a> Visit<'a> for FunctionReturnType<'a> {
109141
fn visit_return_statement(&mut self, stmt: &ReturnStatement<'a>) {
110142
self.return_statement_count += 1;
111143
if self.return_statement_count > 1 {
112-
return;
144+
if let Some(expr) = &self.return_expression {
145+
// if last return statement is not empty, we can't infer return type
146+
if expr.is_some() {
147+
self.return_expression = None;
148+
return;
149+
}
150+
} else {
151+
return;
152+
}
113153
}
114-
self.return_expression = self.ast.copy(&stmt.argument);
154+
self.return_expression = Some(self.ast.copy(&stmt.argument));
115155
}
116156
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
function foo() {
2+
return 1;
3+
}
4+
// inferred type is number
5+
6+
function bar() {
7+
if (true) {
8+
return;
9+
}
10+
return 1;
11+
}
12+
// inferred type is number | undefined
13+
14+
function baz() {
15+
if (true) {
16+
return null;
17+
}
18+
return 1;
19+
}
20+
// We can't infer return type if there are multiple return statements with different types
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
source: crates/oxc_isolated_declarations/tests/mod.rs
3+
input_file: crates/oxc_isolated_declarations/tests/fixtures/infer-return-type.ts
4+
---
5+
==================== .D.TS ====================
6+
7+
declare function foo(): number;
8+
declare function bar(): ((number) | (undefined));
9+
declare function baz();
10+
11+
12+
==================== Errors ====================
13+
14+
x Function must have an explicit return type annotation with
15+
| --isolatedDeclarations.
16+
,-[14:10]
17+
13 |
18+
14 | function baz() {
19+
: ^^^
20+
15 | if (true) {
21+
`----

0 commit comments

Comments
 (0)