com.redhat.ceylon.compiler.java.codegen.BoxingDeclarationVisitor Maven / Gradle / Ivy
/*
* 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 java.util.HashMap;
import java.util.Map;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.AnyAttribute;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.AnyMethod;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.AttributeArgument;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.AttributeDeclaration;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.AttributeSetterDefinition;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.ForComprehensionClause;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.ForIterator;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.FunctionArgument;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.FunctionalParameterDeclaration;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.LazySpecifierExpression;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.MethodArgument;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.PatternIterator;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.SpecifierStatement;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.StatementOrArgument;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.ValueIterator;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.Variable;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;
import com.redhat.ceylon.model.loader.JvmBackendUtil;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Functional;
import com.redhat.ceylon.model.typechecker.model.Function;
import com.redhat.ceylon.model.typechecker.model.FunctionOrValue;
import com.redhat.ceylon.model.typechecker.model.Type;
import com.redhat.ceylon.model.typechecker.model.Setter;
import com.redhat.ceylon.model.typechecker.model.TypeParameter;
import com.redhat.ceylon.model.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.model.typechecker.model.Value;
public abstract class BoxingDeclarationVisitor extends Visitor {
protected abstract boolean isCeylonBasicType(Type type);
protected abstract boolean isNull(Type type);
protected abstract boolean isObject(Type type);
protected abstract boolean isCallable(Type type);
protected abstract boolean hasErasure(Type type);
protected abstract boolean willEraseToObject(Type type);
protected abstract boolean isRaw(Type type);
protected abstract boolean isWideningTypedDeclaration(TypedDeclaration typedDeclaration);
protected abstract boolean hasSubstitutedBounds(Type type);
/**
* This is used to keep track of some optimisations we do, such as inlining the following shortcuts:
* class X() extends T(){ m = function() => e; } and we need to be able to map back the lambda with
* the method it specifies, for boxing and all
*/
private Map optimisedMethodSpecifiersToMethods = new HashMap();
// This is mostly just for testing purposes. Using -Dceylon.compiler.forceBoxedLocals=true
// on the command line you can force all locals to be boxed (instead of the default unboxed)
private static final boolean forceBoxedLocals = Boolean.getBoolean("ceylon.compiler.forceBoxedLocals");
@Override
public void visit(FunctionArgument that) {
super.visit(that);
that.getDeclarationModel().setUnboxed(false);
}
@Override
public void visit(MethodArgument that) {
super.visit(that);
that.getDeclarationModel().setUnboxed(false);
}
@Override
public void visit(AnyMethod that) {
super.visit(that);
visitMethod(that.getDeclarationModel(), that);
}
private void visitMethod(Function method, Node that) {
boxMethod(method, that);
rawTypedDeclaration(method);
setErasureState(method);
}
@Override
public void visit(FunctionalParameterDeclaration that) {
if (Strategy.createMethod(that.getParameterModel())) {
// Box the functional parameter as if it were a method
visitMethod((Function)that.getParameterModel().getModel(), that);
// Visit the parameters of the functional parameter
that.visitChildren(this);
} else {
super.visit(that);
}
}
private void setErasureState(TypedDeclaration decl) {
// deal with invalid input
if(decl == null)
return;
Type type = decl.getType();
if(type != null){
if(hasErasure(type) || hasSubstitutedBounds(type) || type.isTypeConstructor()){
decl.setTypeErased(true);
}
if(decl.isActual()
&& decl.getContainer() instanceof ClassOrInterface
// make sure we did not lose type information due to non-widening
&& isWideningTypedDeclaration(decl)){
// widening means not trusting the type, otherwise we end up thinking that the type is
// something it's not and regular erasure rules don't apply there
decl.setUntrustedType(true);
decl.setTypeErased(true);
}
}
}
private void rawTypedDeclaration(TypedDeclaration decl) {
// deal with invalid input
if(decl == null)
return;
Type type = decl.getType();
if(type != null){
if(isRaw(type))
type.setRaw(true);
}
}
private void boxMethod(Function method, Node that) {
// deal with invalid input
if(method == null)
return;
Declaration refined = CodegenUtil.getTopmostRefinedDeclaration(method, optimisedMethodSpecifiersToMethods);
// deal with invalid input
if(refined == null
|| (!(refined instanceof Function)))
return;
TypedDeclaration refinedMethod = (TypedDeclaration)refined;
if (method.getName() != null) {
// A Callable, which never have primitive parameters
setBoxingState(method, refinedMethod, that);
} else {
// Anonymous methods are always boxed
method.setUnboxed(false);
}
}
private void setBoxingState(TypedDeclaration declaration, TypedDeclaration refinedDeclaration, Node that) {
Type type = declaration.getType();
if(type == null){
// an error must have already been reported
return;
}
// fetch the real refined declaration if required
if (Decl.equal(declaration, refinedDeclaration)
&& declaration instanceof FunctionOrValue
&& ((FunctionOrValue)declaration).isParameter()
&& declaration.getContainer() instanceof Class){
// maybe it is really inherited from a field?
FunctionOrValue methodOrValueForParam = (FunctionOrValue)declaration;
if(methodOrValueForParam != null){
// make sure we get the refined version of that member
refinedDeclaration = (TypedDeclaration) methodOrValueForParam.getRefinedDeclaration();
}
}
// inherit underlying type constraints
if(!Decl.equal(refinedDeclaration, declaration) && type.getUnderlyingType() == null
&& refinedDeclaration.getType() != null)
type.setUnderlyingType(refinedDeclaration.getType().getUnderlyingType());
// abort if our boxing state has already been set
if(declaration.getUnboxed() != null)
return;
// functional parameter return values are always boxed if we're not creating a method for them
if(declaration instanceof Function
&& ((Function)declaration).isParameter()
&& !JvmBackendUtil.createMethod((Function)declaration)){
declaration.setUnboxed(false);
return;
}
if (!Decl.equal(refinedDeclaration, declaration)) {
// make sure refined declarations have already been set
if(refinedDeclaration.getUnboxed() == null)
setBoxingState(refinedDeclaration, refinedDeclaration, that);
// inherit
declaration.setUnboxed(refinedDeclaration.getUnboxed());
} else if (declaration instanceof Function
&& CodegenUtil.isVoid(declaration.getType())
&& Strategy.useBoxedVoid((Function)declaration)
&& !(refinedDeclaration.getTypeDeclaration() instanceof TypeParameter)
&& !CodegenUtil.isContainerFunctionalParameter(refinedDeclaration)
&& !(refinedDeclaration instanceof Functional && Decl.isMpl((Functional)refinedDeclaration))){
declaration.setUnboxed(false);
} else if((isCeylonBasicType(type) || Decl.isUnboxedVoid(declaration))
&& !(refinedDeclaration.getTypeDeclaration() instanceof TypeParameter)
&& (refinedDeclaration.getContainer() instanceof Declaration == false || !CodegenUtil.isContainerFunctionalParameter(refinedDeclaration))
&& !(refinedDeclaration instanceof Functional && Decl.isMpl((Functional)refinedDeclaration))){
boolean unbox = !forceBoxedLocals || !(declaration instanceof Value) || !Decl.isLocal(declaration) || Decl.isParameter(declaration) || Decl.isTransient(declaration);
declaration.setUnboxed(unbox);
} else if (Decl.isValueParameter(declaration)
&& CodegenUtil.isContainerFunctionalParameter(declaration)
&& JvmBackendUtil.createMethod((FunctionOrValue)declaration.getContainer())) {
Function functionalParameter = (Function)declaration.getContainer();
TypedDeclaration refinedFrom = (TypedDeclaration)CodegenUtil.getTopmostRefinedDeclaration(functionalParameter, optimisedMethodSpecifiersToMethods);
if (Decl.equal(refinedFrom, functionalParameter) ) {
// Don't consider Anything to be unboxed, since this is a parameter
// not a method return type (where void would be considered unboxed).
if (declaration.getUnit().getAnythingType().isExactly(declaration.getType())
|| declaration.getUnit().isOptionalType(declaration.getType())) {
declaration.setUnboxed(false);
} else {
declaration.setUnboxed(true);
}
} else {
// make sure refined declarations have already been set
if(refinedFrom.getUnboxed() == null)
setBoxingState(refinedFrom, refinedFrom, that);
// inherit
declaration.setUnboxed(refinedFrom.getUnboxed());
}
} else {
declaration.setUnboxed(false);
}
// Any "@boxed" or "@unboxed" compiler annotation overrides
boxFromAnnotation(declaration, that);
}
private void boxAttribute(TypedDeclaration declaration, Node that) {
// deal with invalid input
if(declaration == null)
return;
TypedDeclaration refinedDeclaration = null;
refinedDeclaration = (TypedDeclaration)CodegenUtil.getTopmostRefinedDeclaration(declaration, optimisedMethodSpecifiersToMethods);
// deal with invalid input
if(refinedDeclaration == null)
return;
setBoxingState(declaration, refinedDeclaration, that);
}
private void boxFromAnnotation(TypedDeclaration declaration, Node that) {
// Let's see if the attribute has a "boxed" or "unboxed" annotation
// and set its state accordingly. NB this is not checked for validity!
if(that instanceof StatementOrArgument) {
if(CodegenUtil.hasCompilerAnnotation((StatementOrArgument)that, "boxed")) {
declaration.setUnboxed(false);
} else if(CodegenUtil.hasCompilerAnnotation((StatementOrArgument)that, "unboxed")) {
declaration.setUnboxed(true);
}
}
}
@Override
public void visit(Tree.Parameter that) {
super.visit(that);
TypedDeclaration declaration = that.getParameterModel().getModel();
visitAttributeOrParameter(declaration, that);
}
@Override
public void visit(Tree.ValueParameterDeclaration that) {
super.visit(that);
}
@Override
public void visit(AnyAttribute that) {
super.visit(that);
TypedDeclaration declaration = that.getDeclarationModel();
visitAttributeOrParameter(declaration, that);
}
private void visitAttributeOrParameter(TypedDeclaration declaration, Node that) {
boxAttribute(declaration, that);
rawTypedDeclaration(declaration);
setErasureState(declaration);
}
@Override
public void visit(AttributeDeclaration that) {
if(that.getSpecifierOrInitializerExpression() != null
&& that.getDeclarationModel() != null
&& that.getType() instanceof Tree.ValueModifier
&& that.getDeclarationModel().getType().equals(that.getSpecifierOrInitializerExpression().getExpression().getTypeModel())){
that.getDeclarationModel().setType(that.getDeclarationModel().getType().withoutUnderlyingType());
}
super.visit(that);
}
@Override
public void visit(AttributeArgument that) {
super.visit(that);
boxAttribute(that.getDeclarationModel(), that);
}
@Override
public void visit(AttributeSetterDefinition that) {
super.visit(that);
Setter declaration = that.getDeclarationModel();
// deal with invalid input
if(declaration == null)
return;
// To determine boxing for a setter we use its parameter
TypedDeclaration paramDeclaration = declaration.getParameter().getModel();
boxAttribute(paramDeclaration, that);
// Now copy the settings from the parameter to the setter itself
declaration.setUnboxed(paramDeclaration.getUnboxed());
// Then we check if there are any overriding compiler annotations
boxFromAnnotation(declaration, that);
// And finally we copy the setting back again to make sure they're really the same
paramDeclaration.setUnboxed(declaration.getUnboxed());
}
@Override
public void visit(Variable that) {
super.visit(that);
TypedDeclaration declaration = that.getDeclarationModel();
// deal with invalid input
if(declaration == null)
return;
setBoxingState(declaration, declaration, that);
rawTypedDeclaration(declaration);
setErasureState(declaration);
}
@Override
public void visit(SpecifierStatement that) {
TypedDeclaration declaration = that.getDeclaration();
Function optimisedDeclaration = null;
// make sure we detect the shortcut refinement inlining cases
if(declaration instanceof Function){
if(that.getSpecifierExpression() != null
&& that.getSpecifierExpression() instanceof LazySpecifierExpression == false){
Tree.Term term = Decl.unwrapExpressionsUntilTerm(that.getSpecifierExpression().getExpression());
if(term != null
&& term instanceof Tree.FunctionArgument){
optimisedDeclaration = ((Tree.FunctionArgument)term).getDeclarationModel();
this.optimisedMethodSpecifiersToMethods.put(optimisedDeclaration, (Function) declaration);
}
}
}
try{
super.visit(that);
}finally{
if(optimisedDeclaration != null)
this.optimisedMethodSpecifiersToMethods.remove(optimisedDeclaration);
}
if(declaration == null)
return;
if(declaration instanceof Function){
visitMethod((Function) declaration, that);
}else if(declaration instanceof Value)
visitAttributeOrParameter(declaration, that);
}
@Override
public void visit(ForComprehensionClause that) {
super.visit(that);
// sort of a hack, because normal visiting rules would declare iterator variables to be potentially
// unboxed, but the implementation always boxes them for now, so override it after we visit the comprehension
ForIterator iter = that.getForIterator();
if (iter instanceof ValueIterator) {
// The exception to the "always boxed for now" rule is Java arrays
Type typeModel = iter.getSpecifierExpression().getExpression().getTypeModel();
((ValueIterator) iter).getVariable().getDeclarationModel().setUnboxed(iter.getUnit().isJavaArrayType(typeModel));
} else if (iter instanceof PatternIterator) {
boxPattern(((PatternIterator) iter).getPattern());
}
}
private void boxPattern(Tree.Pattern pattern) {
if (pattern instanceof Tree.KeyValuePattern) {
boxPattern(((Tree.KeyValuePattern)pattern).getKey());
boxPattern(((Tree.KeyValuePattern)pattern).getValue());
} else if (pattern instanceof Tree.TuplePattern) {
for (Tree.Pattern p : ((Tree.TuplePattern)pattern).getPatterns()) {
boxPattern(p);
}
} else if (pattern instanceof Tree.VariablePattern) {
((Tree.VariablePattern)pattern).getVariable().getDeclarationModel().setUnboxed(false);
} else {
throw BugException.unhandledCase(pattern);
}
}
@Override
public void visit(Tree.TypeParameterDeclaration that) {
super.visit(that);
TypeParameter typeParameter = that.getDeclarationModel();
if(typeParameter != null){
visitTypeParameter(typeParameter);
}
}
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;
}
// The following are not really necessary under normal circumstances,
// they explicitly set what is already set by default, but when using
// the -Dceylon.compiler.forceBoxedLocals option they are needed
@Override
public void visit(ValueIterator that) {
super.visit(that);
// The variable in a for over a Range is always unboxed
if (that.getVariable() != null && that.getSpecifierExpression().getExpression().getTerm() instanceof Tree.RangeOp) {
that.getVariable().getDeclarationModel().setUnboxed(true);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy