diff --git a/internal/binder/binder.go b/internal/binder/binder.go index f4b9274763..f7ceba0bd3 100644 --- a/internal/binder/binder.go +++ b/internal/binder/binder.go @@ -989,9 +989,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 4ea33b61c5..c9aea29aed 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -13516,6 +13516,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 } @@ -14633,7 +14647,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) @@ -15506,8 +15520,31 @@ 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 { + inferred := core.IfElse(symbol.Flags&ast.SymbolFlagsTransient != 0, symbol, nil) + if inferred == nil { + inferred = c.cloneSymbol(symbol) + } + if len(expando.Exports) != 0 { + if inferred.Exports == nil { + inferred.Exports = make(ast.SymbolTable) + } + c.mergeSymbolTable(inferred.Exports, expando.Exports, false, nil) + } + if len(expando.Members) != 0 { + if inferred.Members == nil { + inferred.Members = make(ast.SymbolTable) + } + c.mergeSymbolTable(inferred.Members, expando.Members, false, nil) + } + symbol = inferred + links = c.valueSymbolLinks.Get(inferred) + } links.resolvedType = c.getTypeOfFuncClassEnumModuleWorker(symbol) + originalLinks.resolvedType = links.resolvedType } return links.resolvedType } @@ -16658,6 +16695,10 @@ func (c *Checker) getTypeOfPrototypeProperty(prototype *ast.Symbol) *Type { } func (c *Checker) getWidenedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Type { + annotatedType := c.getAnnotatedTypeForAssignmentDeclaration(symbol) + if annotatedType != nil { + return annotatedType + } var types []*Type for _, declaration := range symbol.Declarations { if ast.IsBinaryExpression(declaration) { @@ -29713,3 +29754,35 @@ 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 { + 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) +} 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