/* * Copyright Red Hat Inc. and/or its affiliates and other contributors * as indicated by the authors tag. All rights reserved. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU General Public License version 2. * * This particular file is subject to the "Classpath" exception as provided in the * LICENSE file that accompanied this code. * * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License, * along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package com.redhat.ceylon.compiler.java.codegen; import static com.redhat.ceylon.compiler.typechecker.analyzer.AnalyzerUtil.isIndirectInvocation; import static com.redhat.ceylon.compiler.typechecker.tree.TreeUtil.unwrapExpressionUntilTerm; import java.util.Stack; import com.redhat.ceylon.common.BooleanUtil; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.compiler.typechecker.tree.Tree.ArithmeticAssignmentOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.ArithmeticOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.AssignOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.BaseMemberExpression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Bound; import com.redhat.ceylon.compiler.typechecker.tree.Tree.CharLiteral; import com.redhat.ceylon.compiler.typechecker.tree.Tree.CompareOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.ComparisonOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.EqualityOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Exists; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Expression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.FloatLiteral; import com.redhat.ceylon.compiler.typechecker.tree.Tree.IdenticalOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.InOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.IndexExpression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.InvocationExpression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.IsOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.LogicalAssignmentOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.LogicalOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.MemberOrTypeExpression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.NaturalLiteral; import com.redhat.ceylon.compiler.typechecker.tree.Tree.NegativeOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Nonempty; import com.redhat.ceylon.compiler.typechecker.tree.Tree.NotOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.ParameterizedExpression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.PositionalArgument; import com.redhat.ceylon.compiler.typechecker.tree.Tree.PositiveOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.PostfixOperatorExpression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.PrefixOperatorExpression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.QualifiedMemberExpression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.StaticMemberOrTypeExpression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.StringLiteral; import com.redhat.ceylon.compiler.typechecker.tree.Tree.StringTemplate; import com.redhat.ceylon.compiler.typechecker.tree.Tree.SwitchCaseList; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Term; import com.redhat.ceylon.compiler.typechecker.tree.Tree.TypeArguments; import com.redhat.ceylon.compiler.typechecker.tree.Tree.WithinOp; import com.redhat.ceylon.compiler.typechecker.tree.Visitor; import com.redhat.ceylon.model.typechecker.model.Class; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.Function; import com.redhat.ceylon.model.typechecker.model.Interface; import com.redhat.ceylon.model.typechecker.model.Reference; import com.redhat.ceylon.model.typechecker.model.Scope; import com.redhat.ceylon.model.typechecker.model.Type; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.TypeParameter; import com.redhat.ceylon.model.typechecker.model.TypedDeclaration; public abstract class BoxingVisitor extends Visitor { protected abstract boolean isBooleanTrue(Declaration decl); protected abstract boolean isBooleanFalse(Declaration decl); protected abstract boolean hasErasure(Type type); protected abstract boolean hasErasedTypeParameters(Reference producedReference); protected abstract boolean willEraseToObject(Type type); protected abstract boolean willEraseToSequence(Type type); protected abstract boolean isTypeParameter(Type type); protected abstract boolean isRaw(Type type); protected abstract boolean needsRawCastForMixinSuperCall(TypeDeclaration declaration, Type type); private Stack<Boolean> nextPreferredExpressionBoxings = null; private Boolean preferredExpressionBoxing = null; @Override public void visit(BaseMemberExpression that) { super.visit(that); // handle errors gracefully if(that.getDeclaration() == null) return; TypedDeclaration decl = (TypedDeclaration) that.getDeclaration(); if(CodegenUtil.isUnBoxed(decl) // special cases for true/false || isBooleanTrue(decl) || isBooleanFalse(decl)) CodegenUtil.markUnBoxed(that); if(CodegenUtil.isRaw(decl)) CodegenUtil.markRaw(that); if(CodegenUtil.hasTypeErased(decl)) CodegenUtil.markTypeErased(that); if(CodegenUtil.hasUntrustedType(decl)) CodegenUtil.markUntrustedType(that); } @Override public void visit(QualifiedMemberExpression that) { super.visit(that); // handle errors gracefully if(that.getDeclaration() == null) return; if(that.getMemberOperator() instanceof Tree.SafeMemberOp){ TypedDeclaration decl = (TypedDeclaration) that.getDeclaration(); if(CodegenUtil.isRaw(decl)) CodegenUtil.markRaw(that); if(CodegenUtil.hasTypeErased(decl)) CodegenUtil.markTypeErased(that); if(CodegenUtil.hasUntrustedType(decl) || hasTypeParameterWithConstraintsOutsideScope(decl.getType(), that.getScope())) CodegenUtil.markUntrustedType(that); // we must be boxed, since safe member op "?." returns an optional type //return; } else if ( that.getMemberOperator() instanceof Tree.MemberOp && Decl.isValueTypeDecl(that.getPrimary()) && CodegenUtil.isUnBoxed(that.getPrimary())) { // it's unboxed if it's an unboxable type or it's declared void if (Decl.isValueTypeDecl((TypedDeclaration)that.getDeclaration()) || (that.getDeclaration() instanceof Function && ((Function)that.getDeclaration()).isDeclaredVoid())) CodegenUtil.markUnBoxed(that); if(CodegenUtil.isRaw((TypedDeclaration) that.getDeclaration())) CodegenUtil.markRaw(that); if(CodegenUtil.hasTypeErased((TypedDeclaration) that.getDeclaration())) CodegenUtil.markTypeErased(that); } else { propagateFromDeclaration(that, (TypedDeclaration)that.getDeclaration()); } // special case for spread op, because even if the primary is erased (ex: <T> T|String), its application may not // be (ex: <String>), and in that case we will generate a proper Sequential<String> which is not raw at all if(that.getMemberOperator() instanceof Tree.SpreadOp){ // find the return element type Type elementType = that.getTarget().getType(); CodegenUtil.markTypeErased(that, hasErasure(elementType)); } if(ExpressionTransformer.isSuperOrSuperOf(that.getPrimary())){ // if the target is an interface whose type arguments have been turned to raw, make this expression // as erased Reference target = that.getTarget(); if(target != null && target.getQualifyingType() != null && target.getQualifyingType().getDeclaration() instanceof Interface){ if(isRaw(target.getQualifyingType())){ CodegenUtil.markTypeErased(that); } // See note in ClassTransformer.makeDelegateToCompanion for a similar test else{ TypeDeclaration declaration = target.getQualifyingType().getDeclaration(); if(needsRawCastForMixinSuperCall(declaration, target.getType())) CodegenUtil.markTypeErased(that); } } } Type primaryType; if (that.getPrimary() instanceof Tree.Package || that.getTarget() == null) { primaryType = that.getPrimary().getTypeModel(); } else { primaryType = that.getTarget().getQualifyingType(); } if(primaryType != null && (isRaw(primaryType) || willEraseToSequence(primaryType)) && that.getTarget() != null && that.getTarget().getDeclaration() instanceof TypedDeclaration && CodegenUtil.containsTypeParameter(((TypedDeclaration)that.getTarget().getDeclaration()).getType())){ CodegenUtil.markTypeErased(that); } if (isRaw(primaryType) && !that.getTypeModel().getDeclaration().getTypeParameters().isEmpty()) { CodegenUtil.markRaw(that); } } @Override public void visit(Expression that) { Stack<Boolean> npebs = setPEB(); super.visit(that); resetPEB(npebs); Term term = that.getTerm(); propagateFromTerm(that, term); // Special case where a method reference surrounded // by an expression will be turned into a Callable // which will need to be marked boxed if (term instanceof MemberOrTypeExpression) { Tree.MemberOrTypeExpression expr = (Tree.MemberOrTypeExpression)term; if (expr.getDeclaration() instanceof Function) { that.setUnboxed(false); } } } @Override public void visit(InvocationExpression that) { super.visit(that); if (isIndirectInvocation(that) && !Decl.isJavaStaticOrInterfacePrimary(that.getPrimary())) { // if the Callable is raw the invocation will be erased if(that.getPrimary().getTypeModel() != null && isRaw(that.getPrimary().getTypeModel())) CodegenUtil.markTypeErased(that); // These are always boxed return; } if(isByteLiteral(that)) CodegenUtil.markUnBoxed(that); else propagateFromTerm(that, that.getPrimary()); // Specifically for method invocations we check if the return type is // a type parameter and if so // * if any of the type arguments is erased, or // * if the invocation itself has a raw type // then we mark the expression itself as erased as well if (that.getPrimary() instanceof StaticMemberOrTypeExpression) { StaticMemberOrTypeExpression expr = (StaticMemberOrTypeExpression)that.getPrimary(); if (expr.getDeclaration() instanceof Function) { Function mth = (Function)expr.getDeclaration(); if (isTypeParameter(mth.getType()) && (hasErasedTypeParameter(expr.getTarget(), expr.getTypeArguments()) || CodegenUtil.isRaw(that))) { CodegenUtil.markTypeErased(that); CodegenUtil.markUntrustedType(that); } } } } private boolean isByteLiteral(Tree.InvocationExpression ce) { // same test as in ExpressionTransformer.checkForByteLiterals if(ce.getPrimary() instanceof Tree.BaseTypeExpression && ce.getPositionalArgumentList() != null){ java.util.List<Tree.PositionalArgument> positionalArguments = ce.getPositionalArgumentList().getPositionalArguments(); if(positionalArguments.size() == 1){ PositionalArgument argument = positionalArguments.get(0); if(argument instanceof Tree.ListedArgument && ((Tree.ListedArgument) argument).getExpression() != null){ Term term = ((Tree.ListedArgument)argument).getExpression().getTerm(); if(term instanceof Tree.NegativeOp){ term = ((Tree.NegativeOp) term).getTerm(); } if(term instanceof Tree.NaturalLiteral){ Declaration decl = ((Tree.BaseTypeExpression)ce.getPrimary()).getDeclaration(); if(decl instanceof Class){ String name = decl.getQualifiedNameString(); if(name.equals("ceylon.language::Byte")){ return true; } } } } } } return false; } private boolean hasErasedTypeParameter(Reference producedReference, TypeArguments typeArguments) { if (typeArguments != null && typeArguments.getTypeModels() != null){ for (Type arg : typeArguments.getTypeModels()) { if (hasErasure(arg) /*|| willEraseToSequential(param.getType())*/) { return true; } } } return hasErasedTypeParameters(producedReference); } @Override public void visit(ParameterizedExpression that) { super.visit(that); propagateFromTerm(that, that.getPrimary()); } @Override public void visit(IndexExpression that) { super.visit(that); // we need to propagate from the underlying method call (item/span) if(that.getPrimary() == null || that.getPrimary().getTypeModel() == null) return; Type lhsModel = that.getPrimary().getTypeModel(); if(lhsModel.getDeclaration() == null) return; String methodName = that.getElementOrRange() instanceof Tree.Element ? "get" : "span"; // find the method from its declaration TypedDeclaration member = (TypedDeclaration) lhsModel.getDeclaration().getMember(methodName, null, false); if(member == null) return; propagateFromDeclaration(that, member); } @Override public void visit(NaturalLiteral that) { super.visit(that); CodegenUtil.markUnBoxed(that); } @Override public void visit(FloatLiteral that) { super.visit(that); CodegenUtil.markUnBoxed(that); } @Override public void visit(StringLiteral that) { super.visit(that); CodegenUtil.markUnBoxed(that); } @Override public void visit(CharLiteral that) { super.visit(that); CodegenUtil.markUnBoxed(that); } @Override public void visit(StringTemplate that) { super.visit(that); // for now we always produce an unboxed string in ExpressionTransformer CodegenUtil.markUnBoxed(that); } @Override public void visit(PositiveOp that) { super.visit(that); // we are unboxed if our term is propagateBoxingFromTerm(that, that.getTerm()); } @Override public void visit(NegativeOp that) { super.visit(that); // we are unboxed if our term is propagateBoxingFromTerm(that, that.getTerm()); } @Override public void visit(ArithmeticOp that) { super.visit(that); // can't optimise the ** operator in Java // we are unboxed if any term is if(that.getLeftTerm().getUnboxed() || that.getRightTerm().getUnboxed() || BooleanUtil.isFalse(preferredExpressionBoxing)) CodegenUtil.markUnBoxed(that); } @Override public void visit(ArithmeticAssignmentOp that) { super.visit(that); // we are unboxed if both terms are if(that.getLeftTerm().getUnboxed() && that.getRightTerm().getUnboxed()) CodegenUtil.markUnBoxed(that); } @Override public void visit(PostfixOperatorExpression that) { super.visit(that); // we are unboxed if the term is propagateBoxingFromTerm(that, that.getTerm()); } @Override public void visit(PrefixOperatorExpression that) { super.visit(that); // we are unboxed if the term is propagateBoxingFromTerm(that, that.getTerm()); } @Override public void visit(NotOp that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } @Override public void visit(LogicalOp that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } @Override public void visit(AssignOp that) { super.visit(that); propagateFromTerm(that, that.getLeftTerm()); } @Override public void visit(LogicalAssignmentOp that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } @Override public void visit(EqualityOp that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } @Override public void visit(IdenticalOp that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } @Override public void visit(ComparisonOp that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } public void visit(CompareOp that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } @Override public void visit(WithinOp that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } @Override public void visit(Bound that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } @Override public void visit(InOp that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } @Override public void visit(IsOp that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } @Override public void visit(Nonempty that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } @Override public void visit(Exists that) { super.visit(that); // this is not conditional CodegenUtil.markUnBoxed(that); } private void propagateFromDeclaration(Term that, TypedDeclaration decl) { if(CodegenUtil.isUnBoxed(decl)) CodegenUtil.markUnBoxed(that); if(CodegenUtil.isRaw(decl)) CodegenUtil.markRaw(that); if(CodegenUtil.hasTypeErased(decl)) CodegenUtil.markTypeErased(that); if(CodegenUtil.hasUntrustedType(decl) || hasTypeParameterWithConstraintsOutsideScope(decl.getType(), that.getScope())) CodegenUtil.markUntrustedType(that); } /** * Only for things that can't produce raw/erased types, such as math operators because * those are always casted */ private void propagateBoxingFromTerm(Term that, Term term) { if(CodegenUtil.isUnBoxed(term)) CodegenUtil.markUnBoxed(that); } private void propagateFromTerm(Term that, Term term) { if(CodegenUtil.isUnBoxed(term)) CodegenUtil.markUnBoxed(that); if(CodegenUtil.isRaw(term)) CodegenUtil.markRaw(that); if(CodegenUtil.hasTypeErased(term)) CodegenUtil.markTypeErased(that); if(CodegenUtil.hasUntrustedType(term)) CodegenUtil.markUntrustedType(that); } private boolean hasTypeParameterWithConstraintsOutsideScope(Type type, Scope scope) { return hasTypeParameterWithConstraintsOutsideScopeResolved(type != null ? type.resolveAliases() : null, scope); } private boolean hasTypeParameterWithConstraintsOutsideScopeResolved(Type type, Scope scope) { if(type == null) return false; if(type.isUnion()){ java.util.List<Type> caseTypes = type.getCaseTypes(); for(Type pt : caseTypes){ if(hasTypeParameterWithConstraintsOutsideScopeResolved(pt, scope)) return true; } return false; } if(type.isIntersection()){ java.util.List<Type> satisfiedTypes = type.getSatisfiedTypes(); for(Type pt : satisfiedTypes){ if(hasTypeParameterWithConstraintsOutsideScopeResolved(pt, scope)) return true; } return false; } TypeDeclaration declaration = type.getDeclaration(); if(declaration == null) return false; if(type.isTypeParameter()){ // only look at it if it is defined outside our scope Scope typeParameterScope = declaration.getContainer(); while(scope != null){ if (Decl.equalScopes(scope, typeParameterScope)) return false; scope = scope.getContainer(); } TypeParameter tp = (TypeParameter) declaration; Boolean nonErasedBounds = tp.hasNonErasedBounds(); if(nonErasedBounds == null) visitTypeParameter(tp); return nonErasedBounds != null ? nonErasedBounds.booleanValue() : false; } // now check its type parameters for(Type pt : type.getTypeArgumentList()){ if(hasTypeParameterWithConstraintsOutsideScopeResolved(pt, scope)) return true; } // no problem here return false; } private void visitTypeParameter(TypeParameter typeParameter) { if(typeParameter.hasNonErasedBounds() != null) return; for(Type pt : typeParameter.getSatisfiedTypes()){ if(!willEraseToObject(pt)){ typeParameter.setNonErasedBounds(true); return; } } typeParameter.setNonErasedBounds(false); return; } @Override public void visit(Tree.TypeLiteral that) { super.visit(that); if (!that.getWantsDeclaration()) { CodegenUtil.markRaw(that); } } @Override public void visit(Tree.IfExpression that){ super.visit(that); if(that.getIfClause() == null || that.getElseClause() == null) return; Tree.Expression ifExpr = that.getIfClause().getExpression(); Tree.Expression elseExpr = that.getElseClause().getExpression(); if(ifExpr == null || elseExpr == null) return; if(CodegenUtil.isUnBoxed(ifExpr) && CodegenUtil.isUnBoxed(elseExpr) && !willEraseToObject(that.getUnit().denotableType(that.getTypeModel()))) CodegenUtil.markUnBoxed(that); if (that.getTypeModel().isExactly(that.getUnit().getNullValueDeclaration().getType())) { CodegenUtil.markTypeErased(that); } // An If expression can never be raw, type erased or untrusted because // it uses a Let with a new variable declaration, so the rawness, // erasedness and untrustedness of its branches cannot propagate further // up the tree. } @Override public void visit(Tree.SwitchExpression that){ super.visit(that); SwitchCaseList caseList = that.getSwitchCaseList(); if(caseList == null || caseList.getCaseClauses() == null) return; boolean unboxed = true; for(Tree.CaseClause caseClause : caseList.getCaseClauses()){ Expression expr = caseClause.getExpression(); if(expr == null) return; // a single boxed one makes the whole switch boxed if(!CodegenUtil.isUnBoxed(expr)) unboxed = false; // A Switch expression can never be raw, type erased or untrusted because // it uses a Let with a new variable declaration, so the rawness, // erasedness and untrustedness of its branches cannot propagate further // up the tree. } if(caseList.getElseClause() != null){ Expression expr = caseList.getElseClause().getExpression(); if(expr == null) return; // a single boxed one makes the whole switch boxed if(!CodegenUtil.isUnBoxed(expr)) unboxed = false; // see comment about about why we don't propagate rawness etc here. } if(unboxed && !willEraseToObject(that.getUnit().denotableType(that.getTypeModel()))) CodegenUtil.markUnBoxed(that); if (that.getTypeModel().isExactly(that.getUnit().getNullValueDeclaration().getType())) { CodegenUtil.markTypeErased(that); } } @Override public void visit(Tree.LetExpression that) { super.visit(that); if(that.getLetClause() == null || that.getLetClause().getExpression() == null) return; propagateFromTerm(that, that.getLetClause().getExpression()); } @Override public void visit(Tree.DefaultOp that) { super.visit(that); if (unwrapExpressionUntilTerm(that.getLeftTerm()) instanceof Tree.ThenOp) { Tree.ThenOp then = (Tree.ThenOp)unwrapExpressionUntilTerm(that.getLeftTerm()); if (CodegenUtil.isUnBoxed(that.getRightTerm()) && CodegenUtil.isUnBoxed(then.getRightTerm()) && !willEraseToObject(that.getUnit().denotableType(that.getTypeModel()))) { CodegenUtil.markUnBoxed(that); } } } // The following methods are only used to set the "Preferred Expression Boxing" @Override public void visit(Tree.Parameter that) { if (that.getParameterModel().getModel() == null) return; Boolean currentPEB = setNextPEBs(that.getParameterModel().getModel().getUnboxed()); super.visit(that); preferredExpressionBoxing = currentPEB; } @Override public void visit(Tree.ElementRange that) { Boolean currentPEB = setNextPEBs(false, true); super.visit(that); preferredExpressionBoxing = currentPEB; } // Set the stack for to preferred boxing that shall be used // for the next N occurrences of Expression as the child // nodes of the current node (where N is the number of // booleans passed to this function) private Boolean setNextPEBs(Boolean... boxings) { nextPreferredExpressionBoxings = new Stack<Boolean>(); for (Boolean b : boxings) { nextPreferredExpressionBoxings.push(b); } return preferredExpressionBoxing; } // Set the next preferred boxing to be the currently active one private Stack<Boolean> setPEB() { Stack<Boolean> npebs = nextPreferredExpressionBoxings; preferredExpressionBoxing = (npebs != null && !npebs.isEmpty()) ? npebs.pop() : null; nextPreferredExpressionBoxings = null; return npebs; } // Unset the currently active preferred boxing and reset the // list of next boxings to what's left of the list private void resetPEB(Stack<Boolean> npebs) { preferredExpressionBoxing = null; nextPreferredExpressionBoxings = npebs; } }