Skip to content
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

Local types #3266

Merged
merged 18 commits into from
May 31, 2015
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,17 +519,17 @@ module ts {
bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.Class, SymbolFlags.ClassExcludes);
break;
case SyntaxKind.InterfaceDeclaration:
bindDeclaration(<Declaration>node, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes, /*isBlockScopeContainer*/ false);
bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes);
break;
case SyntaxKind.TypeAliasDeclaration:
bindDeclaration(<Declaration>node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes, /*isBlockScopeContainer*/ false);
bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
break;
case SyntaxKind.EnumDeclaration:
if (isConst(node)) {
bindDeclaration(<Declaration>node, SymbolFlags.ConstEnum, SymbolFlags.ConstEnumExcludes, /*isBlockScopeContainer*/ false);
bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.ConstEnum, SymbolFlags.ConstEnumExcludes);
}
else {
bindDeclaration(<Declaration>node, SymbolFlags.RegularEnum, SymbolFlags.RegularEnumExcludes, /*isBlockScopeContainer*/ false);
bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.RegularEnum, SymbolFlags.RegularEnumExcludes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess classes are already block scoped?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.

}
break;
case SyntaxKind.ModuleDeclaration:
Expand Down
139 changes: 99 additions & 40 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,15 @@ module ts {
// Locals of a source file are not in scope (because they get merged into the global symbol table)
if (location.locals && !isGlobalSourceFile(location)) {
if (result = getSymbol(location.locals, name, meaning)) {
break loop;
// Type parameters of a function are in scope in the entire function declaration, including the parameter
// list and return type. However, local types are only in scope in the function body.
if (!(meaning & SymbolFlags.Type) ||
!(result.flags & (SymbolFlags.Type & ~SymbolFlags.TypeParameter)) ||
!isFunctionLike(location) ||
lastLocation === (<FunctionLikeDeclaration>location).body) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary? I thought block scope declarations (like local types) are in the .locals of the body, and not of the function itself. Shouldn't that fact make this automatically work out? If that is not true, I think it is a better solution than having this special case.

If this condition is necessary, then isn't this broken for let and const as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Top-level block scoped declarations are always in the locals of the function itself. We need them to be in the same declaration space as parameters and top-level var and function declarations in order to properly detect collisions. I thought about having local types in a locals attached to the body, but it doesn't work when a symbol is declared both as a value and a type--you can't split one meaning off and put it in a different declaration space.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So a top level let in a function can collide with a parameter? If that is the case, can't local types be handled in the same place that we handle block scoped declarations?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a top-level let can collide with a parameter. I don't understand the second question.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My second question has to do with this case

function f(x = a) {
    let a = 0;
}

a is not in scope where it is referenced in the parameter initializer. Yet it is in the locals of f. Presumably there is some logic to skip that symbol when we walk up, similar to what you've implemented here. My question is: Where is that logic, and can we combine your added logic with the logic for let and const?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no logic to skip that symbol, but there is a check that x's initializer only reference identifiers declared before x (checkParameterInitializer in checker.ts).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh so a top level let declaration is in scope in a parameter, it's just not a legal reference. Whereas local types are not even in scope in the parameters or return type. Is there a reason we chose a different behavior from top level block constructs?

interface Foo { }
var x: Foo;
function bar(param: Foo = x) { // x reference is an error, but Foo references the outer interface
    interface Foo {
        prop;
    }
    var x: Foo;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initializers are evaluated inside the function in the generated code. We therefore need identifiers in expressions to bind to locals, even if that is subsequently deemed an error. If the locals weren't in scope at all, the following would produce no errors, but it wouldn't actually work:

var a = 1;
function f(x = a) {  // We don't want this to bind to the global a
    let a = 2;
}

We don't have any such problem with local types, so we can just take them out of scope.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh okay. That makes sense, thanks for explaining. It might be worth adding a comment to that effect.

break loop;
}
result = undefined;
}
}
switch (location.kind) {
Expand Down Expand Up @@ -2465,30 +2473,68 @@ module ts {
}
}

// Return combined list of type parameters from all declarations of a class or interface. Elsewhere we check they're all
// the same, but even if they're not we still need the complete list to ensure instantiations supply type arguments
// for all type parameters.
function getTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] {
// Appends the type parameters given by a list of declarations to a set of type parameters and returns the resulting set.
// The function allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set
// in-place and returns the same array.
function appendTypeParameters(typeParameters: TypeParameter[], declarations: TypeParameterDeclaration[]): TypeParameter[] {
for (let declaration of declarations) {
let tp = getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration));
if (!typeParameters) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could just move this check outside the loop.

if (!typeParameters && declarations.length > 0) {
    typeParameters = [];
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's there is more efficient because it creates the single element array in one go vs. first creating an empty array and then pushing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what this function does, it looks like it should really be written as:

function appendTypeParameters(declarations: TypeParameterDeclaration[], typeParameters?: TypeParameters[]) {
...

The declarations are the actual input. The 'typeParameters' array is the output. We don't generally put output parameters before intput parameters. Also, as the second parameter is optional (based on the !typeParameters check), we should likely mark it as such.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, typeParameters is the object being operated on, but its allocation is deferred and never happens if there are no type parameters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahejlsberg It would be best to document that for consumers so that they can quickly eye the function and know this.

typeParameters = [tp];
}
else if (!contains(typeParameters, tp)) {
typeParameters.push(tp);
}
}
return typeParameters;
}

