/* st-name.cc: Name resolution for simple and qualified names, resolve parsing ambiguities. After this phase, all fields and methods are referred to via ThisFieldAccessNode, SuperFieldAccessNode or ObjectFieldAccessNode. ObjectNode is only used for local variables and parameters. The decl in methods may be wrong, because overloading resolution is done later (when types become available) */ #include "AST.h" #include "NameContext.h" #include "TypeContext.h" #include "compiler.h" #include "decls.h" #include "errors.h" #include "code-util.h" /* Utilities */ static int countEnclosers(TreeNode *declarer, Decl *decl, const TreeNode::NameContext &ctx, bool superClassAllowed) { TreeNode *cclass = ctx.currentClass->decl()->source(); int nesting = 0; while (cclass != declarer && (!superClassAllowed || (decl->modifiers() & Common::Private) || !isSubClass(cclass->decl(), declarer->decl())) && cclass->enclosingType()) { cclass = cclass->enclosingType(); nesting++; } if (cclass == declarer || (superClassAllowed && !(decl->modifiers() & Common::Private) && isSubClass(cclass->decl(), declarer->decl()))) return nesting; else return -1; } static bool checkEnclosingAccess(TreeNode *declarer, Decl *decl, const TreeNode::NameContext &ctx, bool superClassAllowed) { TreeNode *cclass = ctx.currentClass->decl()->source(); while (cclass != declarer && (!superClassAllowed || (decl->modifiers() & Common::Private) || !isSubClass(cclass->decl(), declarer->decl()))) { if (cclass->flags() & TreeNode::Static) return false; cclass = cclass->enclosingType(); } return true; } static Decl *isUnqualifiedEnclosingAccess(TreeNode *name, const TreeNode::NameContext &ctx, int categories) { TreeNode *outermost = name; while (isNameNode(outermost) && !outermost->qualifier()->absent()) outermost = outermost->qualifier(); if (!isNameNode(outermost)) return NULL; int newcategories = name->qualifier()->absent() ? categories : Decl::Field | Decl::LocalVar | Decl::Formal; bool postponed = false; EnvironIter possibles = qualifiedLookup(outermost, *ctx.env, ctx.currentClass, ctx.thisModifiers, ctx.currentPackage, postponed, newcategories); if (postponed || possibles.isDone()) return NULL; Decl *d = &*possibles; if ((d->category() & (Decl::Field | Decl::Method)) == 0) return NULL; Decl *declarer = d->container(); if (declarer != ctx.currentClass->decl() && ((d->modifiers() & Common::Private) || !isSubClass(ctx.currentClass->decl(), declarer))) return d; else return NULL; } static TreeNode *rewriteEnclosingAccess(TreeNode *resolved, TreeNode *declarer, const TreeNode::NameContext &ctx, int n, bool &postponed, int categories, Decl *decl) { TreeNode *qualifier, *outermost = resolved->name(); while (!outermost->qualifier()->absent()) outermost = outermost->qualifier(); if (decl->modifiers() & TreeNode::Static) { qualifier = new NameNode(TreeNode::omitted, declarer->declaredName()->ident(), declarer->decl(), resolved->name()->position()); } else { extern NameNode *buildOuterNameNode(TreeNode *, SourcePosn posn); qualifier = TreeNode::omitted; for (int i = 0; i < n; i++) qualifier = buildOuterNameNode(qualifier, resolved->name()->position()); } outermost->qualifier(qualifier); resolved = resolveAName(resolved->name(), *ctx.env, ctx.currentClass, ctx.thisModifiers, ctx.currentPackage, postponed, categories); return resolved; } static TreeNode *rewriteQualifiedThis(TreeNode *declarer, const TreeNode::NameContext &ctx, int n, SourcePosn posn) { TreeNode *qualifier, *resolved; extern NameNode *buildOuterNameNode(TreeNode *, SourcePosn); qualifier = TreeNode::omitted; for (int i = 0; i < n; i++) qualifier = buildOuterNameNode(qualifier, posn); bool postponed = false; resolved = resolveAName(qualifier, *ctx.env, ctx.currentClass, ctx.thisModifiers, ctx.currentPackage, postponed, Decl::Field); assert(!postponed); return resolved; } static TreeNode *rewriteQualifiedSuper(TreeNode *simpName, TreeNode *declarer, const TreeNode::NameContext &ctx, int n, SourcePosn posn) { TreeNode *qualifier, *resolved; extern NameNode *buildOuterNameNode(TreeNode *, SourcePosn); qualifier = TreeNode::omitted; for (int i = 0; i < n; i++) qualifier = buildOuterNameNode(qualifier, posn); bool postponed = false; TreeNode *name = new NameNode(qualifier, simpName->ident(), NULL, posn); Decl::Category cat = ctx.resolveAsObject ? Decl::Field : Decl::Method; resolved = resolveAName(name, *ctx.env, ctx.currentClass, ctx.thisModifiers, ctx.currentPackage, postponed, cat); assert(!postponed); ObjectFieldAccessNode *ofan = dynamic_cast(resolved); ofan->isRewrittenQSFAN(true); // Currently, ofan is attached to the member defined in the current class. // We need it to be re-resolved in field resolution and attached to the // member defined in the super class, but don't want to repeat an error. if (ofan->simpName()->decl() != UnknownFieldDecl && ofan->simpName()->decl() != UnknownMethodDecl) ofan->simpName()->decl(NULL); return ofan; } bool ObjectFieldAccessNode::isRewrittenQSFAN() const { return _isRewrittenQSFAN; } void ObjectFieldAccessNode::isRewrittenQSFAN(bool val) { _isRewrittenQSFAN = val; } /* Name resolution, pass 2: return a destructively modified */ /* version of *THIS, with all names resolved to ResolvedNodes and */ /* appropriate ObjectNodes re-written as field accesses. ENV is */ /* the environment in which to do the resolution. */ TreeNode* TreeNode::resolveName(const NameContext &ctx) { const int numChildren = arity(); for (int sweep = 0; sweep < numChildren; ++sweep) child(sweep, child(sweep)->resolveName(ctx)); return this; } TreeNode *CompileUnitNode::resolveName(const NameContext &ctx) { NameContext c(ctx, environ()); c.currentPackage = thePackage; types()->resolveName(c); return this; } TreeNode* FieldDeclNode::resolveName(const NameContext &ctx) { NameContext subCtx(ctx); #if 0 // Not true any more (late final initialisation allowed) if ((decl()->modifiers() & Final) && initExpr()->absent()) error() << "initialiser omitted on final "<< decl()->errorName() << endl; #endif // field initialisers are part of the constructor if (ctx.currentClass->kind() == ImmutableKind) subCtx.thisModifiers = None; else subCtx.thisModifiers = (Modifiers) (Local | ctx.constructorSharingLUB); dtype()->resolveName(ctx); initExpr(initExpr()->resolveName(subCtx)); return this; } TreeNode* InstanceInitNode::resolveName(const NameContext &ctx) { NameContext subCtx(ctx); subCtx.inLoop = subCtx.breakOK = NULL; if (ctx.currentClass->kind() == ImmutableKind) subCtx.thisModifiers = None; else subCtx.thisModifiers = (Modifiers) (Local | ctx.constructorSharingLUB); Environ newEnv1(ctx.env); subCtx.env = &newEnv1; if (ctx.currentClass->decl()->isMethodReady()) block(block()->resolveName(subCtx)); else ctx.postponed = true; return this; } TreeNode *StaticInitNode::resolveName(const NameContext &ctx) { if (ctx.currentClass->decl()->isMethodReady()) block(block()->resolveName(ctx)); else ctx.postponed = true; return this; } TreeNode* VarDeclNode::resolveName(const NameContext &ctx) { dtype()->resolveName(ctx); initExpr(initExpr()->resolveName(ctx)); if (!simpName()->decl()) { Decl *other = ctx.env->lookupProper(simpName()->ident(), Decl::LocalVar); if (other != NULL) { error() << "redeclaration of " << other->errorName() << endl; other->source()->error() << " this is the source of the previous declaration" << endl; } else if ((other = ctx.env->lookup(simpName()->ident(), Decl::LocalVar | Decl::Formal)) != NULL) { error() << "declaration shadows " << other->errorName() << endl; other->source()->error() << " this is the source of the previous declaration" << endl; } else { // PR495: initialized final locals not assignable bool initializedfinal = isfinal() && !initExpr()->absent(); Decl* d = new LocalVarDecl(simpName()->ident(), dtype(), this, !initializedfinal); ctx.env->add(d); simpName()->decl(d); } } return this; } TreeNode* MethodDeclNode::resolveName(const NameContext &ctx) { NameContext subCtx(ctx); subCtx.inLoop = subCtx.breakOK = NULL; const Modifiers localBit = ctx.currentClass->isImmutable() ? Local : None; subCtx.thisModifiers = (Modifiers) (flags() | localBit); Environ newEnv1(ctx.env); subCtx.env = &newEnv1; params()->resolveName(subCtx); overlaps()->resolveName(subCtx); returnType()->resolveName(ctx); declaredReturnType()->resolveName(ctx); throws()->resolveName(ctx); Environ newEnv2(&newEnv1); subCtx.env = &newEnv2; if (ctx.currentClass->decl()->isMethodReady()) body(body()->resolveName(subCtx)); else ctx.postponed = true; return this; } static void resolveOverlapName(const TreeNode::NameContext &ctx, TreeNode *name) { Decl *d0 = ctx.env->lookupProper(name->ident(), Decl::Formal); if (!d0) name->error() << "unknown parameter " << *name->ident() << " in overlap() declaration." << endl; name->decl(d0); } TreeNode* OverlapNode::resolveName(const NameContext &ctx) { resolveOverlapName(ctx, opnd0()); resolveOverlapName(ctx, opnd1()); return this; } TreeNode* MethodSignatureNode::resolveName(const NameContext &ctx) { NameContext subCtx(ctx); Environ newEnv1(ctx.env); subCtx.env = &newEnv1; params()->resolveName(subCtx); returnType()->resolveName(ctx); declaredReturnType()->resolveName(ctx); throws()->resolveName(ctx); return this; } TreeNode* ConstructorDeclNode::resolveName(const NameContext &ctx) { NameContext subCtx(ctx); subCtx.inLoop = subCtx.breakOK = NULL; subCtx.thisModifiers = flags(); Environ newEnv1(ctx.env); subCtx.env = &newEnv1; params()->resolveName(subCtx); throws()->resolveName(ctx); Environ newEnv2(&newEnv1); subCtx.env = &newEnv2; if (ctx.currentClass->decl()->isMethodReady()) { initEncloser(initEncloser()->resolveName(subCtx)); constructorCall(constructorCall()->resolveName(subCtx)); body(body()->resolveName(subCtx)); } else ctx.postponed = true; return this; } TreeNode *TemplateDeclNode::resolveName( const NameContext & ) { // : Does any formal name clash with something else, such as an // existing package or class, or the template itself? // : Does this check really belong here, or should it be // somewhere else? IdentSet values; IdentSet types; foriter (child, params()->allChildren(), ChildIter) (*child)->checkTemplateFormal( values, types ); return this; } Common::Modifiers computeConstructorSharingLUB(ClassDeclNode *cdn); TreeNode* TypeDeclNode::resolveName(const NameContext &ctx) { NameContext subCtx(ctx, decl()->environ()); subCtx.currentClass = decl()->asType(); // Must resolve super classes and interfaces so that constants therein can // be folded and decls filled in. if (isClassDeclNode(this)) superClass()->resolveName(subCtx); interfaces()->resolveName(subCtx); if (!decl()->isFieldReady()) { // Cannot resolve anything yet. ctx.postponed = true; return this; } else if (!decl()->isMethodReady()) { ctx.postponed = true; // Must resolve types in method, constructor, and field signatures. // It is the members' responsibility to make sure they do no more than this. members()->resolveName(subCtx); return this; } if (isClassDeclNode(this)) { subCtx.constructorSharingLUB = computeConstructorSharingLUB((ClassDeclNode*)this); #if 0 cout << *simpName()->ident() << " ClassDeclNode constructorSharingLUB: " << stringifyModifiers(subCtx.constructorSharingLUB) << endl; #endif if (flags() & AnonymousClass) { // Defer name resolution of anonymous constructor. for (int i = 0; i < members()->arity(); i++) { if (!(isConstructorDeclNode(members()->child(i)) && (members()->child(i)->flags() & CompilerGenerated))) members()->child(i)->resolveName(subCtx); } return this; } } members()->resolveName(subCtx); return this; } TreeNode *TypeNameNode::resolveName(const NameContext &ctx) { if (!name()->decl()) { // This only occurs if this type name corresponds to a type nested in a // template or is part of a super type declaration. TreeNode *qual = name(); while (!isTemplateNameNode(qual) && !qual->qualifier()->absent()) qual = qual->qualifier(); if (qual->decl()) { // Do deferred type resolution of this type. bool postponed = false; TypeContext tctx(postponed); tctx.package = ctx.currentPackage; tctx.cclass = ctx.currentClass; tctx.fileEnv = ctx.currentPackage->environ(); tctx.typeEnv = ctx.env; resolveTypes(&tctx); ctx.postponed |= postponed; } else ctx.postponed = true; } return TreeNode::resolveName(ctx); } TreeNode *AllocateNode::resolveName(const NameContext &ctx) { region(region()->resolveName(ctx)); if (!qualifier()->absent()) qualifier(qualifier()->resolveName(ctx)); else dtype((TypeNode *) dtype()->resolveName(ctx)); args((TreeListNode *) args()->resolveName(ctx)); return this; } TreeNode* BlockNode::resolveName(const NameContext &ctx) { Environ newEnv(ctx.env); if (environ()) newEnv.copy(environ()); // Allow resolution of local types. NameContext subCtx(ctx, &newEnv); stmts((TreeListNode *)stmts()->resolveName(subCtx)); return this; } TreeNode* LabeledStmtNode::resolveName(const NameContext &ctx) { Decl *d = label()->decl(); if (!d) { Decl *other = ctx.env->lookup(label()->ident(), Decl::StmtLabel); if (other != NULL) error() << "duplicate "<< other->errorName() << endl; d = new StmtLblDecl(label()->ident(), this); label()->decl(d); } Environ newEnv(ctx.env); NameContext subCtx(ctx, &newEnv); newEnv.add(d); stmt(stmt()->resolveName(subCtx)); return this; } TreeNode* SwitchNode::resolveName(const NameContext &ctx) { NameContext subCtx(ctx); expr(expr()->resolveName(ctx)); Environ newEnv(ctx.env); subCtx.breakOK = this; subCtx.env = &newEnv; switchBlocks()->resolveName(subCtx); return this; } TreeNode* LoopNode::resolveName(const NameContext &ctx) { test(test()->resolveName(ctx)); Environ newEnv(ctx.env); NameContext subCtx(ctx, &newEnv); subCtx.breakOK = subCtx.inLoop = this; stmt(stmt()->resolveName(subCtx)); return this; } TreeNode* ForNode::resolveName(const NameContext &ctx) { Environ newEnv(ctx.env); NameContext subCtx(ctx, &newEnv); init((TreeListNode *)init()->resolveName(subCtx)); subCtx.breakOK = subCtx.inLoop = this; test(test()->resolveName(subCtx)); update((TreeListNode *)update()->resolveName(subCtx)); stmt(stmt()->resolveName(subCtx)); return this; } static TreeNode* resolveJump(TreeNode* node, TreeNode *noLabel, Environ *env) { if (node->label()->absent()) node->destination(noLabel); else { Decl *dest = env->lookup(node->label()->ident(), Decl::StmtLabel); node->label()->decl(dest); if (dest == NULL) node->error() << "label " << *node->label()->ident() << " not found" << endl; else node->destination(dest->source()->stmt()); } return node; } TreeNode* BreakNode::resolveName(const NameContext &ctx) { if (label()->absent() && ! ctx.breakOK) error() << "unlabeled break only allowed in loops or switches" << endl; resolveJump(this, ctx.breakOK, ctx.env); return this; } TreeNode* ContinueNode::resolveName(const NameContext &ctx) { if (! ctx.inLoop) error() << "unlabeled continue only allowed in loops" << endl; resolveJump(this, ctx.inLoop, ctx.env); if (destination() && !destination()->isLoop()) error() << "continue's target is not a loop" << endl; return this; } TreeNode* ParameterNode::resolveName(const NameContext &ctx) { dtype()->resolveName(ctx); if (!simpName()->decl()) { Decl *other = ctx.env->lookup(simpName()->ident(), Decl::Formal | Decl::LocalVar); if (other != NULL) { error() << "parameter shadows " << other->errorName() << endl; other->source()->error() << " this is the source of the previous declaration" << endl; simpName()->decl(other); // Prevent duplicate error messages. return this; } Decl* d = new FormalParameterDecl(simpName()->ident(), dtype(), this, !isfinal()); simpName()->decl(d); ctx.env->add(d); } else if (!ctx.env->lookup(simpName()->ident(), Decl::Formal | Decl::LocalVar)) // This is necessary for name resolution to be reentrant. ctx.env->add(simpName()->decl()); return this; } TreeNode* ObjectNode::resolveName(const NameContext &ctx) { NameContext subCtx(ctx); subCtx.resolveAsObject = true; TreeNode::resolveName(subCtx); // With the addition of nested classes in templates, NameNodes can now // be qualified by TemplateNameNodes, which may contain nodes that // themselves need name resolution (e.g. ObjectNode). Thus it is now // necessary to recursively apply name resolution. Since such a descendant // can only be part of a type qualifier, it should be resolved as an object. if (decl()) return this; else { const int categories = ctx.resolveAsObject ? Decl::Field | Decl::LocalVar | Decl::Formal | Decl::ConstPseudonym : Decl::Method; bool postponed = false; TreeNode *resolved; Decl *decl; if ((decl = isUnqualifiedEnclosingAccess(name(), ctx, categories))) { TreeNode *declarer = decl->source()->parent()->parent(); int nesting; nesting = countEnclosers(declarer, decl, ctx, true); if (nesting > 0) { if (!(decl->modifiers() & TreeNode::Static) && !checkEnclosingAccess(declarer, decl, ctx, true)) { error() << "access to non-static " << decl->errorName() << " in static code" << endl; // fix message? } else { resolved = rewriteEnclosingAccess(this, declarer, ctx, nesting, postponed, categories, decl); ctx.postponed |= postponed; return postponed ? this : resolved; } } } resolved = resolveAName(name(), *ctx.env, ctx.currentClass, ctx.thisModifiers, ctx.currentPackage, postponed, categories); ctx.postponed |= postponed; return postponed ? this : resolved; } } TreeNode* ObjectFieldAccessNode::resolveName(const NameContext &ctx) { NameContext subCtx(ctx); subCtx.resolveAsObject = true; object(object()->resolveName(subCtx)); return this; } TreeNode* TypeFieldAccessNode::resolveName(const NameContext &ctx) { if (!decl()) { NameContext subCtx(ctx); subCtx.resolveAsObject = true; ftype((TypeNode *)ftype()->resolveName(subCtx)); if (ctx.resolveAsObject) { TypeDecl * const typeDecl = ftype()->decl(); if (typeDecl) simpName()->decl(resolveAField(*typeDecl)); else { ctx.postponed = true; postpone( "name resolution", "type field access with unresolved container type" ); } } } return this; } TreeNode* ThisFieldAccessNode::resolveName(const NameContext &ctx) { theClass( ctx.currentClass ); flags( ctx.thisModifiers ); return TreeNode::resolveName( ctx ); } TreeNode* SuperFieldAccessNode::resolveName(const NameContext &ctx) { if (theClass()) { return TreeNode::resolveName(ctx); } else if (qualifier()->absent()) { if (ctx.currentClass->decl()->modifiers() & Immutable) error() << "immutable " << ctx.currentClass->decl()->errorName() << " has no superclass" << endl; theClass(ctx.currentClass); flags( ctx.thisModifiers ); return TreeNode::resolveName( ctx ); } else { NameContext subCtx(ctx); subCtx.resolveAsObject = true; qualifier()->resolveName(subCtx); if (!qualifier()->decl()) { ctx.postponed = true; return this; } // The above can happen when the qualifier is a template type or a type // nested in a template. This case is also what necessitates the above // recursion. TreeNode *declarer = qualifier()->decl()->source(); int nesting = countEnclosers(declarer, NULL, ctx, false); if (nesting < 0) { error() << qualifier()->decl()->errorName() << " is not an enclosing class of " << ctx.currentClass->decl()->errorName() << endl; } else if (!checkEnclosingAccess(declarer, NULL, ctx, false)) { error() << "super qualified by " << qualifier()->decl()->errorName() << " in static context" << endl; // fix message? } else if (declarer->flags() & Immutable) { error() << "immutable " << qualifier()->decl()->errorName() << " has no superclass" << endl; } else if (nesting != 0) { return rewriteQualifiedSuper(simpName(), declarer, ctx, nesting, qualifier()->position()); } qualifier(TreeNode::omitted); theClass(ctx.currentClass); flags( ctx.thisModifiers ); return TreeNode::resolveName(ctx); } } TreeNode* ThisNode::resolveName(const NameContext &ctx) { if (theClass()) { return this; } else if (qualifier()->absent()) { theClass(ctx.currentClass); flags(ctx.thisModifiers); return this; } else { NameContext subCtx(ctx); subCtx.resolveAsObject = true; qualifier()->resolveName(subCtx); if (!qualifier()->decl()) { ctx.postponed = true; return this; } // The above can happen when the qualifier is a template type or a type // nested in a template. This case is also what necessitates the above // recursion. TreeNode *declarer = qualifier()->decl()->source(); int nesting = countEnclosers(declarer, NULL, ctx, false); if (nesting < 0) { error() << qualifier()->decl()->errorName() << " is not an enclosing class of " << ctx.currentClass->decl()->errorName() << endl; } else if (!checkEnclosingAccess(declarer, NULL, ctx, false)) { error() << "this qualified by " << qualifier()->decl()->errorName() << " in static context" << endl; // fix message? } else { return rewriteQualifiedThis(declarer, ctx, nesting, qualifier()->position()); } theClass(ctx.currentClass); flags(ctx.thisModifiers); return this; } } TreeNode* MethodCallNode::resolveName(const NameContext &ctx) { args()->resolveName(ctx); if (!ctx.currentClass->decl()->isMethodReady()) { ctx.postponed = true; return this; } NameContext subCtx(ctx); subCtx.resolveAsObject = false; method(method()->resolveName(subCtx)); return this; } TreeNode* CatchNode::resolveName(const NameContext &ctx) { Environ newEnv(ctx.env); NameContext subCtx(ctx, &newEnv); param(param()->resolveName(subCtx)); block(block()->resolveName(subCtx)); return this; } TreeNode* ForEachStmtNode::resolveName(const NameContext &ctx) { Environ newEnv(ctx.env); NameContext subCtx(ctx, &newEnv); // arity cannot yet be computed (no constant folding, etc), so // we give the Decls a dummy type (Point<0>). This will be fixed // later. TypeNode *ptype = makePointType(0); ptype->modifiers(Local); foriter (var, vars()->allChildren(), TreeNode::ChildIter) { (*var)->dtypeopt((*var)->dtypeopt()->resolveName(ctx)); (*var)->initExpr((*var)->initExpr()->resolveName(ctx)); TreeNode &simpName = *(*var)->simpName(); if (!simpName.decl()) { const string *id = simpName.ident(); Decl *other = ctx.env->lookup(id, Decl::Formal | Decl::LocalVar); if (other != NULL) { error() << "foreach Point declaration shadows " << other->errorName() << endl; other->source()->error() << " this is the source of the previous declaration" << endl; } else { other = newEnv.lookupProper(id, Decl::Formal | Decl::LocalVar); if (other != NULL) { (*var)->error() << "redeclaration of " << other->errorName() << endl; other->source()->error() << " this is the source of the previous declaration" << endl; } else { Decl* d = new LocalVarDecl(id, ptype, *var, false); newEnv.add(d); simpName.decl(d); } } } } subCtx.inLoop = subCtx.breakOK = this; stmt(stmt()->resolveName(subCtx)); return this; } TreeNode* PartitionStmtNode::resolveName(const NameContext &ctx) { NameContext subCtx(ctx); Environ newEnv(ctx.env); if (simpName()->absent()) subCtx.partitionEnv = ctx.env; else if (!simpName()->decl()) { Decl *other = ctx.env->lookup(simpName()->ident(), Decl::Formal | Decl::LocalVar); if (other != NULL) { error() << "declaration shadows " << other->errorName() << endl; other->source()->error() << " this is the source of the previous declaration" << endl; } else { Decl* d = new LocalVarDecl(simpName()->ident(), theIntType, this, true); newEnv.add(d); simpName()->decl(d); // The new variable is not in scope in the conditions subCtx.partitionEnv = &newEnv; } } cases()->resolveName(subCtx); return this; } TreeNode *PartitionClauseNode::resolveName(const NameContext &ctx) { // The partition variable is not in scope in the condition, but is // in the statement... condition(condition()->resolveName(ctx)); NameContext subCtx(ctx, ctx.partitionEnv); stmt(stmt()->resolveName(subCtx)); return this; } // Misc support methods bool TreeNode::isLoop() const { return false; } bool ForEachStmtNode::isLoop() const { return true; } bool IterationNode::isLoop() const { return true; }