-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged Declarations for Classes and Interfaces #3333
Changes from 11 commits
813d227
18abf47
05500f4
6b8d033
6c98c67
b864df2
ed46bc3
d25b910
1f74b13
2d77cbd
edc4611
a50ab3e
90e1955
c629c3f
e4bc29e
b293da4
936aea8
0917582
fa06f3e
fa9b6fc
9e1ab92
f3278e2
2b899f1
29c9286
d000e01
143890b
323ce24
365ea3d
015c2c1
5ef426c
3a3479d
19b0c51
91e3a5c
851c7e4
4878cce
c06e5eb
3af3177
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,6 +135,23 @@ module ts { | |
return node.name ? declarationNameToString(node.name) : getDeclarationName(node); | ||
} | ||
|
||
/** | ||
* Checks if the symbol contains a class declaration that is non-ambient. | ||
*/ | ||
function hasNonAmbientClass(symbol: Symbol): boolean { | ||
return symbol && forEach(symbol.declarations, (element: Declaration) => { | ||
return element.kind === SyntaxKind.ClassDeclaration && !(element.flags & NodeFlags.Ambient); | ||
}); | ||
} | ||
|
||
/** | ||
* Declares a Symbol for the Node and add it to symbols. Reports errors for conflicting identifier names. | ||
* @param symbols - The symbolTable which node will be added to. | ||
* @param parent - If node is in a class, parent denotes the parent declaration. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JsonFreeman @CyrusNajmabadi does parent only refer to classes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And if so, just say "denotes that class" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it's for any member relationship. So it can be in a class, interface, object type literal, enum or module (if it's a module the member has to be exported to have a parent). |
||
* @param node - The declaration to be added to the symbol table | ||
* @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.) | ||
* @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations. | ||
*/ | ||
function declareSymbol(symbols: SymbolTable, parent: Symbol, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags): Symbol { | ||
Debug.assert(!hasDynamicName(node)); | ||
|
||
|
@@ -144,15 +161,18 @@ module ts { | |
let symbol: Symbol; | ||
if (name !== undefined) { | ||
symbol = hasProperty(symbols, name) ? symbols[name] : (symbols[name] = createSymbol(0, name)); | ||
if (symbol.flags & excludes) { | ||
|
||
// Check for declarations 'node' cannot be merged with. | ||
// Interfaces declarations cannot be merged with non-ambient class declarations, which isn't encoded in SymbolFlags. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest two possible options:
I have a strong preference for option 1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will move ahead with (1). Thanks! |
||
if (symbol.flags & excludes || (node.kind === SyntaxKind.InterfaceDeclaration && hasNonAmbientClass(symbol))) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leave a comment for the case this covers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the comment include a reference either to the github issue or an entry in the spec? |
||
if (node.name) { | ||
node.name.parent = node; | ||
} | ||
|
||
// Report errors every position with duplicate declaration | ||
// Report errors on previous encountered declarations | ||
let message = symbol.flags & SymbolFlags.BlockScopedVariable | ||
? Diagnostics.Cannot_redeclare_block_scoped_variable_0 | ||
? Diagnostics.Cannot_redeclare_block_scoped_variable_0 | ||
: Diagnostics.Duplicate_identifier_0; | ||
|
||
forEach(symbol.declarations, declaration => { | ||
|
@@ -249,7 +269,7 @@ module ts { | |
// these cases are: | ||
// - node has locals (symbolKind & HasLocals) !== 0 | ||
// - node is a source file | ||
setBlockScopeContainer(node, /*cleanLocals*/ (symbolKind & SymbolFlags.HasLocals) === 0 && node.kind !== SyntaxKind.SourceFile); | ||
setBlockScopeContainer(node, /*cleanLocals*/ (symbolKind & SymbolFlags.HasLocals) === 0 && node.kind !== SyntaxKind.SourceFile); | ||
} | ||
|
||
forEachChild(node, bind); | ||
|
@@ -424,7 +444,7 @@ module ts { | |
|
||
function bind(node: Node) { | ||
node.parent = parent; | ||
|
||
switch (node.kind) { | ||
case SyntaxKind.TypeParameter: | ||
bindDeclaration(<Declaration>node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes, /*isBlockScopeContainer*/ false); | ||
|
@@ -516,7 +536,7 @@ module ts { | |
bindCatchVariableDeclaration(<CatchClause>node); | ||
break; | ||
case SyntaxKind.ClassDeclaration: | ||
bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.Class, SymbolFlags.ClassExcludes); | ||
bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.Class, isAmbientContext(node) ? SymbolFlags.AmbientClassExcludes : SymbolFlags.ClassExcludes); | ||
break; | ||
case SyntaxKind.InterfaceDeclaration: | ||
bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1473,7 +1473,8 @@ module ts { | |
EnumMemberExcludes = Value, | ||
FunctionExcludes = Value & ~(Function | ValueModule), | ||
ClassExcludes = (Value | Type) & ~ValueModule, | ||
InterfaceExcludes = Type & ~Interface, | ||
AmbientClassExcludes = (Value | Type) & ~(ValueModule | Interface), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you do the other change I suggested (https://github.com/Microsoft/TypeScript/pull/3333/files#r31552915), then this goes away, and ClassExcludes becomes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not just letting classes and interfaces merge, and reporting an error later on in CheckClassDeclaration for instance if the class is not ambient? just like we do with merging order for module/class and module/class being in different files. |
||
InterfaceExcludes = Type & ~(Interface | Class), | ||
RegularEnumExcludes = (Value | Type) & ~(RegularEnum | ValueModule), // regular enums merge only with regular enums and modules | ||
ConstEnumExcludes = (Value | Type) & ~ConstEnum, // const enums merge only with const enums | ||
ValueModuleExcludes = Value & ~(Function | Class | RegularEnum | ValueModule), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
tests/cases/conformance/classes/classDeclarations/classAndInterfaceMergeConflictingMembers.ts(2,12): error TS2300: Duplicate identifier 'x'. | ||
tests/cases/conformance/classes/classDeclarations/classAndInterfaceMergeConflictingMembers.ts(6,5): error TS2300: Duplicate identifier 'x'. | ||
tests/cases/conformance/classes/classDeclarations/classAndInterfaceMergeConflictingMembers.ts(10,15): error TS2300: Duplicate identifier 'x'. | ||
tests/cases/conformance/classes/classDeclarations/classAndInterfaceMergeConflictingMembers.ts(14,5): error TS2300: Duplicate identifier 'x'. | ||
tests/cases/conformance/classes/classDeclarations/classAndInterfaceMergeConflictingMembers.ts(18,13): error TS2300: Duplicate identifier 'x'. | ||
tests/cases/conformance/classes/classDeclarations/classAndInterfaceMergeConflictingMembers.ts(22,5): error TS2300: Duplicate identifier 'x'. | ||
|
||
|
||
==== tests/cases/conformance/classes/classDeclarations/classAndInterfaceMergeConflictingMembers.ts (6 errors) ==== | ||
declare class C1 { | ||
public x : number; | ||
~ | ||
!!! error TS2300: Duplicate identifier 'x'. | ||
} | ||
|
||
interface C1 { | ||
x : number; | ||
~ | ||
!!! error TS2300: Duplicate identifier 'x'. | ||
} | ||
|
||
declare class C2 { | ||
protected x : number; | ||
~ | ||
!!! error TS2300: Duplicate identifier 'x'. | ||
} | ||
|
||
interface C2 { | ||
x : number; | ||
~ | ||
!!! error TS2300: Duplicate identifier 'x'. | ||
} | ||
|
||
declare class C3 { | ||
private x : number; | ||
~ | ||
!!! error TS2300: Duplicate identifier 'x'. | ||
} | ||
|
||
interface C3 { | ||
x : number; | ||
~ | ||
!!! error TS2300: Duplicate identifier 'x'. | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
//// [classAndInterfaceMergeConflictingMembers.ts] | ||
declare class C1 { | ||
public x : number; | ||
} | ||
|
||
interface C1 { | ||
x : number; | ||
} | ||
|
||
declare class C2 { | ||
protected x : number; | ||
} | ||
|
||
interface C2 { | ||
x : number; | ||
} | ||
|
||
declare class C3 { | ||
private x : number; | ||
} | ||
|
||
interface C3 { | ||
x : number; | ||
} | ||
|
||
//// [classAndInterfaceMergeConflictingMembers.js] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
tests/cases/conformance/classes/classDeclarations/declaredClassMergedwithSelf.ts(1,15): error TS2300: Duplicate identifier 'C1'. | ||
tests/cases/conformance/classes/classDeclarations/declaredClassMergedwithSelf.ts(3,15): error TS2300: Duplicate identifier 'C1'. | ||
tests/cases/conformance/classes/classDeclarations/declaredClassMergedwithSelf.ts(5,15): error TS2300: Duplicate identifier 'C2'. | ||
tests/cases/conformance/classes/classDeclarations/declaredClassMergedwithSelf.ts(7,11): error TS2300: Duplicate identifier 'C2'. | ||
tests/cases/conformance/classes/classDeclarations/declaredClassMergedwithSelf.ts(9,15): error TS2300: Duplicate identifier 'C2'. | ||
|
||
|
||
==== tests/cases/conformance/classes/classDeclarations/declaredClassMergedwithSelf.ts (5 errors) ==== | ||
declare class C1 {} | ||
~~ | ||
!!! error TS2300: Duplicate identifier 'C1'. | ||
|
||
declare class C1 {} | ||
~~ | ||
!!! error TS2300: Duplicate identifier 'C1'. | ||
|
||
declare class C2 {} | ||
~~ | ||
!!! error TS2300: Duplicate identifier 'C2'. | ||
|
||
interface C2 {} | ||
~~ | ||
!!! error TS2300: Duplicate identifier 'C2'. | ||
|
||
declare class C2 {} | ||
~~ | ||
!!! error TS2300: Duplicate identifier 'C2'. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//// [declaredClassMergedwithSelf.ts] | ||
declare class C1 {} | ||
|
||
declare class C1 {} | ||
|
||
declare class C2 {} | ||
|
||
interface C2 {} | ||
|
||
declare class C2 {} | ||
|
||
//// [declaredClassMergedwithSelf.js] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//// [mergedClassInterface.ts] | ||
declare class C1 {} | ||
|
||
interface C1 {} | ||
|
||
interface C2 {} | ||
|
||
declare class C2 {} | ||
|
||
interface C2 {} | ||
|
||
interface C2 {} | ||
|
||
//// [mergedClassInterface.js] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
=== tests/cases/conformance/classes/classDeclarations/mergedClassInterface.ts === | ||
declare class C1 {} | ||
>C1 : Symbol(C1, Decl(mergedClassInterface.ts, 0, 0), Decl(mergedClassInterface.ts, 0, 19)) | ||
|
||
interface C1 {} | ||
>C1 : Symbol(C1, Decl(mergedClassInterface.ts, 0, 0), Decl(mergedClassInterface.ts, 0, 19)) | ||
|
||
interface C2 {} | ||
>C2 : Symbol(C2, Decl(mergedClassInterface.ts, 2, 15), Decl(mergedClassInterface.ts, 4, 15), Decl(mergedClassInterface.ts, 6, 19), Decl(mergedClassInterface.ts, 8, 15)) | ||
|
||
declare class C2 {} | ||
>C2 : Symbol(C2, Decl(mergedClassInterface.ts, 2, 15), Decl(mergedClassInterface.ts, 4, 15), Decl(mergedClassInterface.ts, 6, 19), Decl(mergedClassInterface.ts, 8, 15)) | ||
|
||
interface C2 {} | ||
>C2 : Symbol(C2, Decl(mergedClassInterface.ts, 2, 15), Decl(mergedClassInterface.ts, 4, 15), Decl(mergedClassInterface.ts, 6, 19), Decl(mergedClassInterface.ts, 8, 15)) | ||
|
||
interface C2 {} | ||
>C2 : Symbol(C2, Decl(mergedClassInterface.ts, 2, 15), Decl(mergedClassInterface.ts, 4, 15), Decl(mergedClassInterface.ts, 6, 19), Decl(mergedClassInterface.ts, 8, 15)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
=== tests/cases/conformance/classes/classDeclarations/mergedClassInterface.ts === | ||
declare class C1 {} | ||
>C1 : C1 | ||
|
||
interface C1 {} | ||
>C1 : C1 | ||
|
||
interface C2 {} | ||
>C2 : C2 | ||
|
||
declare class C2 {} | ||
>C2 : C2 | ||
|
||
interface C2 {} | ||
>C2 : C2 | ||
|
||
interface C2 {} | ||
>C2 : C2 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
declare class C1 { | ||
public x : number; | ||
} | ||
|
||
interface C1 { | ||
x : number; | ||
} | ||
|
||
declare class C2 { | ||
protected x : number; | ||
} | ||
|
||
interface C2 { | ||
x : number; | ||
} | ||
|
||
declare class C3 { | ||
private x : number; | ||
} | ||
|
||
interface C3 { | ||
x : number; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
declare class C1 {} | ||
|
||
declare class C1 {} | ||
|
||
declare class C2 {} | ||
|
||
interface C2 {} | ||
|
||
declare class C2 {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
declare class C1 {} | ||
|
||
interface C1 {} | ||
|
||
interface C2 {} | ||
|
||
declare class C2 {} | ||
|
||
interface C2 {} | ||
|
||
interface C2 {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should not this be inAmbientContext instead?