/* st-field.cc: resolve fields, overloading, and do other random semantic checks */ #include "AST.h" #include "code-util.h" #include "decls.h" #include "errors.h" #include "st-field.h" /* Utilities */ static void checkFieldAccess(Decl *d, Decl *accessDecl, bool thisAccess, bool isSuper, bool isConstructor, TreeNode::FieldContext *ctx, SourcePosn p) { Decl::Modifiers m = d->modifiers(); if (thisAccess && ctx->inStatic && !(m & Common::Static)) Error(p) << "access to non-static " << d->errorName() << " in static code" << endl; if (m & Common::Private) { if (d->container() != ctx->currentClass->decl()) Error(p) << "can't access private " << d->errorName() << endl; } else if (m & Common::Protected) { if (!(d->container()->container() == ctx->currentPackage || isSuper || (!isConstructor && isSubClass(accessDecl, ctx->currentClass->decl())))) Error(p) << "illegal access to protected " << d->errorName() << endl; } else if (!(m & Common::Public)) // no visibility declared, i.e. package { if (d->container()->container() != ctx->currentPackage) Error(p) << "can't access package-only " << d->errorName() << " from other package" << endl; } } TreeNode *TreeNode::resolveField(FieldContext *ctx, bool *) { const int numChildren = arity(); for (int sweep = 0; sweep < numChildren; ++sweep) child(sweep, child(sweep)->resolveField(ctx, NULL)); return _resolveOperators(); } TreeNode *CompileUnitNode::resolveField(FieldContext *, bool *) { FieldContext c; c.currentPackage = thePackage; types()->resolveField(&c, NULL); return this; } TreeNode *TemplateDeclNode::resolveField( FieldContext *context, bool *inAssignment ) { return this; } TreeNode *ClassDeclNode::resolveField(FieldContext *ctx, bool *inAssignment) { TypeDeclNode::resolveField( ctx, inAssignment ); // Check constructors for circularity bool change; do { change = false; foriter (constructor, members()->allChildren(), ChildIter) change = change | (*constructor)->updateConstructorValidity(); } while (change); foriter (constructor, members()->allChildren(), ChildIter) if (!(*constructor)->valid()) (*constructor)->error() << "'this' constructor calls are recursive" << endl; // Check that immutable classes have a 0-arg constructor if (flags() & Immutable) { ClassDecl &d = *decl(); EnvironIter constructors = d.environ()->lookupFirstProper(d.name(), Decl::Constructor); llist* nothing = 0; static TreeListNode *noArgs = new TreeListNode(nothing); foriter (c, constructors, EnvironIter) if (c->type()->isCallableWith(Local, noArgs)) return this; error() << "immutable class " << *simpName()->ident() << " must have a 0-argument constructor" << endl; } return this; } bool TreeNode::updateConstructorValidity() { return false; } bool ConstructorDeclNode::updateConstructorValidity() { if (valid()) return false; Decl *called = constructorCall()->decl(); if (!called->hasSource() || // stubs must be correct called->source()->valid()) { constructorCall()->valid(true); return true; } return false; } bool ConstructorDeclNode::valid() const { return constructorCall()->valid(); } TreeNode *FieldDeclNode::resolveField(FieldContext *ctx, bool *) { FieldContext subCtx(*ctx); subCtx.inStatic = (decl()->modifiers() & Static) != 0; if (ctx->currentClass->kind() != ImmutableKind) subCtx.thisModifiers = (Modifiers) (Local | PolysharedQ); initExpr(initExpr()->resolveField (&subCtx, NULL)); return this; } TreeNode *StaticInitNode::resolveField(FieldContext *ctx, bool *) { FieldContext subCtx(*ctx); subCtx.inStatic = true; block()->resolveField (&subCtx, NULL); return this; } TreeNode *MethodDeclNode::resolveField(FieldContext *ctx, bool *) { FieldContext subCtx(*ctx); const Modifiers localBit = ctx->currentClass->isImmutable() ? Local : None; subCtx.thisModifiers = (Modifiers) (flags() | localBit); subCtx.inStatic = (decl()->modifiers() & Static) != 0; params()->resolveField (&subCtx, NULL); body()->resolveField (&subCtx, NULL); return this; } TreeNode *ConstructorDeclNode::resolveField(FieldContext *ctx, bool *) { FieldContext subCtx(*ctx); subCtx.thisModifiers = flags(); if (simpName()->ident() != subCtx.currentClass->decl()->name()) simpName()->error () << "constructor for " << subCtx.currentClass->decl()->errorName() << " must be named " << *subCtx.currentClass->decl()->name() << endl; params()->resolveField (&subCtx, NULL); constructorCall(constructorCall()->resolveField (&subCtx, NULL)); body()->resolveField (&subCtx, NULL); return this; } TreeNode *ThisConstructorCallNode::resolveField(FieldContext *ctx, bool *) { EnvironIter methods = ctx->currentClass->decl()->environ()->lookupFirstProper (ctx->currentClass->decl()->name(), Decl::Constructor); args()->resolveField (ctx, NULL); decl(resolveCall(methods, ctx->thisModifiers, args(), position())); // no access check needed (the errors checked for can't occur) return this; } TreeNode *SuperConstructorCallNode::resolveField(FieldContext *ctx, bool *) { Decl *super = ctx->currentClass->decl()->superClass(); if (!super) { /* Hack: allow super with no args. Backend should ignore it. Should fix frontend so that generated calls to super are distinguishable from user-written ones */ if (args()->arity() > 0) error() << "Immutable class constructors cannot call a super-class constructor" << endl; return TreeNode::omitted; } EnvironIter methods = super->environ()->lookupFirstProper (super->name(), Decl::Constructor); args()->resolveField(ctx, NULL); decl(resolveCall(methods, ctx->thisModifiers, args(), position())); checkFieldAccess(decl(), ctx->currentClass->decl()->superClass(), true, true, true, ctx, position()); return this; } TreeNode *TypeDeclNode::resolveField(FieldContext *ctx, bool *) { FieldContext subCtx(*ctx); subCtx.currentClass = decl()->asType(); members()->resolveField(&subCtx, NULL); return this; } Decl *TreeNode::resolveAField(TypeDecl &typeDecl) { if (&typeDecl == UnknownClassDecl) return UnknownFieldDecl; else { Decl *d = decl(); if (!d) // don't repeat work { EnvironIter resolutions = typeDecl.environ() ->lookupFirstProper (simpName()->ident(), Decl::Field); bool more = moreThanOne (resolutions); if (resolutions.isDone()) { error () << "no " << *simpName()->ident() << " field in " << typeDecl.errorName() << endl; d = UnknownFieldDecl; } else { d = &*resolutions; if (more) error() << "ambiguous reference to " << d->errorName(); } } return d; } } void TreeNode::resolveAMember(bool thisAccess, bool isSuper, FieldContext *ctx) { TypeNode &oType = *accessedObjectType(); TypeDecl &typeDecl = *oType.decl(); Decl *d; if (!ctx->methodArgs) d = resolveAField(typeDecl); else { EnvironIter resolutions = typeDecl.environ() -> lookupFirstProper (simpName()->ident(), Decl::Method); if (resolutions.isDone()) { // avoid duplicate error messages for errors already reported by // resolveName if (!decl()) error() << "no " << *simpName()->ident() << " method in " << typeDecl.errorName() << endl; d = UnknownMethodDecl; } else d = resolveCall(resolutions, oType.modifiers(), ctx->methodArgs, position()); } simpName()->decl(d); checkFieldAccess(d, &typeDecl, thisAccess, isSuper, false, ctx, position()); } TreeNode *ObjectFieldAccessNode::resolveField(FieldContext *ctx, bool *) { FieldContext subCtx(*ctx); subCtx.methodArgs = NULL; object(object()->resolveField (&subCtx, NULL)); TypeNode *ot = object()->type(); if (!(ot->isReference() || ot->isImmutable())) { if (!decl()) error() << "attempt to select from non-reference type " << ot->typeName() << endl; simpName()->decl(ctx->methodArgs ? UnknownMethodDecl : UnknownFieldDecl); } else resolveAMember(false, false, ctx); // Ok, I hate this kind of test, but adding a method just for this // is a bit painful. if (strcmp(object()->oper_name(), "ThisNode") == 0) return new ThisFieldAccessNode(object()->theClass(), object()->flags(), simpName(), position()); else return this; } TreeNode *TypeFieldAccessNode::resolveField(FieldContext *ctx, bool *) { resolveAMember(true, false, ctx); if (!(decl()->modifiers() & Static)) error() << "must specify object when accessing " << decl()->errorName() << endl; return this; } TreeNode *ThisFieldAccessNode::resolveField(FieldContext *ctx, bool *) { resolveAMember(true, false, ctx); return this; } TreeNode *SuperFieldAccessNode::resolveField(FieldContext *ctx, bool *) { if (ctx->inStatic) { error() << "cannot use 'super' in static code" << endl; simpName()->decl(ctx->methodArgs ? UnknownMethodDecl : UnknownFieldDecl); } else resolveAMember(true, true, ctx); return this; } TreeNode *MethodCallNode::resolveField(FieldContext *ctx, bool *) { args()->resolveField (ctx, NULL); FieldContext subCtx(*ctx); subCtx.methodArgs = args(); method(method()->resolveField (&subCtx, NULL)); return this; } TreeNode *ThisNode::resolveField(FieldContext *ctx, bool *) { if (ctx->inStatic) error() << "cannot use 'this' in static code" << endl; return this; } TreeNode *AllocateNode::resolveField(FieldContext *ctx, bool *) { Decl *constructor = UnknownMethodDecl; region(region()->resolveField(ctx, NULL)); dtype()->resolveField (ctx, NULL); args()->resolveField (ctx, NULL); Kind dkind = dtype()->kind(); if (dkind != ClassKind && dkind != ImmutableKind) { dtype()->error() << "cannot allocate something of non-class type " << dtype()->typeName() << endl; } else if (dtype()->decl()->modifiers() & Abstract) { error() << "can't allocate abstract " << dtype()->decl()->errorName() << endl; } else { EnvironIter methods = dtype()->decl()->environ() ->lookupFirstProper (dtype()->decl()->name(), Decl::Constructor); constructor = resolveCall(methods, (Modifiers) (dtype()->modifiers() | Local), args(), position()); checkFieldAccess(constructor, dtype()->decl(), false, false, true, ctx, position()); } decl(constructor); return this; } TreeNode *ForEachStmtNode::resolveField(FieldContext *ctx, bool *) { TypeNode *ptype = NULL; // the appropriate point type // Fix types in foreach statement (this doesn't really belong here) foriter (var, vars()->allChildren(), TreeNode::ChildIter) { (*var)->initExpr((*var)->initExpr()->resolveField(ctx, NULL)); if (!ptype) // Pick an arity for the statement { TypeNode *initType = (*var)->initExpr()->type(); // non-domain types get a 0 arity if we don't know the arity yet // (i.e. we leave the type introduced by resolveName) // Note: It is important that all (typechecked) foreach // statements use the same point type because st-single.cc // relies on this type being shared to mark all variables // as single (or not) if (initType->isDomainType()) { ptype = makePointType(initType->tiArity()); ptype->modifiers(Local); } } if (ptype) (*var)->simpName()->decl()->type(ptype); } stmt()->resolveField(ctx, NULL); return this; } TreeNode *ArrayAccessNode::resolveField(FieldContext *ctx, bool *inAssignment) { // Remove the implicit point creation added by the parser if // it isn't correct, i.e. if it is a Point<1> and either: // a) the array is not a Titanium array // b) the element of the point is actually a Point // This doesn't belong here with field resolution, but isn't worth a // separate pass. array(array()->resolveField(ctx, NULL)); index(index()->resolveField(ctx, NULL)); // [] overloading if (!array()->type()->isArrayType()) if (inAssignment) { // Notify AssignNode that this is an overloaded array assignment *inAssignment = true; return this; // handle in AssignNode } else return overloadArray(); if (isPointNode( index() )) { TreeNode *pointArgs = index()->args(); if (pointArgs->arity() == 1) // a Point<1> literal if (array()->type()->isJavaArrayType() || array()->type()->isPointType() || pointArgs->child(0)->type()->isPointType()) index(pointArgs->child(0)); } return this; } TreeNode *AssignNode::resolveField(FieldContext *ctx, bool *) { bool overloadedArrayAccess = false; opnd0(opnd0()->resolveField(ctx, &overloadedArrayAccess)); opnd1(opnd1()->resolveField(ctx, NULL)); if (!overloadedArrayAccess) return this; return overloadArrayAssign(); }