Skip to content

Commit 6ca9d04

Browse files
authored
Constructor functions as classes (#32944)
* Initial implementation The original test passes but I haven't run any other tests yet, so I assume the world is now broken. * Append constructor function construct sigs Instead of overwriting them * Grab bag of improvements. 1. Mark @class-tagged functions with Class too. 2. Only gather local type parameters of constructor functions. 3. Remove getJSClassType calls with getDeclaredTypeOfSymbol. 4. Add a couple more failing tests. getDeclaredTypeOfClassOrInterface now needs to understand prototype assignment. That's next, I think. * Prototype assignments work now 1. Binder marks prototype assignments as Class now. 2. Checker merges prototype assignments using the same merge code as for functions and their declarations. No more intersections. Many fewer failing tests now. * Mark prototype-property assignments as Class Even if there are no this-property assignments in them. (Then why are you using a class?). * Simplify getJSClassType, remove calls to its guts It's probably not needed because now it's just a conditional call to getDeclaredTypeOfSymbol, and I think most callers already know whether they have a JS constructor function beforehand. * isJSDocConstructor doesn't need to check prototype anymore Because all the properties are merged during getDeclaredTypeOfSymbol. * outer type parameter lookup follow prototype assignment * this-type and -expression support in ctor funcs Pretty cool! * Fix remaining tests * Fix minor lint * Delete now-unused code * Add class flag to nested class declarations Also remove old TODOs
1 parent fd1e22b commit 6ca9d04

File tree

61 files changed

+1167
-336
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1167
-336
lines changed

src/compiler/binder.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ namespace ts {
227227
symbol.flags |= symbolFlags;
228228

229229
node.symbol = symbol;
230-
symbol.declarations = append(symbol.declarations, node);
230+
symbol.declarations = appendIfUnique(symbol.declarations, node);
231231

232232
if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.Module | SymbolFlags.Variable) && !symbol.exports) {
233233
symbol.exports = createSymbolTable();
@@ -737,6 +737,9 @@ namespace ts {
737737
case SyntaxKind.JSDocEnumTag:
738738
bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag);
739739
break;
740+
case SyntaxKind.JSDocClassTag:
741+
bindJSDocClassTag(node as JSDocClassTag);
742+
break;
740743
// In source files and blocks, bind functions first to match hoisting that occurs at runtime
741744
case SyntaxKind.SourceFile: {
742745
bindEachFunctionsFirst((node as SourceFile).statements);
@@ -1446,6 +1449,14 @@ namespace ts {
14461449
}
14471450
}
14481451

1452+
function bindJSDocClassTag(node: JSDocClassTag) {
1453+
bindEachChild(node);
1454+
const host = getHostSignatureFromJSDoc(node);
1455+
if (host && host.kind !== SyntaxKind.MethodDeclaration) {
1456+
addDeclarationToSymbol(host.symbol, host, SymbolFlags.Class);
1457+
}
1458+
}
1459+
14491460
function bindCallExpressionFlow(node: CallExpression) {
14501461
// If the target of the call expression is a function expression or arrow function we have
14511462
// an immediately invoked function expression (IIFE). Initialize the flowNode property to
@@ -1813,7 +1824,8 @@ namespace ts {
18131824
// typedef anchored to an A.B.C assignment - we need to bind into B's namespace under name C
18141825
const isTopLevel = isTopLevelNamespaceAssignment(declName.parent);
18151826
if (isTopLevel) {
1816-
bindPotentiallyMissingNamespaces(file.symbol, declName.parent, isTopLevel, !!findAncestor(declName, d => isPropertyAccessExpression(d) && d.name.escapedText === "prototype"));
1827+
bindPotentiallyMissingNamespaces(file.symbol, declName.parent, isTopLevel,
1828+
!!findAncestor(declName, d => isPropertyAccessExpression(d) && d.name.escapedText === "prototype"), /*containerIsClass*/ false);
18171829
const oldContainer = container;
18181830
container = isPropertyAccessExpression(declName.parent.expression) ? declName.parent.expression.name : declName.parent.expression;
18191831
declareModuleMember(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
@@ -2510,6 +2522,7 @@ namespace ts {
25102522
constructorSymbol.members = constructorSymbol.members || createSymbolTable();
25112523
// It's acceptable for multiple 'this' assignments of the same identifier to occur
25122524
declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property);
2525+
addDeclarationToSymbol(constructorSymbol, constructorSymbol.valueDeclaration, SymbolFlags.Class);
25132526
}
25142527
break;
25152528

@@ -2558,7 +2571,7 @@ namespace ts {
25582571
node.left.parent = node;
25592572
node.right.parent = node;
25602573
const lhs = node.left as PropertyAccessEntityNameExpression;
2561-
bindPropertyAssignment(lhs.expression, lhs, /*isPrototypeProperty*/ false);
2574+
bindPropertyAssignment(lhs.expression, lhs, /*isPrototypeProperty*/ false, /*containerIsClass*/ true);
25622575
}
25632576

25642577
function bindObjectDefinePrototypeProperty(node: BindableObjectDefinePropertyCall) {
@@ -2581,13 +2594,13 @@ namespace ts {
25812594
constructorFunction.parent = classPrototype;
25822595
classPrototype.parent = lhs;
25832596

2584-
bindPropertyAssignment(constructorFunction, lhs, /*isPrototypeProperty*/ true);
2597+
bindPropertyAssignment(constructorFunction, lhs, /*isPrototypeProperty*/ true, /*containerIsClass*/ true);
25852598
}
25862599

25872600
function bindObjectDefinePropertyAssignment(node: BindableObjectDefinePropertyCall) {
25882601
let namespaceSymbol = lookupSymbolForPropertyAccess(node.arguments[0]);
25892602
const isToplevel = node.parent.parent.kind === SyntaxKind.SourceFile;
2590-
namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, node.arguments[0], isToplevel, /*isPrototypeProperty*/ false);
2603+
namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, node.arguments[0], isToplevel, /*isPrototypeProperty*/ false, /*containerIsClass*/ false);
25912604
bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ false);
25922605
}
25932606

@@ -2618,10 +2631,10 @@ namespace ts {
26182631
*/
26192632
function bindStaticPropertyAssignment(node: PropertyAccessEntityNameExpression) {
26202633
node.expression.parent = node;
2621-
bindPropertyAssignment(node.expression, node, /*isPrototypeProperty*/ false);
2634+
bindPropertyAssignment(node.expression, node, /*isPrototypeProperty*/ false, /*containerIsClass*/ false);
26222635
}
26232636

2624-
function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: EntityNameExpression, isToplevel: boolean, isPrototypeProperty: boolean) {
2637+
function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: EntityNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) {
26252638
if (isToplevel && !isPrototypeProperty) {
26262639
// make symbols or add declarations for intermediate containers
26272640
const flags = SymbolFlags.Module | SymbolFlags.Assignment;
@@ -2638,6 +2651,9 @@ namespace ts {
26382651
}
26392652
});
26402653
}
2654+
if (containerIsClass && namespaceSymbol) {
2655+
addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class);
2656+
}
26412657
return namespaceSymbol;
26422658
}
26432659

@@ -2663,10 +2679,10 @@ namespace ts {
26632679
: propertyAccess.parent.parent.kind === SyntaxKind.SourceFile;
26642680
}
26652681

2666-
function bindPropertyAssignment(name: EntityNameExpression, propertyAccess: PropertyAccessEntityNameExpression, isPrototypeProperty: boolean) {
2682+
function bindPropertyAssignment(name: EntityNameExpression, propertyAccess: PropertyAccessEntityNameExpression, isPrototypeProperty: boolean, containerIsClass: boolean) {
26672683
let namespaceSymbol = lookupSymbolForPropertyAccess(name);
26682684
const isToplevel = isTopLevelNamespaceAssignment(propertyAccess);
2669-
namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty);
2685+
namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty, containerIsClass);
26702686
bindPotentiallyNewExpandoMemberToNamespace(propertyAccess, namespaceSymbol, isPrototypeProperty);
26712687
}
26722688

0 commit comments

Comments
 (0)