// Appends the outer type parameters of a node to a set of type parameters and returns the resulting set. The function
// allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set in-place and
// returns the same array.
function appendOuterTypeParameters(typeParameters: TypeParameter[], node: Node): TypeParameter[]{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space before {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the &(@#$&( auto formatting. Are we ever going to fix that bug?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find the corresponding issue, so I filed #3269.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which is a duplicate of #2628.

while (true) {
node = node.parent;
if (!node) {
return typeParameters;
}
if (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.FunctionDeclaration ||
node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.MethodDeclaration ||
node.kind === SyntaxKind.ArrowFunction) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you use two newlines after { after a wrapped conditional. Right now what happens is that hte next statement is indented exactly as far as the conditional, making it look like it continues the conditional. Providing the extra newline makes it clear that this is a statement inside the body of the code, and is not part of the if-expression.

let declarations = (<ClassDeclaration | FunctionLikeDeclaration>node).typeParameters;
if (declarations) {
return appendTypeParameters(appendOuterTypeParameters(typeParameters, node), declarations);
}
}
}
}

// The outer type parameters are those defined by enclosing generic classes, methods, or functions.
function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] {
var kind = symbol.flags & SymbolFlags.Class ? SyntaxKind.ClassDeclaration : SyntaxKind.InterfaceDeclaration;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better to check SymbolFlags.Interface as well

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to: You can't declare classes or interfaces inside interfaces.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant checking symbol.flags & SymbolFlags.Interface, in case the local type being declared is an interface. I didn't mean inside an interface.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the method is only ever called for a class or interface, so if it isn't a class it must be an interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use let not var

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we'll probably need to come back to this when we do class expressions

return appendOuterTypeParameters(undefined, getDeclarationOfKind(symbol, kind));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

named parameter for the "undefined".

}

// The local type parameters are the combined set of type parameters from all declarations of the class or interface.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"all declarations of the class" - I'm a little confused, when can we get multiple declarations of a class?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless you mean all class and interface declarations of a symbol.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently only permit multiple declarations of interfaces, but we'll likely allow it for ambient classes as part of the work of changing the core type declarations in lib.d.ts to use class declarations.

function getLocalTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] {
let result: TypeParameter[];
forEach(symbol.declarations, node => {
for (let node of symbol.declarations) {
if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration) {
let declaration = <InterfaceDeclaration>node;
if (declaration.typeParameters && declaration.typeParameters.length) {
forEach(declaration.typeParameters, node => {
let tp = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node));
if (!result) {
result = [tp];
}
else if (!contains(result, tp)) {
result.push(tp);
}
});
if (declaration.typeParameters) {
result = appendTypeParameters(result, declaration.typeParameters);
}
}
});
}
return result;
}

