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