Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
const saveExceptionTarget = currentExceptionTarget;
const saveActiveLabelList = activeLabelList;
const saveHasExplicitReturn = hasExplicitReturn;
const saveSeenThisKeyword = seenThisKeyword;
const isImmediatelyInvoked = (
containerFlags & ContainerFlags.IsFunctionExpression &&
!hasSyntacticModifier(node, ModifierFlags.Async) &&
Expand All @@ -1022,19 +1023,22 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
currentContinueTarget = undefined;
activeLabelList = undefined;
hasExplicitReturn = false;
seenThisKeyword = false;
bindChildren(node);
// Reset all reachability check related flags on node (for incremental scenarios)
node.flags &= ~NodeFlags.ReachabilityAndEmitFlags;
// Reset flags (for incremental scenarios)
node.flags &= ~(NodeFlags.ReachabilityAndEmitFlags | NodeFlags.ContainsThis);
if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).body)) {
node.flags |= NodeFlags.HasImplicitReturn;
if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn;
(node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).endFlowNode = currentFlow;
}
if (seenThisKeyword) {
node.flags |= NodeFlags.ContainsThis;
}
if (node.kind === SyntaxKind.SourceFile) {
node.flags |= emitFlags;
(node as SourceFile).endFlowNode = currentFlow;
}

if (currentReturnTarget) {
addAntecedent(currentReturnTarget, currentFlow);
currentFlow = finishFlowLabel(currentReturnTarget);
Expand All @@ -1051,12 +1055,15 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
currentExceptionTarget = saveExceptionTarget;
activeLabelList = saveActiveLabelList;
hasExplicitReturn = saveHasExplicitReturn;
seenThisKeyword = node.kind === SyntaxKind.ArrowFunction ? saveSeenThisKeyword || seenThisKeyword : saveSeenThisKeyword;
}
else if (containerFlags & ContainerFlags.IsInterface) {
const saveSeenThisKeyword = seenThisKeyword;
seenThisKeyword = false;
bindChildren(node);
Debug.assertNotNode(node, isIdentifier); // ContainsThis cannot overlap with HasExtendedUnicodeEscape on Identifier
node.flags = seenThisKeyword ? node.flags | NodeFlags.ContainsThis : node.flags & ~NodeFlags.ContainsThis;
seenThisKeyword = saveSeenThisKeyword;
}
else {
bindChildren(node);
Expand Down Expand Up @@ -2852,6 +2859,9 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
}
// falls through
case SyntaxKind.ThisKeyword:
if (node.kind === SyntaxKind.ThisKeyword) {
seenThisKeyword = true;
}
// TODO: Why use `isExpression` here? both Identifier and ThisKeyword are expressions.
if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) {
(node as Identifier | ThisExpression).flowNode = currentFlow;
Expand Down Expand Up @@ -3833,28 +3843,27 @@ export function getContainerFlags(node: Node): ContainerFlags {
// falls through
case SyntaxKind.Constructor:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ClassStaticBlockDeclaration:
return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike;
case SyntaxKind.MethodSignature:
case SyntaxKind.CallSignature:
case SyntaxKind.JSDocSignature:
case SyntaxKind.JSDocFunctionType:
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructSignature:
case SyntaxKind.ConstructorType:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type-level AST nodes had ContainerFlags.IsControlFlowContainer here. This was messing up some of the changes I made since it was interfering with the implemented seenThisKeyword tracking. I don't see why those would be considered control flow containers and there are no tests proving it was needed.

Other changes in this function are basically of the same kind - I just removed ContainerFlags.IsControlFlowContainer from the type-level nodes.

Copy link
Member

@gabritto gabritto Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We seem to have added at least one of those on purpose:
#8941
So I'm wondering why it's not needed anymore. @ahejlsberg do you remember why this was needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As to SyntaxKind.PropertyDeclaration - given the test from the referenced PR still works just OK, I'd assume that its needs are covered by arrow functions being treated as flow containers (arrows were used as property declaration initializers in that test).

case SyntaxKind.ClassStaticBlockDeclaration:
return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike;
return ContainerFlags.IsContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike;

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

case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsFunctionExpression;

case SyntaxKind.ModuleBlock:
return ContainerFlags.IsControlFlowContainer;
case SyntaxKind.PropertyDeclaration:
return (node as PropertyDeclaration).initializer ? ContainerFlags.IsControlFlowContainer : 0;

case SyntaxKind.CatchClause:
case SyntaxKind.ForStatement:
Expand Down
31 changes: 24 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21143,9 +21143,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const { initializer } = node as JsxAttribute;
return !!initializer && isContextSensitive(initializer);
}
case SyntaxKind.JsxExpression: {
case SyntaxKind.JsxExpression:
case SyntaxKind.YieldExpression: {
// It is possible to that node.expression is undefined (e.g <div x={} />)
const { expression } = node as JsxExpression;
const { expression } = node as JsxExpression | YieldExpression;
return !!expression && isContextSensitive(expression);
}
}
Expand All @@ -21154,7 +21155,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node);
return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node) || hasContextSensitiveYieldExpression(node);
}

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

