Skip to content

Commit 366da34

Browse files
authored
Improve inference by not considering thisless functions to be context-sensitive (#62243)
1 parent 1da8266 commit 366da34

File tree

34 files changed

+5371
-63
lines changed

34 files changed

+5371
-63
lines changed

src/compiler/binder.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
10001000
const saveExceptionTarget = currentExceptionTarget;
10011001
const saveActiveLabelList = activeLabelList;
10021002
const saveHasExplicitReturn = hasExplicitReturn;
1003+
const saveSeenThisKeyword = seenThisKeyword;
10031004
const isImmediatelyInvoked = (
10041005
containerFlags & ContainerFlags.IsFunctionExpression &&
10051006
!hasSyntacticModifier(node, ModifierFlags.Async) &&
@@ -1022,19 +1023,22 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
10221023
currentContinueTarget = undefined;
10231024
activeLabelList = undefined;
10241025
hasExplicitReturn = false;
1026+
seenThisKeyword = false;
10251027
bindChildren(node);
1026-
// Reset all reachability check related flags on node (for incremental scenarios)
1027-
node.flags &= ~NodeFlags.ReachabilityAndEmitFlags;
1028+
// Reset flags (for incremental scenarios)
1029+
node.flags &= ~(NodeFlags.ReachabilityAndEmitFlags | NodeFlags.ContainsThis);
10281030
if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).body)) {
10291031
node.flags |= NodeFlags.HasImplicitReturn;
10301032
if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn;
10311033
(node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).endFlowNode = currentFlow;
10321034
}
1035+
if (seenThisKeyword) {
1036+
node.flags |= NodeFlags.ContainsThis;
1037+
}
10331038
if (node.kind === SyntaxKind.SourceFile) {
10341039
node.flags |= emitFlags;
10351040
(node as SourceFile).endFlowNode = currentFlow;
10361041
}
1037-
10381042
if (currentReturnTarget) {
10391043
addAntecedent(currentReturnTarget, currentFlow);
10401044
currentFlow = finishFlowLabel(currentReturnTarget);
@@ -1051,12 +1055,15 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
10511055
currentExceptionTarget = saveExceptionTarget;
10521056
activeLabelList = saveActiveLabelList;
10531057
hasExplicitReturn = saveHasExplicitReturn;
1058+
seenThisKeyword = node.kind === SyntaxKind.ArrowFunction ? saveSeenThisKeyword || seenThisKeyword : saveSeenThisKeyword;
10541059
}
10551060
else if (containerFlags & ContainerFlags.IsInterface) {
1061+
const saveSeenThisKeyword = seenThisKeyword;
10561062
seenThisKeyword = false;
10571063
bindChildren(node);
10581064
Debug.assertNotNode(node, isIdentifier); // ContainsThis cannot overlap with HasExtendedUnicodeEscape on Identifier
10591065
node.flags = seenThisKeyword ? node.flags | NodeFlags.ContainsThis : node.flags & ~NodeFlags.ContainsThis;
1066+
seenThisKeyword = saveSeenThisKeyword;
10601067
}
10611068
else {
10621069
bindChildren(node);
@@ -2852,6 +2859,9 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
28522859
}
28532860
// falls through
28542861
case SyntaxKind.ThisKeyword:
2862+
if (node.kind === SyntaxKind.ThisKeyword) {
2863+
seenThisKeyword = true;
2864+
}
28552865
// TODO: Why use `isExpression` here? both Identifier and ThisKeyword are expressions.
28562866
if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) {
28572867
(node as Identifier | ThisExpression).flowNode = currentFlow;
@@ -3833,28 +3843,27 @@ export function getContainerFlags(node: Node): ContainerFlags {
38333843
// falls through
38343844
case SyntaxKind.Constructor:
38353845
case SyntaxKind.FunctionDeclaration:
3846+
case SyntaxKind.ClassStaticBlockDeclaration:
3847+
return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike;
38363848
case SyntaxKind.MethodSignature:
38373849
case SyntaxKind.CallSignature:
38383850
case SyntaxKind.JSDocSignature:
38393851
case SyntaxKind.JSDocFunctionType:
38403852
case SyntaxKind.FunctionType:
38413853
case SyntaxKind.ConstructSignature:
38423854
case SyntaxKind.ConstructorType:
3843-
case SyntaxKind.ClassStaticBlockDeclaration:
3844-
return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike;
3855+
return ContainerFlags.IsContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike;
38453856

38463857
case SyntaxKind.JSDocImportTag:
38473858
// treat as a container to prevent using an enclosing effective host, ensuring import bindings are scoped correctly
3848-
return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals;
3859+
return ContainerFlags.IsContainer | ContainerFlags.HasLocals;
38493860

38503861
case SyntaxKind.FunctionExpression:
38513862
case SyntaxKind.ArrowFunction:
38523863
return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsFunctionExpression;
38533864

38543865
case SyntaxKind.ModuleBlock:
38553866
return ContainerFlags.IsControlFlowContainer;
3856-
case SyntaxKind.PropertyDeclaration:
3857-
return (node as PropertyDeclaration).initializer ? ContainerFlags.IsControlFlowContainer : 0;
38583867

38593868
case SyntaxKind.CatchClause:
38603869
case SyntaxKind.ForStatement:

src/compiler/checker.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21143,9 +21143,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2114321143
const { initializer } = node as JsxAttribute;
2114421144
return !!initializer && isContextSensitive(initializer);
2114521145
}
21146-
case SyntaxKind.JsxExpression: {
21146+
case SyntaxKind.JsxExpression:
21147+
case SyntaxKind.YieldExpression: {
2114721148
// It is possible to that node.expression is undefined (e.g <div x={} />)
21148-
const { expression } = node as JsxExpression;
21149+
const { expression } = node as JsxExpression | YieldExpression;
2114921150
return !!expression && isContextSensitive(expression);
2115021151
}
2115121152
}
@@ -21154,7 +21155,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2115421155
}
2115521156

