Skip to content

Commit

Permalink
Introduce the EntityNameExpression type
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy Hanson committed Jul 29, 2016
1 parent 878cf85 commit 97bbbd7
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 67 deletions.
102 changes: 57 additions & 45 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -968,28 +968,39 @@ namespace ts {


function checkAndReportErrorForExtendingInterface(errorLocation: Node): boolean {
let parentClassExpression = errorLocation;
while (parentClassExpression) {
const kind = parentClassExpression.kind;
if (kind === SyntaxKind.Identifier || kind === SyntaxKind.PropertyAccessExpression) {
parentClassExpression = parentClassExpression.parent;
continue;
}
if (kind === SyntaxKind.ExpressionWithTypeArguments) {
break;
}
const parentExpression = climbToSupportedExpressionWithTypeArguments(errorLocation);
if (!parentExpression) {
return false;
}
if (!parentClassExpression) {
return false;
}
const expression = (<ExpressionWithTypeArguments>parentClassExpression).expression;
const expression = parentExpression.expression;

if (resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)) {
error(errorLocation, Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements, getTextOfNode(expression));
return true;
}
return false;
}
/**
* Climbs up parents to a SupportedExpressionWIthTypeArguments.
* Does *not* just climb to an ExpressionWithTypeArguments; instead, ensures that this really is supported.
*/
function climbToSupportedExpressionWithTypeArguments(node: Node): SupportedExpressionWithTypeArguments | undefined {
while (node) {
switch (node.kind) {
case SyntaxKind.Identifier:
case SyntaxKind.PropertyAccessExpression:
node = node.parent;
break;
case SyntaxKind.ExpressionWithTypeArguments:
Debug.assert(isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node));
return <SupportedExpressionWithTypeArguments>node;
default:
return undefined;
}
}
return undefined;
}


function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void {
Debug.assert((result.flags & SymbolFlags.BlockScopedVariable) !== 0);
Expand Down Expand Up @@ -1274,7 +1285,7 @@ namespace ts {
}

// Resolves a qualified name and any involved aliases
function resolveEntityName(name: EntityName | Expression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean): Symbol {
function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean): Symbol | undefined {
if (nodeIsMissing(name)) {
return undefined;
}
Expand All @@ -1289,7 +1300,7 @@ namespace ts {
}
}
else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) {
const left = name.kind === SyntaxKind.QualifiedName ? (<QualifiedName>name).left : (<PropertyAccessExpression>name).expression;
const left = name.kind === SyntaxKind.QualifiedName ? (<QualifiedName>name).left : (<PropertyAccessEntityNameExpression>name).expression;
const right = name.kind === SyntaxKind.QualifiedName ? (<QualifiedName>name).right : (<PropertyAccessExpression>name).name;

const namespace = resolveEntityName(left, SymbolFlags.Namespace, ignoreErrors);
Expand Down Expand Up @@ -1845,7 +1856,7 @@ namespace ts {
}
}

