/* typecheck.cc: Definitions for member functions that check types. */ #include #include #include "utils.h" #include "AST.h" #include "compiler.h" #include "decls.h" #include "errors.h" #include "typecheck.h" #include "code-util.h" #include "ClassDecl.h" #include "FieldDecl.h" #include "PrimitiveDecl.h" #include "Subfield.h" void TreeNode::assertArithType(TypeNode *type) { if (!type->isArithType()) error() << operatorName() << " expects a number, got " << type->typeName() << endl; } void TreeNode::assertBooleanType(TypeNode *type) { if (type->kind() != BoolKind) error() << operatorName() << " expects a boolean, got " << type->typeName() << endl; } void TreeNode::assertIntegralType(TypeNode *type) { if (!type->isIntegralType()) error() << operatorName() << " expects an integer, got " << type->typeName() << endl; } void TreeNode::assertPromoteableInt(TypeNode *type) { if (arithPromoteType(type)->kind() != IntKind) error() << operatorName() << " expects a value promoteable to int, got " << type->typeName() << endl; } void TreeNode::assertReferenceType(TypeNode *type) { if (!type->isReference()) error() << operatorName() << " expects a reference, got " << type->typeName() << endl; } void TreeNode::assertDomainOrRectDomainType(TypeNode *type) { if (!type->isDomainOrRectDomainType()) error() << operatorName() << " expects a Domain or RectDomain, got " << type->typeName() << endl; } void TreeNode::assertRectDomainType(TypeNode *type) { if (!type->isRectDomainType()) error() << operatorName() << " expects a RectDomain, got " << type->typeName() << endl; } void TreeNode::assertAssignable(TypeContext *ctx, TreeNode *tree, TreeNode *with) { bool ignoreInstanceFinal = (ctx->currentConstructor != NULL) || (ctx->inInitializer && !ctx->inStatic); bool ignoreStaticFinal = (ctx->inInitializer && ctx->inStatic); tree->checkAssignable(with, ignoreInstanceFinal, ignoreStaticFinal, ctx->currentClass); } void TreeNode::assertCastable(TypeNode *from, TypeNode *to) { if (!to->isCastableFrom(from)) error() << operatorName() << " requires that " << from->typeName() << " be castable to " << to->typeName() << endl; } void TreeNode::assertComparable(TypeNode *a, TypeNode *b) { if (!a->isComparableTo(b)) error() << operatorName() << " requires that " << a->typeName() << " be comparable to " << b->typeName() << endl; } // Return: True if exception can be thrown in ctx static bool canThrow(TreeNode::TypeContext *ctx, TypeNode *exception) { foreach (exc, llist, *ctx->allowedExceptions) if ((*exc)->asType()->isAssignableFromType(exception)) return true; return false; } /* Report an error if a call to mtype can throw an exception undeclared here */ static void checkThrownExceptions(TreeNode::TypeContext *ctx, TreeNode *x) { Decl *d = x->decl(); TypeNode *mtype = d->type(); foriter (thrown, mtype->throws()->allTypes(), TreeNode::TypeIter) if (!canThrow(ctx, *thrown)) x->error() << "call to " << d->errorName() << " throws uncaught and undeclared exception " << (*thrown)->typeName() << endl; } bool CompileUnitNode::typecheck(TypeContext *) { foriter (type, types()->allChildren(), ChildIter) { TypeContext base; #if 0 cout << "Type:\n"; (*type)->print(cout); cout << "\n"; #endif (*type)->typecheck(&base); } return true; } bool TypeDeclNode::typecheck(TypeContext *context) { TypeContext subCtx(context); subCtx.currentClass = decl(); return TreeNode::typecheck(&subCtx); } bool TemplateDeclNode::typecheck( TypeContext *context ) { return true; } bool TreeNode::typecheck(TypeContext *ctx) { bool terminates = true; foriter (p, allChildren(), ChildIter) { TreeNode * const child = (*p); const bool childTerminates = child->typecheck(ctx); terminates &= childTerminates; } return terminates & _typecheck(ctx); } bool MethodNode::typecheck(TypeContext *) { if (sharingEnforcement == Early) { MethodDecl &declaration = *decl(); const TypeNode * const thisType = declaration.thisType(); if (thisType && !thisType->isLocal() && thisType->sharing() != Shared) error() << declaration.errorName() << " cannot be both global and " << thisType->sharing() << endl; } return true; } bool MethodDeclNode::typecheck(TypeContext *ctx) { MethodNode::typecheck(ctx); params()->typecheck(ctx); returnType()->typecheck(ctx); throws()->typecheck(ctx); TypeContext subCtx(ctx); subCtx.currentMethod = this; subCtx.inStatic = (flags() & Static); subCtx.inInitializer = false; buildAllowedExceptions(&subCtx, throws()); //bool terminates = body()->typecheck(&subCtx); free_all(subCtx.allowedExceptions); // Termination checking superseded by reachability checking in st-reachable.cc. #if 0 if (!(flags() & (Abstract | Native)) && terminates) { TypeNode *rtype = returnType(); if (rtype->kind() != VoidKind) error() << "return required at the end of " << decl()->errorName() << endl; } #endif const string& mname = *simpName()->ident(); if (flags() & Static && !(isalpha(mname[0]) || isdigit(mname[0]) || mname[0] == '$' || mname[0] == '_')) error() << "operator overloading method declarations may not be static" << endl; if (mname == "==" || mname == "!=") { if (decl()->container()->asType()->isReference()) { error() << "reference types may not overload the == or != operators." << endl; } else if (params()->arity() == 1 && params()->child(0)->dtype()->typeIdentNM(decl()->container()->asType())) { if ((flags() & ~(Final|Native|Inline)) != Public || throws()->arity() > 0 || overlaps()->arity() > 0 || !returnType()->typeIdent(theBoolType->withModifiers(Single)) || !params()->child(0)->dtype()->typeIdent(decl()->container()->asType()->withModifiers(Single))) { error() << "immutable T.op" << mname << "(T) method must exactly match signature: " << "public boolean single op" << mname << "(T single)" << endl; } } } return true; } bool ConstructorDeclNode::typecheck(TypeContext *ctx) { TypeContext subCtx(ctx); subCtx.currentConstructor = this; subCtx.inStatic = false; subCtx.inInitializer = false; buildAllowedExceptions(&subCtx, throws()); TreeNode::typecheck(&subCtx); free_all(subCtx.allowedExceptions); return true; } bool DataDeclNode::typecheck (TypeContext *ctx) { dtype()->typecheck(ctx); dtype()->checkQualifiers(); if (!initExpr()->absent()) if (!dtype()->isAssignableFromExpr(initExpr())) error() << decl()->errorName() << " is not assignable with " << initExpr()->declaredType()->typeName() << endl; TypeContext subCtx(ctx); subCtx.dtype = dtype(); initExpr()->typecheck(&subCtx); return true; } /* check for recursively nested immutable types, which are prohibited (PR 370) */ extern bool immutableClassContains(ClassDecl *outer, ClassDecl *target, bool topmostcall=true) { assert(outer->asType()->isImmutable() && target->asType()->isImmutable()); if (outer == target) return true; static set visited; if (topmostcall) visited.clear(); if (visited.count(outer)) return false; /* watch out for cycles */ visited.insert(outer); foriter (field, outer->environ()->allDecls( Decl::Field ), EnvironIter) { const FieldDecl &fieldDecl = static_cast< FieldDecl & >( *field ); if (fieldDecl.type()->isImmutable() && !fieldDecl.isStatic()) { if (immutableClassContains((ClassDecl *)fieldDecl.type()->decl(), target, false)) return true; } } return false; } bool FieldDeclNode::typecheck (TypeContext *ctx) { if (dtype()->isImmutable() && !(dtype()->isArrayType() || dtype()->isTitaniumBuiltinType()) && !((FieldDecl*)decl())->isStatic() && decl()->container()->asType()->isImmutable()) { if (immutableClassContains((ClassDecl*)dtype()->decl(), (ClassDecl*)decl()->container())) { error() << decl()->container()->errorName() << " is a recursive immutable type " << "(via " << decl()->errorName() << "), which is prohibited" << endl; } } TypeContext subCtx(ctx); subCtx.inStatic = (flags() & Static); subCtx.inInitializer = true; buildAllowedExceptions(&subCtx); DataDeclNode::typecheck(&subCtx); free_all(subCtx.allowedExceptions); return true; } bool ParameterNode::_typecheck (TypeContext *ctx) { dtype()->checkQualifiers(); return true; } bool StaticInitNode::typecheck (TypeContext *ctx) { TypeContext subCtx(ctx); subCtx.inStatic = true; subCtx.inInitializer = true; buildAllowedExceptions(&subCtx); TreeNode::typecheck(&subCtx); free_all(subCtx.allowedExceptions); return true; } bool InstanceInitNode::typecheck (TypeContext *ctx) { TypeContext subCtx(ctx); subCtx.inStatic = false; subCtx.inInitializer = true; buildAllowedExceptions(&subCtx); // TODO: should allow exceptions which appear in the throws of all constructors TreeNode::typecheck(&subCtx); free_all(subCtx.allowedExceptions); return true; } void TreeNode::buildAllowedExceptions(TypeContext *ctx) { ctx->allowedExceptions = cons(RuntimeExceptionDecl, cons(ErrorDecl)); } void TreeNode::buildAllowedExceptions(TypeContext *ctx, TreeNode *throws) { buildAllowedExceptions(ctx); foriter (thrown, throws->allChildren(), TreeNode::ChildIter) { if (isSubClass((*thrown)->decl(), ThrowableDecl)) { ClassDecl * const decl = static_cast((*thrown)->decl()); assert(decl->category() == Decl::Class); ctx->allowedExceptions = cons(decl, ctx->allowedExceptions); } else error() << (*thrown)->typeName() << "is not a subclass of Throwable" << endl; } } bool TreeNode::_typecheck(TypeContext *) { return true; } bool IncrDecrNode::_typecheck(TypeContext *ctx) { TypeNode *type0 = opnd0()->type(); assertAssignable(ctx, opnd0(), opnd0()); assertArithType(type0); return true; } bool UnaryArithNode::_typecheck(TypeContext *) { TypeNode *type0 = opnd0()->type(); assertArithType(type0); return true; } bool BinaryArithNode::_typecheck(TypeContext *) { TypeNode *type0 = opnd0()->type(); TypeNode *type1 = opnd1()->type(); assertArithType(type0); assertArithType(type1); return true; } bool BinaryArithAssignNode::_typecheck(TypeContext *ctx) { TypeNode *type0 = opnd0()->type(); TypeNode *type1 = opnd1()->type(); /* narrowing conversions *are* allowed here, so don't check that opnd1() assignable to opnd0() */ assertAssignable(ctx, opnd0(), opnd0()); assertArithType(type0); assertArithType(type1); return true; } bool ShiftNode::_typecheck(TypeContext *) { TypeNode *type0 = opnd0()->type(); TypeNode *type1 = opnd1()->type(); assertIntegralType(type0); assertIntegralType(type1); return true; } bool ShiftAssignNode::_typecheck(TypeContext *ctx) { TypeNode *type0 = opnd0()->type(); TypeNode *type1 = opnd1()->type(); /* narrowing conversions *are* allowed here, so don't check that opnd1() assignable to opnd0() */ assertAssignable(ctx, opnd0(), opnd0()); assertIntegralType(type0); assertIntegralType(type1); return true; } bool RelationNode::_typecheck(TypeContext *) { TypeNode *type0 = opnd0()->type(); TypeNode *type1 = opnd1()->type(); assertArithType(type0); assertArithType(type1); return true; } bool EqualityNode::_typecheck(TypeContext *) { TypeNode *type0 = opnd0()->type(); TypeNode *type1 = opnd1()->type(); if (type0->isArithType()) assertArithType(type1); else if (type0->kind() == BoolKind) assertBooleanType(type1); else { assert(type0->isReference() || type0->isImmutable()); assertComparable(type0, type1); } return true; } void ExprNode::bitwiseTypecheck() { TypeNode *type0 = opnd0()->type(); TypeNode *type1 = opnd1()->type(); if (type0->isIntegralType()) assertIntegralType(type1); else { assertBooleanType(type0); assertBooleanType(type1); } } bool BitwiseNode::_typecheck(TypeContext *) { bitwiseTypecheck(); return true; } bool BitwiseAssignNode::_typecheck(TypeContext *ctx) { /* narrowing conversions *are* allowed here, so don't check that opnd1() assignable to opnd0() */ assertAssignable(ctx, opnd0(), opnd0()); bitwiseTypecheck(); return true; } bool LogCondNode::_typecheck(TypeContext *) { TypeNode *type0 = opnd0()->type(); TypeNode *type1 = opnd1()->type(); assertBooleanType(type0); assertBooleanType(type1); return true; } bool FieldAccessNode::_typecheck( TypeContext *ctx ) { if (!(decl()->modifiers() & Static)) { const TypeNode &container = *accessedObjectType(); if (!container.isLocal()) { if (fieldChainEnd()) checkEmbeddedLocals(); container.checkShared( *this, "object", "examine" ); } } return true; } bool ArrayAccessNode::typecheck(TypeContext *ctx) { TypeNode *atype = array()->type(); TypeNode *itype = index()->type(); array()->typecheck(ctx); if (!atype->isArrayType()) { error() << "Type " << atype->typeName() << " is not an array" << endl; index()->args()->typecheck(ctx); } else { if (!atype->indexType()->isAssignableFromType(itype)) error() << itype->typeName() << " is not an index type for " << atype->typeName() << endl; index()->typecheck(ctx); } if (!atype->isLocal()) atype->checkShared( *this, "array", "examine" ); return true; } bool ThisConstructorCallNode::_typecheck(TypeContext *ctx) { checkThrownExceptions(ctx, this); return true; } bool SuperConstructorCallNode::_typecheck(TypeContext *ctx) { checkThrownExceptions(ctx, this); return true; } bool AllocateNode::_typecheck(TypeContext *ctx) { dtype()->checkQualifiers(); if (!region()->absent()) { if (!RegionDecl->asType()->isAssignableFromType(region()->type())) region()->error() << "new must be given a region as argument, got " << region()->type()->typeName() << endl; Decl *cdecl = dtype()->decl(); if (cdecl == SharedRegionDecl || cdecl == PrivateRegionDecl) error() << "a region cannot be allocated inside another region" << endl; } checkThrownExceptions(ctx, this); return true; } bool AllocateNode::typecheck( TypeContext *ctx ) { if (dtype()->kind() != ImmutableKind) dtype( dtype()->addModifiers( Local ) ); if (dtype()->sharing() == Polyshared) dtype()->error() << "allocation may optionally be nonshared, but cannot be polyshared" << endl; return TreeNode::typecheck( ctx ); } bool AllocateArrayNode::_typecheck(TypeContext *ctx) { #if 0 if (dtype()->isImmutable()) { /* Find default constructor */ defcons = /*??*/; checkThrownExceptions(ctx, /*??*/); } #endif if (!region()->absent()) if (!RegionDecl->asType()->isAssignableFromType(region()->type())) region()->error() << "new must be given a region as argument, got " << region()->type()->typeName() << endl; type()->checkQualifiers(); foriter (dimExpr, dimExprs()->allChildren(), TreeNode::ChildIter) { TypeNode *dtype = (*dimExpr)->expr()->type(); if (!(dtype->isRectDomainType() || arithPromoteType(dtype)->kind() == IntKind)) (*dimExpr)->error() << "new array dimension must be RectDomains or promoteable to int, got " << dtype->typeName() << endl; } return true; } bool AllocateArrayNode::typecheck( TypeContext *ctx ) { if (dimExprs()->arity()) { TreeNode &top = *dimExprs()->child( 0 ); top.flags( (Modifiers) (top.flags() | Local) ); if (modifiersToSharing( top.flags() ) == Polyshared) top.error() << "array allocation may optionally be nonshared, but cannot be polyshared" << endl; } TypeContext subCtx(ctx); subCtx.dtype = dtype(); return TreeNode::typecheck( &subCtx ); } bool ArrayInitNode::typecheck(TypeContext *ctx) { TypeNode *etype = ctx->dtype->isArrayType() ? ctx->dtype->elementType() : ctx->dtype; // as good a guess as any... // save the real type now that we have it if (ctx->dtype->isArrayType() || ctx->dtype->isTitaniumArrayType()) _type = ctx->dtype->newModifiers((Modifiers)(ctx->dtype->modifiers() | Local)); else // avoid extraneous error messages (due to dtype not being a reference) _type = ctx->dtype; TypeContext subCtx(ctx); subCtx.dtype = etype; foriter (initializer, initializers()->allChildren(), ChildIter) { if (!etype->isAssignableFromExpr(*initializer)) (*initializer)->error() << (*initializer)->declaredType()->typeName() << " is not assignable to array " << "element type " << etype->typeName() << endl; (*initializer)->typecheck(&subCtx); } return true; } bool ComplementNode::_typecheck(TypeContext *) { TypeNode *type0 = opnd0()->type(); assertIntegralType(type0); return true; } bool NotNode::_typecheck(TypeContext *) { TypeNode *type0 = opnd0()->type(); assertBooleanType(type0); return true; } bool DynamicTypeNode::_typecheck(TypeContext *) { TypeNode *dt = dtype(); dt->checkQualifiers(); assertCastable(opnd0()->type(), dt); return true; } bool PlusNode::_typecheck(TypeContext *) { TypeNode *type0 = opnd0()->type(); TypeNode *type1 = opnd1()->type(); if (!(type0->isStringType() || type1->isStringType())) { assertArithType(type0); assertArithType(type1); } return true; } bool InstanceOfNode::_typecheck(TypeContext *ctx) { assertReferenceType(dtype()); return DynamicTypeNode::_typecheck(ctx); } bool IfExprNode::_typecheck(TypeContext *) { TypeNode *thenType = thenOpnd()->type(); TypeNode *elseType = elseOpnd()->type(); assertBooleanType(condition()->type()); if (thenType->kind() == TypeNode::VoidKind) thenOpnd()->error() << "operand must yield a value" << endl; else if (elseType->kind() == TypeNode::VoidKind) elseOpnd()->error() << "operand must yield a value" << endl; else if (!type()->isAssignableFromExpr (elseOpnd()) || !type()->isAssignableFromExpr (thenOpnd())) error() << "incompatible types " << thenType->typeName() << " and " << elseType->typeName() << " to clauses of `? :' operator" << endl; return true; // return/throw are statements, so can't occur with ?: } bool AssignNode::_typecheck(TypeContext *ctx) { assertAssignable(ctx, opnd0(), opnd1()); return true; } bool PlusAssignNode::_typecheck(TypeContext *ctx) { TypeNode *type0 = opnd0()->type(); TypeNode *type1 = opnd1()->type(); /* narrowing conversions *are* allowed here, so don't check that opnd1() assignable to opnd0() */ assertAssignable(ctx, opnd0(), opnd0()); if (!type0->isStringType()) { assertArithType(type0); assertArithType(type1); } return true; } bool PointNode::_typecheck(TypeContext *) { foriter (expr, args()->allChildren(), TreeNode::ChildIter) assertPromoteableInt((*expr)->type()); return true; } bool DomainNode::_typecheck(TypeContext *) { TreeNode *a = args(); TypeNode *pointType; // Handle both domain syntaxes if (a->arity() == 1 && (pointType = a->child(0)->child(0)->type())->isPointType()) { Decl *pointDecl = pointType->decl(); foriter (point, a->child(0)->allChildren(), TreeNode::ChildIter) { TypeNode *ptype = (*point)->type(); if (!ptype->isPointType()) error() << "point type expected" << endl; else if (pointDecl != ptype->decl()) error() << "mismatched types " << pointDecl->asType()->typeName() << " and " << ptype->decl()->asType()->typeName() << " in domain expression" << endl; } } else { foriter (range, a->allChildren(), TreeNode::ChildIter) foriter (expr, (*range)->allChildren(), TreeNode::ChildIter) assertPromoteableInt((*expr)->type()); } return true; } /* Statements */ bool SwitchNode::typecheck(TypeContext *ctx) { expr()->typecheck(ctx); TypeNode *etype = expr()->type(); bool etypeok = arithPromoteType(etype)->kind() == IntKind; if (!etypeok) expr()->error() << "switch expression type " << etype->typeName() << " is not promoteable to int" << endl; bool terminates = false; bool hasDefault = false; foriter (block, switchBlocks()->allChildren(), ChildIter) { (*block)->cases()->typecheck(ctx); terminates |= (*block)->stmts()->typecheck(ctx); foriter (acase, (*block)->cases()->allChildren(), ChildIter) { TreeNode *cexpr = (*acase)->expr(); if (cexpr->absent()) { if (hasDefault) (*acase)->error() << "duplicate default case" << endl; hasDefault = true; } else if (!cexpr->constantType()) cexpr->error() << "case expression is not a constant" << endl; else if (!etype->isAssignableFromExpr(cexpr)) cexpr->error() << "case type " << cexpr->type()->typeName() << " is incompatible with " << etype->typeName() << endl; else if (etypeok) { int64 value = cexpr->literal().intValue(); // Check for duplicate case labels (inefficient) foriter (prevblock, switchBlocks()->allChildren(), ChildIter) { foriter (prevcase, (*prevblock)->cases()->allChildren(), ChildIter) { if (*prevcase == *acase) break; TreeNode *prevexpr = (*prevcase)->expr(); if (!prevexpr->absent() && prevexpr->constantType() && etype->isAssignableFromExpr(prevexpr) && value == prevexpr->literal().intValue()) { cexpr->error() << "duplicate case label" << endl; prevexpr->error() << "this is the previous label" << endl; } } if (*prevblock == *block) break; } } } } return !hasDefault | terminates; } bool WhileNode::typecheck(TypeContext *ctx) { const bool terminates = test()->typecheck(ctx); assertBooleanType(test()->type()); stmt()->typecheck(ctx); return terminates; } bool DoNode::typecheck(TypeContext *ctx) { const bool terminates = stmt()->typecheck(ctx); test()->typecheck(ctx); assertBooleanType(test()->type()); return terminates; } bool ForNode::typecheck(TypeContext *ctx) { init()->typecheck(ctx); test()->typecheck(ctx); update()->typecheck(ctx); stmt()->typecheck(ctx); if (!test()->absent()) { assertBooleanType(test()->type()); return true; } else return false; } extern bool rewroteConstructorReturn; bool ReturnNode::_typecheck (TypeContext *ctx) { if (ctx->currentMethod) { TypeNode *rtype = ctx->currentMethod->returnType(); if (rtype->kind() == VoidKind) { if (!expr()->absent()) error() << "no return expression allowed in void " << ctx->currentMethod->decl()->errorName() << endl; } else if (expr()->absent()) error() << "return expression of type " << rtype->typeName() << " expected" << endl; else if (!rtype->isAssignableFromExpr(expr())) error() << "expected type " << rtype->typeName() << " in return, got " << expr()->declaredType()->typeName() << endl; } else if (ctx->currentConstructor) { if (!expr()->absent() && !rewroteConstructorReturn) error() << "no return expression allowed in constructors" << endl; } else error() << "return not in method or constructor" << endl; return false; } bool IfStmtNode::typecheck(TypeContext *ctx) { assertBooleanType(condition()->type()); condition()->typecheck(ctx); return thenPart()->typecheck(ctx) | elsePart()->typecheck(ctx); } bool AssertNode::typecheck(TypeContext *ctx) { assertBooleanType(condition()->type()); condition()->typecheck(ctx); if (!value()->absent() && value()->type()->kind() == VoidKind) error() << "second operand to assert may not be void" << endl; value()->typecheck(ctx); return true; /* only an Error is possible */ } bool ThrowNode::_typecheck(TypeContext *ctx) { if (!canThrow(ctx, expr()->type())) error() << "throw of undeclared exception " << expr()->type()->typeName() << endl; return false; } bool SynchronizedNode::_typecheck(TypeContext *) { if (!expr()->absent()) { const TypeNode &type = *expr()->type(); if (!type.isReference()) expr()->error() << "synchronized expression type " << type.typeName() << " is not a reference" << endl; if (!type.isLocal()) type.checkShared( *expr(), "object", "synchronize with" ); } return true; } bool MethodCallNode::_typecheck(TypeContext *ctx) { checkThrownExceptions(ctx, this); Decl *d = method()->simpName()->decl(); TypeNode *classtype = d->container()->asType(); if ((classtype->isTitaniumArrayType() || classtype->isRectDomainType()) && *d->name() == "slice" && classtype->tiArity() == 1) error() << "slice() may not be called on 1d RectDomains or 1d TiArrays" << endl; if (classtype->isDomainOrRectDomainType() && *d->name() == "isNull") warning("deprecated-isnull") << "RD.isNull() is deprecated - use RD.isEmpty() instead" << endl; return true; } bool MethodCallAssignNode::_typecheck(TypeContext *ctx) { checkThrownExceptions(ctx, this); TreeNode *lhs; if (isRewrittenRHSOpOverload()) lhs = args()->child(0); else lhs = method()->object(); /* check lhs is an assignable lval */ lhs->checkAssignable(this, false, false, ctx->currentClass); if (!lhs->type()->isCastableFrom(method()->decl()->type()->returnType())) error() << "bad assignment: result of " << method()->decl()->errorName() << " cannot be cast to " << lhs->type()->typeName() << endl; return true; } bool TryStmtNode::typecheck(TypeContext *ctx) { TypeContext subCtx(ctx); // Can throw any of the caught exceptions foriter (acatch, catches()->allChildren(), ChildIter) { TypeNode &etype = *(*acatch)->param()->dtype(); if (etype.isReference() && isSubClass(etype.decl(), ThrowableDecl)) { ClassDecl * const edecl = static_cast< ClassDecl * >(etype.decl()); assert(edecl->category() == Decl::Class); subCtx.allowedExceptions = cons(edecl, subCtx.allowedExceptions); } else (*acatch)->error() << "catch type " << etype.typeName() << " is not a subclass of Throwable" << endl; } bool blockTerminates = block()->typecheck(&subCtx); // Free exception stuff llist *exceptions = subCtx.allowedExceptions; while (exceptions != ctx->allowedExceptions) exceptions = exceptions->free(); // To be correct, the boolean that indicates 'may terminate' should // be replaced by a list of possible termination causes, because // the catch clauses may not be capturing *any* of the possible // exceptions - but this does not seem worth the effort (javac // apparently uses the same test...) bool caught = false; foriter (acatch, catches()->allChildren(), ChildIter) caught = caught | (*acatch)->typecheck(ctx); bool finallyTerminates = finally()->typecheck(ctx); return (blockTerminates | caught) & finallyTerminates; } bool ForEachStmtNode::typecheck(TypeContext *ctx) { vars()->typecheck(ctx); stmt()->typecheck(ctx); TreeNode *variables = vars(); if (variables->arity() == 1) assertDomainOrRectDomainType(variables->child(0)->initExpr()->type()); else { Decl *rectDomainDecl = NULL; foriter (var, variables->allChildren(), ChildIter) { TypeNode *rtype = (*var)->initExpr()->type(); if (!rtype->isRectDomainType()) (*var)->error() << "Multi-variable foreach expects a RectDomain, got " << rtype->typeName() << endl; else if (!rectDomainDecl) rectDomainDecl = rtype->decl(); else if (rectDomainDecl != rtype->decl()) (*var)->error() << "mismatched types " << rectDomainDecl->asType()->typeName() << " and " << rtype->decl()->asType()->typeName() << " in foreach" << endl; } } foriter (var, variables->allChildren(), ChildIter) { if (!(*var)->dtypeopt()->absent()) { // check optional Point type declaration TypeNode *rtype = (*var)->initExpr()->type(); TypeNode *ptype = (TypeNode*)(*var)->dtypeopt(); assert(ptype->isPointType()); if (ptype->tiArity() != rtype->tiArity()) (*var)->error() << "mismatched foreach arities: Point<" << ptype->tiArity() << "> should be Point<" << rtype->tiArity() << ">" << endl; } } return true; } bool PartitionStmtNode::typecheck(TypeContext *ctx) { cases()->typecheck(ctx); foriter (partition, cases()->allChildren(), TreeNode::ChildIter) (*partition)->assertBooleanType((*partition)->condition()->type()); // ??: we need some restrictions on throw/return/break/continue // within a partition - think about it return true; } /* Typechecking of types ... */ void TypeNode::checktiArity(const char *name) { if (!tiArity()) error() << name << " arity must be a constant >= 1" << endl; if (tiArity() > MAX_TIARITY) error() << "Ti built-in types (" << name << ") above " << MAX_TIARITY << "-d not supported in this compiler installation." << endl; } bool PointTypeNode::_typecheck (TypeContext *) { checktiArity("Point"); return true; } bool DomainTypeNode::_typecheck (TypeContext *) { checktiArity("Domain"); return true; } bool RectDomainTypeNode::_typecheck (TypeContext *) { checktiArity("RectDomain"); return true; } bool TitaniumArrayTypeNode::_typecheck (TypeContext *ctx) { checktiArity("Titanium array"); return ArrayTypeNode::_typecheck( ctx ); } bool BroadcastNode::_typecheck(TypeContext *) { TypeNode * const procType = proc()->type(); const TypeNode &exprType = *expr()->type(); if (arithPromoteType(procType)->kind() != IntKind) proc()->error() << "broadcast process number expects an integer, got " << procType->typeName() << endl; if (sharingEnforcement == Early && exprType.sharing() != Shared) expr()->error() << "cannot broadcast " << exprType.sharing() << " data" << endl; checkEmbeddedLocals(); return true; }