diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f56f56223cf..2f466267f9d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5396,7 +5396,7 @@ namespace ts { return undefined; } - function getWidenedTypeFromAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) { + function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) { // function/class/{} initializers are themselves containers, so they won't merge in the same way as other initializers const container = getAssignedExpandoInitializer(symbol.valueDeclaration); if (container) { @@ -5429,7 +5429,7 @@ namespace ts { } } if (!isCallExpression(expression)) { - jsdocType = getJSDocTypeFromAssignmentDeclaration(jsdocType, expression, symbol, declaration); + jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); } if (!jsdocType) { (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); @@ -5478,8 +5478,8 @@ namespace ts { return type; } - function getJSDocTypeFromAssignmentDeclaration(declaredType: Type | undefined, expression: Expression, _symbol: Symbol, declaration: Declaration) { - const typeNode = getJSDocType(expression.parent); + function getAnnotatedTypeForAssignmentDeclaration(declaredType: Type | undefined, expression: Expression, symbol: Symbol, declaration: Declaration) { + const typeNode = getEffectiveTypeAnnotationNode(expression.parent); if (typeNode) { const type = getWidenedType(getTypeFromTypeNode(typeNode)); if (!declaredType) { @@ -5489,6 +5489,13 @@ namespace ts { errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type); } } + if (symbol.parent) { + const typeNode = getEffectiveTypeAnnotationNode(symbol.parent.valueDeclaration); + if (typeNode) { + return getTypeOfPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); + } + } + return declaredType; } @@ -5783,7 +5790,7 @@ namespace ts { } else if (isInJSFile(declaration) && (isCallExpression(declaration) || isBinaryExpression(declaration) || isPropertyAccessExpression(declaration) && isBinaryExpression(declaration.parent))) { - type = getWidenedTypeFromAssignmentDeclaration(symbol); + type = getWidenedTypeForAssignmentDeclaration(symbol); } else if (isJSDocPropertyLikeTag(declaration) || isPropertyAccessExpression(declaration) @@ -5798,7 +5805,7 @@ namespace ts { return getTypeOfFuncClassEnumModule(symbol); } type = isBinaryExpression(declaration.parent) ? - getWidenedTypeFromAssignmentDeclaration(symbol) : + getWidenedTypeForAssignmentDeclaration(symbol) : tryGetTypeFromEffectiveTypeNode(declaration) || anyType; } else if (isPropertyAssignment(declaration)) { @@ -5969,7 +5976,7 @@ namespace ts { } else if (declaration.kind === SyntaxKind.BinaryExpression || declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) { - return getWidenedTypeFromAssignmentDeclaration(symbol); + return getWidenedTypeForAssignmentDeclaration(symbol); } else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) { const resolvedModule = resolveExternalModuleSymbol(symbol); @@ -5978,7 +5985,7 @@ namespace ts { return errorType; } const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!); - const type = getWidenedTypeFromAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule); + const type = getWidenedTypeForAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule); if (!popTypeResolution()) { return reportCircularityError(symbol); } @@ -19157,7 +19164,7 @@ namespace ts { } /** - * Woah! Do you really want to use this function? + * Whoa! Do you really want to use this function? * * Unless you're trying to get the *non-apparent* type for a * value-literal type or you're authoring relevant portions of this algorithm, diff --git a/tests/baselines/reference/expandoFunctionContextualTypes.types b/tests/baselines/reference/expandoFunctionContextualTypes.types index e1eca92659c..915b168776f 100644 --- a/tests/baselines/reference/expandoFunctionContextualTypes.types +++ b/tests/baselines/reference/expandoFunctionContextualTypes.types @@ -12,7 +12,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 >null : null diff --git a/tests/baselines/reference/expandoFunctionContextualTypesJs.types b/tests/baselines/reference/expandoFunctionContextualTypesJs.types index 081c57c0dac..82442a9db4a 100644 --- a/tests/baselines/reference/expandoFunctionContextualTypesJs.types +++ b/tests/baselines/reference/expandoFunctionContextualTypesJs.types @@ -10,7 +10,7 @@ */ const MyComponent = () => /* @type {any} */(null); >MyComponent : { (): any; defaultProps?: Partial<{ color: "red" | "blue"; }>; } ->() => /* @type {any} */(null) : { (): any; defaultProps: { color: "red"; }; } +>() => /* @type {any} */(null) : { (): any; defaultProps: Partial<{ color: "red" | "blue"; }>; } >(null) : null >null : null diff --git a/tests/baselines/reference/propertyAssignmentUseParentType1.js b/tests/baselines/reference/propertyAssignmentUseParentType1.js new file mode 100644 index 00000000000..5e839a31da4 --- /dev/null +++ b/tests/baselines/reference/propertyAssignmentUseParentType1.js @@ -0,0 +1,26 @@ +//// [propertyAssignmentUseParentType1.ts] +interface N { + (): boolean + num: 123; +} +export const interfaced: N = () => true; +interfaced.num = 123; + +export const inlined: { (): boolean; nun: 456 } = () => true; +inlined.nun = 456; + +export const ignoreJsdoc = () => true; +/** @type {string} make sure to ignore jsdoc! */ +ignoreJsdoc.extra = 111 + + +//// [propertyAssignmentUseParentType1.js] +"use strict"; +exports.__esModule = true; +exports.interfaced = function () { return true; }; +exports.interfaced.num = 123; +exports.inlined = function () { return true; }; +exports.inlined.nun = 456; +exports.ignoreJsdoc = function () { return true; }; +/** @type {string} make sure to ignore jsdoc! */ +exports.ignoreJsdoc.extra = 111; diff --git a/tests/baselines/reference/propertyAssignmentUseParentType1.symbols b/tests/baselines/reference/propertyAssignmentUseParentType1.symbols new file mode 100644 index 00000000000..081e2f12604 --- /dev/null +++ b/tests/baselines/reference/propertyAssignmentUseParentType1.symbols @@ -0,0 +1,35 @@ +=== tests/cases/conformance/salsa/propertyAssignmentUseParentType1.ts === +interface N { +>N : Symbol(N, Decl(propertyAssignmentUseParentType1.ts, 0, 0)) + + (): boolean + num: 123; +>num : Symbol(N.num, Decl(propertyAssignmentUseParentType1.ts, 1, 15)) +} +export const interfaced: N = () => true; +>interfaced : Symbol(interfaced, Decl(propertyAssignmentUseParentType1.ts, 4, 12), Decl(propertyAssignmentUseParentType1.ts, 4, 40)) +>N : Symbol(N, Decl(propertyAssignmentUseParentType1.ts, 0, 0)) + +interfaced.num = 123; +>interfaced.num : Symbol(N.num, Decl(propertyAssignmentUseParentType1.ts, 1, 15)) +>interfaced : Symbol(interfaced, Decl(propertyAssignmentUseParentType1.ts, 4, 12), Decl(propertyAssignmentUseParentType1.ts, 4, 40)) +>num : Symbol(N.num, Decl(propertyAssignmentUseParentType1.ts, 1, 15)) + +export const inlined: { (): boolean; nun: 456 } = () => true; +>inlined : Symbol(inlined, Decl(propertyAssignmentUseParentType1.ts, 7, 12), Decl(propertyAssignmentUseParentType1.ts, 7, 61)) +>nun : Symbol(nun, Decl(propertyAssignmentUseParentType1.ts, 7, 36)) + +inlined.nun = 456; +>inlined.nun : Symbol(nun, Decl(propertyAssignmentUseParentType1.ts, 7, 36)) +>inlined : Symbol(inlined, Decl(propertyAssignmentUseParentType1.ts, 7, 12), Decl(propertyAssignmentUseParentType1.ts, 7, 61)) +>nun : Symbol(nun, Decl(propertyAssignmentUseParentType1.ts, 7, 36)) + +export const ignoreJsdoc = () => true; +>ignoreJsdoc : Symbol(ignoreJsdoc, Decl(propertyAssignmentUseParentType1.ts, 10, 12), Decl(propertyAssignmentUseParentType1.ts, 10, 38)) + +/** @type {string} make sure to ignore jsdoc! */ +ignoreJsdoc.extra = 111 +>ignoreJsdoc.extra : Symbol(ignoreJsdoc.extra, Decl(propertyAssignmentUseParentType1.ts, 10, 38)) +>ignoreJsdoc : Symbol(ignoreJsdoc, Decl(propertyAssignmentUseParentType1.ts, 10, 12), Decl(propertyAssignmentUseParentType1.ts, 10, 38)) +>extra : Symbol(ignoreJsdoc.extra, Decl(propertyAssignmentUseParentType1.ts, 10, 38)) + diff --git a/tests/baselines/reference/propertyAssignmentUseParentType1.types b/tests/baselines/reference/propertyAssignmentUseParentType1.types new file mode 100644 index 00000000000..f3bd5010b20 --- /dev/null +++ b/tests/baselines/reference/propertyAssignmentUseParentType1.types @@ -0,0 +1,44 @@ +=== tests/cases/conformance/salsa/propertyAssignmentUseParentType1.ts === +interface N { + (): boolean + num: 123; +>num : 123 +} +export const interfaced: N = () => true; +>interfaced : N +>() => true : { (): true; num: 123; } +>true : true + +interfaced.num = 123; +>interfaced.num = 123 : 123 +>interfaced.num : 123 +>interfaced : N +>num : 123 +>123 : 123 + +export const inlined: { (): boolean; nun: 456 } = () => true; +>inlined : { (): boolean; nun: 456; } +>nun : 456 +>() => true : { (): true; nun: 456; } +>true : true + +inlined.nun = 456; +>inlined.nun = 456 : 456 +>inlined.nun : 456 +>inlined : { (): boolean; nun: 456; } +>nun : 456 +>456 : 456 + +export const ignoreJsdoc = () => true; +>ignoreJsdoc : { (): boolean; extra: number; } +>() => true : { (): boolean; extra: number; } +>true : true + +/** @type {string} make sure to ignore jsdoc! */ +ignoreJsdoc.extra = 111 +>ignoreJsdoc.extra = 111 : 111 +>ignoreJsdoc.extra : number +>ignoreJsdoc : { (): boolean; extra: number; } +>extra : number +>111 : 111 + diff --git a/tests/baselines/reference/propertyAssignmentUseParentType2.errors.txt b/tests/baselines/reference/propertyAssignmentUseParentType2.errors.txt new file mode 100644 index 00000000000..b88e276b405 --- /dev/null +++ b/tests/baselines/reference/propertyAssignmentUseParentType2.errors.txt @@ -0,0 +1,24 @@ +tests/cases/conformance/salsa/propertyAssignmentUseParentType2.js(11,14): error TS2322: Type '{ (): boolean; nuo: 1000; }' is not assignable to type '{ (): boolean; nuo: 789; }'. + Types of property 'nuo' are incompatible. + Type '1000' is not assignable to type '789'. + + +==== tests/cases/conformance/salsa/propertyAssignmentUseParentType2.js (1 errors) ==== + /** @type {{ (): boolean; nuo: 789 }} */ + export const inlined = () => true + inlined.nuo = 789 + + /** @type {{ (): boolean; nuo: 789 }} */ + export const duplicated = () => true + /** @type {789} */ + duplicated.nuo = 789 + + /** @type {{ (): boolean; nuo: 789 }} */ + export const conflictingDuplicated = () => true + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ (): boolean; nuo: 1000; }' is not assignable to type '{ (): boolean; nuo: 789; }'. +!!! error TS2322: Types of property 'nuo' are incompatible. +!!! error TS2322: Type '1000' is not assignable to type '789'. + /** @type {1000} */ + conflictingDuplicated.nuo = 789 + \ No newline at end of file diff --git a/tests/baselines/reference/propertyAssignmentUseParentType2.symbols b/tests/baselines/reference/propertyAssignmentUseParentType2.symbols new file mode 100644 index 00000000000..93b8b9290da --- /dev/null +++ b/tests/baselines/reference/propertyAssignmentUseParentType2.symbols @@ -0,0 +1,30 @@ +=== tests/cases/conformance/salsa/propertyAssignmentUseParentType2.js === +/** @type {{ (): boolean; nuo: 789 }} */ +export const inlined = () => true +>inlined : Symbol(inlined, Decl(propertyAssignmentUseParentType2.js, 1, 12), Decl(propertyAssignmentUseParentType2.js, 1, 33)) + +inlined.nuo = 789 +>inlined.nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 0, 25)) +>inlined : Symbol(inlined, Decl(propertyAssignmentUseParentType2.js, 1, 12), Decl(propertyAssignmentUseParentType2.js, 1, 33)) +>nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 0, 25)) + +/** @type {{ (): boolean; nuo: 789 }} */ +export const duplicated = () => true +>duplicated : Symbol(duplicated, Decl(propertyAssignmentUseParentType2.js, 5, 12), Decl(propertyAssignmentUseParentType2.js, 5, 36)) + +/** @type {789} */ +duplicated.nuo = 789 +>duplicated.nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 4, 25)) +>duplicated : Symbol(duplicated, Decl(propertyAssignmentUseParentType2.js, 5, 12), Decl(propertyAssignmentUseParentType2.js, 5, 36)) +>nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 4, 25)) + +/** @type {{ (): boolean; nuo: 789 }} */ +export const conflictingDuplicated = () => true +>conflictingDuplicated : Symbol(conflictingDuplicated, Decl(propertyAssignmentUseParentType2.js, 10, 12), Decl(propertyAssignmentUseParentType2.js, 10, 47)) + +/** @type {1000} */ +conflictingDuplicated.nuo = 789 +>conflictingDuplicated.nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 9, 25)) +>conflictingDuplicated : Symbol(conflictingDuplicated, Decl(propertyAssignmentUseParentType2.js, 10, 12), Decl(propertyAssignmentUseParentType2.js, 10, 47)) +>nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 9, 25)) + diff --git a/tests/baselines/reference/propertyAssignmentUseParentType2.types b/tests/baselines/reference/propertyAssignmentUseParentType2.types new file mode 100644 index 00000000000..24118a9ee73 --- /dev/null +++ b/tests/baselines/reference/propertyAssignmentUseParentType2.types @@ -0,0 +1,42 @@ +=== tests/cases/conformance/salsa/propertyAssignmentUseParentType2.js === +/** @type {{ (): boolean; nuo: 789 }} */ +export const inlined = () => true +>inlined : { (): boolean; nuo: 789; } +>() => true : { (): boolean; nuo: 789; } +>true : true + +inlined.nuo = 789 +>inlined.nuo = 789 : 789 +>inlined.nuo : 789 +>inlined : { (): boolean; nuo: 789; } +>nuo : 789 +>789 : 789 + +/** @type {{ (): boolean; nuo: 789 }} */ +export const duplicated = () => true +>duplicated : { (): boolean; nuo: 789; } +>() => true : { (): boolean; nuo: 789; } +>true : true + +/** @type {789} */ +duplicated.nuo = 789 +>duplicated.nuo = 789 : 789 +>duplicated.nuo : 789 +>duplicated : { (): boolean; nuo: 789; } +>nuo : 789 +>789 : 789 + +/** @type {{ (): boolean; nuo: 789 }} */ +export const conflictingDuplicated = () => true +>conflictingDuplicated : { (): boolean; nuo: 789; } +>() => true : { (): boolean; nuo: 1000; } +>true : true + +/** @type {1000} */ +conflictingDuplicated.nuo = 789 +>conflictingDuplicated.nuo = 789 : 789 +>conflictingDuplicated.nuo : 789 +>conflictingDuplicated : { (): boolean; nuo: 789; } +>nuo : 789 +>789 : 789 + diff --git a/tests/cases/conformance/salsa/propertyAssignmentUseParentType1.ts b/tests/cases/conformance/salsa/propertyAssignmentUseParentType1.ts new file mode 100644 index 00000000000..500ae6f2fe3 --- /dev/null +++ b/tests/cases/conformance/salsa/propertyAssignmentUseParentType1.ts @@ -0,0 +1,13 @@ +interface N { + (): boolean + num: 123; +} +export const interfaced: N = () => true; +interfaced.num = 123; + +export const inlined: { (): boolean; nun: 456 } = () => true; +inlined.nun = 456; + +export const ignoreJsdoc = () => true; +/** @type {string} make sure to ignore jsdoc! */ +ignoreJsdoc.extra = 111 diff --git a/tests/cases/conformance/salsa/propertyAssignmentUseParentType2.ts b/tests/cases/conformance/salsa/propertyAssignmentUseParentType2.ts new file mode 100644 index 00000000000..53696abbf81 --- /dev/null +++ b/tests/cases/conformance/salsa/propertyAssignmentUseParentType2.ts @@ -0,0 +1,18 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: propertyAssignmentUseParentType2.js + +/** @type {{ (): boolean; nuo: 789 }} */ +export const inlined = () => true +inlined.nuo = 789 + +/** @type {{ (): boolean; nuo: 789 }} */ +export const duplicated = () => true +/** @type {789} */ +duplicated.nuo = 789 + +/** @type {{ (): boolean; nuo: 789 }} */ +export const conflictingDuplicated = () => true +/** @type {1000} */ +conflictingDuplicated.nuo = 789