function isEntityNameVisible(entityName: EntityName | Expression, enclosingDeclaration: Node): SymbolVisibilityResult {
function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult {
// get symbol of the first identifier of the entityName
let meaning: SymbolFlags;
if (entityName.parent.kind === SyntaxKind.TypeQuery || isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent)) {
Expand Down Expand Up @@ -5022,7 +5033,7 @@ namespace ts {
return getDeclaredTypeOfSymbol(symbol);
}

function getTypeReferenceName(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference): LeftHandSideExpression | EntityName {
function getTypeReferenceName(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference): EntityNameOrEntityNameExpression | undefined {
switch (node.kind) {
case SyntaxKind.TypeReference:
return (<TypeReferenceNode>node).typeName;
Expand All @@ -5031,8 +5042,9 @@ namespace ts {
case SyntaxKind.ExpressionWithTypeArguments:
// We only support expressions that are simple qualified names. For other
// expressions this produces undefined.
if (isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node)) {
return (<ExpressionWithTypeArguments>node).expression;
const expr = <ExpressionWithTypeArguments>node;
if (isSupportedExpressionWithTypeArguments(expr)) {
return expr.expression;
}

// fall through;
Expand All @@ -5043,7 +5055,7 @@ namespace ts {

function resolveTypeReferenceName(
node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference,
typeReferenceName: LeftHandSideExpression | EntityName) {
typeReferenceName: EntityNameExpression | EntityName) {

if (!typeReferenceName) {
return unknownSymbol;
Expand Down Expand Up @@ -5084,15 +5096,14 @@ namespace ts {
const typeReferenceName = getTypeReferenceName(node);
symbol = resolveTypeReferenceName(node, typeReferenceName);
type = getTypeReferenceType(node, symbol);

links.resolvedSymbol = symbol;
links.resolvedType = type;
}
else {
// We only support expressions that are simple qualified names. For other expressions this produces undefined.
const typeNameOrExpression = node.kind === SyntaxKind.TypeReference ? (<TypeReferenceNode>node).typeName :
isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node) ? (<ExpressionWithTypeArguments>node).expression :
undefined;
const typeNameOrExpression: EntityNameOrEntityNameExpression = node.kind === SyntaxKind.TypeReference
? (<TypeReferenceNode>node).typeName
: isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node)
? (<SupportedExpressionWithTypeArguments>node).expression
: undefined;
symbol = typeNameOrExpression && resolveEntityName(typeNameOrExpression, SymbolFlags.Type) || unknownSymbol;
type = symbol === unknownSymbol ? unknownType :
symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? getTypeFromClassOrInterfaceReference(node, symbol) :
Expand Down Expand Up @@ -16951,20 +16962,21 @@ namespace ts {
}
}

function getFirstIdentifier(node: EntityName | Expression): Identifier {
while (true) {
if (node.kind === SyntaxKind.QualifiedName) {
node = (<QualifiedName>node).left;
}
else if (node.kind === SyntaxKind.PropertyAccessExpression) {
node = (<PropertyAccessExpression>node).expression;
}
else {
break;
}
function getFirstIdentifier(node: EntityNameOrEntityNameExpression): Identifier {
switch (node.kind) {
case SyntaxKind.Identifier:
return <Identifier>node;
case SyntaxKind.QualifiedName:
do {
node = (<QualifiedName>node).left;
} while (node.kind !== SyntaxKind.Identifier);
return <Identifier>node;
case SyntaxKind.PropertyAccessExpression:
do {
node = (<PropertyAccessEntityNameExpression>node).expression;
} while (node.kind !== SyntaxKind.Identifier);
return <Identifier>node;
}
Debug.assert(node.kind === SyntaxKind.Identifier);
return <Identifier>node;
}

function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean {
Expand Down Expand Up @@ -17663,7 +17675,7 @@ namespace ts {
return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined;
}

function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): Symbol {
function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): Symbol | undefined {
if (isDeclarationName(entityName)) {
return getSymbolOfNode(entityName.parent);
}
Expand All @@ -17682,8 +17694,8 @@ namespace ts {
}
}

if (entityName.parent.kind === SyntaxKind.ExportAssignment) {
return resolveEntityName(<Identifier>entityName,
if (entityName.parent.kind === SyntaxKind.ExportAssignment && isEntityNameExpression(<Identifier | PropertyAccessExpression>entityName)) {
return resolveEntityName(<EntityNameExpression>entityName,
/*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias);
}

Expand All @@ -17697,7 +17709,7 @@ namespace ts {
}

if (isRightSideOfQualifiedNameOrPropertyAccess(entityName)) {
entityName = <QualifiedName | PropertyAccessExpression>entityName.parent;
entityName = <QualifiedName | PropertyAccessEntityNameExpression>entityName.parent;
}

if (isHeritageClauseElementIdentifier(<EntityName>entityName)) {
Expand Down Expand Up @@ -18410,7 +18422,7 @@ namespace ts {
};

// defined here to avoid outer scope pollution
function getTypeReferenceDirectivesForEntityName(node: EntityName | PropertyAccessExpression): string[] {
function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): string[] {
// program does not have any files with type reference directives - bail out
if (!fileToDirective) {
return undefined;
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ namespace ts {
}
}

function emitEntityName(entityName: EntityName | PropertyAccessExpression) {
function emitEntityName(entityName: EntityNameOrEntityNameExpression) {
const visibilityResult = resolver.isEntityNameVisible(entityName,
// Aliases can be written asynchronously so use correct enclosing declaration
entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration ? entityName.parent : enclosingDeclaration);
Expand All @@ -454,7 +454,7 @@ namespace ts {
function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) {
if (isSupportedExpressionWithTypeArguments(node)) {
Debug.assert(node.expression.kind === SyntaxKind.Identifier || node.expression.kind === SyntaxKind.PropertyAccessExpression);
emitEntityName(<Identifier | PropertyAccessExpression>node.expression);
emitEntityName(node.expression);
if (node.typeArguments) {
write("<");
emitCommaList(node.typeArguments, emitType);
Expand Down
18 changes: 14 additions & 4 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -982,13 +982,19 @@ namespace ts {
multiLine?: boolean;
}

export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression;
export type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression;

// @kind(SyntaxKind.PropertyAccessExpression)
export interface PropertyAccessExpression extends MemberExpression, Declaration {
expression: LeftHandSideExpression;
name: Identifier;
}

export type IdentifierOrPropertyAccess = Identifier | PropertyAccessExpression;
/** Brand for a PropertyAccessExpression which, like a QualifiedName, consists of a sequence of identifiers separated by dots. */
export interface PropertyAccessEntityNameExpression extends PropertyAccessExpression {
_propertyAccessExpressionLikeQualifiedNameBrand?: any;
expression: EntityNameExpression;
}

// @kind(SyntaxKind.ElementAccessExpression)
export interface ElementAccessExpression extends MemberExpression {
Expand All @@ -1008,6 +1014,10 @@ namespace ts {
expression: LeftHandSideExpression;
typeArguments?: NodeArray<TypeNode>;
}
export interface SupportedExpressionWithTypeArguments extends ExpressionWithTypeArguments {
_supportedExpressionWithTypeArgumentsBrand?: any;
expression: EntityNameExpression;
}

// @kind(SyntaxKind.NewExpression)
export interface NewExpression extends CallExpression, PrimaryExpression { }
Expand Down Expand Up @@ -2021,7 +2031,7 @@ namespace ts {
writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
writeBaseConstructorTypeOfClass(node: ClassLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags): SymbolAccessibilityResult;
isEntityNameVisible(entityName: EntityName | Expression, enclosingDeclaration: Node): SymbolVisibilityResult;
isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult;
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number;
getReferencedValueDeclaration(reference: Identifier): Declaration;
Expand All @@ -2030,7 +2040,7 @@ namespace ts {
moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean;
isArgumentsLocalBinding(node: Identifier): boolean;
getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): SourceFile;
getTypeReferenceDirectivesForEntityName(name: EntityName | PropertyAccessExpression): string[];
getTypeReferenceDirectivesForEntityName(name: EntityNameOrEntityNameExpression): string[];
getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[];
}

Expand Down
36 changes: 20 additions & 16 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1033,14 +1033,14 @@ namespace ts {
&& (<PropertyAccessExpression | ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword;
}


export function getEntityNameFromTypeNode(node: TypeNode): EntityName | Expression {
export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression {
if (node) {
switch (node.kind) {
case SyntaxKind.TypeReference:
return (<TypeReferenceNode>node).typeName;
case SyntaxKind.ExpressionWithTypeArguments:
return (<ExpressionWithTypeArguments>node).expression;
Debug.assert(isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node));
return (<SupportedExpressionWithTypeArguments>node).expression;
case SyntaxKind.Identifier:
case SyntaxKind.QualifiedName:
return (<EntityName><Node>node);
Expand Down Expand Up @@ -2680,24 +2680,28 @@ namespace ts {
isClassLike(node.parent.parent);
}

// Returns false if this heritage clause element's expression contains something unsupported
// (i.e. not a name or dotted name).
export function isSupportedExpressionWithTypeArguments(node: ExpressionWithTypeArguments): boolean {
return isSupportedExpressionWithTypeArgumentsRest(node.expression);
export function isSupportedExpressionWithTypeArguments(node: ExpressionWithTypeArguments): node is SupportedExpressionWithTypeArguments {
return isEntityNameExpression(node.expression);
}

function isSupportedExpressionWithTypeArgumentsRest(node: Expression): boolean {
if (node.kind === SyntaxKind.Identifier) {
return true;
}
else if (isPropertyAccessExpression(node)) {
return isSupportedExpressionWithTypeArgumentsRest(node.expression);
}
else {
return false;
export function isEntityNameExpression(node: Expression): node is EntityNameExpression {
for (; ; ) {
switch (node.kind) {
case SyntaxKind.Identifier:
return true;
case SyntaxKind.PropertyAccessExpression:
node = (<PropertyAccessExpression>node).expression;
break;
default:
return false;
}
}
}

export function isPropertyAccessAnEntityNameExpression(node: PropertyAccessExpression): node is PropertyAccessEntityNameExpression {
return isEntityNameExpression(node.expression);
}

export function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) {
return (node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node) ||
(node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node);
Expand Down
8 changes: 8 additions & 0 deletions tests/baselines/reference/exportDefaultProperty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//// [exportDefaultProperty.ts]
export default "".length


//// [exportDefaultProperty.js]
"use strict";
exports.__esModule = true;
exports["default"] = "".length;
5 changes: 5 additions & 0 deletions tests/baselines/reference/exportDefaultProperty.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
=== tests/cases/compiler/exportDefaultProperty.ts ===
export default "".length
>"".length : Symbol(String.length, Decl(lib.d.ts, --, --))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))

6 changes: 6 additions & 0 deletions tests/baselines/reference/exportDefaultProperty.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
=== tests/cases/compiler/exportDefaultProperty.ts ===
export default "".length
>"".length : number
>"" : string
>length : number

1 change: 1 addition & 0 deletions tests/cases/compiler/exportDefaultProperty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "".length

0 comments on commit 97bbbd7

Please sign in to comment.