2115621157
function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
21157-
return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node);
21158+
return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node) || hasContextSensitiveYieldExpression(node);
2115821159
}
2115921160

2116021161
function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) {
@@ -21167,6 +21168,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2116721168
return !!forEachReturnStatement(node.body as Block, statement => !!statement.expression && isContextSensitive(statement.expression));
2116821169
}
2116921170

21171+
function hasContextSensitiveYieldExpression(node: FunctionLikeDeclaration): boolean {
21172+
// yield expressions can be context sensitive in situations like:
21173+
//
21174+
// declare function test(gen: () => Generator<(arg: number) => string, void, void>): void;
21175+
//
21176+
// test(function* () {
21177+
// yield (arg) => String(arg);
21178+
// });
21179+
return !!(getFunctionFlags(node) & FunctionFlags.Generator && node.body && forEachYieldExpression(node.body as Block, isContextSensitive));
21180+
}
21181+
2117021182
function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration {
2117121183
return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
2117221184
isContextSensitiveFunctionLikeDeclaration(func);
@@ -32629,17 +32641,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3262932641
if (inferenceContext && contextFlags! & ContextFlags.Signature && some(inferenceContext.inferences, hasInferenceCandidatesOrDefault)) {
3263032642
// For contextual signatures we incorporate all inferences made so far, e.g. from return
3263132643
// types as well as arguments to the left in a function call.
32632-
return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
32644+
const type = instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
32645+
if (!(type.flags & TypeFlags.AnyOrUnknown)) {
32646+
return type;
32647+
}
3263332648
}
3263432649
if (inferenceContext?.returnMapper) {
3263532650
// For other purposes (e.g. determining whether to produce literal types) we only
3263632651
// incorporate inferences made from the return type in a function call. We remove
3263732652
// the 'boolean' type from the contextual type such that contextually typed boolean
3263832653
// literals actually end up widening to 'boolean' (see #48363).
3263932654
const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper);
32640-
return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ?
32641-
filterType(type, t => t !== regularFalseType && t !== regularTrueType) :
32642-
type;
32655+
if (!(type.flags & TypeFlags.AnyOrUnknown)) {
32656+
return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ?
32657+
filterType(type, t => t !== regularFalseType && t !== regularTrueType) :
32658+
type;
32659+
}
3264332660
}
3264432661
}
3264532662
return contextualType;

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6526,7 +6526,7 @@ export const enum ObjectFlags {
65266526
/** @internal */
65276527
ContainsObjectOrArrayLiteral = 1 << 17, // Type is or contains object literal type
65286528
/** @internal */
6529-
NonInferrableType = 1 << 18, // Type is or contains anyFunctionType or silentNeverType
6529+
NonInferrableType = 1 << 18, // Type is or contains anyFunctionType or silentNeverType, or it's a context free `returnTypeOnly`
65306530
/** @internal */
65316531
CouldContainTypeVariablesComputed = 1 << 19, // CouldContainTypeVariables flag has been computed
65326532
/** @internal */

src/compiler/utilities.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2880,19 +2880,24 @@ export function forEachReturnStatement<T>(body: Block | Statement, visitor: (stm
28802880
}
28812881
}
28822882

2883+
// Warning: This has the same semantics as the forEach family of functions,
2884+
// in that traversal terminates in the event that 'visitor' supplies a truthy value.
28832885
/** @internal */
2884-
export function forEachYieldExpression(body: Block, visitor: (expr: YieldExpression) => void): void {
2886+
export function forEachYieldExpression<T>(body: Block, visitor: (expr: YieldExpression) => T): T | undefined {
28852887
return traverse(body);
28862888

2887-
function traverse(node: Node): void {
2889+
function traverse(node: Node): T | undefined {
28882890
switch (node.kind) {
28892891
case SyntaxKind.YieldExpression:
2890-
visitor(node as YieldExpression);
2892+
const value = visitor(node as YieldExpression);
2893+
if (value) {
2894+
return value;
2895+
}
28912896
const operand = (node as YieldExpression).expression;
2892-
if (operand) {
2893-
traverse(operand);
2897+
if (!operand) {
2898+
return;
28942899
}
2895-
return;
2900+
return traverse(operand);
28962901
case SyntaxKind.EnumDeclaration:
28972902
case SyntaxKind.InterfaceDeclaration:
28982903
case SyntaxKind.ModuleDeclaration:
@@ -2905,14 +2910,13 @@ export function forEachYieldExpression(body: Block, visitor: (expr: YieldExpress
29052910
if (node.name && node.name.kind === SyntaxKind.ComputedPropertyName) {
29062911
// Note that we will not include methods/accessors of a class because they would require
29072912
// first descending into the class. This is by design.
2908-
traverse(node.name.expression);
2909-
return;
2913+
return traverse(node.name.expression);
29102914
}
29112915
}
29122916
else if (!isPartOfTypeNode(node)) {
29132917
// This is the general case, which should include mostly expressions and statements.
29142918
// Also includes NodeArrays.
2915-
forEachChild(node, traverse);
2919+
return forEachChild(node, traverse);
29162920
}
29172921
}
29182922
}
@@ -10829,7 +10833,7 @@ export function hasContextSensitiveParameters(node: FunctionLikeDeclaration): bo
1082910833
// an implicit 'this' parameter which is subject to contextual typing.
1083010834
const parameter = firstOrUndefined(node.parameters);
1083110835
if (!(parameter && parameterIsThisKeyword(parameter))) {
10832-
return true;
10836+
return !!(node.flags & NodeFlags.ContainsThis);
1083310837
}
1083410838
}
1083510839
}

tests/baselines/reference/circularlySimplifyingConditionalTypesNoCrash.types

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
//// [tests/cases/compiler/circularlySimplifyingConditionalTypesNoCrash.ts] ////
22

3-
=== Performance Stats ===
4-
Instantiation count: 1,000
5-
63
=== circularlySimplifyingConditionalTypesNoCrash.ts ===
74
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
85
>Omit : Omit<T, K>
@@ -71,8 +68,8 @@ declare var connect: Connect;
7168
const myStoreConnect: Connect = function(
7269
>myStoreConnect : Connect
7370
> : ^^^^^^^
74-
>function( mapStateToProps?: any, mapDispatchToProps?: any, mergeProps?: any, options: unknown = {},) { return connect( mapStateToProps, mapDispatchToProps, mergeProps, options, );} : <TStateProps, TOwnProps>(mapStateToProps?: any, mapDispatchToProps?: any, mergeProps?: any, options?: unknown) => InferableComponentEnhancerWithProps<TStateProps, Omit<P, Extract<keyof TStateProps, keyof P>> & TOwnProps>
75-
> : ^ ^^ ^^ ^^^ ^^ ^^^ ^^ ^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
71+
>function( mapStateToProps?: any, mapDispatchToProps?: any, mergeProps?: any, options: unknown = {},) { return connect( mapStateToProps, mapDispatchToProps, mergeProps, options, );} : (mapStateToProps?: any, mapDispatchToProps?: any, mergeProps?: any, options?: unknown) => InferableComponentEnhancerWithProps<TStateProps, Omit<P, Extract<keyof TStateProps, keyof P>> & TOwnProps>
72+
> : ^ ^^^ ^^ ^^^ ^^ ^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7673

7774
mapStateToProps?: any,
7875
>mapStateToProps : any

tests/baselines/reference/classCanExtendConstructorFunction.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
first.js(23,9): error TS2554: Expected 1 arguments, but got 0.
22
first.js(31,5): error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'.
3-
Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'.
3+
Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[] | undefined) => void'.
44
Target signature provides too few arguments. Expected 2 or more, but got 1.
55
first.js(47,24): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type.
66
generic.js(19,19): error TS2554: Expected 1 arguments, but got 0.
77
generic.js(20,32): error TS2345: Argument of type 'number' is not assignable to parameter of type '{ claim: "ignorant" | "malicious"; }'.
88
second.ts(8,25): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type.
99
second.ts(14,7): error TS2417: Class static side 'typeof Conestoga' incorrectly extends base class static side 'typeof Wagon'.
1010
Types of property 'circle' are incompatible.
11-
Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[]) => number'.
11+
Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[] | undefined) => number'.
1212
Types of parameters 'others' and 'wagons' are incompatible.
1313
Type 'Wagon[]' is not assignable to type '(typeof Wagon)[]'.
1414
Property 'circle' is missing in type 'Wagon' but required in type 'typeof Wagon'.
@@ -52,7 +52,7 @@ second.ts(17,15): error TS2345: Argument of type 'string' is not assignable to p
5252
load(files, format) {
5353
~~~~
5454
!!! error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'.
55-
!!! error TS2416: Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'.
55+
!!! error TS2416: Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[] | undefined) => void'.
5656
!!! error TS2416: Target signature provides too few arguments. Expected 2 or more, but got 1.
5757
if (format === "xmlolololol") {
5858
throw new Error("please do not use XML. It was a joke.");
@@ -95,7 +95,7 @@ second.ts(17,15): error TS2345: Argument of type 'string' is not assignable to p
9595
~~~~~~~~~
9696
!!! error TS2417: Class static side 'typeof Conestoga' incorrectly extends base class static side 'typeof Wagon'.
9797
!!! error TS2417: Types of property 'circle' are incompatible.
98-
!!! error TS2417: Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[]) => number'.
98+
!!! error TS2417: Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[] | undefined) => number'.
9999
!!! error TS2417: Types of parameters 'others' and 'wagons' are incompatible.
100100
!!! error TS2417: Type 'Wagon[]' is not assignable to type '(typeof Wagon)[]'.
101101
!!! error TS2417: Property 'circle' is missing in type 'Wagon' but required in type 'typeof Wagon'.

0 commit comments

Comments
 (0)