Skip to content

Commit 772bee5

Browse files
authored
Property assignment uses parent type annotation (microsoft#32553)
* Property assignment uses parent type annotation First draft, will write full explanation later. Also makes sure that jsdoc is ignored in TS. It was not before. * Update baselines
1 parent aa12ec4 commit 772bee5

11 files changed

+250
-11
lines changed

src/compiler/checker.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5396,7 +5396,7 @@ namespace ts {
53965396
return undefined;
53975397
}
53985398

5399-
function getWidenedTypeFromAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) {
5399+
function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) {
54005400
// function/class/{} initializers are themselves containers, so they won't merge in the same way as other initializers
54015401
const container = getAssignedExpandoInitializer(symbol.valueDeclaration);
54025402
if (container) {
@@ -5429,7 +5429,7 @@ namespace ts {
54295429
}
54305430
}
54315431
if (!isCallExpression(expression)) {
5432-
jsdocType = getJSDocTypeFromAssignmentDeclaration(jsdocType, expression, symbol, declaration);
5432+
jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration);
54335433
}
54345434
if (!jsdocType) {
54355435
(types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType);
@@ -5478,8 +5478,8 @@ namespace ts {
54785478
return type;
54795479
}
54805480

5481-
function getJSDocTypeFromAssignmentDeclaration(declaredType: Type | undefined, expression: Expression, _symbol: Symbol, declaration: Declaration) {
5482-
const typeNode = getJSDocType(expression.parent);
5481+
function getAnnotatedTypeForAssignmentDeclaration(declaredType: Type | undefined, expression: Expression, symbol: Symbol, declaration: Declaration) {
5482+
const typeNode = getEffectiveTypeAnnotationNode(expression.parent);
54835483
if (typeNode) {
54845484
const type = getWidenedType(getTypeFromTypeNode(typeNode));
54855485
if (!declaredType) {
@@ -5489,6 +5489,13 @@ namespace ts {
54895489
errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type);
54905490
}
54915491
}
5492+
if (symbol.parent) {
5493+
const typeNode = getEffectiveTypeAnnotationNode(symbol.parent.valueDeclaration);
5494+
if (typeNode) {
5495+
return getTypeOfPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName);
5496+
}
5497+
}
5498+
54925499
return declaredType;
54935500
}
54945501

@@ -5783,7 +5790,7 @@ namespace ts {
57835790
}
57845791
else if (isInJSFile(declaration) &&
57855792
(isCallExpression(declaration) || isBinaryExpression(declaration) || isPropertyAccessExpression(declaration) && isBinaryExpression(declaration.parent))) {
5786-
type = getWidenedTypeFromAssignmentDeclaration(symbol);
5793+
type = getWidenedTypeForAssignmentDeclaration(symbol);
57875794
}
57885795
else if (isJSDocPropertyLikeTag(declaration)
57895796
|| isPropertyAccessExpression(declaration)
@@ -5798,7 +5805,7 @@ namespace ts {
57985805
return getTypeOfFuncClassEnumModule(symbol);
57995806
}
58005807
type = isBinaryExpression(declaration.parent) ?
5801-
getWidenedTypeFromAssignmentDeclaration(symbol) :
5808+
getWidenedTypeForAssignmentDeclaration(symbol) :
58025809
tryGetTypeFromEffectiveTypeNode(declaration) || anyType;
58035810
}
58045811
else if (isPropertyAssignment(declaration)) {
@@ -5969,7 +5976,7 @@ namespace ts {
59695976
}
59705977
else if (declaration.kind === SyntaxKind.BinaryExpression ||
59715978
declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) {
5972-
return getWidenedTypeFromAssignmentDeclaration(symbol);
5979+
return getWidenedTypeForAssignmentDeclaration(symbol);
59735980
}
59745981
else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) {
59755982
const resolvedModule = resolveExternalModuleSymbol(symbol);
@@ -5978,7 +5985,7 @@ namespace ts {
59785985
return errorType;
59795986
}
59805987
const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!);
5981-
const type = getWidenedTypeFromAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule);
5988+
const type = getWidenedTypeForAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule);
59825989
if (!popTypeResolution()) {
59835990
return reportCircularityError(symbol);
59845991
}
@@ -19157,7 +19164,7 @@ namespace ts {
1915719164
}
1915819165

