Skip to content

Commit 366e9de

Browse files
authored
Fix compiler crash on property symbols without declarations (microsoft#45190)
* don't track computed name if symbol has no declaration * add compiler test * add non serializable property declaration emit error * don't track computed name if symbol has no declaration * fix small stuff * rebase: add non serializable property declaration emit error * use symbolToString instead of symbolName
1 parent 6277305 commit 366e9de

14 files changed

+114
-10
lines changed

src/compiler/checker.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4536,6 +4536,7 @@ namespace ts {
45364536
reportLikelyUnsafeImportRequiredError: wrapReportedDiagnostic(tracker.reportLikelyUnsafeImportRequiredError),
45374537
reportNonlocalAugmentation: wrapReportedDiagnostic(tracker.reportNonlocalAugmentation),
45384538
reportPrivateInBaseOfClassExpression: wrapReportedDiagnostic(tracker.reportPrivateInBaseOfClassExpression),
4539+
reportNonSerializableProperty: wrapReportedDiagnostic(tracker.reportNonSerializableProperty),
45394540
trackSymbol: oldTrackSymbol && ((...args) => {
45404541
const result = oldTrackSymbol(...args);
45414542
if (result) {
@@ -5243,17 +5244,22 @@ namespace ts {
52435244
const saveEnclosingDeclaration = context.enclosingDeclaration;
52445245
context.enclosingDeclaration = undefined;
52455246
if (context.tracker.trackSymbol && getCheckFlags(propertySymbol) & CheckFlags.Late && isLateBoundName(propertySymbol.escapedName)) {
5246-
const decl = first(propertySymbol.declarations!);
5247-
if (propertySymbol.declarations && hasLateBindableName(decl)) {
5248-
if (isBinaryExpression(decl)) {
5249-
const name = getNameOfDeclaration(decl);
5250-
if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) {
5251-
trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context);
5247+
if (propertySymbol.declarations) {
5248+
const decl = first(propertySymbol.declarations);
5249+
if (hasLateBindableName(decl)) {
5250+
if (isBinaryExpression(decl)) {
5251+
const name = getNameOfDeclaration(decl);
5252+
if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) {
5253+
trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context);
5254+
}
5255+
}
5256+
else {
5257+
trackComputedName(decl.name.expression, saveEnclosingDeclaration, context);
52525258
}
52535259
}
5254-
else {
5255-
trackComputedName(decl.name.expression, saveEnclosingDeclaration, context);
5256-
}
5260+
}
5261+
else if (context.tracker?.reportNonSerializableProperty) {
5262+
context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol));
52575263
}
52585264
}
52595265
context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration;

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3801,6 +3801,10 @@
38013801
"category": "Error",
38023802
"code": 4117
38033803
},
3804+
"The type of this node cannot be serialized because its property '{0}' cannot be serialized.": {
3805+
"category": "Error",
3806+
"code": 4118
3807+
},
38043808

38053809
"The current host does not support the '{0}' option.": {
38063810
"category": "Error",

src/compiler/transformers/declarations.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ namespace ts {
7777
moduleResolverHost: host,
7878
trackReferencedAmbientModule,
7979
trackExternalModuleSymbolOfImportTypeNode,
80-
reportNonlocalAugmentation
80+
reportNonlocalAugmentation,
81+
reportNonSerializableProperty
8182
};
8283
let errorNameNode: DeclarationName | undefined;
8384
let errorFallbackNode: Declaration | undefined;
@@ -228,6 +229,12 @@ namespace ts {
228229
}
229230
}
230231

232+
function reportNonSerializableProperty(propertyName: string) {
233+
if (errorNameNode || errorFallbackNode) {
234+
context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_type_of_this_node_cannot_be_serialized_because_its_property_0_cannot_be_serialized, propertyName));
235+
}
236+
}
237+
231238
function transformDeclarationsForJS(sourceFile: SourceFile, bundled?: boolean) {
232239
const oldDiag = getSymbolAccessibilityDiagnostic;
233240
getSymbolAccessibilityDiagnostic = (s) => (s.errorNode && canProduceDiagnostics(s.errorNode) ? createGetSymbolAccessibilityDiagnosticForNode(s.errorNode)(s) : ({

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8238,6 +8238,7 @@ namespace ts {
82388238
trackReferencedAmbientModule?(decl: ModuleDeclaration, symbol: Symbol): void;
82398239
trackExternalModuleSymbolOfImportTypeNode?(symbol: Symbol): void;
82408240
reportNonlocalAugmentation?(containingFile: SourceFile, parentSymbol: Symbol, augmentingSymbol: Symbol): void;
8241+
reportNonSerializableProperty?(propertyName: string): void;
82418242
}
82428243

82438244
export interface TextSpan {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
tests/cases/compiler/mappedTypeWithAsClauseAndLateBoundProperty.ts(3,1): error TS2741: Property 'length' is missing in type '{ [x: number]: number; toString: () => string; toLocaleString: () => string; pop: () => number; push: (...items: number[]) => number; concat: { (...items: ConcatArray<number>[]): number[]; (...items: (number | ConcatArray<number>)[]): number[]; }; join: (separator?: string) => string; reverse: () => number[]; shift: () => number; slice: (start?: number, end?: number) => number[]; sort: (compareFn?: (a: number, b: number) => number) => number[]; splice: { (start: number, deleteCount?: number): number[]; (start: number, deleteCount: number, ...items: number[]): number[]; }; unshift: (...items: number[]) => number; indexOf: (searchElement: number, fromIndex?: number) => number; lastIndexOf: (searchElement: number, fromIndex?: number) => number; every: { <S extends number>(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): boolean; }; some: (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any) => boolean; forEach: (callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void; map: <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]; filter: { <S extends number>(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): S[]; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): number[]; }; reduce: { (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; <U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; }; reduceRight: { (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; <U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; }; find: { <S extends number>(predicate: (this: void, value: number, index: number, obj: number[]) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, obj: number[]) => unknown, thisArg?: any): number; }; findIndex: (predicate: (value: number, index: number, obj: number[]) => unknown, thisArg?: any) => number; fill: (value: number, start?: number, end?: number) => number[]; copyWithin: (target: number, start: number, end?: number) => number[]; entries: () => IterableIterator<[number, number]>; keys: () => IterableIterator<number>; values: () => IterableIterator<number>; includes: (searchElement: number, fromIndex?: number) => boolean; flatMap: <U, This = undefined>(callback: (this: This, value: number, index: number, array: number[]) => U | readonly U[], thisArg?: This) => U[]; flat: <A, D extends number = 1>(this: A, depth?: D) => FlatArray<A, D>[]; [iterator]: () => IterableIterator<number>; [unscopables]: () => { copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: boolean; values: boolean; }; }' but required in type 'number[]'.
2+
3+
4+
==== tests/cases/compiler/mappedTypeWithAsClauseAndLateBoundProperty.ts (1 errors) ====
5+
declare let tgt2: number[];
6+
declare let src2: { [K in keyof number[] as Exclude<K, "length">]: (number[])[K] };
7+
tgt2 = src2; // Should error
8+
~~~~
9+
!!! error TS2741: Property 'length' is missing in type '{ [x: number]: number; toString: () => string; toLocaleString: () => string; pop: () => number; push: (...items: number[]) => number; concat: { (...items: ConcatArray<number>[]): number[]; (...items: (number | ConcatArray<number>)[]): number[]; }; join: (separator?: string) => string; reverse: () => number[]; shift: () => number; slice: (start?: number, end?: number) => number[]; sort: (compareFn?: (a: number, b: number) => number) => number[]; splice: { (start: number, deleteCount?: number): number[]; (start: number, deleteCount: number, ...items: number[]): number[]; }; unshift: (...items: number[]) => number; indexOf: (searchElement: number, fromIndex?: number) => number; lastIndexOf: (searchElement: number, fromIndex?: number) => number; every: { <S extends number>(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): boolean; }; some: (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any) => boolean; forEach: (callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void; map: <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]; filter: { <S extends number>(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): S[]; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): number[]; }; reduce: { (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; <U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; }; reduceRight: { (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; <U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; }; find: { <S extends number>(predicate: (this: void, value: number, index: number, obj: number[]) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, obj: number[]) => unknown, thisArg?: any): number; }; findIndex: (predicate: (value: number, index: number, obj: number[]) => unknown, thisArg?: any) => number; fill: (value: number, start?: number, end?: number) => number[]; copyWithin: (target: number, start: number, end?: number) => number[]; entries: () => IterableIterator<[number, number]>; keys: () => IterableIterator<number>; values: () => IterableIterator<number>; includes: (searchElement: number, fromIndex?: number) => boolean; flatMap: <U, This = undefined>(callback: (this: This, value: number, index: number, array: number[]) => U | readonly U[], thisArg?: This) => U[]; flat: <A, D extends number = 1>(this: A, depth?: D) => FlatArray<A, D>[]; [iterator]: () => IterableIterator<number>; [unscopables]: () => { copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: boolean; values: boolean; }; }' but required in type 'number[]'.
10+
!!! related TS2728 /.ts/lib.es5.d.ts:1224:5: 'length' is declared here.
11+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//// [mappedTypeWithAsClauseAndLateBoundProperty.ts]
2+
declare let tgt2: number[];
3+
declare let src2: { [K in keyof number[] as Exclude<K, "length">]: (number[])[K] };
4+
tgt2 = src2; // Should error
5+
6+
7+
//// [mappedTypeWithAsClauseAndLateBoundProperty.js]
8+
tgt2 = src2; // Should error
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/compiler/mappedTypeWithAsClauseAndLateBoundProperty.ts ===
2+
declare let tgt2: number[];
3+
>tgt2 : Symbol(tgt2, Decl(mappedTypeWithAsClauseAndLateBoundProperty.ts, 0, 11))
4+
5+
declare let src2: { [K in keyof number[] as Exclude<K, "length">]: (number[])[K] };
6+
>src2 : Symbol(src2, Decl(mappedTypeWithAsClauseAndLateBoundProperty.ts, 1, 11))
7+
>K : Symbol(K, Decl(mappedTypeWithAsClauseAndLateBoundProperty.ts, 1, 21))
8+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
9+
>K : Symbol(K, Decl(mappedTypeWithAsClauseAndLateBoundProperty.ts, 1, 21))
10+
>K : Symbol(K, Decl(mappedTypeWithAsClauseAndLateBoundProperty.ts, 1, 21))
11+
12+
tgt2 = src2; // Should error
13+
>tgt2 : Symbol(tgt2, Decl(mappedTypeWithAsClauseAndLateBoundProperty.ts, 0, 11))
14+
>src2 : Symbol(src2, Decl(mappedTypeWithAsClauseAndLateBoundProperty.ts, 1, 11))
15+

0 commit comments

Comments
 (0)