Skip to content

Commit 403df45

Browse files
authored
Rework entity name decorator metadata fallback emit to not throw at runtime (microsoft#25421)
* Rework entity name decorator metadata fallback emit to not throw at runtime * Remove trailing whitespace
1 parent 831be5d commit 403df45

25 files changed

+202
-59
lines changed

src/compiler/transformers/ts.ts

+43-40
Original file line numberDiff line numberDiff line change
@@ -1982,18 +1982,16 @@ namespace ts {
19821982
const kind = resolver.getTypeReferenceSerializationKind(node.typeName, currentNameScope || currentLexicalScope);
19831983
switch (kind) {
19841984
case TypeReferenceSerializationKind.Unknown:
1985-
const serialized = serializeEntityNameAsExpression(node.typeName, /*useFallback*/ true);
1985+
const serialized = serializeEntityNameAsExpressionFallback(node.typeName);
19861986
const temp = createTempVariable(hoistVariableDeclaration);
1987-
return createLogicalOr(
1988-
createLogicalAnd(
1989-
createTypeCheck(createAssignment(temp, serialized), "function"),
1990-
temp
1991-
),
1987+
return createConditional(
1988+
createTypeCheck(createAssignment(temp, serialized), "function"),
1989+
temp,
19921990
createIdentifier("Object")
19931991
);
19941992

19951993
case TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue:
1996-
return serializeEntityNameAsExpression(node.typeName, /*useFallback*/ false);
1994+
return serializeEntityNameAsExpression(node.typeName);
19971995

19981996
case TypeReferenceSerializationKind.VoidNullableOrNeverType:
19991997
return createVoidZero();
@@ -2028,14 +2026,46 @@ namespace ts {
20282026
}
20292027
}
20302028

2029+
function createCheckedValue(left: Expression, right: Expression) {
2030+
return createLogicalAnd(
2031+
createStrictInequality(createTypeOf(left), createLiteral("undefined")),
2032+
right
2033+
);
2034+
}
2035+
2036+
/**
2037+
* Serializes an entity name which may not exist at runtime, but whose access shouldn't throw
2038+
*
2039+
* @param node The entity name to serialize.
2040+
*/
2041+
function serializeEntityNameAsExpressionFallback(node: EntityName): BinaryExpression {
2042+
if (node.kind === SyntaxKind.Identifier) {
2043+
// A -> typeof A !== undefined && A
2044+
const copied = serializeEntityNameAsExpression(node);
2045+
return createCheckedValue(copied, copied);
2046+
}
2047+
if (node.left.kind === SyntaxKind.Identifier) {
2048+
// A.B -> typeof A !== undefined && A.B
2049+
return createCheckedValue(serializeEntityNameAsExpression(node.left), serializeEntityNameAsExpression(node));
2050+
}
2051+
// A.B.C -> typeof A !== undefined && (_a = A.B) !== void 0 && _a.C
2052+
const left = serializeEntityNameAsExpressionFallback(node.left);
2053+
const temp = createTempVariable(hoistVariableDeclaration);
2054+
return createLogicalAnd(
2055+
createLogicalAnd(
2056+
left.left,
2057+
createStrictInequality(createAssignment(temp, left.right), createVoidZero())
2058+
),
2059+
createPropertyAccess(temp, node.right)
2060+
);
2061+
}
2062+
20312063
/**
20322064
* Serializes an entity name as an expression for decorator type metadata.
20332065
*
20342066
* @param node The entity name to serialize.
2035-
* @param useFallback A value indicating whether to use logical operators to test for the
2036-
* entity name at runtime.
20372067
*/
2038-
function serializeEntityNameAsExpression(node: EntityName, useFallback: boolean): SerializedEntityNameAsExpression {
2068+
function serializeEntityNameAsExpression(node: EntityName): SerializedEntityNameAsExpression {
20392069
switch (node.kind) {
20402070
case SyntaxKind.Identifier:
20412071
// Create a clone of the name with a new parent, and treat it as if it were
@@ -2044,20 +2074,11 @@ namespace ts {
20442074
name.flags &= ~NodeFlags.Synthesized;
20452075
name.original = undefined;
20462076
name.parent = getParseTreeNode(currentLexicalScope); // ensure the parent is set to a parse tree node.
2047-
if (useFallback) {
2048-
return createLogicalAnd(
2049-
createStrictInequality(
2050-
createTypeOf(name),
2051-
createLiteral("undefined")
2052-
),
2053-
name
2054-
);
2055-
}
20562077

20572078
return name;
20582079

20592080
case SyntaxKind.QualifiedName:
2060-
return serializeQualifiedNameAsExpression(node, useFallback);
2081+
return serializeQualifiedNameAsExpression(node);
20612082
}
20622083
}
20632084

@@ -2068,26 +2089,8 @@ namespace ts {
20682089
* @param useFallback A value indicating whether to use logical operators to test for the
20692090
* qualified name at runtime.
20702091
*/
2071-
function serializeQualifiedNameAsExpression(node: QualifiedName, useFallback: boolean): PropertyAccessExpression {
2072-
let left: SerializedEntityNameAsExpression;
2073-
if (node.left.kind === SyntaxKind.Identifier) {
2074-
left = serializeEntityNameAsExpression(node.left, useFallback);
2075-
}
2076-
else if (useFallback) {
2077-
const temp = createTempVariable(hoistVariableDeclaration);
2078-
left = createLogicalAnd(
2079-
createAssignment(
2080-
temp,
2081-
serializeEntityNameAsExpression(node.left, /*useFallback*/ true)
2082-
),
2083-
temp
2084-
);
2085-
}
2086-
else {
2087-
left = serializeEntityNameAsExpression(node.left, /*useFallback*/ false);
2088-
}
2089-
2090-
return createPropertyAccess(left, node.right);
2092+
function serializeQualifiedNameAsExpression(node: QualifiedName): SerializedEntityNameAsExpression {
2093+
return createPropertyAccess(serializeEntityNameAsExpression(node.left), node.right);
20912094
}
20922095

20932096
/**

tests/baselines/reference/decoratorMetadataNoLibIsolatedModulesTypes.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ var B = /** @class */ (function () {
2323
var _a;
2424
__decorate([
2525
Decorate,
26-
__metadata("design:type", typeof (_a = typeof Map !== "undefined" && Map) === "function" && _a || Object)
26+
__metadata("design:type", typeof (_a = typeof Map !== "undefined" && Map) === "function" ? _a : Object)
2727
], B.prototype, "member");
2828
return B;
2929
}());

tests/baselines/reference/decoratorMetadataWithImportDeclarationNameCollision4.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ var MyClass = /** @class */ (function () {
4949
var _a;
5050
MyClass = __decorate([
5151
someDecorator,
52-
__metadata("design:paramtypes", [typeof (_a = (typeof db_1.default !== "undefined" && db_1.default).db) === "function" && _a || Object])
52+
__metadata("design:paramtypes", [typeof (_a = typeof db_1.default !== "undefined" && db_1.default.db) === "function" ? _a : Object])
5353
], MyClass);
5454
return MyClass;
5555
}());

tests/baselines/reference/decoratorMetadataWithImportDeclarationNameCollision7.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ var MyClass = /** @class */ (function () {
4949
var _a;
5050
MyClass = __decorate([
5151
someDecorator,
52-
__metadata("design:paramtypes", [typeof (_a = (typeof db_1.default !== "undefined" && db_1.default).db) === "function" && _a || Object])
52+
__metadata("design:paramtypes", [typeof (_a = typeof db_1.default !== "undefined" && db_1.default.db) === "function" ? _a : Object])
5353
], MyClass);
5454
return MyClass;
5555
}());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
tests/cases/compiler/usage.ts(2,8): error TS2304: Cannot find name 'decorate'.
2+
tests/cases/compiler/usage.ts(2,31): error TS2694: Namespace 'A.B.C.D' has no exported member 'E'.
3+
4+
5+
==== tests/cases/compiler/types.d.ts (0 errors) ====
6+
declare namespace A {
7+
export namespace B {
8+
export namespace C {
9+
export namespace D {
10+
}
11+
}
12+
}
13+
}
14+
==== tests/cases/compiler/usage.ts (2 errors) ====
15+
class Foo {
16+
f(@decorate user: A.B.C.D.E): void {}
17+
~~~~~~~~
18+
!!! error TS2304: Cannot find name 'decorate'.
19+
~
20+
!!! error TS2694: Namespace 'A.B.C.D' has no exported member 'E'.
21+
}
22+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//// [tests/cases/compiler/experimentalDecoratorMetadataUnresolvedTypeObjectInEmit.ts] ////
2+
3+
//// [types.d.ts]
4+
declare namespace A {
5+
export namespace B {
6+
export namespace C {
7+
export namespace D {
8+
}
9+
}
10+
}
11+
}
12+
//// [usage.ts]
13+
class Foo {
14+
f(@decorate user: A.B.C.D.E): void {}
15+
}
16+
17+
18+
//// [usage.js]
19+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
20+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
21+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
22+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
23+
return c > 3 && r && Object.defineProperty(target, key, r), r;
24+
};
25+
var __metadata = (this && this.__metadata) || function (k, v) {
26+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
27+
};
28+
var __param = (this && this.__param) || function (paramIndex, decorator) {
29+
return function (target, key) { decorator(target, key, paramIndex); }
30+
};
31+
var Foo = /** @class */ (function () {
32+
function Foo() {
33+
}
34+
Foo.prototype.f = function (user) { };
35+
var _a, _b, _c, _d;
36+
__decorate([
37+
__param(0, decorate),
38+
__metadata("design:type", Function),
39+
__metadata("design:paramtypes", [typeof (_d = typeof A !== "undefined" && (_a = A.B) !== void 0 && (_b = _a.C) !== void 0 && (_c = _b.D) !== void 0 && _c.E) === "function" ? _d : Object]),
40+
__metadata("design:returntype", void 0)
41+
], Foo.prototype, "f");
42+
return Foo;
43+
}());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/compiler/types.d.ts ===
2+
declare namespace A {
3+
>A : Symbol(A, Decl(types.d.ts, 0, 0))
4+
5+
export namespace B {
6+
>B : Symbol(B, Decl(types.d.ts, 0, 21))
7+
8+
export namespace C {
9+
>C : Symbol(C, Decl(types.d.ts, 1, 24))
10+
11+
export namespace D {
12+
>D : Symbol(D, Decl(types.d.ts, 2, 28))
13+
}
14+
}
15+
}
16+
}
17+
=== tests/cases/compiler/usage.ts ===
18+
class Foo {
19+
>Foo : Symbol(Foo, Decl(usage.ts, 0, 0))
20+
21+
f(@decorate user: A.B.C.D.E): void {}
22+
>f : Symbol(Foo.f, Decl(usage.ts, 0, 11))
23+
>user : Symbol(user, Decl(usage.ts, 1, 6))
24+
>A : Symbol(A, Decl(types.d.ts, 0, 0))
25+
>B : Symbol(A.B, Decl(types.d.ts, 0, 21))
26+
>C : Symbol(A.B.C, Decl(types.d.ts, 1, 24))
27+
>D : Symbol(A.B.C.D, Decl(types.d.ts, 2, 28))
28+
}
29+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
=== tests/cases/compiler/types.d.ts ===
2+
declare namespace A {
3+
>A : any
4+
5+
export namespace B {
6+
>B : any
7+
8+
export namespace C {
9+
>C : any
10+
11+
export namespace D {
12+
>D : any
13+
}
14+
}
15+
}
16+
}
17+
=== tests/cases/compiler/usage.ts ===
18+
class Foo {
19+
>Foo : Foo
20+
21+
f(@decorate user: A.B.C.D.E): void {}
22+
>f : (user: any) => void
23+
>decorate : any
24+
>user : any
25+
>A : any
26+
>B : any
27+
>C : any
28+
>D : any
29+
>E : No type information available!
30+
}
31+

tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModules/amd/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ define(["require", "exports", "angular2/core"], function (require, exports, ng)
1717
var _a;
1818
MyClass1 = __decorate([
1919
foo,
20-
__metadata("design:paramtypes", [typeof (_a = (typeof ng !== "undefined" && ng).ElementRef) === "function" && _a || Object])
20+
__metadata("design:paramtypes", [typeof (_a = typeof ng !== "undefined" && ng.ElementRef) === "function" ? _a : Object])
2121
], MyClass1);
2222
return MyClass1;
2323
}());

tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModules/node/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var MyClass1 = /** @class */ (function () {
1717
var _a;
1818
MyClass1 = __decorate([
1919
foo,
20-
__metadata("design:paramtypes", [typeof (_a = (typeof ng !== "undefined" && ng).ElementRef) === "function" && _a || Object])
20+
__metadata("design:paramtypes", [typeof (_a = typeof ng !== "undefined" && ng.ElementRef) === "function" ? _a : Object])
2121
], MyClass1);
2222
return MyClass1;
2323
}());

tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModulesNoResolve/amd/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ define(["require", "exports", "angular2/core"], function (require, exports, ng)
1717
var _a;
1818
MyClass1 = __decorate([
1919
foo,
20-
__metadata("design:paramtypes", [typeof (_a = (typeof ng !== "undefined" && ng).ElementRef) === "function" && _a || Object])
20+
__metadata("design:paramtypes", [typeof (_a = typeof ng !== "undefined" && ng.ElementRef) === "function" ? _a : Object])
2121
], MyClass1);
2222
return MyClass1;
2323
}());

tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModulesNoResolve/node/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var MyClass1 = /** @class */ (function () {
1717
var _a;
1818
MyClass1 = __decorate([
1919
foo,
20-
__metadata("design:paramtypes", [typeof (_a = (typeof ng !== "undefined" && ng).ElementRef) === "function" && _a || Object])
20+
__metadata("design:paramtypes", [typeof (_a = typeof ng !== "undefined" && ng.ElementRef) === "function" ? _a : Object])
2121
], MyClass1);
2222
return MyClass1;
2323
}());

tests/baselines/reference/project/emitDecoratorMetadataSystemJS/amd/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ define(["require", "exports", "angular2/core"], function (require, exports, ng)
1717
var _a;
1818
MyClass1 = __decorate([
1919
foo,
20-
__metadata("design:paramtypes", [typeof (_a = (typeof ng !== "undefined" && ng).ElementRef) === "function" && _a || Object])
20+
__metadata("design:paramtypes", [typeof (_a = typeof ng !== "undefined" && ng.ElementRef) === "function" ? _a : Object])
2121
], MyClass1);
2222
return MyClass1;
2323
}());

tests/baselines/reference/project/emitDecoratorMetadataSystemJS/node/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var MyClass1 = /** @class */ (function () {
1717
var _a;
1818
MyClass1 = __decorate([
1919
foo,
20-
__metadata("design:paramtypes", [typeof (_a = (typeof ng !== "undefined" && ng).ElementRef) === "function" && _a || Object])
20+
__metadata("design:paramtypes", [typeof (_a = typeof ng !== "undefined" && ng.ElementRef) === "function" ? _a : Object])
2121
], MyClass1);
2222
return MyClass1;
2323
}());

tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModules/amd/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ define(["require", "exports", "angular2/core"], function (require, exports, ng)
1717
var _a;
1818
MyClass1 = __decorate([
1919
foo,
20-
__metadata("design:paramtypes", [typeof (_a = (typeof ng !== "undefined" && ng).ElementRef) === "function" && _a || Object])
20+
__metadata("design:paramtypes", [typeof (_a = typeof ng !== "undefined" && ng.ElementRef) === "function" ? _a : Object])
2121
], MyClass1);
2222
return MyClass1;
2323
}());

tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModules/node/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var MyClass1 = /** @class */ (function () {
1717
var _a;
1818
MyClass1 = __decorate([
1919
foo,
20-
__metadata("design:paramtypes", [typeof (_a = (typeof ng !== "undefined" && ng).ElementRef) === "function" && _a || Object])
20+
__metadata("design:paramtypes", [typeof (_a = typeof ng !== "undefined" && ng.ElementRef) === "function" ? _a : Object])
2121
], MyClass1);
2222
return MyClass1;
2323
}());

tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModulesNoResolve/amd/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ define(["require", "exports", "angular2/core"], function (require, exports, ng)
1717
var _a;
1818
MyClass1 = __decorate([
1919
foo,
20-
__metadata("design:paramtypes", [typeof (_a = (typeof ng !== "undefined" && ng).ElementRef) === "function" && _a || Object])
20+
__metadata("design:paramtypes", [typeof (_a = typeof ng !== "undefined" && ng.ElementRef) === "function" ? _a : Object])
2121
], MyClass1);
2222
return MyClass1;
2323
}());

tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModulesNoResolve/node/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var MyClass1 = /** @class */ (function () {
1717
var _a;
1818
MyClass1 = __decorate([
1919
foo,
20-
__metadata("design:paramtypes", [typeof (_a = (typeof ng !== "undefined" && ng).ElementRef) === "function" && _a || Object])
20+
__metadata("design:paramtypes", [typeof (_a = typeof ng !== "undefined" && ng.ElementRef) === "function" ? _a : Object])
2121
], MyClass1);
2222
return MyClass1;
2323
}());

tests/baselines/reference/transpile/Correctly serialize metadata when transpile with CommonJS option.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/baselines/reference/transpile/Correctly serialize metadata when transpile with CommonJS option.oldTranspile.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/baselines/reference/transpile/Correctly serialize metadata when transpile with System option.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)