Skip to content

Commit 236deaa

Browse files
authored
fix(core): return type inference no longer picks up nested returns (#7106)
1 parent 29fcb05 commit 236deaa

6 files changed

Lines changed: 138 additions & 84 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed [#6985](https://github.com/biomejs/biome/issues/6985): Inference of return types no longer mistakenly picks up return types of nested functions.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* should not generate diagnostics */
2+
3+
function doSth() {
4+
const innerFn = () => {
5+
return Promise.resolve(1);
6+
}
7+
return 'hah'
8+
}
9+
10+
doSth()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: issue6985Valid.ts
4+
---
5+
# Input
6+
```ts
7+
/* should not generate diagnostics */
8+
9+
function doSth() {
10+
const innerFn = () => {
11+
return Promise.resolve(1);
12+
}
13+
return 'hah'
14+
}
15+
16+
doSth()
17+
18+
```

crates/biome_js_type_info/src/local_inference.rs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,24 @@ use std::str::FromStr;
33

44
use biome_js_syntax::{
55
AnyJsArrayBindingPatternElement, AnyJsArrayElement, AnyJsArrowFunctionParameters,
6-
AnyJsBindingPattern, AnyJsCallArgument, AnyJsClass, AnyJsClassMember, AnyJsDeclaration,
6+
AnyJsBindingPattern, AnyJsCallArgument, AnyJsClassMember, AnyJsDeclaration,
77
AnyJsDeclarationClause, AnyJsExportDefaultDeclaration, AnyJsExpression, AnyJsFormalParameter,
8-
AnyJsFunctionBody, AnyJsLiteralExpression, AnyJsName, AnyJsObjectBindingPatternMember,
9-
AnyJsObjectMember, AnyJsObjectMemberName, AnyJsParameter, AnyTsModuleName, AnyTsName,
10-
AnyTsReturnType, AnyTsTupleTypeElement, AnyTsType, AnyTsTypeMember,
8+
AnyJsFunction, AnyJsFunctionBody, AnyJsLiteralExpression, AnyJsName,
9+
AnyJsObjectBindingPatternMember, AnyJsObjectMember, AnyJsObjectMemberName, AnyJsParameter,
10+
AnyTsModuleName, AnyTsName, AnyTsReturnType, AnyTsTupleTypeElement, AnyTsType, AnyTsTypeMember,
1111
AnyTsTypePredicateParameterName, ClassMemberName, JsArrayBindingPattern,
1212
JsArrowFunctionExpression, JsBinaryExpression, JsBinaryOperator, JsCallArguments,
1313
JsClassDeclaration, JsClassExportDefaultDeclaration, JsClassExpression, JsForInStatement,
1414
JsForOfStatement, JsForVariableDeclaration, JsFormalParameter, JsFunctionBody,
15-
JsFunctionDeclaration, JsFunctionExpression, JsGetterObjectMember, JsLogicalExpression,
16-
JsLogicalOperator, JsMethodObjectMember, JsNewExpression, JsObjectBindingPattern,
17-
JsObjectExpression, JsParameters, JsReferenceIdentifier, JsReturnStatement,
18-
JsSetterObjectMember, JsSyntaxNode, JsSyntaxToken, JsUnaryExpression, JsUnaryOperator,
19-
JsVariableDeclaration, JsVariableDeclarator, TsDeclareFunctionDeclaration,
20-
TsExternalModuleDeclaration, TsInterfaceDeclaration, TsModuleDeclaration, TsReferenceType,
21-
TsReturnTypeAnnotation, TsTypeAliasDeclaration, TsTypeAnnotation, TsTypeArguments, TsTypeList,
22-
TsTypeParameter, TsTypeParameters, TsTypeofType, inner_string_text, unescape_js_string,
15+
JsFunctionDeclaration, JsFunctionExpression, JsGetterObjectMember, JsInitializerClause,
16+
JsLogicalExpression, JsLogicalOperator, JsMethodObjectMember, JsNewExpression,
17+
JsObjectBindingPattern, JsObjectExpression, JsParameters, JsPropertyClassMember,
18+
JsPropertyObjectMember, JsReferenceIdentifier, JsReturnStatement, JsSetterObjectMember,
19+
JsSyntaxNode, JsSyntaxToken, JsUnaryExpression, JsUnaryOperator, JsVariableDeclaration,
20+
JsVariableDeclarator, TsDeclareFunctionDeclaration, TsExternalModuleDeclaration,
21+
TsInterfaceDeclaration, TsModuleDeclaration, TsReferenceType, TsReturnTypeAnnotation,
22+
TsTypeAliasDeclaration, TsTypeAnnotation, TsTypeArguments, TsTypeList, TsTypeParameter,
23+
TsTypeParameters, TsTypeofType, inner_string_text, unescape_js_string,
2324
};
2425
use biome_rowan::{AstNode, SyntaxResult, Text, TokenText};
2526

@@ -2292,16 +2293,16 @@ fn is_direct_class_or_object_member(node: &JsSyntaxNode) -> bool {
22922293
) {
22932294
None
22942295
} else {
2295-
Some(matches!(
2296-
node,
2297-
AnyJsExpression::JsObjectExpression(_)
2298-
| AnyJsExpression::JsClassExpression(_)
2299-
))
2296+
Some(false)
23002297
}
2301-
} else if AnyJsClass::can_cast(node.kind()) {
2302-
Some(true)
23032298
} else {
2304-
None
2299+
Some(
2300+
JsInitializerClause::can_cast(node.kind())
2301+
&& node
2302+
.parent()
2303+
.is_some_and(|parent| JsPropertyClassMember::can_cast(parent.kind()))
2304+
|| JsPropertyObjectMember::can_cast(node.kind()),
2305+
)
23052306
}
23062307
})
23072308
.unwrap_or_default()
@@ -2548,7 +2549,7 @@ fn type_from_function_body(
25482549
) -> TypeData {
25492550
let mut return_types: Vec<_> = body
25502551
.syntax()
2551-
.descendants()
2552+
.pruned_descendents(|node| !AnyJsFunction::can_cast(node.kind()))
25522553
.filter_map(JsReturnStatement::cast)
25532554
.map(|return_statement| {
25542555
return_statement.argument().map_or(

crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_wrong_scope.snap

Lines changed: 41 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -71,151 +71,129 @@ Imports {
7171
## Registered types
7272

7373
```
74-
Module TypeId(0) => instanceof Module(0) TypeId(37)
74+
Module TypeId(0) => value: foo
7575
76-
Module TypeId(1) => Module(0) TypeId(4)
76+
Module TypeId(1) => unknown
7777
7878
Module TypeId(2) => sync Function {
7979
accepts: {
8080
params: []
8181
type_args: []
8282
}
83-
returns: Module(0) TypeId(1)
83+
returns: Module(0) TypeId(20)
8484
}
8585
86-
Module TypeId(3) => Module(0) TypeId(2)
86+
Module TypeId(3) => Module(0) TypeId(30)
8787
88-
Module TypeId(4) => value: foo
89-
90-
Module TypeId(5) => unknown
91-
92-
Module TypeId(6) => Module(0) TypeId(38)
93-
94-
Module TypeId(7) => Object {
88+
Module TypeId(4) => Object {
9589
prototype: No prototype
96-
members: ["fn": Module(0) TypeId(8)]
90+
members: ["fn": Module(0) TypeId(5)]
9791
}
9892
99-
Module TypeId(8) => sync Function {
93+
Module TypeId(5) => sync Function {
10094
accepts: {
10195
params: []
10296
type_args: []
10397
}
104-
returns: Module(0) TypeId(11)
98+
returns: Module(0) TypeId(22)
10599
}
106100
107-
Module TypeId(9) => Module(0) TypeId(7)
108-
109-
Module TypeId(10) => Module(0) TypeId(8)
101+
Module TypeId(6) => Module(0) TypeId(4)
110102
111-
Module TypeId(11) => Module(0) TypeId(7).x
103+
Module TypeId(7) => Module(0) TypeId(5)
112104
113-
Module TypeId(12) => Object {
105+
Module TypeId(8) => Object {
114106
prototype: No prototype
115-
members: ["fn": Module(0) TypeId(13)]
107+
members: ["fn": Module(0) TypeId(9)]
116108
}
117109
118-
Module TypeId(13) => sync Function "fn" {
110+
Module TypeId(9) => sync Function "fn" {
119111
accepts: {
120112
params: []
121113
type_args: []
122114
}
123-
returns: Module(0) TypeId(16)
115+
returns: Module(0) TypeId(12)
124116
}
125117
126-
Module TypeId(14) => Module(0) TypeId(12)
118+
Module TypeId(10) => Module(0) TypeId(8)
127119
128-
Module TypeId(15) => Module(0) TypeId(13)
120+
Module TypeId(11) => Module(0) TypeId(9)
129121
130-
Module TypeId(16) => Module(0) TypeId(12).x
122+
Module TypeId(12) => Module(0) TypeId(8).x
131123
132-
Module TypeId(17) => sync Function {
133-
accepts: {
134-
params: []
135-
type_args: []
136-
}
137-
returns: Module(0) TypeId(19)
138-
}
139-
140-
Module TypeId(18) => Module(0) TypeId(17)
141-
142-
Module TypeId(19) => Module(0) TypeId(5).x
124+
Module TypeId(13) => Module(0) TypeId(2)
143125
144-
Module TypeId(20) => sync Function {
126+
Module TypeId(14) => sync Function {
145127
accepts: {
146128
params: []
147129
type_args: []
148130
}
149-
returns: Module(0) TypeId(28)
131+
returns: Module(0) TypeId(20)
150132
}
151133
152-
Module TypeId(21) => Module(0) TypeId(37)
134+
Module TypeId(15) => Module(0) TypeId(29)
153135
154-
Module TypeId(22) => instanceof Module(0) TypeId(21)
136+
Module TypeId(16) => instanceof Module(0) TypeId(15)
155137
156-
Module TypeId(23) => Module(0) TypeId(33)
138+
Module TypeId(17) => Module(0) TypeId(25)
157139
158-
Module TypeId(24) => Module(0) TypeId(1) | Module(0) TypeId(4)
140+
Module TypeId(18) => Module(0) TypeId(26)
159141
160-
Module TypeId(25) => Module(0) TypeId(34)
142+
Module TypeId(19) => Module(0) TypeId(14)
161143
162-
Module TypeId(26) => Module(0) TypeId(19) | Module(0) TypeId(19)
144+
Module TypeId(20) => Module(0) TypeId(1).x
163145
164-
Module TypeId(27) => Module(0) TypeId(20)
146+
Module TypeId(21) => Module(0) TypeId(27)
165147
166-
Module TypeId(28) => Module(0) TypeId(19) | Module(0) TypeId(19)
148+
Module TypeId(22) => Module(0) TypeId(4).x
167149
168-
Module TypeId(29) => Module(0) TypeId(35)
150+
Module TypeId(23) => Module(0) TypeId(28)
169151
170-
Module TypeId(30) => Module(0) TypeId(11) | Module(0) TypeId(11)
152+
Module TypeId(24) => Module(0) TypeId(12) | Module(0) TypeId(12)
171153
172-
Module TypeId(31) => Module(0) TypeId(36)
173-
174-
Module TypeId(32) => Module(0) TypeId(16) | Module(0) TypeId(16)
175-
176-
Module TypeId(33) => sync Function "nested" {
154+
Module TypeId(25) => sync Function "nested" {
177155
accepts: {
178156
params: []
179157
type_args: []
180158
}
181-
returns: Module(0) TypeId(24)
159+
returns: Module(0) TypeId(20)
182160
}
183161
184-
Module TypeId(34) => sync Function "nested2" {
162+
Module TypeId(26) => sync Function "nested2" {
185163
accepts: {
186164
params: []
187165
type_args: []
188166
}
189-
returns: Module(0) TypeId(26)
167+
returns: Module(0) TypeId(20)
190168
}
191169
192-
Module TypeId(35) => sync Function "nestedObject" {
170+
Module TypeId(27) => sync Function "nestedObject" {
193171
accepts: {
194172
params: []
195173
type_args: []
196174
}
197-
returns: Module(0) TypeId(30)
175+
returns: Module(0) TypeId(22)
198176
}
199177
200-
Module TypeId(36) => sync Function "nestedObject2" {
178+
Module TypeId(28) => sync Function "nestedObject2" {
201179
accepts: {
202180
params: []
203181
type_args: []
204182
}
205-
returns: Module(0) TypeId(32)
183+
returns: Module(0) TypeId(24)
206184
}
207185
208-
Module TypeId(37) => class "Foo" {
186+
Module TypeId(29) => class "Foo" {
209187
extends: none
210188
implements: []
211189
type_args: []
212190
}
213191
214-
Module TypeId(38) => sync Function "fn" {
192+
Module TypeId(30) => sync Function "fn" {
215193
accepts: {
216194
params: []
217195
type_args: []
218196
}
219-
returns: Module(0) TypeId(19)
197+
returns: Module(0) TypeId(20)
220198
}
221199
```

crates/biome_rowan/src/syntax/node.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,23 @@ impl<L: Language> SyntaxNode<L> {
362362
.map(NodeOrToken::from)
363363
}
364364

365+
/// Traverses the subtree rooted at the current node (including the current
366+
/// node) in preorder, excluding tokens, as long as `predicate` returns
367+
/// `true`.
368+
///
369+
/// `predicate` is used to prune subtrees that fail the predicate test. Any
370+
/// time `predicate` returns `false`, that node **as well as its children**
371+
/// are skipped during the traversal.
372+
pub fn pruned_descendents<P: Fn(&Self) -> bool>(
373+
&self,
374+
predicate: P,
375+
) -> impl Iterator<Item = Self> + use<L, P> {
376+
PrunedDescendents {
377+
preorder: self.preorder(),
378+
predicate,
379+
}
380+
}
381+
365382
/// Traverse the subtree rooted at the current node (including the current
366383
/// node) in preorder, excluding tokens.
367384
pub fn preorder(&self) -> Preorder<L> {
@@ -918,6 +935,31 @@ impl<L: Language> Iterator for Preorder<L> {
918935
}
919936
}
920937

938+
pub struct PrunedDescendents<L: Language, P: Fn(&SyntaxNode<L>) -> bool> {
939+
preorder: Preorder<L>,
940+
predicate: P,
941+
}
942+
943+
impl<L: Language, P: Fn(&SyntaxNode<L>) -> bool> Iterator for PrunedDescendents<L, P> {
944+
type Item = SyntaxNode<L>;
945+
fn next(&mut self) -> Option<Self::Item> {
946+
loop {
947+
match self.preorder.next() {
948+
Some(WalkEvent::Enter(node)) => {
949+
let predicate = &self.predicate;
950+
if predicate(&node) {
951+
break Some(node);
952+
} else {
953+
self.preorder.skip_subtree();
954+
}
955+
}
956+
Some(WalkEvent::Leave(_)) => {}
957+
None => break None,
958+
}
959+
}
960+
}
961+
}
962+
921963
pub struct PreorderTokens<L: Language> {
922964
raw: cursor::PreorderTokens,
923965
_p: PhantomData<L>,

0 commit comments

Comments
 (0)