• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
2// See LICENSE.txt in the project root for complete license information.
3
4///<reference path='typescript.ts' />
5
6module TypeScript {
7
8    export class AssignScopeContext {
9        constructor (public scopeChain: ScopeChain,
10                     public typeFlow: TypeFlow,
11                     public modDeclChain: ModuleDeclaration[]) {
12        }
13    }
14
15    export function pushAssignScope(scope: SymbolScope,
16        context: AssignScopeContext,
17        type: Type,
18        classType: Type,
19        fnc: FuncDecl) {
20
21        var chain = new ScopeChain(null, context.scopeChain, scope);
22        chain.thisType = type;
23        chain.classType = classType;
24        chain.fnc = fnc;
25        context.scopeChain = chain;
26    }
27
28    export function popAssignScope(context: AssignScopeContext) {
29        context.scopeChain = context.scopeChain.previous;
30    }
31
32    export function instanceCompare(a: Symbol, b: Symbol) {
33        if (((a == null) || (!a.isInstanceProperty()))) {
34            return b;
35        }
36        else {
37            return a;
38        }
39    }
40
41    export function instanceFilterStop(s: Symbol) {
42        return s.isInstanceProperty();
43    }
44
45    export class ScopeSearchFilter {
46
47        constructor (public select: (a: Symbol, b: Symbol) =>Symbol,
48                            public stop: (s: Symbol) =>boolean) { }
49
50        public result: Symbol = null;
51
52        public reset() {
53            this.result = null;
54        }
55
56        public update(b: Symbol): boolean {
57            this.result = this.select(this.result, b);
58            if (this.result) {
59                return this.stop(this.result);
60            }
61            else {
62                return false;
63            }
64        }
65    }
66
67    export var instanceFilter = new ScopeSearchFilter(instanceCompare, instanceFilterStop);
68
69    export function preAssignModuleScopes(ast: AST, context: AssignScopeContext) {
70        var moduleDecl = <ModuleDeclaration>ast;
71        var memberScope: SymbolTableScope = null;
72        var aggScope: SymbolAggregateScope = null;
73
74        if (moduleDecl.name && moduleDecl.mod) {
75            moduleDecl.name.sym = moduleDecl.mod.symbol;
76        }
77
78        var mod = moduleDecl.mod;
79
80        // We're likely here because of error recovery
81        if (!mod) {
82            return;
83        }
84
85        memberScope = new SymbolTableScope(mod.members, mod.ambientMembers, mod.enclosedTypes, mod.ambientEnclosedTypes, mod.symbol);
86        mod.memberScope = memberScope;
87        context.modDeclChain.push(moduleDecl);
88        context.typeFlow.checker.currentModDecl = moduleDecl;
89        aggScope = new SymbolAggregateScope(mod.symbol);
90        aggScope.addParentScope(memberScope);
91        aggScope.addParentScope(context.scopeChain.scope);
92        pushAssignScope(aggScope, context, null, null, null);
93        mod.containedScope = aggScope;
94        if (mod.symbol) {
95            context.typeFlow.addLocalsFromScope(mod.containedScope, mod.symbol, moduleDecl.vars, mod.members.privateMembers, true);
96        }
97    }
98
99    export function preAssignClassScopes(ast: AST, context: AssignScopeContext) {
100        var classDecl = <InterfaceDeclaration>ast;
101        var memberScope: SymbolTableScope = null;
102        var aggScope: SymbolAggregateScope = null;
103
104        if (classDecl.name && classDecl.type) {
105            classDecl.name.sym = classDecl.type.symbol;
106        }
107
108        var classType = ast.type;
109
110        if (classType) {
111            var classSym = classType.symbol;
112            memberScope = <SymbolTableScope>context.typeFlow.checker.scopeOf(classType);
113
114            aggScope = new SymbolAggregateScope(classType.symbol);
115            aggScope.addParentScope(memberScope);
116            aggScope.addParentScope(context.scopeChain.scope);
117
118            classType.containedScope = aggScope;
119            classType.memberScope = memberScope;
120
121            var instanceType = classType.instanceType;
122            memberScope = <SymbolTableScope>context.typeFlow.checker.scopeOf(instanceType);
123            instanceType.memberScope = memberScope;
124
125            aggScope = new SymbolAggregateScope(instanceType.symbol);
126            aggScope.addParentScope(context.scopeChain.scope);
127
128            pushAssignScope(aggScope, context, instanceType, classType, null);
129            instanceType.containedScope = aggScope;
130        }
131        else {
132            ast.type = context.typeFlow.anyType;
133        }
134    }
135
136    export function preAssignInterfaceScopes(ast: AST, context: AssignScopeContext) {
137        var interfaceDecl = <InterfaceDeclaration>ast;
138        var memberScope: SymbolTableScope = null;
139        var aggScope: SymbolAggregateScope = null;
140
141        if (interfaceDecl.name && interfaceDecl.type) {
142            interfaceDecl.name.sym = interfaceDecl.type.symbol;
143        }
144
145        var interfaceType = ast.type;
146        memberScope = <SymbolTableScope>context.typeFlow.checker.scopeOf(interfaceType);
147        interfaceType.memberScope = memberScope;
148        aggScope = new SymbolAggregateScope(interfaceType.symbol);
149        aggScope.addParentScope(memberScope);
150        aggScope.addParentScope(context.scopeChain.scope);
151        pushAssignScope(aggScope, context, null, null, null);
152        interfaceType.containedScope = aggScope;
153    }
154
155    export function preAssignWithScopes(ast: AST, context: AssignScopeContext) {
156        var withStmt = <WithStatement>ast;
157        var withType = withStmt.type;
158
159        var members = new ScopedMembers(new DualStringHashTable(new StringHashTable(), new StringHashTable()));
160        var ambientMembers = new ScopedMembers(new DualStringHashTable(new StringHashTable(), new StringHashTable()));
161
162        var withType = new Type();
163        var withSymbol = new WithSymbol(withStmt.minChar, context.typeFlow.checker.locationInfo.unitIndex, withType);
164        withType.members = members;
165        withType.ambientMembers = ambientMembers;
166        withType.symbol = withSymbol;
167        withType.setHasImplementation();
168        withStmt.type = withType;
169
170        var withScope = new TypeScript.SymbolScopeBuilder(withType.members, withType.ambientMembers, null, null, context.scopeChain.scope, withType.symbol);
171
172        pushAssignScope(withScope, context, null, null, null);
173        withType.containedScope = withScope;
174    }
175
176    export function preAssignFuncDeclScopes(ast: AST, context: AssignScopeContext) {
177        var funcDecl = <FuncDecl>ast;
178
179        var container: Symbol = null;
180        var localContainer: Symbol = null;
181        if (funcDecl.type) {
182            localContainer = ast.type.symbol;
183        }
184
185        var isStatic = hasFlag(funcDecl.fncFlags, FncFlags.Static);
186        var isInnerStatic = isStatic && context.scopeChain.fnc != null;
187        // for inner static functions, use the parent's member scope, so local vars cannot be captured
188        var parentScope = isInnerStatic ? context.scopeChain.fnc.type.memberScope : context.scopeChain.scope;
189
190        // if this is not a method, but enclosed by class, use constructor as
191        // the enclosing scope
192        // REVIEW: Some twisted logic here - this needs to be cleaned up once old classes are removed
193        //  - if it's a new class, always use the contained scope, since we initialize the constructor scope below
194        if (context.scopeChain.thisType &&
195            (!funcDecl.isConstructor || hasFlag(funcDecl.fncFlags, FncFlags.ClassMethod))) {
196            var instType = context.scopeChain.thisType;
197
198            if (!(instType.typeFlags & TypeFlags.IsClass) && !hasFlag(funcDecl.fncFlags, FncFlags.ClassMethod)) {
199                if (!funcDecl.isMethod() || isStatic) {
200                    parentScope = instType.constructorScope;
201                }
202                else {
203                    // use constructor scope if a method as well
204                    parentScope = instType.containedScope;
205                }
206            }
207            else {
208                if (context.scopeChain.previous.scope.container &&
209                    context.scopeChain.previous.scope.container.declAST &&
210                    context.scopeChain.previous.scope.container.declAST.nodeType == NodeType.FuncDecl &&
211                    (<FuncDecl>context.scopeChain.previous.scope.container.declAST).isConstructor) {
212
213                        // if the parent is the class constructor, use the constructor scope
214                    parentScope = instType.constructorScope;
215                }
216                else if (isStatic && context.scopeChain.classType) {
217                    parentScope = context.scopeChain.classType.containedScope;
218                }
219                else {
220                    // else, use the contained scope
221                    parentScope = instType.containedScope;
222                }
223            }
224            container = instType.symbol;
225        }
226        else if (funcDecl.isConstructor && context.scopeChain.thisType) {
227            // sets the container to the class type's symbol (which is shared by the instance type)
228            container = context.scopeChain.thisType.symbol;
229        }
230
231        if (funcDecl.type == null || hasFlag(funcDecl.type.symbol.flags, SymbolFlags.TypeSetDuringScopeAssignment)) {
232            if (context.scopeChain.fnc && context.scopeChain.fnc.type) {
233                container = context.scopeChain.fnc.type.symbol;
234            }
235
236            var funcScope = null;
237            var outerFnc: FuncDecl = context.scopeChain.fnc;
238            var nameText = funcDecl.name ? funcDecl.name.actualText : null;
239            var fgSym: TypeSymbol = null;
240
241            if (isStatic) {
242                // In the case of function-nested statics, no member list will have bee initialized for the function, so we need
243                // to copy it over.  We don't set this by default because having a non-null member list will throw off assignment
244                // compatibility tests
245                if (outerFnc.type.members == null && container.getType().memberScope) {
246                    outerFnc.type.members = (<SymbolScopeBuilder>(<TypeSymbol>container).type.memberScope).valueMembers;
247                }
248                funcScope = context.scopeChain.fnc.type.memberScope;
249                outerFnc.innerStaticFuncs[outerFnc.innerStaticFuncs.length] = funcDecl;
250            }
251            else {
252
253                if (!funcDecl.isConstructor &&
254                    container &&
255                    container.declAST &&
256                    container.declAST.nodeType == NodeType.FuncDecl &&
257                    (<FuncDecl>container.declAST).isConstructor &&
258                    !funcDecl.isMethod()) {
259                    funcScope = context.scopeChain.thisType.constructorScope;//locals;
260                }
261                else {
262                    funcScope = context.scopeChain.scope;
263                }
264            }
265
266            // REVIEW: We don't search for another sym for accessors to prevent us from
267            // accidentally coalescing function signatures with the same name (E.g., a function
268            // 'f' the outer scope and a setter 'f' in an object literal within that scope)
269            if (nameText && nameText != "__missing" && !funcDecl.isAccessor()) {
270                if (isStatic) {
271                    fgSym = funcScope.findLocal(nameText, false, false);
272                }
273                else {
274                    // REVIEW: This logic should be symmetric with preCollectClassTypes
275                    fgSym = funcScope.findLocal(nameText, false, false);
276                }
277            }
278
279            context.typeFlow.checker.createFunctionSignature(funcDecl, container,
280                                                            funcScope, fgSym, fgSym == null);
281
282            // it's a getter or setter for a class property
283            if (!funcDecl.accessorSymbol &&
284                (funcDecl.fncFlags & FncFlags.ClassMethod) &&
285                container &&
286                ((!fgSym || fgSym.declAST.nodeType != NodeType.FuncDecl) && funcDecl.isAccessor()) ||
287                    (fgSym && fgSym.isAccessor()))
288            {
289                funcDecl.accessorSymbol = context.typeFlow.checker.createAccessorSymbol(funcDecl, fgSym, container.getType(), (funcDecl.isMethod() && isStatic), true, funcScope, container);
290            }
291
292            funcDecl.type.symbol.flags |= SymbolFlags.TypeSetDuringScopeAssignment;
293        }
294
295        // Set the symbol for functions and their overloads
296        if (funcDecl.name && funcDecl.type) {
297            funcDecl.name.sym = funcDecl.type.symbol;
298        }
299
300        // Keep track of the original scope type, because target typing might override
301        // the "type" member. We need the original "Scope type" for completion list, etc.
302        funcDecl.scopeType = funcDecl.type;
303
304        // Overloads have no scope, so bail here
305        if (funcDecl.isOverload) {
306            return;
307        }
308
309        var funcTable = new StringHashTable();
310        var funcMembers = new ScopedMembers(new DualStringHashTable(funcTable, new StringHashTable()));
311        var ambientFuncTable = new StringHashTable();
312        var ambientFuncMembers = new ScopedMembers(new DualStringHashTable(ambientFuncTable, new StringHashTable()));
313        var funcStaticTable = new StringHashTable();
314        var funcStaticMembers = new ScopedMembers(new DualStringHashTable(funcStaticTable, new StringHashTable()));
315        var ambientFuncStaticTable = new StringHashTable();
316        var ambientFuncStaticMembers = new ScopedMembers(new DualStringHashTable(ambientFuncStaticTable, new StringHashTable()));
317
318        // REVIEW: Is it a problem that this is being set twice for properties and constructors?
319        funcDecl.unitIndex = context.typeFlow.checker.locationInfo.unitIndex;
320
321        var locals = new SymbolScopeBuilder(funcMembers, ambientFuncMembers, null, null, parentScope, localContainer);
322        var statics = new SymbolScopeBuilder(funcStaticMembers, ambientFuncStaticMembers, null, null, parentScope, null);
323
324        if (funcDecl.isConstructor && context.scopeChain.thisType) {
325            context.scopeChain.thisType.constructorScope = locals;
326        }
327
328        // basically, there are two problems
329        // - Above, for new classes, we were overwriting the constructor scope with the containing scope.  This caused constructor params to be
330        // in scope everywhere
331        // - Below, we're setting the contained scope table to the same table we were overwriting the constructor scope with, which we need to
332        // fish lambda params, etc, out (see funcTable below)
333        //
334        // A good first approach to solving this would be to change addLocalsFromScope to take a scope instead of a table, and add to the
335        // constructor scope as appropriate
336
337        funcDecl.symbols = funcTable;
338
339        if (!funcDecl.isSpecialFn()) {
340            var group = funcDecl.type;
341            var signature = funcDecl.signature;
342
343            if (!funcDecl.isConstructor) {
344                group.containedScope = locals;
345                locals.container = group.symbol;
346
347                group.memberScope = statics;
348                statics.container = group.symbol;
349            }
350            funcDecl.enclosingFnc = context.scopeChain.fnc;
351            group.enclosingType = isStatic ? context.scopeChain.classType : context.scopeChain.thisType;
352            // for mapping when type checking
353            var fgSym = <TypeSymbol>ast.type.symbol;
354            if (((funcDecl.fncFlags & FncFlags.Signature) == FncFlags.None) && funcDecl.vars) {
355                context.typeFlow.addLocalsFromScope(locals, fgSym, funcDecl.vars,
356                                                    funcTable, false);
357                context.typeFlow.addLocalsFromScope(statics, fgSym, funcDecl.statics,
358                                                    funcStaticTable, false);
359            }
360            if (signature.parameters) {
361                var len = signature.parameters.length;
362                for (var i = 0; i < len; i++) {
363                    var paramSym: ParameterSymbol = signature.parameters[i];
364                    context.typeFlow.checker.resolveTypeLink(locals,
365                                                                paramSym.parameter.typeLink, true);
366                }
367            }
368            context.typeFlow.checker.resolveTypeLink(locals, signature.returnType,
369                                                        funcDecl.isSignature());
370        }
371
372        if (!funcDecl.isConstructor || hasFlag(funcDecl.fncFlags, FncFlags.ClassMethod)) {
373            var thisType = (funcDecl.isConstructor && hasFlag(funcDecl.fncFlags, FncFlags.ClassMethod)) ? context.scopeChain.thisType : null;
374            pushAssignScope(locals, context, thisType, null, funcDecl);
375        }
376    }
377
378    export function preAssignCatchScopes(ast: AST, context: AssignScopeContext) {
379        var catchBlock = <Catch>ast;
380        if (catchBlock.param) {
381            var catchTable = new ScopedMembers(new DualStringHashTable(new StringHashTable(), new StringHashTable())); // REVIEW: Should we be allocating a public table instead of a private one?
382            var catchLocals = new SymbolScopeBuilder(catchTable, null, null, null, context.scopeChain.scope,
383                                                   context.scopeChain.scope.container);
384            catchBlock.containedScope = catchLocals;
385            pushAssignScope(catchLocals, context, context.scopeChain.thisType, context.scopeChain.classType, context.scopeChain.fnc);
386        }
387    }
388
389    export function preAssignScopes(ast: AST, parent: AST, walker: IAstWalker) {
390        var context:AssignScopeContext = walker.state;
391        var go = true;
392
393        if (ast) {
394            if (ast.nodeType == NodeType.List) {
395                var list = <ASTList>ast;
396                list.enclosingScope = context.scopeChain.scope;
397            }
398            else if (ast.nodeType == NodeType.ModuleDeclaration) {
399                preAssignModuleScopes(ast, context);
400            }
401            else if (ast.nodeType == NodeType.ClassDeclaration) {
402                preAssignClassScopes(ast, context);
403            }
404            else if (ast.nodeType == NodeType.InterfaceDeclaration) {
405                preAssignInterfaceScopes(ast, context);
406            }
407            else if (ast.nodeType == NodeType.With) {
408                preAssignWithScopes(ast, context);
409            }
410            else if (ast.nodeType == NodeType.FuncDecl) {
411                preAssignFuncDeclScopes(ast, context);
412            }
413            else if (ast.nodeType == NodeType.Catch) {
414                preAssignCatchScopes(ast, context);
415            }
416            else if (ast.nodeType == NodeType.TypeRef) {
417                go = false;
418            }
419        }
420        walker.options.goChildren = go;
421        return ast;
422    }
423
424    export function postAssignScopes(ast: AST, parent: AST, walker: IAstWalker) {
425        var context:AssignScopeContext = walker.state;
426        var go = true;
427        if (ast) {
428            if (ast.nodeType == NodeType.ModuleDeclaration) {
429                var prevModDecl = <ModuleDeclaration>ast;
430
431                popAssignScope(context);
432
433                context.modDeclChain.pop();
434                if (context.modDeclChain.length >= 1) {
435                    context.typeFlow.checker.currentModDecl = context.modDeclChain[context.modDeclChain.length - 1];
436                }
437            }
438            else if (ast.nodeType == NodeType.ClassDeclaration) {
439                popAssignScope(context);
440            }
441            else if (ast.nodeType == NodeType.InterfaceDeclaration) {
442                popAssignScope(context);
443            }
444            else if (ast.nodeType == NodeType.With) {
445                popAssignScope(context);
446            }
447            else if (ast.nodeType == NodeType.FuncDecl) {
448                var funcDecl = <FuncDecl>ast;
449                if ((!funcDecl.isConstructor || hasFlag(funcDecl.fncFlags, FncFlags.ClassMethod)) && !funcDecl.isOverload) {
450                    popAssignScope(context);
451                }
452            }
453            else if (ast.nodeType == NodeType.Catch) {
454                var catchBlock = <Catch>ast;
455                if (catchBlock.param) {
456                    popAssignScope(context);
457                }
458            }
459            else {
460                go = false;
461            }
462        }
463        walker.options.goChildren = go;
464        return ast;
465    }
466}