Skip to content

Commit 6419240

Browse files
authored
Declaration emit includes function properties (#26499)
* Declaration emit includes function properties It does this by printing the type as an object literal type: ```ts function f() { } f.p = 1 ``` Appears in a d.ts as ```ts declare var f: { (): void; p: number; } ``` It would also be possible to represent it as a namespace merge. I'm not sure which is better. ```ts declare function f(): void; declare namespace f { export var p: number; } ``` In order to avoid a private-name-used error (though I think it was actually *unused*), I also had to change the nodeBuilder code to match. This is arguably harder to read. So it's possible that I should instead keep the nodeBuilder version as `typeof f` and make an exception for private name use. * Emit namespace merge instead of object type This makes the change smaller, overall. * Fix isJSContainerFunctionDeclaration+namespace merges Also improve emit style to match other namespace emit. * Add isPrivate + test case from PR comments
1 parent e411381 commit 6419240

File tree

8 files changed

+541
-106
lines changed

8 files changed

+541
-106
lines changed

src/compiler/checker.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27767,6 +27767,27 @@ namespace ts {
2776727767
hasModifier(parameter, ModifierFlags.ParameterPropertyModifier);
2776827768
}
2776927769

27770+
function isJSContainerFunctionDeclaration(node: Declaration): boolean {
27771+
const declaration = getParseTreeNode(node, isFunctionDeclaration);
27772+
if (!declaration) {
27773+
return false;
27774+
}
27775+
const symbol = getSymbolOfNode(declaration);
27776+
if (!symbol || !(symbol.flags & SymbolFlags.Function)) {
27777+
return false;
27778+
}
27779+
return !!forEachEntry(getExportsOfSymbol(symbol), p => isPropertyAccessExpression(p.valueDeclaration));
27780+
}
27781+
27782+
function getPropertiesOfContainerFunction(node: Declaration): Symbol[] {
27783+
const declaration = getParseTreeNode(node, isFunctionDeclaration);
27784+
if (!declaration) {
27785+
return emptyArray;
27786+
}
27787+
const symbol = getSymbolOfNode(declaration);
27788+
return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray;
27789+
}
27790+
2777027791
function getNodeCheckFlags(node: Node): NodeCheckFlags {
2777127792
return getNodeLinks(node).flags || 0;
2777227793
}
@@ -27874,7 +27895,7 @@ namespace ts {
2787427895
}
2787527896
}
2787627897