1915919166
/**
19160-
* Woah! Do you really want to use this function?
19167+
* Whoa! Do you really want to use this function?
1916119168
*
1916219169
* Unless you're trying to get the *non-apparent* type for a
1916319170
* value-literal type or you're authoring relevant portions of this algorithm,

tests/baselines/reference/expandoFunctionContextualTypes.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface StatelessComponent<P> {
1212

1313
const MyComponent: StatelessComponent<MyComponentProps> = () => null as any;
1414
>MyComponent : StatelessComponent<MyComponentProps>
15-
>() => null as any : { (): any; defaultProps: { color: "red"; }; }
15+
>() => null as any : { (): any; defaultProps: Partial<MyComponentProps>; }
1616
>null as any : any
1717
>null : null
1818

tests/baselines/reference/expandoFunctionContextualTypesJs.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*/
1111
const MyComponent = () => /* @type {any} */(null);
1212
>MyComponent : { (): any; defaultProps?: Partial<{ color: "red" | "blue"; }>; }
13-
>() => /* @type {any} */(null) : { (): any; defaultProps: { color: "red"; }; }
13+
>() => /* @type {any} */(null) : { (): any; defaultProps: Partial<{ color: "red" | "blue"; }>; }
1414
>(null) : null
1515
>null : null
1616

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [propertyAssignmentUseParentType1.ts]
2+
interface N {
3+
(): boolean
4+
num: 123;
5+
}
6+
export const interfaced: N = () => true;
7+
interfaced.num = 123;
8+
9+
export const inlined: { (): boolean; nun: 456 } = () => true;
10+
inlined.nun = 456;
11+
12+
export const ignoreJsdoc = () => true;
13+
/** @type {string} make sure to ignore jsdoc! */
14+
ignoreJsdoc.extra = 111
15+
16+
17+
//// [propertyAssignmentUseParentType1.js]
18+
"use strict";
19+
exports.__esModule = true;
20+
exports.interfaced = function () { return true; };
21+
exports.interfaced.num = 123;
22+
exports.inlined = function () { return true; };
23+
exports.inlined.nun = 456;
24+
exports.ignoreJsdoc = function () { return true; };
25+
/** @type {string} make sure to ignore jsdoc! */
26+
exports.ignoreJsdoc.extra = 111;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
=== tests/cases/conformance/salsa/propertyAssignmentUseParentType1.ts ===
2+
interface N {
3+
>N : Symbol(N, Decl(propertyAssignmentUseParentType1.ts, 0, 0))
4+
5+
(): boolean
6+
num: 123;
7+
>num : Symbol(N.num, Decl(propertyAssignmentUseParentType1.ts, 1, 15))
8+
}
9+
export const interfaced: N = () => true;
10+
>interfaced : Symbol(interfaced, Decl(propertyAssignmentUseParentType1.ts, 4, 12), Decl(propertyAssignmentUseParentType1.ts, 4, 40))
11+
>N : Symbol(N, Decl(propertyAssignmentUseParentType1.ts, 0, 0))
12+
13+
interfaced.num = 123;
14+
>interfaced.num : Symbol(N.num, Decl(propertyAssignmentUseParentType1.ts, 1, 15))
15+
>interfaced : Symbol(interfaced, Decl(propertyAssignmentUseParentType1.ts, 4, 12), Decl(propertyAssignmentUseParentType1.ts, 4, 40))
16+
>num : Symbol(N.num, Decl(propertyAssignmentUseParentType1.ts, 1, 15))
17+
18+
export const inlined: { (): boolean; nun: 456 } = () => true;
19+
>inlined : Symbol(inlined, Decl(propertyAssignmentUseParentType1.ts, 7, 12), Decl(propertyAssignmentUseParentType1.ts, 7, 61))
20+
>nun : Symbol(nun, Decl(propertyAssignmentUseParentType1.ts, 7, 36))
21+
22+
inlined.nun = 456;
23+
>inlined.nun : Symbol(nun, Decl(propertyAssignmentUseParentType1.ts, 7, 36))
24+
>inlined : Symbol(inlined, Decl(propertyAssignmentUseParentType1.ts, 7, 12), Decl(propertyAssignmentUseParentType1.ts, 7, 61))
25+
>nun : Symbol(nun, Decl(propertyAssignmentUseParentType1.ts, 7, 36))
26+
27+
export const ignoreJsdoc = () => true;
28+
>ignoreJsdoc : Symbol(ignoreJsdoc, Decl(propertyAssignmentUseParentType1.ts, 10, 12), Decl(propertyAssignmentUseParentType1.ts, 10, 38))
29+
30+
/** @type {string} make sure to ignore jsdoc! */
31+
ignoreJsdoc.extra = 111
32+
>ignoreJsdoc.extra : Symbol(ignoreJsdoc.extra, Decl(propertyAssignmentUseParentType1.ts, 10, 38))
33+
>ignoreJsdoc : Symbol(ignoreJsdoc, Decl(propertyAssignmentUseParentType1.ts, 10, 12), Decl(propertyAssignmentUseParentType1.ts, 10, 38))
34+
>extra : Symbol(ignoreJsdoc.extra, Decl(propertyAssignmentUseParentType1.ts, 10, 38))
35+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
=== tests/cases/conformance/salsa/propertyAssignmentUseParentType1.ts ===
2+
interface N {
3+
(): boolean
4+
num: 123;
5+
>num : 123
6+
}
7+
export const interfaced: N = () => true;
8+
>interfaced : N
9+
>() => true : { (): true; num: 123; }
10+
>true : true
11+
12+
interfaced.num = 123;
13+
>interfaced.num = 123 : 123
14+
>interfaced.num : 123
15+
>interfaced : N
16+
>num : 123
17+
>123 : 123
18+
19+
export const inlined: { (): boolean; nun: 456 } = () => true;
20+
>inlined : { (): boolean; nun: 456; }
21+
>nun : 456
22+
>() => true : { (): true; nun: 456; }
23+
>true : true
24+
25+
inlined.nun = 456;
26+
>inlined.nun = 456 : 456
27+
>inlined.nun : 456
28+
>inlined : { (): boolean; nun: 456; }
29+
>nun : 456
30+
>456 : 456
31+
32+
export const ignoreJsdoc = () => true;
33+
>ignoreJsdoc : { (): boolean; extra: number; }
34+
>() => true : { (): boolean; extra: number; }
35+
>true : true
36+
37+
/** @type {string} make sure to ignore jsdoc! */
38+
ignoreJsdoc.extra = 111
39+
>ignoreJsdoc.extra = 111 : 111
40+
>ignoreJsdoc.extra : number
41+
>ignoreJsdoc : { (): boolean; extra: number; }
42+
>extra : number
43+
>111 : 111
44+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
tests/cases/conformance/salsa/propertyAssignmentUseParentType2.js(11,14): error TS2322: Type '{ (): boolean; nuo: 1000; }' is not assignable to type '{ (): boolean; nuo: 789; }'.
2+
Types of property 'nuo' are incompatible.
3+
Type '1000' is not assignable to type '789'.
4+
5+
6+
==== tests/cases/conformance/salsa/propertyAssignmentUseParentType2.js (1 errors) ====
7+
/** @type {{ (): boolean; nuo: 789 }} */
8+
export const inlined = () => true
9+
inlined.nuo = 789
10+
11+
/** @type {{ (): boolean; nuo: 789 }} */
12+
export const duplicated = () => true
13+
/** @type {789} */
14+
duplicated.nuo = 789
15+
16+
/** @type {{ (): boolean; nuo: 789 }} */
17+
export const conflictingDuplicated = () => true
18+
~~~~~~~~~~~~~~~~~~~~~
19+
!!! error TS2322: Type '{ (): boolean; nuo: 1000; }' is not assignable to type '{ (): boolean; nuo: 789; }'.
20+
!!! error TS2322: Types of property 'nuo' are incompatible.
21+
!!! error TS2322: Type '1000' is not assignable to type '789'.
22+
/** @type {1000} */
23+
conflictingDuplicated.nuo = 789
24+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
=== tests/cases/conformance/salsa/propertyAssignmentUseParentType2.js ===
2+
/** @type {{ (): boolean; nuo: 789 }} */
3+
export const inlined = () => true
4+
>inlined : Symbol(inlined, Decl(propertyAssignmentUseParentType2.js, 1, 12), Decl(propertyAssignmentUseParentType2.js, 1, 33))
5+
6+
inlined.nuo = 789
7+
>inlined.nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 0, 25))
8+
>inlined : Symbol(inlined, Decl(propertyAssignmentUseParentType2.js, 1, 12), Decl(propertyAssignmentUseParentType2.js, 1, 33))
9+
>nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 0, 25))
10+
11+
/** @type {{ (): boolean; nuo: 789 }} */
12+
export const duplicated = () => true
13+
>duplicated : Symbol(duplicated, Decl(propertyAssignmentUseParentType2.js, 5, 12), Decl(propertyAssignmentUseParentType2.js, 5, 36))
14+
15+
/** @type {789} */
16+
duplicated.nuo = 789
17+
>duplicated.nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 4, 25))
18+
>duplicated : Symbol(duplicated, Decl(propertyAssignmentUseParentType2.js, 5, 12), Decl(propertyAssignmentUseParentType2.js, 5, 36))
19+
>nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 4, 25))
20+
21+
/** @type {{ (): boolean; nuo: 789 }} */
22+
export const conflictingDuplicated = () => true
23+
>conflictingDuplicated : Symbol(conflictingDuplicated, Decl(propertyAssignmentUseParentType2.js, 10, 12), Decl(propertyAssignmentUseParentType2.js, 10, 47))
24+
25+
/** @type {1000} */
26+
conflictingDuplicated.nuo = 789
27+
>conflictingDuplicated.nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 9, 25))
28+
>conflictingDuplicated : Symbol(conflictingDuplicated, Decl(propertyAssignmentUseParentType2.js, 10, 12), Decl(propertyAssignmentUseParentType2.js, 10, 47))
29+
>nuo : Symbol(nuo, Decl(propertyAssignmentUseParentType2.js, 9, 25))
30+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/conformance/salsa/propertyAssignmentUseParentType2.js ===
2+
/** @type {{ (): boolean; nuo: 789 }} */
3+
export const inlined = () => true
4+
>inlined : { (): boolean; nuo: 789; }
5+
>() => true : { (): boolean; nuo: 789; }
6+
>true : true
7+
8+
inlined.nuo = 789
9+
>inlined.nuo = 789 : 789
10+
>inlined.nuo : 789
11+
>inlined : { (): boolean; nuo: 789; }
12+
>nuo : 789
13+
>789 : 789
14+
15+
/** @type {{ (): boolean; nuo: 789 }} */
16+
export const duplicated = () => true
17+
>duplicated : { (): boolean; nuo: 789; }
18+
>() => true : { (): boolean; nuo: 789; }
19+
>true : true
20+
21+
/** @type {789} */
22+
duplicated.nuo = 789
23+
>duplicated.nuo = 789 : 789
24+
>duplicated.nuo : 789
25+
>duplicated : { (): boolean; nuo: 789; }
26+
>nuo : 789
27+
>789 : 789
28+
29+
/** @type {{ (): boolean; nuo: 789 }} */
30+
export const conflictingDuplicated = () => true
31+
>conflictingDuplicated : { (): boolean; nuo: 789; }
32+
>() => true : { (): boolean; nuo: 1000; }
33+
>true : true
34+
35+
/** @type {1000} */
36+
conflictingDuplicated.nuo = 789
37+
>conflictingDuplicated.nuo = 789 : 789
38+
>conflictingDuplicated.nuo : 789
39+
>conflictingDuplicated : { (): boolean; nuo: 789; }
40+
>nuo : 789
41+
>789 : 789
42+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
interface N {
2+
(): boolean
3+
num: 123;
4+
}
5+
export const interfaced: N = () => true;
6+
interfaced.num = 123;
7+
8+
export const inlined: { (): boolean; nun: 456 } = () => true;
9+
inlined.nun = 456;
10+
11+
export const ignoreJsdoc = () => true;
12+
/** @type {string} make sure to ignore jsdoc! */
13+
ignoreJsdoc.extra = 111
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @noEmit: true
4+
// @Filename: propertyAssignmentUseParentType2.js
5+
6+
/** @type {{ (): boolean; nuo: 789 }} */
7+
export const inlined = () => true
8+
inlined.nuo = 789
9+
10+
/** @type {{ (): boolean; nuo: 789 }} */
11+
export const duplicated = () => true
12+
/** @type {789} */
13+
duplicated.nuo = 789
14+
15+
/** @type {{ (): boolean; nuo: 789 }} */
16+
export const conflictingDuplicated = () => true
17+
/** @type {1000} */
18+
conflictingDuplicated.nuo = 789

0 commit comments

Comments
 (0)