function hasContextSensitiveYieldExpression(node: FunctionLikeDeclaration): boolean {
// yield expressions can be context sensitive in situations like:
//
// declare function test(gen: () => Generator<(arg: number) => string, void, void>): void;
//
// test(function* () {
// yield (arg) => String(arg);
// });
return !!(getFunctionFlags(node) & FunctionFlags.Generator && node.body && forEachYieldExpression(node.body as Block, isContextSensitive));
}

function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration {
return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
isContextSensitiveFunctionLikeDeclaration(func);
Expand Down Expand Up @@ -32629,17 +32641,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (inferenceContext && contextFlags! & ContextFlags.Signature && some(inferenceContext.inferences, hasInferenceCandidatesOrDefault)) {
// For contextual signatures we incorporate all inferences made so far, e.g. from return
// types as well as arguments to the left in a function call.
return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
const type = instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
if (!(type.flags & TypeFlags.AnyOrUnknown)) {
return type;
}
}
if (inferenceContext?.returnMapper) {
// For other purposes (e.g. determining whether to produce literal types) we only
// incorporate inferences made from the return type in a function call. We remove
// the 'boolean' type from the contextual type such that contextually typed boolean
// literals actually end up widening to 'boolean' (see #48363).
const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper);
return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ?
filterType(type, t => t !== regularFalseType && t !== regularTrueType) :
type;
if (!(type.flags & TypeFlags.AnyOrUnknown)) {
return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ?
filterType(type, t => t !== regularFalseType && t !== regularTrueType) :
type;
}
}
}
return contextualType;
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6526,7 +6526,7 @@ export const enum ObjectFlags {
/** @internal */
ContainsObjectOrArrayLiteral = 1 << 17, // Type is or contains object literal type
/** @internal */
NonInferrableType = 1 << 18, // Type is or contains anyFunctionType or silentNeverType
NonInferrableType = 1 << 18, // Type is or contains anyFunctionType or silentNeverType, or it's a context free `returnTypeOnly`
/** @internal */
CouldContainTypeVariablesComputed = 1 << 19, // CouldContainTypeVariables flag has been computed
/** @internal */
Expand Down
24 changes: 14 additions & 10 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2880,19 +2880,24 @@ export function forEachReturnStatement<T>(body: Block | Statement, visitor: (stm
}
}

// Warning: This has the same semantics as the forEach family of functions,
// in that traversal terminates in the event that 'visitor' supplies a truthy value.
/** @internal */
export function forEachYieldExpression(body: Block, visitor: (expr: YieldExpression) => void): void {
export function forEachYieldExpression<T>(body: Block, visitor: (expr: YieldExpression) => T): T | undefined {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this basically changes this function to work in the same way as forEachReturnStatement defined above

return traverse(body);

function traverse(node: Node): void {
function traverse(node: Node): T | undefined {
switch (node.kind) {
case SyntaxKind.YieldExpression:
visitor(node as YieldExpression);
const value = visitor(node as YieldExpression);
if (value) {
return value;
}
const operand = (node as YieldExpression).expression;
if (operand) {
traverse(operand);
if (!operand) {
return;
}
return;
return traverse(operand);
case SyntaxKind.EnumDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.ModuleDeclaration:
Expand All @@ -2905,14 +2910,13 @@ export function forEachYieldExpression(body: Block, visitor: (expr: YieldExpress
if (node.name && node.name.kind === SyntaxKind.ComputedPropertyName) {
// Note that we will not include methods/accessors of a class because they would require
// first descending into the class. This is by design.
traverse(node.name.expression);
return;
return traverse(node.name.expression);
}
}
else if (!isPartOfTypeNode(node)) {
// This is the general case, which should include mostly expressions and statements.
// Also includes NodeArrays.
forEachChild(node, traverse);
return forEachChild(node, traverse);
}
}
}
Expand Down Expand Up @@ -10829,7 +10833,7 @@ export function hasContextSensitiveParameters(node: FunctionLikeDeclaration): bo
// an implicit 'this' parameter which is subject to contextual typing.
const parameter = firstOrUndefined(node.parameters);
if (!(parameter && parameterIsThisKeyword(parameter))) {
return true;
return !!(node.flags & NodeFlags.ContainsThis);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
//// [tests/cases/compiler/circularlySimplifyingConditionalTypesNoCrash.ts] ////

=== Performance Stats ===
Instantiation count: 1,000

=== circularlySimplifyingConditionalTypesNoCrash.ts ===
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
>Omit : Omit<T, K>
Expand Down Expand Up @@ -71,8 +68,8 @@ declare var connect: Connect;
const myStoreConnect: Connect = function(
>myStoreConnect : Connect
> : ^^^^^^^
>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>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this just changes the inferred type to match the type inferred from the equivalent arrow function with type parameters, it's purely a result of making a thisless function context-insensitive

> : ^ ^^ ^^ ^^^ ^^ ^^^ ^^ ^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>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>
> : ^ ^^^ ^^ ^^^ ^^ ^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

mapStateToProps?: any,
>mapStateToProps : any
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
first.js(23,9): error TS2554: Expected 1 arguments, but got 0.
first.js(31,5): error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'.
Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'.
Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[] | undefined) => void'.
Target signature provides too few arguments. Expected 2 or more, but got 1.
first.js(47,24): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type.
generic.js(19,19): error TS2554: Expected 1 arguments, but got 0.
generic.js(20,32): error TS2345: Argument of type 'number' is not assignable to parameter of type '{ claim: "ignorant" | "malicious"; }'.
second.ts(8,25): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type.
second.ts(14,7): error TS2417: Class static side 'typeof Conestoga' incorrectly extends base class static side 'typeof Wagon'.
Types of property 'circle' are incompatible.
Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[]) => number'.
Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[] | undefined) => number'.
Types of parameters 'others' and 'wagons' are incompatible.
Type 'Wagon[]' is not assignable to type '(typeof Wagon)[]'.
Property 'circle' is missing in type 'Wagon' but required in type 'typeof Wagon'.
Expand Down Expand Up @@ -52,7 +52,7 @@ second.ts(17,15): error TS2345: Argument of type 'string' is not assignable to p
load(files, format) {
~~~~
!!! error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'.
!!! error TS2416: Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'.
!!! error TS2416: Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[] | undefined) => void'.
!!! error TS2416: Target signature provides too few arguments. Expected 2 or more, but got 1.
if (format === "xmlolololol") {
throw new Error("please do not use XML. It was a joke.");
Expand Down Expand Up @@ -95,7 +95,7 @@ second.ts(17,15): error TS2345: Argument of type 'string' is not assignable to p
~~~~~~~~~
!!! error TS2417: Class static side 'typeof Conestoga' incorrectly extends base class static side 'typeof Wagon'.
!!! error TS2417: Types of property 'circle' are incompatible.
!!! error TS2417: Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[]) => number'.
!!! error TS2417: Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[] | undefined) => number'.
!!! error TS2417: Types of parameters 'others' and 'wagons' are incompatible.
!!! error TS2417: Type 'Wagon[]' is not assignable to type '(typeof Wagon)[]'.
!!! error TS2417: Property 'circle' is missing in type 'Wagon' but required in type 'typeof Wagon'.
Expand Down
Loading