27877-
function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) {
27898+
function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) {
2787827899
const declaration = getParseTreeNode(declarationIn, isVariableLikeOrAccessor);
2787927900
if (!declaration) {
2788027901
return createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode;
@@ -28007,6 +28028,8 @@ namespace ts {
2800728028
isImplementationOfOverload,
2800828029
isRequiredInitializedParameter,
2800928030
isOptionalUninitializedParameterProperty,
28031+
isJSContainerFunctionDeclaration,
28032+
getPropertiesOfContainerFunction,
2801028033
createTypeOfDeclaration,
2801128034
createReturnTypeOfSignatureDeclaration,
2801228035
createTypeOfExpression,

src/compiler/transformers/declarations.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,7 @@ namespace ts {
971971
}
972972
case SyntaxKind.FunctionDeclaration: {
973973
// Generators lose their generator-ness, excepting their return type
974-
return cleanup(updateFunctionDeclaration(
974+
const clean = cleanup(updateFunctionDeclaration(
975975
input,
976976
/*decorators*/ undefined,
977977
ensureModifiers(input, isPrivate),
@@ -982,6 +982,21 @@ namespace ts {
982982
ensureType(input, input.type),
983983
/*body*/ undefined
984984
));
985+
if (clean && resolver.isJSContainerFunctionDeclaration(input)) {
986+
const declarations = mapDefined(resolver.getPropertiesOfContainerFunction(input), p => {
987+
if (!isPropertyAccessExpression(p.valueDeclaration)) {
988+
return undefined;
989+
}
990+
const type = resolver.createTypeOfDeclaration(p.valueDeclaration, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker);
991+
const varDecl = createVariableDeclaration(unescapeLeadingUnderscores(p.escapedName), type, /*initializer*/ undefined);
992+
return createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([varDecl]));
993+
});
994+
const namespaceDecl = createModuleDeclaration(/*decorators*/ undefined, ensureModifiers(input, isPrivate), input.name!, createModuleBlock(declarations), NodeFlags.Namespace);
995+
return [clean, namespaceDecl];
996+
}
997+
else {
998+
return clean;
999+
}
9851000
}
9861001
case SyntaxKind.ModuleDeclaration: {
9871002
needsDeclare = false;

src/compiler/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3374,7 +3374,9 @@ namespace ts {
33743374
isImplementationOfOverload(node: FunctionLike): boolean | undefined;
33753375
isRequiredInitializedParameter(node: ParameterDeclaration): boolean;
33763376
isOptionalUninitializedParameterProperty(node: ParameterDeclaration): boolean;
3377-
createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean): TypeNode | undefined;
3377+
isJSContainerFunctionDeclaration(node: FunctionDeclaration): boolean;
3378+
getPropertiesOfContainerFunction(node: Declaration): Symbol[];
3379+
createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean): TypeNode | undefined;
33783380
createReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined;
33793381
createTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined;
33803382
createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): Expression;

tests/baselines/reference/typeFromPropertyAssignment29.errors.txt

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(30,14): error TS2339: Property 'prop' does not exist on type '(n: number) => string'.
2-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(31,14): error TS2339: Property 'm' does not exist on type '(n: number) => string'.
3-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(34,22): error TS2339: Property 'prop' does not exist on type '(n: number) => string'.
4-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(34,42): error TS2339: Property 'm' does not exist on type '(n: number) => string'.
5-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(40,14): error TS2339: Property 'prop' does not exist on type 'typeof ExpandoClass'.
6-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(41,14): error TS2339: Property 'm' does not exist on type 'typeof ExpandoClass'.
7-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(44,22): error TS2339: Property 'prop' does not exist on type 'typeof ExpandoClass'.
8-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(44,42): error TS2339: Property 'm' does not exist on type 'typeof ExpandoClass'.
9-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(50,14): error TS2339: Property 'prop' does not exist on type 'typeof ExpandoExpr3'.
10-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(51,14): error TS2339: Property 'm' does not exist on type 'typeof ExpandoExpr3'.
11-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(54,22): error TS2339: Property 'prop' does not exist on type 'typeof ExpandoExpr3'.
12-
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(54,42): error TS2339: Property 'm' does not exist on type 'typeof ExpandoExpr3'.
1+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(60,14): error TS2339: Property 'prop' does not exist on type '(n: number) => string'.
2+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(61,14): error TS2339: Property 'm' does not exist on type '(n: number) => string'.
3+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(64,22): error TS2339: Property 'prop' does not exist on type '(n: number) => string'.
4+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(64,42): error TS2339: Property 'm' does not exist on type '(n: number) => string'.
5+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(70,14): error TS2339: Property 'prop' does not exist on type 'typeof ExpandoClass'.
6+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(71,14): error TS2339: Property 'm' does not exist on type 'typeof ExpandoClass'.
7+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(74,22): error TS2339: Property 'prop' does not exist on type 'typeof ExpandoClass'.
8+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(74,42): error TS2339: Property 'm' does not exist on type 'typeof ExpandoClass'.
9+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(80,14): error TS2339: Property 'prop' does not exist on type 'typeof ExpandoExpr3'.
10+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(81,14): error TS2339: Property 'm' does not exist on type 'typeof ExpandoExpr3'.
11+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(84,22): error TS2339: Property 'prop' does not exist on type 'typeof ExpandoExpr3'.
12+
tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(84,42): error TS2339: Property 'm' does not exist on type 'typeof ExpandoExpr3'.
1313

1414

1515
==== tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts (12 errors) ====
@@ -25,11 +25,12 @@ tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(54,42): error TS23
2525
const ExpandoExpr = function (n: number) {
2626
return n.toString();
2727
}
28-
ExpandoExpr.prop = 2
28+
ExpandoExpr.prop = { x: 2 }
29+
ExpandoExpr.prop = { y: "" }
2930
ExpandoExpr.m = function(n: number) {
3031
return n + 1;
3132
}
32-
var n = ExpandoExpr.prop + ExpandoExpr.m(12) + ExpandoExpr(101).length
33+
var n = (ExpandoExpr.prop.x || 0) + ExpandoExpr.m(12) + ExpandoExpr(101).length
3334

3435
const ExpandoArrow = (n: number) => n.toString();
3536
ExpandoArrow.prop = 2
@@ -38,6 +39,35 @@ tests/cases/conformance/salsa/typeFromPropertyAssignment29.ts(54,42): error TS23
3839

3940
}
4041

42+
function ExpandoNested(n: number) {
43+
const nested = function (m: number) {
44+
return n + m;
45+
};
46+
nested.total = n + 1_000_000;
47+
return nested;
48+
}
49+
ExpandoNested.also = -1;
50+
51+
function ExpandoMerge(n: number) {
52+
return n * 100;
53+
}
54+
ExpandoMerge.p1 = 111
55+
namespace ExpandoMerge {
56+
export var p2 = 222;
57+
}
58+
namespace ExpandoMerge {
59+
export var p3 = 333;
60+
}
61+
var n = ExpandoMerge.p1 + ExpandoMerge.p2 + ExpandoMerge.p3 + ExpandoMerge(1);
62+
63+
namespace Ns {
64+
function ExpandoNamespace(): void {}
65+
ExpandoNamespace.p6 = 42;
66+
export function foo() {
67+
return ExpandoNamespace;
68+
}
69+
}
70+
4171
// Should not work in Typescript -- must be const
4272
var ExpandoExpr2 = function (n: number) {
4373
return n.toString();

tests/baselines/reference/typeFromPropertyAssignment29.js

Lines changed: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ var n = ExpandoDecl.prop + ExpandoDecl.m(12) + ExpandoDecl(101).length
1111
const ExpandoExpr = function (n: number) {
1212
return n.toString();
1313
}
14-
ExpandoExpr.prop = 2
14+
ExpandoExpr.prop = { x: 2 }
15+
ExpandoExpr.prop = { y: "" }
1516
ExpandoExpr.m = function(n: number) {
1617
return n + 1;
1718
}
18-
var n = ExpandoExpr.prop + ExpandoExpr.m(12) + ExpandoExpr(101).length
19+
var n = (ExpandoExpr.prop.x || 0) + ExpandoExpr.m(12) + ExpandoExpr(101).length
1920

2021
const ExpandoArrow = (n: number) => n.toString();
2122
ExpandoArrow.prop = 2
@@ -24,6 +25,35 @@ ExpandoArrow.m = function(n: number) {
2425

2526
}
2627

28+
function ExpandoNested(n: number) {
29+
const nested = function (m: number) {
30+
return n + m;
31+
};
32+
nested.total = n + 1_000_000;
33+
return nested;
34+
}
35+
ExpandoNested.also = -1;
36+
37+
function ExpandoMerge(n: number) {
38+
return n * 100;
39+
}
40+
ExpandoMerge.p1 = 111
41+
namespace ExpandoMerge {
42+
export var p2 = 222;
43+
}
44+
namespace ExpandoMerge {
45+
export var p3 = 333;
46+
}
47+
var n = ExpandoMerge.p1 + ExpandoMerge.p2 + ExpandoMerge.p3 + ExpandoMerge(1);
48+
49+
namespace Ns {
50+
function ExpandoNamespace(): void {}
51+
ExpandoNamespace.p6 = 42;
52+
export function foo() {
53+
return ExpandoNamespace;
54+
}
55+
}
56+
2757
// Should not work in Typescript -- must be const
2858
var ExpandoExpr2 = function (n: number) {
2959
return n.toString();
@@ -68,16 +98,45 @@ var n = ExpandoDecl.prop + ExpandoDecl.m(12) + ExpandoDecl(101).length;
6898
var ExpandoExpr = function (n) {
6999
return n.toString();
70100
};
71-
ExpandoExpr.prop = 2;
101+
ExpandoExpr.prop = { x: 2 };
102+
ExpandoExpr.prop = { y: "" };
72103
ExpandoExpr.m = function (n) {
73104
return n + 1;
74105
};
75-
var n = ExpandoExpr.prop + ExpandoExpr.m(12) + ExpandoExpr(101).length;
106+
var n = (ExpandoExpr.prop.x || 0) + ExpandoExpr.m(12) + ExpandoExpr(101).length;
76107
var ExpandoArrow = function (n) { return n.toString(); };
77108
ExpandoArrow.prop = 2;
78109
ExpandoArrow.m = function (n) {
79110
return n + 1;
80111
};
112+
function ExpandoNested(n) {
113+
var nested = function (m) {
114+
return n + m;
115+
};
116+
nested.total = n + 1000000;
117+
return nested;
118+
}
119+
ExpandoNested.also = -1;
120+
function ExpandoMerge(n) {
121+
return n * 100;
122+
}
123+
ExpandoMerge.p1 = 111;
124+
(function (ExpandoMerge) {
125+
ExpandoMerge.p2 = 222;
126+
})(ExpandoMerge || (ExpandoMerge = {}));
127+
(function (ExpandoMerge) {
128+
ExpandoMerge.p3 = 333;
129+
})(ExpandoMerge || (ExpandoMerge = {}));
130+
var n = ExpandoMerge.p1 + ExpandoMerge.p2 + ExpandoMerge.p3 + ExpandoMerge(1);
131+
var Ns;
132+
(function (Ns) {
133+
function ExpandoNamespace() { }
134+
ExpandoNamespace.p6 = 42;
135+
function foo() {
136+
return ExpandoNamespace;
137+
}
138+
Ns.foo = foo;
139+
})(Ns || (Ns = {}));
81140
// Should not work in Typescript -- must be const
82141
var ExpandoExpr2 = function (n) {
83142
return n.toString();
@@ -111,3 +170,66 @@ ExpandoExpr3.m = function (n) {
111170
return n + 1;
112171
};
113172
var n = ExpandoExpr3.prop + ExpandoExpr3.m(13) + new ExpandoExpr3().n;
173+
174+
175+
//// [typeFromPropertyAssignment29.d.ts]
176+
declare function ExpandoDecl(n: number): string;
177+
declare namespace ExpandoDecl {
178+
var prop: number;
179+
var m: (n: number) => number;
180+
}
181+
declare var n: number;
182+
declare const ExpandoExpr: {
183+
(n: number): string;
184+
prop: {
185+
x: number;
186+
y?: undefined;
187+
} | {
188+
y: string;
189+
x?: undefined;
190+
};
191+
m(n: number): number;
192+
};
193+
declare var n: number;
194+
declare const ExpandoArrow: {
195+
(n: number): string;
196+
prop: number;
197+
m(n: number): number;
198+
};
199+
declare function ExpandoNested(n: number): {
200+
(m: number): number;
201+
total: number;
202+
};
203+
declare namespace ExpandoNested {
204+
var also: number;
205+
}
206+
declare function ExpandoMerge(n: number): number;
207+
declare namespace ExpandoMerge {
208+
var p1: number;
209+
}
210+
declare namespace ExpandoMerge {
211+
var p2: number;
212+
}
213+
declare namespace ExpandoMerge {
214+
var p3: number;
215+
}
216+
declare var n: number;
217+
declare namespace Ns {
218+
function ExpandoNamespace(): void;
219+
namespace ExpandoNamespace {
220+
var p6: number;
221+
}
222+
function foo(): typeof ExpandoNamespace;
223+
}
224+
declare var ExpandoExpr2: (n: number) => string;
225+
declare var n: number;
226+
declare class ExpandoClass {
227+
n: number;
228+
}
229+
declare var n: number;
230+
declare var ExpandoExpr3: {
231+
new (): {
232+
n: number;
233+
};
234+
};
235+
declare var n: number;

0 commit comments

Comments
 (0)