// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0. // See LICENSE.txt in the project root for complete license information. /// module TypeScript { export class AssignScopeContext { constructor (public scopeChain: ScopeChain, public typeFlow: TypeFlow, public modDeclChain: ModuleDeclaration[]) { } } export function pushAssignScope(scope: SymbolScope, context: AssignScopeContext, type: Type, classType: Type, fnc: FuncDecl) { var chain = new ScopeChain(null, context.scopeChain, scope); chain.thisType = type; chain.classType = classType; chain.fnc = fnc; context.scopeChain = chain; } export function popAssignScope(context: AssignScopeContext) { context.scopeChain = context.scopeChain.previous; } export function instanceCompare(a: Symbol, b: Symbol) { if (((a == null) || (!a.isInstanceProperty()))) { return b; } else { return a; } } export function instanceFilterStop(s: Symbol) { return s.isInstanceProperty(); } export class ScopeSearchFilter { constructor (public select: (a: Symbol, b: Symbol) =>Symbol, public stop: (s: Symbol) =>boolean) { } public result: Symbol = null; public reset() { this.result = null; } public update(b: Symbol): boolean { this.result = this.select(this.result, b); if (this.result) { return this.stop(this.result); } else { return false; } } } export var instanceFilter = new ScopeSearchFilter(instanceCompare, instanceFilterStop); export function preAssignModuleScopes(ast: AST, context: AssignScopeContext) { var moduleDecl = ast; var memberScope: SymbolTableScope = null; var aggScope: SymbolAggregateScope = null; if (moduleDecl.name && moduleDecl.mod) { moduleDecl.name.sym = moduleDecl.mod.symbol; } var mod = moduleDecl.mod; // We're likely here because of error recovery if (!mod) { return; } memberScope = new SymbolTableScope(mod.members, mod.ambientMembers, mod.enclosedTypes, mod.ambientEnclosedTypes, mod.symbol); mod.memberScope = memberScope; context.modDeclChain.push(moduleDecl); context.typeFlow.checker.currentModDecl = moduleDecl; aggScope = new SymbolAggregateScope(mod.symbol); aggScope.addParentScope(memberScope); aggScope.addParentScope(context.scopeChain.scope); pushAssignScope(aggScope, context, null, null, null); mod.containedScope = aggScope; if (mod.symbol) { context.typeFlow.addLocalsFromScope(mod.containedScope, mod.symbol, moduleDecl.vars, mod.members.privateMembers, true); } } export function preAssignClassScopes(ast: AST, context: AssignScopeContext) { var classDecl = ast; var memberScope: SymbolTableScope = null; var aggScope: SymbolAggregateScope = null; if (classDecl.name && classDecl.type) { classDecl.name.sym = classDecl.type.symbol; } var classType = ast.type; if (classType) { var classSym = classType.symbol; memberScope = context.typeFlow.checker.scopeOf(classType); aggScope = new SymbolAggregateScope(classType.symbol); aggScope.addParentScope(memberScope); aggScope.addParentScope(context.scopeChain.scope); classType.containedScope = aggScope; classType.memberScope = memberScope; var instanceType = classType.instanceType; memberScope = context.typeFlow.checker.scopeOf(instanceType); instanceType.memberScope = memberScope; aggScope = new SymbolAggregateScope(instanceType.symbol); aggScope.addParentScope(context.scopeChain.scope); pushAssignScope(aggScope, context, instanceType, classType, null); instanceType.containedScope = aggScope; } else { ast.type = context.typeFlow.anyType; } } export function preAssignInterfaceScopes(ast: AST, context: AssignScopeContext) { var interfaceDecl = ast; var memberScope: SymbolTableScope = null; var aggScope: SymbolAggregateScope = null; if (interfaceDecl.name && interfaceDecl.type) { interfaceDecl.name.sym = interfaceDecl.type.symbol; } var interfaceType = ast.type; memberScope = context.typeFlow.checker.scopeOf(interfaceType); interfaceType.memberScope = memberScope; aggScope = new SymbolAggregateScope(interfaceType.symbol); aggScope.addParentScope(memberScope); aggScope.addParentScope(context.scopeChain.scope); pushAssignScope(aggScope, context, null, null, null); interfaceType.containedScope = aggScope; } export function preAssignWithScopes(ast: AST, context: AssignScopeContext) { var withStmt = ast; var withType = withStmt.type; var members = new ScopedMembers(new DualStringHashTable(new StringHashTable(), new StringHashTable())); var ambientMembers = new ScopedMembers(new DualStringHashTable(new StringHashTable(), new StringHashTable())); var withType = new Type(); var withSymbol = new WithSymbol(withStmt.minChar, context.typeFlow.checker.locationInfo.unitIndex, withType); withType.members = members; withType.ambientMembers = ambientMembers; withType.symbol = withSymbol; withType.setHasImplementation(); withStmt.type = withType; var withScope = new TypeScript.SymbolScopeBuilder(withType.members, withType.ambientMembers, null, null, context.scopeChain.scope, withType.symbol); pushAssignScope(withScope, context, null, null, null); withType.containedScope = withScope; } export function preAssignFuncDeclScopes(ast: AST, context: AssignScopeContext) { var funcDecl = ast; var container: Symbol = null; var localContainer: Symbol = null; if (funcDecl.type) { localContainer = ast.type.symbol; } var isStatic = hasFlag(funcDecl.fncFlags, FncFlags.Static); var isInnerStatic = isStatic && context.scopeChain.fnc != null; // for inner static functions, use the parent's member scope, so local vars cannot be captured var parentScope = isInnerStatic ? context.scopeChain.fnc.type.memberScope : context.scopeChain.scope; // if this is not a method, but enclosed by class, use constructor as // the enclosing scope // REVIEW: Some twisted logic here - this needs to be cleaned up once old classes are removed // - if it's a new class, always use the contained scope, since we initialize the constructor scope below if (context.scopeChain.thisType && (!funcDecl.isConstructor || hasFlag(funcDecl.fncFlags, FncFlags.ClassMethod))) { var instType = context.scopeChain.thisType; if (!(instType.typeFlags & TypeFlags.IsClass) && !hasFlag(funcDecl.fncFlags, FncFlags.ClassMethod)) { if (!funcDecl.isMethod() || isStatic) { parentScope = instType.constructorScope; } else { // use constructor scope if a method as well parentScope = instType.containedScope; } } else { if (context.scopeChain.previous.scope.container && context.scopeChain.previous.scope.container.declAST && context.scopeChain.previous.scope.container.declAST.nodeType == NodeType.FuncDecl && (context.scopeChain.previous.scope.container.declAST).isConstructor) { // if the parent is the class constructor, use the constructor scope parentScope = instType.constructorScope; } else if (isStatic && context.scopeChain.classType) { parentScope = context.scopeChain.classType.containedScope; } else { // else, use the contained scope parentScope = instType.containedScope; } } container = instType.symbol; } else if (funcDecl.isConstructor && context.scopeChain.thisType) { // sets the container to the class type's symbol (which is shared by the instance type) container = context.scopeChain.thisType.symbol; } if (funcDecl.type == null || hasFlag(funcDecl.type.symbol.flags, SymbolFlags.TypeSetDuringScopeAssignment)) { if (context.scopeChain.fnc && context.scopeChain.fnc.type) { container = context.scopeChain.fnc.type.symbol; } var funcScope = null; var outerFnc: FuncDecl = context.scopeChain.fnc; var nameText = funcDecl.name ? funcDecl.name.actualText : null; var fgSym: TypeSymbol = null; if (isStatic) { // In the case of function-nested statics, no member list will have bee initialized for the function, so we need // to copy it over. We don't set this by default because having a non-null member list will throw off assignment // compatibility tests if (outerFnc.type.members == null && container.getType().memberScope) { outerFnc.type.members = ((container).type.memberScope).valueMembers; } funcScope = context.scopeChain.fnc.type.memberScope; outerFnc.innerStaticFuncs[outerFnc.innerStaticFuncs.length] = funcDecl; } else { if (!funcDecl.isConstructor && container && container.declAST && container.declAST.nodeType == NodeType.FuncDecl && (container.declAST).isConstructor && !funcDecl.isMethod()) { funcScope = context.scopeChain.thisType.constructorScope;//locals; } else { funcScope = context.scopeChain.scope; } } // REVIEW: We don't search for another sym for accessors to prevent us from // accidentally coalescing function signatures with the same name (E.g., a function // 'f' the outer scope and a setter 'f' in an object literal within that scope) if (nameText && nameText != "__missing" && !funcDecl.isAccessor()) { if (isStatic) { fgSym = funcScope.findLocal(nameText, false, false); } else { // REVIEW: This logic should be symmetric with preCollectClassTypes fgSym = funcScope.findLocal(nameText, false, false); } } context.typeFlow.checker.createFunctionSignature(funcDecl, container, funcScope, fgSym, fgSym == null); // it's a getter or setter for a class property if (!funcDecl.accessorSymbol && (funcDecl.fncFlags & FncFlags.ClassMethod) && container && ((!fgSym || fgSym.declAST.nodeType != NodeType.FuncDecl) && funcDecl.isAccessor()) || (fgSym && fgSym.isAccessor())) { funcDecl.accessorSymbol = context.typeFlow.checker.createAccessorSymbol(funcDecl, fgSym, container.getType(), (funcDecl.isMethod() && isStatic), true, funcScope, container); } funcDecl.type.symbol.flags |= SymbolFlags.TypeSetDuringScopeAssignment; } // Set the symbol for functions and their overloads if (funcDecl.name && funcDecl.type) { funcDecl.name.sym = funcDecl.type.symbol; } // Keep track of the original scope type, because target typing might override // the "type" member. We need the original "Scope type" for completion list, etc. funcDecl.scopeType = funcDecl.type; // Overloads have no scope, so bail here if (funcDecl.isOverload) { return; } var funcTable = new StringHashTable(); var funcMembers = new ScopedMembers(new DualStringHashTable(funcTable, new StringHashTable())); var ambientFuncTable = new StringHashTable(); var ambientFuncMembers = new ScopedMembers(new DualStringHashTable(ambientFuncTable, new StringHashTable())); var funcStaticTable = new StringHashTable(); var funcStaticMembers = new ScopedMembers(new DualStringHashTable(funcStaticTable, new StringHashTable())); var ambientFuncStaticTable = new StringHashTable(); var ambientFuncStaticMembers = new ScopedMembers(new DualStringHashTable(ambientFuncStaticTable, new StringHashTable())); // REVIEW: Is it a problem that this is being set twice for properties and constructors? funcDecl.unitIndex = context.typeFlow.checker.locationInfo.unitIndex; var locals = new SymbolScopeBuilder(funcMembers, ambientFuncMembers, null, null, parentScope, localContainer); var statics = new SymbolScopeBuilder(funcStaticMembers, ambientFuncStaticMembers, null, null, parentScope, null); if (funcDecl.isConstructor && context.scopeChain.thisType) { context.scopeChain.thisType.constructorScope = locals; } // basically, there are two problems // - Above, for new classes, we were overwriting the constructor scope with the containing scope. This caused constructor params to be // in scope everywhere // - Below, we're setting the contained scope table to the same table we were overwriting the constructor scope with, which we need to // fish lambda params, etc, out (see funcTable below) // // A good first approach to solving this would be to change addLocalsFromScope to take a scope instead of a table, and add to the // constructor scope as appropriate funcDecl.symbols = funcTable; if (!funcDecl.isSpecialFn()) { var group = funcDecl.type; var signature = funcDecl.signature; if (!funcDecl.isConstructor) { group.containedScope = locals; locals.container = group.symbol; group.memberScope = statics; statics.container = group.symbol; } funcDecl.enclosingFnc = context.scopeChain.fnc; group.enclosingType = isStatic ? context.scopeChain.classType : context.scopeChain.thisType; // for mapping when type checking var fgSym = ast.type.symbol; if (((funcDecl.fncFlags & FncFlags.Signature) == FncFlags.None) && funcDecl.vars) { context.typeFlow.addLocalsFromScope(locals, fgSym, funcDecl.vars, funcTable, false); context.typeFlow.addLocalsFromScope(statics, fgSym, funcDecl.statics, funcStaticTable, false); } if (signature.parameters) { var len = signature.parameters.length; for (var i = 0; i < len; i++) { var paramSym: ParameterSymbol = signature.parameters[i]; context.typeFlow.checker.resolveTypeLink(locals, paramSym.parameter.typeLink, true); } } context.typeFlow.checker.resolveTypeLink(locals, signature.returnType, funcDecl.isSignature()); } if (!funcDecl.isConstructor || hasFlag(funcDecl.fncFlags, FncFlags.ClassMethod)) { var thisType = (funcDecl.isConstructor && hasFlag(funcDecl.fncFlags, FncFlags.ClassMethod)) ? context.scopeChain.thisType : null; pushAssignScope(locals, context, thisType, null, funcDecl); } } export function preAssignCatchScopes(ast: AST, context: AssignScopeContext) { var catchBlock = ast; if (catchBlock.param) { var catchTable = new ScopedMembers(new DualStringHashTable(new StringHashTable(), new StringHashTable())); // REVIEW: Should we be allocating a public table instead of a private one? var catchLocals = new SymbolScopeBuilder(catchTable, null, null, null, context.scopeChain.scope, context.scopeChain.scope.container); catchBlock.containedScope = catchLocals; pushAssignScope(catchLocals, context, context.scopeChain.thisType, context.scopeChain.classType, context.scopeChain.fnc); } } export function preAssignScopes(ast: AST, parent: AST, walker: IAstWalker) { var context:AssignScopeContext = walker.state; var go = true; if (ast) { if (ast.nodeType == NodeType.List) { var list = ast; list.enclosingScope = context.scopeChain.scope; } else if (ast.nodeType == NodeType.ModuleDeclaration) { preAssignModuleScopes(ast, context); } else if (ast.nodeType == NodeType.ClassDeclaration) { preAssignClassScopes(ast, context); } else if (ast.nodeType == NodeType.InterfaceDeclaration) { preAssignInterfaceScopes(ast, context); } else if (ast.nodeType == NodeType.With) { preAssignWithScopes(ast, context); } else if (ast.nodeType == NodeType.FuncDecl) { preAssignFuncDeclScopes(ast, context); } else if (ast.nodeType == NodeType.Catch) { preAssignCatchScopes(ast, context); } else if (ast.nodeType == NodeType.TypeRef) { go = false; } } walker.options.goChildren = go; return ast; } export function postAssignScopes(ast: AST, parent: AST, walker: IAstWalker) { var context:AssignScopeContext = walker.state; var go = true; if (ast) { if (ast.nodeType == NodeType.ModuleDeclaration) { var prevModDecl = ast; popAssignScope(context); context.modDeclChain.pop(); if (context.modDeclChain.length >= 1) { context.typeFlow.checker.currentModDecl = context.modDeclChain[context.modDeclChain.length - 1]; } } else if (ast.nodeType == NodeType.ClassDeclaration) { popAssignScope(context); } else if (ast.nodeType == NodeType.InterfaceDeclaration) { popAssignScope(context); } else if (ast.nodeType == NodeType.With) { popAssignScope(context); } else if (ast.nodeType == NodeType.FuncDecl) { var funcDecl = ast; if ((!funcDecl.isConstructor || hasFlag(funcDecl.fncFlags, FncFlags.ClassMethod)) && !funcDecl.isOverload) { popAssignScope(context); } } else if (ast.nodeType == NodeType.Catch) { var catchBlock = ast; if (catchBlock.param) { popAssignScope(context); } } else { go = false; } } walker.options.goChildren = go; return ast; } }