From 096812e28adf960236e3ea3d6a815ab1686774b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 25 Mar 2025 11:24:28 +0100 Subject: [PATCH 1/2] Fixed contextually typed expando members --- internal/binder/binder.go | 10 ++- internal/checker/checker.go | 83 ++++++++++++++++++- .../expandoFunctionContextualTypes.types | 2 +- .../expandoFunctionContextualTypes.types.diff | 9 -- ...ropertyAssignmentUseParentType1.errors.txt | 31 ------- ...tyAssignmentUseParentType1.errors.txt.diff | 36 -------- .../propertyAssignmentUseParentType1.types | 4 +- ...ropertyAssignmentUseParentType1.types.diff | 20 ----- .../typeFromPropertyAssignment30.types | 2 +- .../typeFromPropertyAssignment30.types.diff | 11 +++ 10 files changed, 102 insertions(+), 106 deletions(-) delete mode 100644 testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.errors.txt delete mode 100644 testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.errors.txt.diff delete mode 100644 testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.types.diff create mode 100644 testdata/baselines/reference/submodule/conformance/typeFromPropertyAssignment30.types.diff diff --git a/internal/binder/binder.go b/internal/binder/binder.go index f398ddba48..85584a76dc 100644 --- a/internal/binder/binder.go +++ b/internal/binder/binder.go @@ -987,9 +987,13 @@ func (b *Binder) bindFunctionPropertyAssignment(node *ast.Node) { case ast.IsFunctionDeclaration(symbol.ValueDeclaration): funcSymbol = symbol case ast.IsVariableDeclaration(symbol.ValueDeclaration) && symbol.ValueDeclaration.Parent.Flags&ast.NodeFlagsConst != 0: - initializer := symbol.ValueDeclaration.Initializer() - if initializer != nil && ast.IsFunctionExpressionOrArrowFunction(initializer) { - funcSymbol = initializer.Symbol() + if symbol.ValueDeclaration.Type() != nil { + funcSymbol = symbol + } else { + initializer := symbol.ValueDeclaration.Initializer() + if initializer != nil && ast.IsFunctionExpressionOrArrowFunction(initializer) { + funcSymbol = initializer.Parent.Symbol() + } } } if funcSymbol != nil { diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 73348a35da..67a3734456 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -13399,6 +13399,20 @@ func (c *Checker) getParentOfSymbol(symbol *ast.Symbol) *ast.Symbol { return nil } +func (c *Checker) getFunctionExpressionParentSymbolOrSymbol(symbol *ast.Symbol) *ast.Symbol { + declaration := symbol.ValueDeclaration + if declaration == nil { + return symbol + } + if declaration.Kind == ast.KindArrowFunction || declaration.Kind == ast.KindFunctionExpression { + parentSymbol := c.getSymbolOfNode(declaration.Parent) + if parentSymbol != nil { + return parentSymbol + } + } + return symbol +} + func (c *Checker) recordMergedSymbol(target *ast.Symbol, source *ast.Symbol) { c.mergedSymbols[source] = target } @@ -14516,7 +14530,7 @@ func (c *Checker) getResolvedMembersOrExportsOfSymbol(symbol *ast.Symbol, resolu } } if isStatic { - for member := range symbol.AssignmentDeclarationMembers.Keys() { + for member := range c.getFunctionExpressionParentSymbolOrSymbol(symbol).AssignmentDeclarationMembers.Keys() { if c.hasLateBindableName(member) { if lateSymbols == nil { lateSymbols = make(ast.SymbolTable) @@ -15389,8 +15403,33 @@ func (c *Checker) widenTypeInferredFromInitializer(declaration *ast.Node, t *Typ func (c *Checker) getTypeOfFuncClassEnumModule(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) + originalLinks := links if links.resolvedType == nil { + expando := c.getSymbolOfExpando(symbol.ValueDeclaration) + if expando != nil { + target := symbol + source := expando + inferred := core.IfElse(target.Flags&ast.SymbolFlagsTransient != 0, target, nil) + if inferred == nil { + inferred = c.cloneSymbol(target) + } + if len(source.Exports) != 0 { + if inferred.Exports == nil { + inferred.Exports = make(ast.SymbolTable) + } + c.mergeSymbolTable(inferred.Exports, source.Exports, false, nil) + } + if len(source.Members) != 0 { + if inferred.Members == nil { + inferred.Members = make(ast.SymbolTable) + } + c.mergeSymbolTable(inferred.Members, source.Members, false, nil) + } + symbol = inferred + links = c.valueSymbolLinks.Get(inferred) + } links.resolvedType = c.getTypeOfFuncClassEnumModuleWorker(symbol) + originalLinks.resolvedType = links.resolvedType } return links.resolvedType } @@ -16541,13 +16580,21 @@ func (c *Checker) getTypeOfPrototypeProperty(prototype *ast.Symbol) *Type { } func (c *Checker) getWidenedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Type { + var annotatedType *Type var types []*Type for _, declaration := range symbol.Declarations { if ast.IsBinaryExpression(declaration) { - types = core.AppendIfUnique(types, c.checkExpressionForMutableLocation(declaration.AsBinaryExpression().Right, CheckModeNormal)) + annotatedType = c.getAnnotatedTypeForAssignmentDeclaration(symbol) + if annotatedType == nil { + types = core.AppendIfUnique(types, c.checkExpressionForMutableLocation(declaration.AsBinaryExpression().Right, CheckModeNormal)) + } } } - return c.getWidenedType(c.getUnionType(types)) + t := annotatedType + if t == nil { + t = c.getUnionType(types) + } + return c.getWidenedType(t) } func (c *Checker) widenTypeForVariableLikeDeclaration(t *Type, declaration *ast.Node, reportErrors bool) *Type { @@ -29590,3 +29637,33 @@ func (c *Checker) GetEmitResolver(file *ast.SourceFile, skipDiagnostics bool) pr } return c.emitResolver } + +func (c *Checker) getSymbolOfExpando(node *ast.Node) *ast.Symbol { + if node == nil || node.Parent == nil { + return nil + } + if ast.IsVariableDeclaration(node.Parent) && node.Parent.AsVariableDeclaration().Initializer == node { + if !ast.IsInJSFile(node) && !(ast.IsVarConstLike(node.Parent) && ast.IsFunctionLikeDeclaration(node)) { + return nil + } + return c.getSymbolOfDeclaration(node.Parent) + + } + return nil +} + +func (c *Checker) getAnnotatedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Type { + if symbol.Parent != nil && symbol.Parent.ValueDeclaration != nil { + possiblyAnnotatedSymbol := c.getFunctionExpressionParentSymbolOrSymbol(symbol.Parent) + if possiblyAnnotatedSymbol != nil && possiblyAnnotatedSymbol.ValueDeclaration != nil && possiblyAnnotatedSymbol.ValueDeclaration.Kind != ast.KindFunctionDeclaration { + typeNode := possiblyAnnotatedSymbol.ValueDeclaration.Type() + if typeNode != nil { + annotationSymbol := c.getPropertyOfType(c.getTypeFromTypeNode(typeNode), symbol.Name) + if annotationSymbol != nil { + return c.getNonMissingTypeOfSymbol(annotationSymbol) + } + } + } + } + return nil +} diff --git a/testdata/baselines/reference/submodule/compiler/expandoFunctionContextualTypes.types b/testdata/baselines/reference/submodule/compiler/expandoFunctionContextualTypes.types index 0a20ac08dd..82db0d253f 100644 --- a/testdata/baselines/reference/submodule/compiler/expandoFunctionContextualTypes.types +++ b/testdata/baselines/reference/submodule/compiler/expandoFunctionContextualTypes.types @@ -14,7 +14,7 @@ interface StatelessComponent

{ const MyComponent: StatelessComponent = () => null as any; >MyComponent : StatelessComponent ->() => null as any : { (): any; defaultProps: { color: "red"; }; } +>() => null as any : { (): any; defaultProps: Partial; } >null as any : any MyComponent.defaultProps = { diff --git a/testdata/baselines/reference/submodule/compiler/expandoFunctionContextualTypes.types.diff b/testdata/baselines/reference/submodule/compiler/expandoFunctionContextualTypes.types.diff index 9c769213b9..5ce0c72037 100644 --- a/testdata/baselines/reference/submodule/compiler/expandoFunctionContextualTypes.types.diff +++ b/testdata/baselines/reference/submodule/compiler/expandoFunctionContextualTypes.types.diff @@ -9,12 +9,3 @@ } interface StatelessComponent

{ -@@= skipped -11, +11 lines =@@ - - const MyComponent: StatelessComponent = () => null as any; - >MyComponent : StatelessComponent -->() => null as any : { (): any; defaultProps: Partial; } -+>() => null as any : { (): any; defaultProps: { color: "red"; }; } - >null as any : any - - MyComponent.defaultProps = { diff --git a/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.errors.txt b/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.errors.txt deleted file mode 100644 index 0a369032a5..0000000000 --- a/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.errors.txt +++ /dev/null @@ -1,31 +0,0 @@ -propertyAssignmentUseParentType1.ts(5,14): error TS2322: Type '{ (): true; num: number; }' is not assignable to type 'N'. - Types of property 'num' are incompatible. - Type 'number' is not assignable to type '123'. -propertyAssignmentUseParentType1.ts(8,14): error TS2322: Type '{ (): true; nun: number; }' is not assignable to type '{ (): boolean; nun: 456; }'. - Types of property 'nun' are incompatible. - Type 'number' is not assignable to type '456'. - - -==== propertyAssignmentUseParentType1.ts (2 errors) ==== - interface N { - (): boolean - num: 123; - } - export const interfaced: N = () => true; - ~~~~~~~~~~ -!!! error TS2322: Type '{ (): true; num: number; }' is not assignable to type 'N'. -!!! error TS2322: Types of property 'num' are incompatible. -!!! error TS2322: Type 'number' is not assignable to type '123'. - interfaced.num = 123; - - export const inlined: { (): boolean; nun: 456 } = () => true; - ~~~~~~~ -!!! error TS2322: Type '{ (): true; nun: number; }' is not assignable to type '{ (): boolean; nun: 456; }'. -!!! error TS2322: Types of property 'nun' are incompatible. -!!! error TS2322: Type 'number' is not assignable to type '456'. - inlined.nun = 456; - - export const ignoreJsdoc = () => true; - /** @type {string} make sure to ignore jsdoc! */ - ignoreJsdoc.extra = 111 - \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.errors.txt.diff deleted file mode 100644 index 355b48b33e..0000000000 --- a/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.errors.txt.diff +++ /dev/null @@ -1,36 +0,0 @@ ---- old.propertyAssignmentUseParentType1.errors.txt -+++ new.propertyAssignmentUseParentType1.errors.txt -@@= skipped -0, +-1 lines =@@ -- -@@= skipped --1, +1 lines =@@ -+propertyAssignmentUseParentType1.ts(5,14): error TS2322: Type '{ (): true; num: number; }' is not assignable to type 'N'. -+ Types of property 'num' are incompatible. -+ Type 'number' is not assignable to type '123'. -+propertyAssignmentUseParentType1.ts(8,14): error TS2322: Type '{ (): true; nun: number; }' is not assignable to type '{ (): boolean; nun: 456; }'. -+ Types of property 'nun' are incompatible. -+ Type 'number' is not assignable to type '456'. -+ -+ -+==== propertyAssignmentUseParentType1.ts (2 errors) ==== -+ interface N { -+ (): boolean -+ num: 123; -+ } -+ export const interfaced: N = () => true; -+ ~~~~~~~~~~ -+!!! error TS2322: Type '{ (): true; num: number; }' is not assignable to type 'N'. -+!!! error TS2322: Types of property 'num' are incompatible. -+!!! error TS2322: Type 'number' is not assignable to type '123'. -+ interfaced.num = 123; -+ -+ export const inlined: { (): boolean; nun: 456 } = () => true; -+ ~~~~~~~ -+!!! error TS2322: Type '{ (): true; nun: number; }' is not assignable to type '{ (): boolean; nun: 456; }'. -+!!! error TS2322: Types of property 'nun' are incompatible. -+!!! error TS2322: Type 'number' is not assignable to type '456'. -+ inlined.nun = 456; -+ -+ export const ignoreJsdoc = () => true; -+ /** @type {string} make sure to ignore jsdoc! */ -+ ignoreJsdoc.extra = 111 -+ diff --git a/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.types b/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.types index a502c2264d..cb7970b949 100644 --- a/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.types +++ b/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.types @@ -8,7 +8,7 @@ interface N { } export const interfaced: N = () => true; >interfaced : N ->() => true : { (): true; num: number; } +>() => true : { (): true; num: 123; } >true : true interfaced.num = 123; @@ -21,7 +21,7 @@ interfaced.num = 123; export const inlined: { (): boolean; nun: 456 } = () => true; >inlined : { (): boolean; nun: 456; } >nun : 456 ->() => true : { (): true; nun: number; } +>() => true : { (): true; nun: 456; } >true : true inlined.nun = 456; diff --git a/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.types.diff b/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.types.diff deleted file mode 100644 index fba020ad40..0000000000 --- a/testdata/baselines/reference/submodule/conformance/propertyAssignmentUseParentType1.types.diff +++ /dev/null @@ -1,20 +0,0 @@ ---- old.propertyAssignmentUseParentType1.types -+++ new.propertyAssignmentUseParentType1.types -@@= skipped -7, +7 lines =@@ - } - export const interfaced: N = () => true; - >interfaced : N -->() => true : { (): true; num: 123; } -+>() => true : { (): true; num: number; } - >true : true - - interfaced.num = 123; -@@= skipped -13, +13 lines =@@ - export const inlined: { (): boolean; nun: 456 } = () => true; - >inlined : { (): boolean; nun: 456; } - >nun : 456 -->() => true : { (): true; nun: 456; } -+>() => true : { (): true; nun: number; } - >true : true - - inlined.nun = 456; diff --git a/testdata/baselines/reference/submodule/conformance/typeFromPropertyAssignment30.types b/testdata/baselines/reference/submodule/conformance/typeFromPropertyAssignment30.types index a335a1f7dc..73d2364a29 100644 --- a/testdata/baselines/reference/submodule/conformance/typeFromPropertyAssignment30.types +++ b/testdata/baselines/reference/submodule/conformance/typeFromPropertyAssignment30.types @@ -9,7 +9,7 @@ interface Combo { } const c: Combo = () => 1 >c : Combo ->() => 1 : { (): number; p: {}; } +>() => 1 : { (): number; p: { [s: string]: number; }; } >1 : 1 // should not be an expando object, but contextually typed by Combo.p diff --git a/testdata/baselines/reference/submodule/conformance/typeFromPropertyAssignment30.types.diff b/testdata/baselines/reference/submodule/conformance/typeFromPropertyAssignment30.types.diff new file mode 100644 index 0000000000..3721e69557 --- /dev/null +++ b/testdata/baselines/reference/submodule/conformance/typeFromPropertyAssignment30.types.diff @@ -0,0 +1,11 @@ +--- old.typeFromPropertyAssignment30.types ++++ new.typeFromPropertyAssignment30.types +@@= skipped -8, +8 lines =@@ + } + const c: Combo = () => 1 + >c : Combo +->() => 1 : { (): number; p: {}; } ++>() => 1 : { (): number; p: { [s: string]: number; }; } + >1 : 1 + + // should not be an expando object, but contextually typed by Combo.p From 5a9e3da31b7c4d715711b66467a2563a2e8eea06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 25 Mar 2025 11:42:10 +0100 Subject: [PATCH 2/2] code tweaks --- internal/checker/checker.go | 58 +++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 90c36b6ca6..c9aea29aed 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -15524,23 +15524,21 @@ func (c *Checker) getTypeOfFuncClassEnumModule(symbol *ast.Symbol) *Type { if links.resolvedType == nil { expando := c.getSymbolOfExpando(symbol.ValueDeclaration) if expando != nil { - target := symbol - source := expando - inferred := core.IfElse(target.Flags&ast.SymbolFlagsTransient != 0, target, nil) + inferred := core.IfElse(symbol.Flags&ast.SymbolFlagsTransient != 0, symbol, nil) if inferred == nil { - inferred = c.cloneSymbol(target) + inferred = c.cloneSymbol(symbol) } - if len(source.Exports) != 0 { + if len(expando.Exports) != 0 { if inferred.Exports == nil { inferred.Exports = make(ast.SymbolTable) } - c.mergeSymbolTable(inferred.Exports, source.Exports, false, nil) + c.mergeSymbolTable(inferred.Exports, expando.Exports, false, nil) } - if len(source.Members) != 0 { + if len(expando.Members) != 0 { if inferred.Members == nil { inferred.Members = make(ast.SymbolTable) } - c.mergeSymbolTable(inferred.Members, source.Members, false, nil) + c.mergeSymbolTable(inferred.Members, expando.Members, false, nil) } symbol = inferred links = c.valueSymbolLinks.Get(inferred) @@ -16697,21 +16695,17 @@ func (c *Checker) getTypeOfPrototypeProperty(prototype *ast.Symbol) *Type { } func (c *Checker) getWidenedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Type { - var annotatedType *Type + annotatedType := c.getAnnotatedTypeForAssignmentDeclaration(symbol) + if annotatedType != nil { + return annotatedType + } var types []*Type for _, declaration := range symbol.Declarations { if ast.IsBinaryExpression(declaration) { - annotatedType = c.getAnnotatedTypeForAssignmentDeclaration(symbol) - if annotatedType == nil { - types = core.AppendIfUnique(types, c.checkExpressionForMutableLocation(declaration.AsBinaryExpression().Right, CheckModeNormal)) - } + types = core.AppendIfUnique(types, c.checkExpressionForMutableLocation(declaration.AsBinaryExpression().Right, CheckModeNormal)) } } - t := annotatedType - if t == nil { - t = c.getUnionType(types) - } - return c.getWidenedType(t) + return c.getWidenedType(c.getUnionType(types)) } func (c *Checker) widenTypeForVariableLikeDeclaration(t *Type, declaration *ast.Node, reportErrors bool) *Type { @@ -29770,23 +29764,25 @@ func (c *Checker) getSymbolOfExpando(node *ast.Node) *ast.Symbol { return nil } return c.getSymbolOfDeclaration(node.Parent) - } return nil } func (c *Checker) getAnnotatedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Type { - if symbol.Parent != nil && symbol.Parent.ValueDeclaration != nil { - possiblyAnnotatedSymbol := c.getFunctionExpressionParentSymbolOrSymbol(symbol.Parent) - if possiblyAnnotatedSymbol != nil && possiblyAnnotatedSymbol.ValueDeclaration != nil && possiblyAnnotatedSymbol.ValueDeclaration.Kind != ast.KindFunctionDeclaration { - typeNode := possiblyAnnotatedSymbol.ValueDeclaration.Type() - if typeNode != nil { - annotationSymbol := c.getPropertyOfType(c.getTypeFromTypeNode(typeNode), symbol.Name) - if annotationSymbol != nil { - return c.getNonMissingTypeOfSymbol(annotationSymbol) - } - } - } + if symbol.Parent == nil || symbol.Parent.ValueDeclaration == nil { + return nil } - return nil + possiblyAnnotatedSymbol := c.getFunctionExpressionParentSymbolOrSymbol(symbol.Parent) + if possiblyAnnotatedSymbol == nil || possiblyAnnotatedSymbol.ValueDeclaration == nil || possiblyAnnotatedSymbol.ValueDeclaration.Kind == ast.KindFunctionDeclaration { + return nil + } + typeNode := possiblyAnnotatedSymbol.ValueDeclaration.Type() + if typeNode == nil { + return nil + } + annotationSymbol := c.getPropertyOfType(c.getTypeFromTypeNode(typeNode), symbol.Name) + if annotationSymbol == nil { + return nil + } + return c.getNonMissingTypeOfSymbol(annotationSymbol) }