// The full set of type parameters for a generic class or interface type consists of its outer type parameters plus
// its locally declared type parameters.
function getTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] {
return concatenate(getOuterTypeParametersOfClassOrInterface(symbol), getLocalTypeParametersOfClassOrInterface(symbol));
}

function getBaseTypes(type: InterfaceType): ObjectType[] {
let typeWithBaseTypes = <InterfaceTypeWithBaseTypes>type;
if (!typeWithBaseTypes.baseTypes) {
Expand Down Expand Up @@ -2558,10 +2604,13 @@ module ts {
if (!links.declaredType) {
let kind = symbol.flags & SymbolFlags.Class ? TypeFlags.Class : TypeFlags.Interface;
let type = links.declaredType = <InterfaceType>createObjectType(kind, symbol);
let typeParameters = getTypeParametersOfClassOrInterface(symbol);
if (typeParameters) {
let outerTypeParameters = getOuterTypeParametersOfClassOrInterface(symbol);
let localTypeParameters = getLocalTypeParametersOfClassOrInterface(symbol);
if (outerTypeParameters || localTypeParameters) {
type.flags |= TypeFlags.Reference;
type.typeParameters = typeParameters;
type.typeParameters = concatenate(outerTypeParameters, localTypeParameters);
type.outerTypeParameters = outerTypeParameters;
type.localTypeParameters = localTypeParameters;
(<GenericType>type).instantiations = {};
(<GenericType>type).instantiations[getTypeListId(type.typeParameters)] = <GenericType>type;
(<GenericType>type).target = <GenericType>type;
Expand Down Expand Up @@ -2751,12 +2800,12 @@ module ts {
return map(baseSignatures, baseSignature => {
let signature = baseType.flags & TypeFlags.Reference ?
getSignatureInstantiation(baseSignature, (<TypeReference>baseType).typeArguments) : cloneSignature(baseSignature);
signature.typeParameters = classType.typeParameters;
signature.typeParameters = classType.localTypeParameters;
signature.resolvedReturnType = classType;
return signature;
});
}
return [createSignature(undefined, classType.typeParameters, emptyArray, classType, 0, false, false)];
return [createSignature(undefined, classType.localTypeParameters, emptyArray, classType, 0, false, false)];
}

function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable {
Expand Down Expand Up @@ -3105,7 +3154,7 @@ module ts {
let links = getNodeLinks(declaration);
if (!links.resolvedSignature) {
let classType = declaration.kind === SyntaxKind.Constructor ? getDeclaredTypeOfClassOrInterface((<ClassDeclaration>declaration.parent).symbol) : undefined;
let typeParameters = classType ? classType.typeParameters :
let typeParameters = classType ? classType.localTypeParameters :
declaration.typeParameters ? getTypeParametersFromDeclaration(declaration.typeParameters) : undefined;
let parameters: Symbol[] = [];
let hasStringLiterals = false;
Expand Down Expand Up @@ -3422,12 +3471,18 @@ module ts {
else {
type = getDeclaredTypeOfSymbol(symbol);
if (type.flags & (TypeFlags.Class | TypeFlags.Interface) && type.flags & TypeFlags.Reference) {
let typeParameters = (<InterfaceType>type).typeParameters;
if (node.typeArguments && node.typeArguments.length === typeParameters.length) {
type = createTypeReference(<GenericType>type, map(node.typeArguments, getTypeFromTypeNode));
// In a type reference, the outer type parameters of the referenced class or interface are automatically
// supplied as type arguments and the type reference only specifies arguments for the local type parameters
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do the outer type parameters have to be supplied as type arguments to the TypeReference? Why can't we just create a type reference that doesn't have any mappings for the outer type parameters? All the mappers for the outer type parameters will just end up mapping things to themselves anyway.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is true only for written type references. Type references created by instantiations will indeed end up having the type parameters mapped to different types. For example:

function f<T>() {
    class C {
        x: T;
    }
    var c: C;  // Really a C<T>
    c = new C();
    return c;
}
var c = f<string>();  // Type of c is C<string>
var s = c.x;          // string

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but why is it necessary to map them at all for written type references? Why can't we just have them unmapped, and then later in an instantiation, they will get mapped?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would then need instantiateType to instantiate all local type declarations (because they potentially reference outer type parameters) which it isn't prepared to do. Also, if those local type declarations themselves have type parameters, we'd end up with type references where both the target and the type arguments need to be instantiated. These are all "landmines" for runaway generic instantiation that we could never detect. Right now, we can only detect runaway instantiations that originate in type references and only by making the outer type parameters be type parameters of the class/interface do we see all of them in one place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the appending of the outer type parameters to the type parameters of the class happens in getDeclaredTypeOfClassOrInterface. I was asking why type arguments need to be supplied for those outer type parameters. Why can't the type reference have these outer type parameters as type parameters, but with no type arguments for them. I thought that if an instantiation is missing type arguments for its type parameters, the mapper will just return the original type parameter.

I think what I am asking is: Is not passing the outer type parameters as type arguments a suitable optimization?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's an example. Enjoy! The infinite expansion is stopped by our isDeeplyNestedGeneric check for type references, but I don't know of a mechanism that could stop what instantiateType would do.

function f<T>() {
    class C {
        x = f<C>();
    }
    return new C();
}
function g<T>() {
    class D {
        x = g<D>();
    }
    return new D();
}
var a = f<string>();
var b = g<string>();
a = b;
b = a;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh how interesting! I see how the cyclic instantiation happens.

Btw, I came up with another example of this, based on a combination of the example you just gave, and the one from #3237. It looks like this:

function foo<T>() {
    var z = foo();
    var y: {
        y2: typeof z
    };
    return y;
}


function bar<T>() {
    var z = bar();
    var y: {
        y2: typeof z;
    }
    return y;
}

var a = foo<number>();
var b = bar<number>();
a = b;

Seems to stack overflow for me. Does your change fix this?

What's interesting here is that T is never referenced in foo or bar. Its mere presence causes us to instantiate infinitely. Although I admit I don't really understand what is going on here.

Perhaps this is more related to #3237, because we don't reuse the mapper between signature instantiations.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, my change doesn't affect this. Looks like the issue is runaway recursion in instantiateSignature, similar to what we fixed for instantiateType in #3237. We should be able to fix it in the same way, except signatures don't currently have an id property. We might have to introduce one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Even if we put in place a cache for already instantiated signatures, I think you can use this as the same kind of infinitely expanding generics device as type references. For example, var z = foo<T[]>() in the example above would continually create new signatures. Makes me think we need the same kind of infinite expansion protection in place as we have for type references. That could get rather painful.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've opened up #3309 for this

// of the class or interface.
let localTypeParameters = (<InterfaceType>type).localTypeParameters;
let expectedTypeArgCount = localTypeParameters ? localTypeParameters.length : 0;
let typeArgCount = node.typeArguments ? node.typeArguments.length : 0;
if (typeArgCount === expectedTypeArgCount) {
type = createTypeReference(<GenericType>type, concatenate((<InterfaceType>type).outerTypeParameters,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, I think there is an optimization you can do here. If typeArgCount and expectedTypeArgCount are zero, then type is already the correct thing because all the type arguments are exactly the same as the type arguments of the declared type. createTypeReference will just look up the type in the instantiations cache and find the type itself.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it.

map(node.typeArguments, getTypeFromTypeNode)));
}
else {
error(node, Diagnostics.Generic_type_0_requires_1_type_argument_s, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType), typeParameters.length);
error(node, Diagnostics.Generic_type_0_requires_1_type_argument_s, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType), expectedTypeArgCount);
type = undefined;
}
}
Expand Down Expand Up @@ -3884,7 +3939,7 @@ module ts {
return mapper(<TypeParameter>type);
}
if (type.flags & TypeFlags.Anonymous) {
return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) ?
return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) ?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for anonymous class expressions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's for the anonymous type created for the static side of a class.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, because previously the static side of a class could never get instantiated.

instantiateAnonymousType(<ObjectType>type, mapper) : type;
}
if (type.flags & TypeFlags.Reference) {
Expand Down Expand Up @@ -8968,7 +9023,6 @@ module ts {
function checkFunctionDeclaration(node: FunctionDeclaration): void {
if (produceDiagnostics) {
checkFunctionLikeDeclaration(node) ||
checkGrammarDisallowedModifiersInBlockOrObjectLiteralExpression(node) ||
checkGrammarFunctionName(node.name) ||
checkGrammarForGenerator(node);

Expand Down Expand Up @@ -9330,7 +9384,7 @@ module ts {

function checkVariableStatement(node: VariableStatement) {
// Grammar checking
checkGrammarDecorators(node) || checkGrammarDisallowedModifiersInBlockOrObjectLiteralExpression(node) || checkGrammarModifiers(node) || checkGrammarVariableDeclarationList(node.declarationList) || checkGrammarForDisallowedLetOrConstStatement(node);
checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarVariableDeclarationList(node.declarationList) || checkGrammarForDisallowedLetOrConstStatement(node);

forEach(node.declarationList.declarations, checkSourceElement);
}
Expand Down Expand Up @@ -9992,10 +10046,6 @@ module ts {
function checkClassDeclaration(node: ClassDeclaration) {
checkGrammarDeclarationNameInStrictMode(node);
// Grammar checking
if (node.parent.kind !== SyntaxKind.ModuleBlock && node.parent.kind !== SyntaxKind.SourceFile) {
grammarErrorOnNode(node, Diagnostics.class_declarations_are_only_supported_directly_inside_a_module_or_as_a_top_level_declaration);
}

if (!node.name && !(node.flags & NodeFlags.Default)) {
grammarErrorOnFirstToken(node, Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name);
}
Expand Down Expand Up @@ -12247,19 +12297,28 @@ module ts {
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.IndexSignature:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.VariableStatement:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.ImportDeclaration:
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ExportDeclaration:
case SyntaxKind.ExportAssignment:
case SyntaxKind.Parameter:
break;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.VariableStatement:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.TypeAliasDeclaration:
if (node.modifiers && node.parent.kind !== SyntaxKind.ModuleBlock && node.parent.kind !== SyntaxKind.SourceFile) {
return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here);
}
break;
case SyntaxKind.EnumDeclaration:
if (node.modifiers && (node.modifiers.length > 1 || node.modifiers[0].kind !== SyntaxKind.ConstKeyword) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Break into multiple checks. The mix of &&s and ||s make this difficult to read.

node.parent.kind !== SyntaxKind.ModuleBlock && node.parent.kind !== SyntaxKind.SourceFile) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider: extract common helper for checking that the parent is a module or source file (repeated above as well, as well as in "shouldWriteTypeOfFunctionSymbol", "checkGrammarModifiers", "checkGrammarStatementInAmbientContext", ).

return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here);
}
break;
default:
return false;
}
Expand Down
1 change: 0 additions & 1 deletion src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,5 @@ module ts {
Generators_are_not_currently_supported: { code: 9001, category: DiagnosticCategory.Error, key: "Generators are not currently supported." },
Only_identifiers_Slashqualified_names_with_optional_type_arguments_are_currently_supported_in_a_class_extends_clauses: { code: 9002, category: DiagnosticCategory.Error, key: "Only identifiers/qualified-names with optional type arguments are currently supported in a class 'extends' clauses." },
class_expressions_are_not_currently_supported: { code: 9003, category: DiagnosticCategory.Error, key: "'class' expressions are not currently supported." },
class_declarations_are_only_supported_directly_inside_a_module_or_as_a_top_level_declaration: { code: 9004, category: DiagnosticCategory.Error, key: "'class' declarations are only supported directly inside a module or as a top level declaration." },
};
}
4 changes: 0 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2168,9 +2168,5 @@
"'class' expressions are not currently supported.": {
"category": "Error",
"code": 9003
},
"'class' declarations are only supported directly inside a module or as a top level declaration.": {
"category": "Error",
"code": 9004
}
}
Loading