in Public/Src/FrontEnd/TypeScript.Net/TypeScript.Net/TypeChecking/Checker.cs [1038:1424]
private ISymbol ResolveName(INode location, string name, SymbolFlags meaning, IDiagnosticMessage nameNotFoundMessage, string nameArgAsString, IIdentifier nameArg = null)
{
ISymbol result = null;
INode lastLocation = null;
INode propertyWithInvalidInitializer = null;
INode errorLocation = location;
INode grandparent = null;
// HINT: TypeScript implementation uses goto-style label to break out of loop from the switch statement
// Switching to state check in the loop's condition.
bool breakLoop = false;
// DScript-specific. If we are trying to resolve the special namespace $, then
// the resolution is fixed: this name cannot be shadowed by any other name
// (there are lint rules that block naming any entity $ anyway)
// Additionally, $ has to be the first qualification on a qualified name, and therefore it is always
// a reference to the module (or source file in legacy modules) associated to the place
// where the reference occurs
if (name == Names.RootNamespace && (meaning & SymbolFlags.Namespace) != SymbolFlags.None)
{
var sourceFile = location.GetSourceFile();
ModuleName moduleName;
// V2 case, we return a (unique per module) symbol representing all internal values
if (TryGetOwningModuleWithImplicitSemantics(sourceFile.FileName, out moduleName))
{
return GetInternalModuleSymbol(moduleName.Name);
}
// V1 case, the $ symbol represents the source file, there are no implicit semantics
return sourceFile.Symbol;
}
while (location != null && !breakLoop)
{
// Locals of a source file are not in scope (because they get merged into the global symbol table)
if (location.Locals != null && !IsGlobalSourceFile(location))
{
result = GetSymbol(location.Locals, name, meaning);
if (result != null)
{
var useResult = true;
if (IsFunctionLike(location) != null &&
lastLocation != null &&
// Can't compare nodes directly, because Body could be a union wrapper
lastLocation.ResolveUnionType() != location.Cast<IFunctionLikeDeclaration>().Body.ResolveUnionType())
{
// symbol lookup restrictions for function-like declarations
// - 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.
// - parameters are only in the scope of function body
if ((meaning & result.Flags & SymbolFlags.Type) != SymbolFlags.None)
{
useResult = (result.Flags & SymbolFlags.TypeParameter) != SymbolFlags.None
// type parameters are visible in parameter list, return type and type parameter list
? lastLocation.ResolveUnionType() == location.Cast<IFunctionLikeDeclaration>().Type.ResolveUnionType() ||
lastLocation.Kind == SyntaxKind.Parameter ||
lastLocation.Kind == SyntaxKind.TypeParameter
// local types not visible outside the body
: false;
}
if ((meaning & SymbolFlags.Value) != SymbolFlags.None &&
(result.Flags & SymbolFlags.FunctionScopedVariable) != SymbolFlags.None)
{
// parameters are visible only inside function body, parameter list and return type
// technically for parameter list case here we might mix parameters and variables declared in function,
// however it is detected separately when checking initializers of parameters
// to make sure that they reference no variables declared after them.
useResult =
lastLocation.Kind == SyntaxKind.Parameter ||
(lastLocation.ResolveUnionType() == location.Cast<IFunctionLikeDeclaration>().Type.ResolveUnionType() &&
result.ValueDeclaration.Kind == SyntaxKind.Parameter);
}
}
if (useResult)
{
breakLoop = true;
break;
}
else
{
result = null;
}
}
}
switch (location.Kind)
{
case SyntaxKind.SourceFile:
case SyntaxKind.ModuleDeclaration:
if (IsGlobalSourceFile(location))
{
break;
}
// DScript-specific. If the lookup reached the source file level and the source file is owned by a module with implicit
// reference semantics, the module exports that is used is the one that holds all internal values from the module. In that
// way all internal values will behave as implicitly imported. Otherwise, we preserve the original TS logic.
ModuleName moduleName;
ISymbolTable moduleExports = GetInternalModuleExports(location, out moduleName);
// TODO: revisit. Adding this to avoid a crash
if (moduleExports == null)
{
result = null;
breakLoop = true;
break;
}
if ((location.Kind == SyntaxKind.SourceFile) ||
(location.Kind == SyntaxKind.ModuleDeclaration &&
location.Cast<IModuleDeclaration>().Name.Kind == SyntaxKind.StringLiteral))
{
// It's an external module. First see if the module has an export default and if the local
// name of that export default matches.
result = moduleExports["default"];
if (result != null)
{
ISymbol localSymbol = GetLocalSymbolForExportDefault(result);
if ((localSymbol != null) &&
(result.Flags & meaning) != SymbolFlags.None &&
(localSymbol.Name == name))
{
breakLoop = true;
break;
}
result = null;
}
// Because of module/namespace merging, a module's exports are in scope,
// yet we never want to treat an export specifier as putting a member in scope.
// Therefore, if the name we find is purely an export specifier, it is not actually considered in scope.
// Two things to note about this:
// 1. We have to check this without calling getSymbol. The problem with calling getSymbol
// on an export specifier is that it might find the export specifier itself, and try to
// resolve it as an alias. This will cause the checker to consider the export specifier
// a circular alias reference when it might not be.
// 2. We check == SymbolFlags.Alias in order to check that the symbol is *purely*
// an alias. If we used &, we'd be throwing out symbols that have non alias aspects,
// which is not the desired behavior.
if (HasProperty(moduleExports, name) &&
moduleExports[name].Flags == SymbolFlags.Alias)
{
var moduleExportDeclaration = GetDeclarationOfKind(moduleExports[name], SyntaxKind.ExportSpecifier);
if (moduleExportDeclaration != null)
{
// DScript-specific. If the export specifier resides in a module with V2 semantics
// the symbol *is* in scope for other specs in the same module
// But this is the case only for other specs, in the same spec the symbol is not in scope, so we keep
// the semantics of export specifiers (e.g. export {a as b} does not introduce b in the local scope)
if (moduleName != ModuleName.Invalid && moduleExportDeclaration.GetSourceFile() != location)
{
result = moduleExports[name];
breakLoop = true;
}
break;
}
}
}
result = GetSymbol(moduleExports, name, meaning & SymbolFlags.ModuleMember);
if (result != null)
{
breakLoop = true;
break;
}
break;
case SyntaxKind.EnumDeclaration:
result = GetSymbol(GetSymbolOfNode(location).Exports, name, meaning & SymbolFlags.EnumMember);
if (result != null)
{
breakLoop = true;
break;
}
break;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
// TypeScript 1.0 spec (April 2014): 8.4.1
// Initializer expressions for instance member variables are evaluated in the scope
// of the class constructor body but are not permitted to reference parameters or
// local variables of the constructor. This effectively means that entities from outer scopes
// by the same name as a constructor parameter or local variable are inaccessible
// in initializer expressions for instance member variables.
if ((IsClassLike(location.Parent) != null) &&
(location.Flags & NodeFlags.Static) == NodeFlags.None)
{
IConstructorDeclaration ctor = FindConstructorDeclaration(location.Parent.Cast<IClassLikeDeclaration>());
if (ctor != null && ctor.Locals != null)
{
if (GetSymbol(ctor.Locals, name, meaning & SymbolFlags.Value) != null)
{
// Remember the property node, it will be used later to report appropriate error
propertyWithInvalidInitializer = location;
}
}
}
break;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.InterfaceDeclaration:
result = GetSymbol(GetSymbolOfNode(location).Members, name, meaning & SymbolFlags.Type);
if (result != null)
{
if ((lastLocation != null) &&
(lastLocation.Flags & NodeFlags.Static) != NodeFlags.None)
{
// TypeScript 1.0 spec (April 2014): 3.4.1
// The scope of a type parameter extends over the entire declaration with which the type
// parameter list is associated, with the exception of static member declarations in classes.
Error(errorLocation, Errors.Static_members_cannot_reference_class_type_parameters);
return null;
}
breakLoop = true;
break;
}
if ((location.Kind == SyntaxKind.ClassExpression) &&
(meaning & SymbolFlags.Class) != SymbolFlags.None)
{
var className = location.Cast<IClassExpression>().Name;
if (className != null && name.Equals(className.Text))
{
result = location.Symbol;
breakLoop = true;
break;
}
}
break;
// It is not legal to reference a class's own type parameters from a computed property name that
// belongs to the class. For example:
//
// function foo<T>() { return '' }
// class C<T> { // <-- Class's own type parameter T
// [foo<T>()]() { } // <-- Reference to T from class's own computed property
// }
case SyntaxKind.ComputedPropertyName:
grandparent = location.Parent.Parent;
if (IsClassLike(grandparent) != null ||
grandparent.Kind == SyntaxKind.InterfaceDeclaration)
{
// A reference to this grandparent's type parameters would be an error
result = GetSymbol(GetSymbolOfNode(grandparent).Members, name, meaning & SymbolFlags.Type);
if (result != null)
{
Error(errorLocation, Errors.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type);
return null;
}
}
break;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ArrowFunction:
if ((meaning & SymbolFlags.Variable) != SymbolFlags.None && name.Equals("arguments"))
{
result = m_argumentsSymbol;
breakLoop = true;
break;
}
break;
case SyntaxKind.FunctionExpression:
if ((meaning & SymbolFlags.Variable) != SymbolFlags.None && name.Equals("arguments"))
{
result = m_argumentsSymbol;
breakLoop = true;
break;
}
if ((meaning & SymbolFlags.Function) != SymbolFlags.None)
{
var functionName = location.Cast<IFunctionExpression>().Name;
if (functionName != null && name.Equals(functionName.Text))
{
result = location.Symbol;
breakLoop = true;
break;
}
}
break;
case SyntaxKind.Decorator:
// Decorators are resolved at the class declaration. Resolving at the parameter
// or member would result in looking up locals in the method.
//
// function y() {}
// class C {
// method(@y x, y) {} // <-- decorator y should be resolved at the class declaration, not the parameter.
// }
if (location.Parent != null && location.Parent.Kind == SyntaxKind.Parameter)
{
location = location.Parent;
}
// function y() {}
// class C {
// @y method(x, y) {} // <-- decorator y should be resolved at the class declaration, not the method.
// }
if (location.Parent != null && IsClassElement(location.Parent))
{
location = location.Parent;
}
break;
}
lastLocation = location;
location = location.Parent;
}
if (result == null)
{
result = GetSymbol(m_globals, name, meaning);
}
if (result == null)
{
if (nameNotFoundMessage != null)
{
Error(errorLocation, nameNotFoundMessage, nameArgAsString ?? (nameArgAsString = DeclarationNameToString(nameArg)));
}
return null;
}
// Perform extra checks only if error reporting was requested
if (nameNotFoundMessage != null)
{
if (propertyWithInvalidInitializer != null)
{
// We have a match, but the reference occurred within a property initializer and the identifier also binds
// to a local variable in the constructor where the code will be emitted.
// TODO: Verify equivalence - const propertyName = (<PropertyDeclaration>propertyWithInvalidInitializer).Name;
var propertyName = propertyWithInvalidInitializer.Kind == SyntaxKind.PropertyDeclaration
? propertyWithInvalidInitializer.Cast<IPropertyDeclaration>().Name
: propertyWithInvalidInitializer.Cast<IPropertySignature>().Name;
Error(
errorLocation,
Errors.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor,
DeclarationNameToString(propertyName),
nameArgAsString ?? (nameArgAsString = DeclarationNameToString(nameArg)));
return null;
}
// Only check for block-scoped variable if we are looking for the
// name with variable meaning
// For example,
// declare module foo {
// interface bar {}
// }
// const foo/*1*/: foo/*2*/.bar;
// The foo at /*1*/ and /*2*/ will share same symbol with two meaning
// block - scope variable and namespace module. However, only when we
// try to resolve name in /*1*/ which is used in variable position,
// we want to check for block- scoped
if ((meaning & SymbolFlags.BlockScopedVariable) != SymbolFlags.None)
{
ISymbol exportOrLocalSymbol = GetExportSymbolOfValueSymbolIfExported(result);
if ((exportOrLocalSymbol.Flags & SymbolFlags.BlockScopedVariable) != SymbolFlags.None)
{
CheckResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation);
}
}
}
return result;
}