/*
* 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.model.Util.producedType;
import static com.redhat.ceylon.compiler.typechecker.tree.Util.hasUncheckedNulls;
import static com.sun.tools.javac.code.Flags.FINAL;
import static com.sun.tools.javac.code.Flags.PRIVATE;
import static com.sun.tools.javac.code.Flags.PROTECTED;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.antlr.runtime.Token;
import com.redhat.ceylon.ceylondoc.Util;
import com.redhat.ceylon.common.Versions;
import com.redhat.ceylon.compiler.java.codegen.Naming.DeclNameFlag;
import com.redhat.ceylon.compiler.java.codegen.Naming.SyntheticName;
import com.redhat.ceylon.compiler.java.codegen.Naming.Unfix;
import com.redhat.ceylon.compiler.java.codegen.recovery.Errors;
import com.redhat.ceylon.compiler.java.codegen.recovery.HasErrorException;
import com.redhat.ceylon.compiler.java.codegen.recovery.LocalizedError;
import com.redhat.ceylon.compiler.java.loader.CeylonModelLoader;
import com.redhat.ceylon.compiler.java.loader.TypeFactory;
import com.redhat.ceylon.compiler.java.tools.CeylonLog;
import com.redhat.ceylon.compiler.loader.AbstractModelLoader;
import com.redhat.ceylon.compiler.typechecker.model.Annotation;
import com.redhat.ceylon.compiler.typechecker.model.Class;
import com.redhat.ceylon.compiler.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.Functional;
import com.redhat.ceylon.compiler.typechecker.model.Interface;
import com.redhat.ceylon.compiler.typechecker.model.IntersectionType;
import com.redhat.ceylon.compiler.typechecker.model.Method;
import com.redhat.ceylon.compiler.typechecker.model.MethodOrValue;
import com.redhat.ceylon.compiler.typechecker.model.Module;
import com.redhat.ceylon.compiler.typechecker.model.ModuleImport;
import com.redhat.ceylon.compiler.typechecker.model.NothingType;
import com.redhat.ceylon.compiler.typechecker.model.Package;
import com.redhat.ceylon.compiler.typechecker.model.Parameter;
import com.redhat.ceylon.compiler.typechecker.model.ParameterList;
import com.redhat.ceylon.compiler.typechecker.model.ProducedReference;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.ProducedTypedReference;
import com.redhat.ceylon.compiler.typechecker.model.Scope;
import com.redhat.ceylon.compiler.typechecker.model.SiteVariance;
import com.redhat.ceylon.compiler.typechecker.model.TypeAlias;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.TypeParameter;
import com.redhat.ceylon.compiler.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.UnionType;
import com.redhat.ceylon.compiler.typechecker.model.UnknownType;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.Comprehension;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.PositionalArgument;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.Term;
import com.redhat.ceylon.compiler.typechecker.util.ProducedTypeNamePrinter;
import com.sun.tools.javac.code.BoundKind;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeTags;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.Factory;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCBinary;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCCase;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCNewArray;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCThrow;
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.LetExpr;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import com.sun.tools.javac.util.Position;
import com.sun.tools.javac.util.Position.LineMap;
/**
* Base class for all delegating transformers
*/
public abstract class AbstractTransformer implements Transformation {
private final static ProducedTypeNamePrinter typeSerialiser = new ProducedTypeNamePrinter(false){
protected boolean printFullyQualified() {
return true;
}
protected boolean printQualifier() {
return true;
}
};
private Context context;
private TreeMaker make;
private Names names;
private Symtab syms;
private AbstractModelLoader loader;
private TypeFactory typeFact;
protected Log log;
final Naming naming;
private Errors errors;
public AbstractTransformer(Context context) {
this.context = context;
make = TreeMaker.instance(context);
names = Names.instance(context);
syms = Symtab.instance(context);
loader = CeylonModelLoader.instance(context);
typeFact = TypeFactory.instance(context);
log = CeylonLog.instance(context);
naming = Naming.instance(context);
}
Context getContext() {
return context;
}
Errors errors() {
if (this.errors == null) {
this.errors = Errors.instance(context);
}
return errors;
}
@Override
public TreeMaker make() {
return make;
}
private static JavaPositionsRetriever javaPositionsRetriever = null;
public static void trackNodePositions(JavaPositionsRetriever positionsRetriever) {
javaPositionsRetriever = positionsRetriever;
}
public int position(Node node) {
if (node == null || node.getToken() == null) {
return Position.NOPOS;
} else {
Token token = node.getToken();
return getMap().getPosition(token.getLine(), token.getCharPositionInLine());
}
}
@Override
public Factory at(Node node) {
if (node == null) {
make.at(Position.NOPOS);
}
else {
Token token = node.getToken();
if (token != null) {
int tokenStartPosition = getMap().getStartPosition(token.getLine()) + token.getCharPositionInLine();
make().at(tokenStartPosition);
if (javaPositionsRetriever != null) {
javaPositionsRetriever.addCeylonNode(tokenStartPosition, node);
}
}
}
return make();
}
/**
* An AutoCloseable for restoring a captured source position
*/
class SavedPosition implements AutoCloseable {
private final int pos;
SavedPosition(int pos) {
this.pos = pos;
}
/**
* Restores the captured source position
*/
@Override
public void close() {
make.at(pos);
}
}
/**
* Returns an AutoCloseable whose {@link SavedPosition#close()} will
* restore the current position, and sets the position to the given value
*/
public SavedPosition savePosition(int at) {
SavedPosition saved = new SavedPosition(make.pos);
make.at(at);
return saved;
}
public SavedPosition savePosition(Node node) {
SavedPosition saved = new SavedPosition(make.pos);
at(node);
return saved;
}
/**
* Returns an AutoCloseable whose {@link SavedPosition#close()} will
* restore the current position, and sets the position to Position.NOPOS
* (i.e. useful for compiler book-keeping code).
*/
public SavedPosition noPosition() {
SavedPosition saved = new SavedPosition(make.pos);
make.at(Position.NOPOS);
return saved;
}
@Override
public Symtab syms() {
return syms;
}
@Override
public Names names() {
return names;
}
@Override
public AbstractModelLoader loader() {
return loader;
}
@Override
public TypeFactory typeFact() {
return typeFact;
}
void setMap(LineMap map) {
gen().setMap(map);
}
LineMap getMap() {
return gen().getMap();
}
@Override
public CeylonTransformer gen() {
return CeylonTransformer.getInstance(context);
}
@Override
public ExpressionTransformer expressionGen() {
return ExpressionTransformer.getInstance(context);
}
@Override
public StatementTransformer statementGen() {
return StatementTransformer.getInstance(context);
}
@Override
public ClassTransformer classGen() {
return ClassTransformer.getInstance(context);
}
/**
* Makes an <strong>unquoted</strong> simple identifier
* @param ident The identifier
* @return The ident
*/
JCExpression makeUnquotedIdent(String ident) {
return naming.makeUnquotedIdent(ident);
}
/**
* Makes an <strong>unquoted</strong> simple identifier
* @param ident The identifier
* @return The ident
*/
JCExpression makeUnquotedIdent(Name ident) {
return naming.makeUnquotedIdent(ident);
}
/**
* Makes an <strong>quoted</strong> simple identifier
* @param ident The identifier
* @return The ident
*/
JCIdent makeQuotedIdent(String ident) {
// TODO Only 3 callers
return naming.makeQuotedIdent(ident);
}
/**
* Makes a <strong>quoted</strong> qualified (compound) identifier from
* the given qualified name. Each part of the name will be
* quoted if it is a Java keyword.
* @param qualifiedName The qualified name
*/
JCExpression makeQuotedQualIdentFromString(String qualifiedName) {
return naming.makeQuotedQualIdentFromString(qualifiedName);
}
/**
* Makes an <strong>unquoted</strong> qualified (compound) identifier
* from the given qualified name components
* @param expr A starting expression (may be null)
* @param names The components of the name (may be null)
* @see #makeQuotedQualIdentFromString(String)
*/
JCExpression makeQualIdent(JCExpression expr, String name) {
return naming.makeQualIdent(expr, name);
}
JCExpression makeQuotedQualIdent(JCExpression expr, String... names) {
// TODO Remove this method: Only 1 caller
return naming.makeQuotedQualIdent(expr, names);
}
JCExpression makeQuotedFQIdent(String qualifiedName) {
// TODO Remove this method??: Only 2 callers
return naming.makeQuotedFQIdent(qualifiedName);
}
JCExpression makeIdent(Type type) {
return naming.makeIdent(type);
}
/**
* Makes a <strong>unquoted</strong> field access
* @param s1 The base expression
* @param s2 The field to access
* @return The field access
*/
JCFieldAccess makeSelect(JCExpression s1, String s2) {
return naming.makeSelect(s1, s2);
}
/**
* Makes a <strong>unquoted</strong> field access
* @param s1 The base expression
* @param s2 The field to access
* @return The field access
*/
JCFieldAccess makeSelect(String s1, String s2) {
return naming.makeSelect(s1, s2);
}
JCLiteral makeNull() {
return make().Literal(TypeTags.BOT, null);
}
JCExpression makeByte(byte i) {
return make().Literal(Byte.valueOf(i));
}
JCExpression makeInteger(int i) {
return make().Literal(Integer.valueOf(i));
}
JCExpression makeLong(long i) {
return make().Literal(Long.valueOf(i));
}
/** Makes a boxed Ceylon String */
JCExpression makeCeylonString(String s) {
return boxString(make().Literal(s));
}
JCExpression makeBoolean(boolean b) {
JCExpression expr;
if (b) {
expr = make().Literal(TypeTags.BOOLEAN, Integer.valueOf(1));
} else {
expr = make().Literal(TypeTags.BOOLEAN, Integer.valueOf(0));
}
return expr;
}
JCExpression makeDefaultExprForType(ProducedType type) {
if (canUnbox(type)) {
if (isCeylonBoolean(type)) {
return makeBoolean(false);
} else if (isCeylonFloat(type)) {
return make().Literal(0.0);
} else if (isCeylonInteger(type)) {
return makeLong(0);
} else if (isCeylonCharacter(type)) {
return make().Literal(0);
} else if (isCeylonByte(type)) {
return makeByte((byte)0);
}
}
// The default value cannot be seen from the Ceylon code, so it's
// OK to assign it to null even though it may not be an
// optional type
return makeNull();
}
// Creates a "foo foo = new foo();"
JCTree.JCVariableDecl makeLocalIdentityInstance(String varName, String className, boolean isShared) {
JCExpression typeExpr = makeQuotedIdent(className);
return makeLocalIdentityInstance(typeExpr, varName, className, isShared, null);
}
// Creates a "foo foo = new foo(parameter);"
JCTree.JCVariableDecl makeLocalIdentityInstance(JCExpression typeExpr, String varName, String className, boolean isShared, JCTree.JCExpression parameter) {
JCExpression initValue = makeNewClass(className, false, parameter);
int modifiers = isShared ? 0 : FINAL;
JCTree.JCVariableDecl var = make().VarDef(
make().Modifiers(modifiers),
names().fromString(Naming.quoteLocalValueName(varName)),
typeExpr,
initValue);
return var;
}
// Creates a "new foo();"
JCTree.JCNewClass makeNewClass(String className, boolean fullyQualified, JCTree.JCExpression parameter) {
JCExpression name = fullyQualified ? naming.makeQuotedFQIdent(className) : makeQuotedQualIdentFromString(className);
List<JCTree.JCExpression> params = parameter != null ? List.of(parameter) : List.<JCTree.JCExpression>nil();
return makeNewClass(name, params);
}
/** Creates a "new foo();" */
JCTree.JCNewClass makeSyntheticInstance(Declaration decl) {
JCExpression clazz = naming.makeSyntheticClassname(decl);
return makeNewClass(clazz, List.<JCTree.JCExpression>nil());
}
JCTree.JCNewClass makeNewClass(JCExpression clazz) {
return makeNewClass(clazz, null);
}
// Creates a "new foo(arg1, arg2, ...);"
JCTree.JCNewClass makeNewClass(JCExpression clazz, List<JCTree.JCExpression> args) {
if (args == null) {
args = List.<JCTree.JCExpression>nil();
}
return make().NewClass(null, null, clazz, args, null);
}
JCBlock makeGetterBlock(TypedDeclaration declarationModel,
final Tree.Block block,
final Tree.SpecifierOrInitializerExpression expression) {
List<JCStatement> stats;
if (block != null) {
stats = statementGen().transformBlock(block);
} else {
BoxingStrategy boxing = CodegenUtil.getBoxingStrategy(declarationModel);
ProducedType type = declarationModel.getType();
JCStatement transStat;
HasErrorException error = errors().getFirstExpressionErrorAndMarkBrokenness(expression.getExpression());
if (error != null) {
transStat = this.makeThrowUnresolvedCompilationError(error);
} else {
transStat = make().Return(expressionGen().transformExpression(expression.getExpression(), boxing, type));
}
stats = List.<JCStatement>of(transStat);
}
JCBlock getterBlock = make().Block(0, stats);
return getterBlock;
}
JCBlock makeGetterBlock(final JCExpression expression) {
List<JCStatement> stats = List.<JCStatement>of(make().Return(expression));
JCBlock getterBlock = make().Block(0, stats);
return getterBlock;
}
JCBlock makeSetterBlock(TypedDeclaration declarationModel,
final Tree.Block block,
final Tree.SpecifierOrInitializerExpression expression) {
List<JCStatement> stats;
if (block != null) {
stats = statementGen().transformBlock(block);
} else {
ProducedType type = declarationModel.getType();
JCStatement transStmt;
HasErrorException error = errors().getFirstExpressionErrorAndMarkBrokenness(expression.getExpression());
if (error != null) {
transStmt = this.makeThrowUnresolvedCompilationError(error);
} else {
transStmt = make().Exec(expressionGen().transformExpression(expression.getExpression(), BoxingStrategy.INDIFFERENT, type));
}
stats = List.<JCStatement>of(transStmt);
}
JCBlock setterBlock = make().Block(0, stats);
return setterBlock;
}
JCVariableDecl makeVar(long mods, String varName, JCExpression typeExpr, JCExpression valueExpr) {
return make().VarDef(make().Modifiers(mods), names().fromString(varName), typeExpr, valueExpr);
}
JCVariableDecl makeVar(String varName, JCExpression typeExpr, JCExpression valueExpr) {
return makeVar(0, varName, typeExpr, valueExpr);
}
JCVariableDecl makeVar(Naming.SyntheticName varName, JCExpression typeExpr, JCExpression valueExpr) {
return makeVar(0L, varName, typeExpr, valueExpr);
}
JCVariableDecl makeVar(long mods, Naming.SyntheticName varName, JCExpression typeExpr, JCExpression valueExpr) {
return make().VarDef(make().Modifiers(mods), varName.asName(), typeExpr, valueExpr);
}
/**
* Creates a {@code VariableBox<T>}, {@code VariableBoxBoolean},
* {@code VariableBoxLong} etc depending on the given declaration model.
*/
private JCExpression makeVariableBoxType(TypedDeclaration declarationModel) {
JCExpression boxClass;
boolean unboxed = CodegenUtil.isUnBoxed(declarationModel);
if (unboxed && isCeylonBoolean(declarationModel.getType())) {
boxClass = make().Type(syms().ceylonVariableBoxBooleanType);
} else if (unboxed && isCeylonInteger(declarationModel.getType())) {
boxClass = make().Type(syms().ceylonVariableBoxLongType);
} else if (unboxed && isCeylonFloat(declarationModel.getType())) {
boxClass = make().Type(syms().ceylonVariableBoxDoubleType);
} else if (unboxed && isCeylonCharacter(declarationModel.getType())) {
boxClass = make().Type(syms().ceylonVariableBoxIntType);
} else if (unboxed && isCeylonByte(declarationModel.getType())) {
boxClass = make().Type(syms().ceylonVariableBoxByteType);
} else {
boxClass = make().Ident(syms().ceylonVariableBoxType.tsym);
int flags = unboxed ? 0 : JT_TYPE_ARGUMENT;
boxClass = make().TypeApply(boxClass,
List.<JCExpression>of(
makeJavaType(declarationModel.getType(), flags)));
}
return boxClass;
}
/**
* Makes a final {@code VariableBox<T>} (or {@code VariableBoxBoolean},
* {@code VariableBoxLong}, etc) variable decl, so that a variable can
* be captured.
* @param init The initial value
* @param The (value/parameter) declaration which is being accessed through the box.
*/
JCVariableDecl makeVariableBoxDecl(JCExpression init, TypedDeclaration declarationModel) {
List<JCExpression> args = init != null ? List.<JCExpression>of(init) : List.<JCExpression>nil();
JCExpression newBox = make().NewClass(
null, List.<JCExpression>nil(),
makeVariableBoxType(declarationModel), args, null);
String varName = naming.getVariableBoxName(declarationModel);
JCTree.JCVariableDecl var = make().VarDef(
make().Modifiers(FINAL),
names().fromString(varName),
makeVariableBoxType(declarationModel),
newBox);
return var;
}
/**
* Creates a {@code ( let var1=expr1,var2=expr2,...,varN=exprN in varN; )}
* or a {@code ( let var1=expr1,var2=expr2,...,varN=exprN,exprO in exprO; )}
* @param args
* @return
*/
JCExpression makeLetExpr(JCExpression... args) {
return makeLetExpr(naming.temp(), null, args);
}
/** Creates a
* {@code ( let var1=expr1,var2=expr2,...,varN=exprN in statements; varN; )}
* or a {@code ( let var1=expr1,var2=expr2,...,varN=exprN in statements; exprO; )}
*
*/
JCExpression makeLetExpr(Naming.SyntheticName varBaseName, List<JCStatement> statements, JCExpression... args) {
return makeLetExpr(varBaseName.getName(), statements, args);
}
private JCExpression makeLetExpr(String varBaseName, List<JCStatement> statements, JCExpression... args) {
String varName = null;
ListBuffer<JCStatement> decls = ListBuffer.lb();
int i;
for (i = 0; (i + 1) < args.length; i += 2) {
JCExpression typeExpr = args[i];
JCExpression valueExpr = args[i+1];
varName = varBaseName + ((args.length > 3) ? "$" + i : "");
JCVariableDecl varDecl = makeVar(varName, typeExpr, valueExpr);
decls.append(varDecl);
}
JCExpression result;
if (i == args.length) {
result = makeUnquotedIdent(varName);
} else {
result = args[i];
}
if (statements != null) {
decls.appendList(statements);
}
return make().LetExpr(decls.toList(), result);
}
/*
* Type handling
*/
boolean isBooleanTrue(Declaration decl) {
return Decl.equal(decl, typeFact.getBooleanTrueDeclaration());
}
boolean isBooleanFalse(Declaration decl) {
return Decl.equal(decl, typeFact.getBooleanFalseDeclaration());
}
boolean isNullValue(Declaration decl) {
return Decl.equal(decl, typeFact.getNullValueDeclaration());
}
/**
* Determines whether the given type is optional.
*/
boolean isOptional(ProducedType type) {
// Note we don't use typeFact().isOptionalType(type) because
// that implements a stricter test used in the type checker.
return typeFact().getNullValueDeclaration().getType().isSubtypeOf(type);
}
boolean isNull(ProducedType type) {
return type.getSupertype(typeFact.getNullDeclaration()) != null;
}
boolean isNullValue(ProducedType type) {
return type.getSupertype(typeFact.getNullValueDeclaration().getTypeDeclaration()) != null;
}
public static boolean isAnything(ProducedType type) {
return CodegenUtil.isVoid(type);
}
private boolean isObject(ProducedType type) {
return typeFact.getObjectDeclaration().getType().isExactly(type);
}
public boolean isAlias(ProducedType type) {
return type.getDeclaration().isAlias() || typeFact.getDefiniteType(type).getDeclaration().isAlias();
}
ProducedType simplifyType(ProducedType orgType) {
if(orgType == null)
return null;
ProducedType type = orgType.resolveAliases();
if (isOptional(type)) {
// For an optional type T?:
// - The Ceylon type T? results in the Java type T
type = typeFact().getDefiniteType(type);
if (type.getUnderlyingType() != null) {
// A definite type should not have its underlyingType set so we make a copy
type = type.withoutUnderlyingType();
}
}
TypeDeclaration tdecl = type.getDeclaration();
if (tdecl instanceof UnionType && tdecl.getCaseTypes().size() == 1) {
// Special case when the Union contains only a single CaseType
// FIXME This is not correct! We might lose information about type arguments!
type = tdecl.getCaseTypes().get(0);
} else if (tdecl instanceof IntersectionType) {
java.util.List<ProducedType> satisfiedTypes = tdecl.getSatisfiedTypes();
if (satisfiedTypes.size() == 1) {
// Special case when the Intersection contains only a single SatisfiedType
// FIXME This is not correct! We might lose information about type arguments!
type = satisfiedTypes.get(0);
} else if (satisfiedTypes.size() == 2) {
// special case for T? simplified as T&Object
if (isTypeParameter(satisfiedTypes.get(0)) && isObject(satisfiedTypes.get(1))) {
type = satisfiedTypes.get(0);
}
}
}
return type;
}
ProducedTypedReference getTypedReference(TypedDeclaration decl){
java.util.List<ProducedType> typeArgs = Collections.<ProducedType>emptyList();
if (decl instanceof Method) {
// For methods create type arguments for any type parameters it might have
Method m = (Method)decl;
if (!m.getTypeParameters().isEmpty()) {
typeArgs = new ArrayList<ProducedType>(m.getTypeParameters().size());
for (TypeParameter p: m.getTypeParameters()) {
ProducedType pt = p.getType();
typeArgs.add(pt);
}
}
}
if(decl.getContainer() instanceof TypeDeclaration){
TypeDeclaration containerDecl = (TypeDeclaration) decl.getContainer();
return containerDecl.getType().getTypedMember(decl, typeArgs);
}
return decl.getProducedTypedReference(null, typeArgs);
}
ProducedTypedReference nonWideningTypeDecl(ProducedTypedReference typedReference) {
return nonWideningTypeDecl(typedReference, typedReference.getQualifyingType());
}
ProducedTypedReference nonWideningTypeDecl(ProducedTypedReference typedReference, ProducedType currentType) {
ProducedTypedReference refinedTypedReference = getRefinedDeclaration(typedReference, currentType);
if(refinedTypedReference != null){
/*
* We are widening if the type:
* - is not object
* - is erased to object
* - refines a declaration that is not erased to object
*/
ProducedType declType = typedReference.getType();
ProducedType refinedDeclType = refinedTypedReference.getType();
if(declType == null || refinedDeclType == null)
return typedReference;
boolean isWidening = isWidening(declType, refinedDeclType);
if(!isWidening){
// make sure we get the instantiated refined decl
if(refinedDeclType.getDeclaration() instanceof TypeParameter
&& !(declType.getDeclaration() instanceof TypeParameter))
refinedDeclType = nonWideningType(typedReference, refinedTypedReference);
isWidening = isWideningTypeArguments(declType, refinedDeclType, true);
}
// note that we don't use the type erased info to determine the refined decl, as we do
// in isWideningTypeDecl(), because we get around needing that by using raw types if
// required in actual implementations
if(isWidening)
return refinedTypedReference;
}
return typedReference;
}
public boolean isWideningTypeDecl(TypedDeclaration typedDeclaration) {
ProducedTypedReference typedReference = getTypedReference(typedDeclaration);
return isWideningTypeDecl(typedReference, typedReference.getQualifyingType());
}
public boolean isWideningTypeDecl(ProducedTypedReference typedReference, ProducedType currentType) {
ProducedTypedReference refinedTypedReference = getRefinedDeclaration(typedReference, currentType);
if(refinedTypedReference == null)
return false;
/*
* We are widening if the type:
* - is not object
* - is erased to object
* - refines a declaration that is not erased to object
*/
ProducedType declType = typedReference.getType();
ProducedType refinedDeclType = refinedTypedReference.getType();
if(declType == null || refinedDeclType == null)
return false;
if(isWidening(declType, refinedDeclType))
return true;
// make sure we get the instantiated refined decl
if(refinedDeclType.getDeclaration() instanceof TypeParameter
&& !(declType.getDeclaration() instanceof TypeParameter))
refinedDeclType = nonWideningType(typedReference, refinedTypedReference);
if(isWideningTypeArguments(declType, refinedDeclType, true))
return true;
if(CodegenUtil.hasTypeErased(refinedTypedReference.getDeclaration())
&& !willEraseToObject(declType))
return true;
return false;
}
/*
* We have several special cases here to find the best non-widening refinement in case of multiple inheritace:
*
* - The first special case is for some decls like None.first, which inherits from ContainerWithFirstElement
* twice: once with Nothing (erased to j.l.Object) and once with Element (a type param). Now, in order to not widen the
* return type it can't be Nothing (j.l.Object), it must be Element (a type param that is not instantiated), because in Java
* a type param refines j.l.Object but not the other way around.
* - The second special case is when implementing an interface first with a non-erased type, then with an erased type. In this
* case we want the refined decl to be the one with the non-erased type.
* - The third special case is when we implement a declaration via multiple super types, without having any refining
* declarations in those supertypes, simply by instantiating a common super type with different type parameters
*/
private ProducedTypedReference getRefinedDeclaration(ProducedTypedReference typedReference, ProducedType currentType) {
TypedDeclaration decl = typedReference.getDeclaration();
TypedDeclaration modelRefinedDecl = (TypedDeclaration)decl.getRefinedDeclaration();
ProducedType referenceQualifyingType = typedReference.getQualifyingType();
boolean forMixinMethod =
currentType != null
&& decl.getContainer() instanceof ClassOrInterface
&& referenceQualifyingType != null
&& !Decl.equal(referenceQualifyingType.getDeclaration(), currentType.getDeclaration());
// quick exit
if (Decl.equal(decl, modelRefinedDecl) && !forMixinMethod)
return null;
// modelRefinedDecl exists, but perhaps it's the toplevel refinement and not the one Java will look at
if(!forMixinMethod)
modelRefinedDecl = getFirstRefinedDeclaration(decl);
TypeDeclaration qualifyingDeclaration = currentType.getDeclaration();
if(qualifyingDeclaration instanceof ClassOrInterface){
// if both are returning unboxed void we're good
if(Decl.isUnboxedVoid(decl)
&& Decl.isUnboxedVoid(modelRefinedDecl))
return null;
// only try to find better if we're erasing to Object and we're not returning a type param
if(willEraseToObject(typedReference.getType())
|| isWideningTypeArguments(decl.getType(), modelRefinedDecl.getType(), true)
&& !isTypeParameter(typedReference.getType())){
ClassOrInterface declaringType = (ClassOrInterface) qualifyingDeclaration;
Set<TypedDeclaration> refinedMembers = getRefinedMembers(declaringType, decl.getName(),
com.redhat.ceylon.compiler.typechecker.model.Util.getSignature(decl), false);
// now we must select a different refined declaration if we refine it more than once
if(refinedMembers.size() > (forMixinMethod ? 0 : 1)){
// first case
for(TypedDeclaration refinedDecl : refinedMembers){
// get the type reference to see if any eventual type param is instantiated in our inheritance of this type/method
ProducedTypedReference refinedTypedReference = getRefinedTypedReference(typedReference, refinedDecl);
// if it is not instantiated, that's the one we're looking for
if(isTypeParameter(refinedTypedReference.getType()))
return refinedTypedReference;
}
// second case
for(TypedDeclaration refinedDecl : refinedMembers){
// get the type reference to see if any eventual type param is instantiated in our inheritance of this type/method
ProducedTypedReference refinedTypedReference = getRefinedTypedReference(typedReference, refinedDecl);
// if we're not erasing this one to Object let's select it
if(!willEraseToObject(refinedTypedReference.getType()) && !isWideningTypeArguments(refinedDecl.getType(), modelRefinedDecl.getType(), true))
return refinedTypedReference;
}
// third case
if(isTypeParameter(modelRefinedDecl.getType())){
// it can happen that we have inherited a method twice from a single refined declaration
// via different supertype instantiations, without having ever refined them in supertypes
// so we try each super type to see if we already have a matching typed reference
// first super type
ProducedType extendedType = declaringType.getExtendedType();
if(extendedType != null){
ProducedTypedReference refinedTypedReference = getRefinedTypedReference(extendedType, modelRefinedDecl);
ProducedType refinedType = refinedTypedReference.getType();
if(!isTypeParameter(refinedType)
&& !willEraseToObject(refinedType))
return refinedTypedReference;
}
// then satisfied interfaces
for(ProducedType satisfiedType : declaringType.getSatisfiedTypes()){
ProducedTypedReference refinedTypedReference = getRefinedTypedReference(satisfiedType, modelRefinedDecl);
ProducedType refinedType = refinedTypedReference.getType();
if(!isTypeParameter(refinedType)
&& !willEraseToObject(refinedType))
return refinedTypedReference;
}
}
}
}
/*
* Now there's another crazy case:
*
* interface Top<out Element> {
* Top<Element> ret => nothing;
* }
* interface Left satisfies Top<Integer> {}
* interface Right satisfies Top<String> {}
* class Bottom() satisfies Left&Right {}
*
* Where Bottom.ret does not exist and is typed as returning Integer&String which is Nothing, erased to Object,
* and we look at what it refines and find only a single definition Top.ret typed as returning Integer&String (Nothing),
* so we think there's no widening, but Java will only see Top<Integer>.ret from Left, and that's the one we want
* to use for determining widening.
* See https://github.com/ceylon/ceylon-compiler/issues/1765
*/
ProducedType firstInstantiation = isInheritedWithDifferentTypeArguments(modelRefinedDecl.getContainer(), currentType);
if(firstInstantiation != null){
ProducedTypedReference firstInstantiationTypedReference = getRefinedTypedReference(firstInstantiation, modelRefinedDecl);
ProducedType firstInstantiationType = firstInstantiationTypedReference.getType();
if(isWidening(decl.getType(), firstInstantiationType)
|| isWideningTypeArguments(decl.getType(), firstInstantiationType, true))
return firstInstantiationTypedReference;
}
}
return getRefinedTypedReference(typedReference, modelRefinedDecl);
}
private ProducedType isInheritedWithDifferentTypeArguments(Scope container, ProducedType currentType) {
// only interfaces can be inherited twice
if(container instanceof Interface == false)
return null;
if(currentType.getDeclaration() instanceof ClassOrInterface == false)
return null;
Interface iface = (Interface) container;
// if we have no type parameter there's no problem
if(iface.getTypeParameters().isEmpty())
return null;
ProducedType[] arg = new ProducedType[1];
return findFirstInheritedTypeIfInheritedTwiceWithDifferentTypeArguments(iface, currentType, arg);
}
private ProducedType findFirstInheritedTypeIfInheritedTwiceWithDifferentTypeArguments(Interface iface, ProducedType currentType, ProducedType[] found) {
if(Decl.equal(currentType.getDeclaration(), iface)){
if(found[0] == null){
// first time we find it, just record it
found[0] = currentType;
// stop there
return null;
}else if(found[0].isExactly(currentType)){
// we already found the same type, ignore it and stop there
return null;
}else{
// we found a second type, let's return the first one found
return found[0];
}
}
// first extended type
ProducedType extendedType = currentType.getExtendedType();
if(extendedType != null){
ProducedType ret = findFirstInheritedTypeIfInheritedTwiceWithDifferentTypeArguments(iface, extendedType, found);
// stop there if we found a result
if(ret != null)
return ret;
}
// then satisfied interfaces
for(ProducedType satisfiedType : currentType.getSatisfiedTypes()){
ProducedType ret = findFirstInheritedTypeIfInheritedTwiceWithDifferentTypeArguments(iface, satisfiedType, found);
// stop there if we found a result
if(ret != null)
return ret;
}
// not found
return null;
}
private TypedDeclaration getFirstRefinedDeclaration(TypedDeclaration decl) {
if(decl.getContainer() instanceof ClassOrInterface == false)
return decl;
java.util.List<ProducedType> signature = com.redhat.ceylon.compiler.typechecker.model.Util.getSignature(decl);
ClassOrInterface container = (ClassOrInterface) decl.getContainer();
HashSet<TypeDeclaration> visited = new HashSet<TypeDeclaration>();
// start looking for it, but skip this type, only lookup upwards of it
TypedDeclaration firstRefinedDeclaration = getFirstRefinedDeclaration(container, decl, signature, visited, true);
// only keep the first refined decl if its type can be trusted: if it is not itself widening
if(firstRefinedDeclaration != null){
if(CodegenUtil.hasUntrustedType(firstRefinedDeclaration))
firstRefinedDeclaration = getFirstRefinedDeclaration(firstRefinedDeclaration);
}
return firstRefinedDeclaration != null ? firstRefinedDeclaration : decl;
}
private TypedDeclaration getFirstRefinedDeclaration(TypeDeclaration typeDecl, TypedDeclaration decl,
java.util.List<ProducedType> signature, HashSet<TypeDeclaration> visited,
boolean skipType) {
if(!visited.add(typeDecl))
return null;
if(!skipType){
TypedDeclaration member = (TypedDeclaration) typeDecl.getDirectMember(decl.getName(), signature, false);
if(member != null)
return member;
}
// look up
// first look in super types
if(typeDecl.getExtendedTypeDeclaration() != null){
TypedDeclaration refinedDecl = getFirstRefinedDeclaration(typeDecl.getExtendedTypeDeclaration(), decl, signature, visited, false);
if(refinedDecl != null)
return refinedDecl;
}
// look in interfaces
for(TypeDeclaration interf : typeDecl.getSatisfiedTypeDeclarations()){
TypedDeclaration refinedDecl = getFirstRefinedDeclaration(interf, decl, signature, visited, false);
if(refinedDecl != null)
return refinedDecl;
}
// not found
return null;
}
// Finds all member declarations (original and refinements) with the
// given name and signature within the given type and it's super
// classes and interfaces
public Set<TypedDeclaration> getRefinedMembers(TypeDeclaration decl,
String name,
java.util.List<ProducedType> signature, boolean ellipsis) {
Set<TypedDeclaration> ret = new HashSet<TypedDeclaration>();
collectRefinedMembers(decl.getType(), name, signature, ellipsis,
new HashSet<TypeDeclaration>(), ret, true);
return ret;
}
private void collectRefinedMembers(ProducedType currentType, String name,
java.util.List<ProducedType> signature, boolean ellipsis,
java.util.Set<TypeDeclaration> visited, Set<TypedDeclaration> ret,
boolean ignoreFirst) {
TypeDeclaration decl = currentType.getDeclaration();
if (visited.contains(decl)) {
return;
}
else {
visited.add(decl);
ProducedType et = decl.getExtendedType();
if (et!=null) {
collectRefinedMembers(et, name, signature, ellipsis, visited, ret, false);
}
for (ProducedType st: decl.getSatisfiedTypes()) {
collectRefinedMembers(st, name, signature, ellipsis, visited, ret, false);
}
// we're collecting refined members, not the refining one
if(!ignoreFirst){
TypedDeclaration found = (TypedDeclaration) decl.getDirectMember(name, signature, ellipsis);
if(found instanceof Method){
// do not trust getDirectMember because if you ask it for [Integer,String] and it has [Integer,E] it does not
// know that E=String and will not make it match, and will just return any member when there is overloading,
// including one with signature [String] when you asked for [Integer,String]
java.util.List<ProducedType> typedSignature = getTypedSignature(currentType, found);
if(typedSignature != null
&& hasMatchingSignature(signature, typedSignature))
ret.add(found);
}
}
}
}
private java.util.List<ProducedType> getTypedSignature(ProducedType currentType, TypedDeclaration found) {
// check that its signature is compatible
java.util.List<ParameterList> parameterLists = ((Method) found).getParameterLists();
if(parameterLists == null || parameterLists.isEmpty())
return null;
// only consider first param list
java.util.List<Parameter> parameters = parameterLists.get(0).getParameters();
if(parameters == null)
return null;
ProducedTypedReference typedMember = currentType.getTypedMember(found, Collections.<ProducedType>emptyList());
if(typedMember == null)
return null;
java.util.List<ProducedType> typedSignature = new ArrayList<ProducedType>(parameters.size());
for(Parameter p : parameters){
ProducedType parameterType = typedMember.getTypedParameter(p).getFullType();
typedSignature.add(parameterType);
}
return typedSignature;
}
private boolean hasMatchingSignature(java.util.List<ProducedType> signature, java.util.List<ProducedType> typedSignature) {
if(signature.size() != typedSignature.size())
return false;
for(int i=0;i<signature.size();i++){
ProducedType signatureArg = signature.get(i);
ProducedType typedSignatureArg = typedSignature.get(i);
if(signatureArg != null
&& typedSignatureArg != null
&& !com.redhat.ceylon.compiler.typechecker.model.Util.matches(signatureArg, typedSignatureArg, typeFact()))
return false;
}
return true;
}
private ProducedTypedReference getRefinedTypedReference(ProducedTypedReference typedReference,
TypedDeclaration refinedDeclaration) {
return getRefinedTypedReference(typedReference.getQualifyingType(), refinedDeclaration);
}
private ProducedTypedReference getRefinedTypedReference(ProducedType qualifyingType,
TypedDeclaration refinedDeclaration) {
TypeDeclaration refinedContainer = (TypeDeclaration)refinedDeclaration.getContainer();
ProducedType refinedContainerType = qualifyingType.getSupertype(refinedContainer);
return refinedDeclaration.getProducedTypedReference(refinedContainerType, Collections.<ProducedType>emptyList());
}
public boolean isWidening(ProducedType declType, ProducedType refinedDeclType) {
return !isCeylonObject(declType)
&& willEraseToObject(declType)
&& !willEraseToObject(refinedDeclType);
}
private boolean isWideningTypeArguments(ProducedType declType, ProducedType refinedDeclType, boolean allowSubtypes) {
if(declType == null || refinedDeclType == null)
return false;
// make sure we work on simplified types, to avoid stuff like optional or size-1 unions
declType = simplifyType(declType);
refinedDeclType = simplifyType(refinedDeclType);
// special case for type parameters
if(declType.getDeclaration() instanceof TypeParameter
&& refinedDeclType.getDeclaration() instanceof TypeParameter){
// consider them equivalent if they have the same bounds
TypeParameter tp = (TypeParameter) declType.getDeclaration();
TypeParameter refinedTP = (TypeParameter) refinedDeclType.getDeclaration();
if(haveSameBounds(tp, refinedTP))
return false;
// if they don't have the same bounds and we don't allow subtypes then we're widening
if(!allowSubtypes)
return false;
// if we allow subtypes, we're widening if tp is not a subtype of refinedTP
return !tp.getType().isSubtypeOf(refinedTP.getType());
}
if(allowSubtypes){
if((willEraseToObject(refinedDeclType))){
// if we refine something that erases to object, and:
// - we don't erase to object -> we can't possibly be widening, or
// - similarly if we both erase to object we're not widening
return false;
}
// if we have exactly the same type don't bother finding a common ancestor
if(!declType.isExactly(refinedDeclType)){
// check if we can form an informed decision
if(refinedDeclType.getDeclaration() == null)
return true;
// find the instantiation of the refined decl type in the decl type
// special case for optional types: let's find the definite type since
// in java they are equivalent
ProducedType definiteType = typeFact().getDefiniteType(refinedDeclType);
if(definiteType != null)
refinedDeclType = definiteType;
declType = declType.getSupertype(refinedDeclType.getDeclaration());
// could not find common type, we must be widening somehow
if(declType == null)
return true;
}
}
Map<TypeParameter, ProducedType> typeArguments = declType.getTypeArguments();
Map<TypeParameter, ProducedType> refinedTypeArguments = refinedDeclType.getTypeArguments();
java.util.List<TypeParameter> typeParameters = declType.getDeclaration().getTypeParameters();
for(TypeParameter tp : typeParameters){
ProducedType typeArgument = typeArguments.get(tp);
if(typeArgument == null)
return true; // something fishy here
ProducedType refinedTypeArgument = refinedTypeArguments.get(tp);
if(refinedTypeArgument == null)
return true; // something fishy here
// check if the type arg is widening due to erasure
if(isWidening(typeArgument, refinedTypeArgument))
return true;
// check if we are refining a covariant param which we must "fix" because it is dependend on, like Tuple's first TP
if(declType.isCovariant(tp)
&& hasDependentTypeParameters(typeParameters, tp)
&& !typeArgument.isExactly(refinedTypeArgument)
// it is not widening if we refine Object with a TP, though
&& !(willEraseToObject(refinedTypeArgument)
&& (isTypeParameter(typeArgument)
// it is also not widening if we erase both to Object
|| willEraseToObject(typeArgument))
)
)
return true;
// check if the type arg is a subtype, or if its type args are widening
if(isWideningTypeArguments(typeArgument, refinedTypeArgument, tp.isCovariant()))
return true;
}
// so far so good
return false;
}
public boolean haveSameBounds(TypeParameter tp, TypeParameter refinedTP) {
java.util.List<ProducedType> satTP = tp.getSatisfiedTypes();
java.util.List<ProducedType> satRefinedTP = new LinkedList<ProducedType>();
satRefinedTP.addAll(refinedTP.getSatisfiedTypes());
// same number of bounds
if(satTP.size() != satRefinedTP.size())
return false;
// make sure all the bounds are the same
OUT:
for(ProducedType satisfiedType : satTP){
for(ProducedType refinedSatisfiedType : satRefinedTP){
// if we found it, remove it from the second list to not match it again
if(satisfiedType.isExactly(refinedSatisfiedType)){
satRefinedTP.remove(refinedSatisfiedType);
continue OUT;
}
}
// not found
return false;
}
// all bounds are equal
return true;
}
ProducedType nonWideningType(ProducedTypedReference declaration, ProducedTypedReference refinedDeclaration){
final ProducedReference pr;
if (declaration.equals(refinedDeclaration)) {
pr = declaration;
} else {
ProducedType refinedType = refinedDeclaration.getType();
// if the refined type is a method TypeParam, use the original decl that will be more correct,
// since it may have changed name
if(refinedType.getDeclaration() instanceof TypeParameter
&& refinedType.getDeclaration().getContainer() instanceof Method){
// find its index in the refined declaration
TypeParameter refinedTypeParameter = (TypeParameter) refinedType.getDeclaration();
Method refinedMethod = (Method) refinedTypeParameter.getContainer();
int i=0;
for(TypeParameter tp : refinedMethod.getTypeParameters()){
if(tp.getName().equals(refinedTypeParameter.getName()))
break;
i++;
}
if(i >= refinedMethod.getTypeParameters().size()){
throw new BugException("can't find type parameter "+refinedTypeParameter.getName()+" in its container "+refinedMethod.getName());
}
// the refining method type parameter should be at the same index
if(declaration.getDeclaration() instanceof Method == false)
throw new BugException("refining declaration is not a method: "+declaration);
Method refiningMethod = (Method) declaration.getDeclaration();
if(i >= refiningMethod.getTypeParameters().size()){
throw new BugException("refining method does not have enough type parameters to refine "+refinedMethod.getName());
}
pr = refiningMethod.getTypeParameters().get(i).getType();
} else {
pr = refinedType;
}
}
if (pr.getDeclaration() instanceof Functional
&& Decl.isMpl((Functional)pr.getDeclaration())) {
// Methods with MPL have a Callable return type, not the type of
// the innermost Callable.
return getReturnTypeOfCallable(pr.getFullType());
}
return pr.getType();
}
private ProducedType javacCeylonTypeToProducedType(com.sun.tools.javac.code.Type t) {
return loader().getType(getLanguageModule(), t.tsym.packge().getQualifiedName().toString(), t.tsym.getQualifiedName().toString(), null);
}
private ProducedType javacJavaTypeToProducedType(com.sun.tools.javac.code.Type t) {
return loader().getType(getJDKBaseModule(), t.tsym.packge().getQualifiedName().toString(), t.tsym.getQualifiedName().toString(), null);
}
/**
* Determines if a type will be erased to java.lang.Object once converted to Java
* @param type
* @return
*/
boolean willEraseToObject(ProducedType type) {
if(type == null)
return false;
type = simplifyType(type);
TypeDeclaration decl = type.getDeclaration();
// All the following types either are Object or erase to Object
if (Decl.equal(decl, typeFact.getObjectDeclaration())
|| Decl.equal(decl, typeFact.getIdentifiableDeclaration())
|| Decl.equal(decl, typeFact.getBasicDeclaration())
|| Decl.equal(decl, typeFact.getNullDeclaration())
|| Decl.equal(decl, typeFact.getNullValueDeclaration().getTypeDeclaration())
|| Decl.equal(decl, typeFact.getAnythingDeclaration())
|| decl instanceof NothingType) {
return true;
}
// Any Unions and Intersections erase to Object as well
// except for the ones that erase to Sequential
return ((decl instanceof UnionType || decl instanceof IntersectionType));
}
boolean willEraseToPrimitive(ProducedType type) {
return (isCeylonBoolean(type)
|| isCeylonInteger(type)
|| isCeylonFloat(type)
|| isCeylonCharacter(type)
|| isCeylonByte(type));
}
boolean willEraseToException(ProducedType type) {
type = simplifyType(type);
return type != null && type.isExactly(typeFact.getExceptionDeclaration().getType());
}
boolean willEraseToThrowable(ProducedType type) {
type = simplifyType(type);
TypeDeclaration decl = type.getDeclaration();
return Decl.equal(decl, typeFact.getThrowableDeclaration());
}
boolean willEraseToSequence(ProducedType type) {
type = simplifyType(type);
TypeDeclaration decl = type.getDeclaration();
return Decl.equal(decl, typeFact.getTupleDeclaration());
}
// keep in sync with MethodDefinitionBuilder.paramType()
public boolean willEraseToBestBounds(Parameter param) {
ProducedType type = param.getType();
if (typeFact().isUnion(type)
|| typeFact().isIntersection(type)) {
final TypeDeclaration refinedTypeDecl = ((TypedDeclaration)CodegenUtil.getTopmostRefinedDeclaration(param.getModel())).getType().getDeclaration();
if (refinedTypeDecl instanceof TypeParameter
&& !refinedTypeDecl.getSatisfiedTypes().isEmpty()) {
return true;
}
}
return false;
}
boolean hasErasure(ProducedType type) {
return hasErasureResolved(type.resolveAliases());
}
private boolean hasErasureResolved(ProducedType type) {
if(type == null)
return false;
TypeDeclaration declaration = type.getDeclaration();
if(declaration == null)
return false;
if(declaration instanceof UnionType){
UnionType ut = (UnionType) declaration;
java.util.List<ProducedType> caseTypes = ut.getCaseTypes();
// special case for optional types
if(caseTypes.size() == 2){
if(isOptional(caseTypes.get(0)))
return hasErasureResolved(caseTypes.get(1));
if(isOptional(caseTypes.get(1)))
return hasErasureResolved(caseTypes.get(0));
}
// must be erased
return true;
}
if(declaration instanceof IntersectionType){
IntersectionType ut = (IntersectionType) declaration;
java.util.List<ProducedType> satisfiedTypes = ut.getSatisfiedTypes();
// special case for non-optional types
if(satisfiedTypes.size() == 2){
if(isObject(satisfiedTypes.get(0)))
return hasErasureResolved(satisfiedTypes.get(1));
if(isObject(satisfiedTypes.get(1)))
return hasErasureResolved(satisfiedTypes.get(0));
}
// must be erased
return true;
}
if(declaration instanceof TypeParameter){
// consider type parameters with non-erased bounds as erased to force a cast
// see https://github.com/ceylon/ceylon-compiler/issues/1327
for(ProducedType bound : declaration.getSatisfiedTypes()){
if(!willEraseToObject(bound))
return true;
}
return false;
}
// Note: we don't consider types like Anything, Null, Basic, Identifiable as erased because
// they can never be better than Object as far as Java is concerned
// FIXME: what about Nothing then?
// special case for Callable where we stop after the first type param
boolean isCallable = isCeylonCallable(type);
// now check its type parameters
for(ProducedType pt : type.getTypeArgumentList()){
if(hasErasureResolved(pt))
return true;
if(isCallable)
break;
}
// no erasure here
return false;
}
/**
* This method should do the same sort of logic as AbstractTransformer.makeTypeArgs to determine
* that the given type will be turned raw as a return type
*/
boolean isTurnedToRaw(ProducedType type){
return isTurnedToRawResolved(type.resolveAliases());
}
private boolean isTurnedToRawResolved(ProducedType type) {
// if we don't have type arguments we can't be raw
if(type.getTypeArguments().isEmpty())
return false;
// we only go raw if every type param is an erased union/intersection
boolean everyTypeArgumentIsErasedUnionIntersection = true;
// start with type but consider ever qualifying type
ProducedType singleType = type;
do{
// special case for Callable where we stop after the first type param
boolean isCallable = isCeylonCallable(singleType);
TypeDeclaration declaration = singleType.getDeclaration();
Map<TypeParameter, ProducedType> typeArguments = singleType.getTypeArguments();
for(TypeParameter tp : declaration.getTypeParameters()){
ProducedType ta = typeArguments.get(tp);
// skip invalid input
if(tp == null || ta == null)
return false;
// see makeTypeArgs: Nothing in contravariant position causes a raw type
if(singleType.isContravariant(tp) && ta.getDeclaration() instanceof NothingType)
return true;
everyTypeArgumentIsErasedUnionIntersection &= isErasedUnionOrIntersection(ta);
// Callable really has a single type arg in Java
if(isCallable)
break;
// don't recurse
}
// move on to next qualifying type
}while((singleType = singleType.getQualifyingType()) != null);
// we're only raw if every type param is an erased union/intersection
return everyTypeArgumentIsErasedUnionIntersection;
}
private boolean isErasedUnionOrIntersection(ProducedType producedType) {
TypeDeclaration typeDeclaration = producedType.getDeclaration();
if(typeDeclaration instanceof UnionType){
UnionType ut = (UnionType) typeDeclaration;
java.util.List<ProducedType> caseTypes = ut.getCaseTypes();
// special case for optional types
if(caseTypes.size() == 2){
if(isNull(caseTypes.get(0))){
return isErasedUnionOrIntersection(caseTypes.get(1));
}else if(isNull(caseTypes.get(1))){
return isErasedUnionOrIntersection(caseTypes.get(0));
}
}
// it is erased
return true;
}
if(typeDeclaration instanceof IntersectionType){
IntersectionType ut = (IntersectionType) typeDeclaration;
java.util.List<ProducedType> satisfiedTypes = ut.getSatisfiedTypes();
// special case for non-optional types
if(satisfiedTypes.size() == 2){
if(isObject(satisfiedTypes.get(0))){
return isErasedUnionOrIntersection(satisfiedTypes.get(1));
}else if(isObject(satisfiedTypes.get(1))){
return isErasedUnionOrIntersection(satisfiedTypes.get(0));
}
}
// it is erased
return true;
}
// we found something which is not erased entirely
return false;
}
boolean isCeylonString(ProducedType type) {
return type != null && type.isExactly(typeFact.getStringDeclaration().getType());
}
boolean isCeylonBoolean(ProducedType type) {
TypeDeclaration declaration = type.getDeclaration();
return declaration != null
&& (type.isExactly(typeFact.getBooleanDeclaration().getType())
|| isBooleanTrue(declaration)
|| Decl.equal(declaration, typeFact.getBooleanTrueClassDeclaration())
|| isBooleanFalse(declaration)
|| Decl.equal(declaration, typeFact.getBooleanFalseClassDeclaration()));
}
boolean isCeylonInteger(ProducedType type) {
return type != null && type.isExactly(typeFact.getIntegerDeclaration().getType());
}
boolean isCeylonFloat(ProducedType type) {
return type != null && type.isExactly(typeFact.getFloatDeclaration().getType());
}
boolean isCeylonCharacter(ProducedType type) {
return type != null && type.isExactly(typeFact.getCharacterDeclaration().getType());
}
boolean isCeylonByte(ProducedType type) {
return type != null && type.isExactly(typeFact.getByteDeclaration().getType());
}
boolean isCeylonArray(ProducedType type) {
return type.getSupertype(typeFact.getArrayDeclaration()) != null;
}
boolean isCeylonObject(ProducedType type) {
return type != null && type.isExactly(typeFact.getObjectDeclaration().getType());
}
boolean isCeylonBasicType(ProducedType type) {
return (isCeylonString(type)
|| isCeylonBoolean(type)
|| isCeylonInteger(type)
|| isCeylonFloat(type)
|| isCeylonCharacter(type)
|| isCeylonByte(type));
}
boolean isCeylonCallable(ProducedType type) {
// only say yes for exactly Callable, as this is mostly used for erasure of its second type parameter
// but we want subtypes of Callable such as the metamodel to have those extra type parameters ATM
return Decl.equal(type.getDeclaration(), typeFact.getCallableDeclaration());
// return type.getDeclaration().getUnit().isCallableType(type);
}
boolean isCeylonCallableSubtype(ProducedType type) {
return typeFact().isCallableType(type);
}
boolean isExactlySequential(ProducedType type) {
return Decl.equal(typeFact().getDefiniteType(type).getDeclaration(), typeFact.getSequentialDeclaration());
}
boolean isCeylonMetamodelDeclaration(ProducedType type) {
return type.isSubtypeOf(typeFact().getMetamodelDeclarationDeclaration().getType());
}
boolean isCeylonSequentialMetamodelDeclaration(ProducedType type) {
return type.isSubtypeOf(typeFact().getSequentialType(typeFact().getMetamodelDeclarationDeclaration().getType()));
}
/*
* Java Type creation
*/
/** For use in {@code implements} clauses. */
static final int JT_SATISFIES = 1 << 0;
/** For use in {@code extends} clauses. */
static final int JT_EXTENDS = 1 << 1;
/** For use when a primitive type won't do. */
static final int JT_NO_PRIMITIVES = 1 << 2;
/** For generating a type without type arguments. */
static final int JT_RAW = 1 << 3;
/** For use in {@code catch} statements. */
static final int JT_CATCH = 1 << 4;
/**
* Generate a 'small' primitive type (if the type is primitive and has a
* small variant).
*/
static final int JT_SMALL = 1 << 5;
/** For use in {@code new} expressions. */
static final int JT_CLASS_NEW = 1 << 6;
/** Generates the Java type of the companion class of the given type */
static final int JT_COMPANION = 1 << 7;
static final int JT_NON_QUALIFIED = 1 << 8;
private static final int __JT_RAW_TP_BOUND = 1 << 9;
/**
* If the type is a type parameter, return the Java type for its upper bound.
* Implies {@link #JT_RAW}
*/
static final int JT_RAW_TP_BOUND = JT_RAW | __JT_RAW_TP_BOUND;
private static final int __JT_TYPE_ARGUMENT = 1 << 10;
/** For use when generating a type argument. Implies {@code JT_NO_PRIMITIVES} */
static final int JT_TYPE_ARGUMENT = JT_NO_PRIMITIVES | __JT_TYPE_ARGUMENT;
/** For use when we want a value type class. */
static final int JT_VALUE_TYPE = 1 << 11;
/** Generates the Java type of the companion class of the given class */
static final int JT_ANNOTATION = 1 << 12;
/** Generates the Java type of the companion class of the given class */
static final int JT_ANNOTATIONS = 1 << 13;
/** Do not resolve aliases, useful if we want a class literal pointing to the alias class itself. */
static final int JT_CLASS_LITERAL = 1 << 14;
/**
* This function is used solely for method return types and parameters
*/
JCExpression makeJavaType(TypedDeclaration typeDecl, ProducedType type, int flags) {
if (typeDecl instanceof Method
&& ((Method)typeDecl).isParameter()) {
Method p = (Method)typeDecl;
ProducedType pt = type;
for (int ii = 1; ii < p.getParameterLists().size(); ii++) {
pt = typeFact().getCallableType(pt);
}
return makeJavaType(typeFact().getCallableType(pt), flags);
} else {
boolean usePrimitives = CodegenUtil.isUnBoxed(typeDecl);
return makeJavaType(type, flags | (usePrimitives ? 0 : AbstractTransformer.JT_NO_PRIMITIVES));
}
}
JCExpression makeJavaType(TypeSymbol tsym){
return make().QualIdent(tsym);
}
JCExpression makeJavaType(ProducedType producedType) {
return makeJavaType(producedType, 0);
}
JCExpression makeJavaType(final ProducedType ceylonType, final int flags) {
ProducedType type = ceylonType;
if(type == null
|| type.getDeclaration() instanceof UnknownType)
return make().Erroneous();
// resolve aliases
if((flags & JT_CLASS_LITERAL) == 0)
type = type.resolveAliases();
if ((flags & __JT_RAW_TP_BOUND) != 0
&& type.getDeclaration() instanceof TypeParameter) {
type = ((TypeParameter)type.getDeclaration()).getExtendedType();
}
// ERASURE
if ((flags & JT_CLASS_LITERAL) == 0
// don't consider erasure for class literals since it would resolve aliases and we want class
// literals to the alias class
&& willEraseToObject(type)) {
// For an erased type:
// - Any of the Ceylon types Anything, Object, Null,
// Basic, and Nothing result in the Java type Object
// For any other union type U|V (U nor V is Optional):
// - The Ceylon type U|V results in the Java type Object
if ((flags & JT_SATISFIES) != 0) {
return null;
} else {
return make().Type(syms().objectType);
}
} else if (willEraseToException(type)) {
if ((flags & JT_CLASS_NEW) != 0
|| (flags & JT_EXTENDS) != 0) {
return makeIdent(syms().ceylonExceptionType);
} else {
return make().Type(syms().exceptionType);
}
} else if (willEraseToThrowable(type)) {
if ((flags & JT_CLASS_NEW) != 0
|| (flags & JT_EXTENDS) != 0) {
return makeIdent(syms().throwableType);
} else {
return make().Type(syms().throwableType);
}
} else if (willEraseToSequence(type)) {
if ((flags & (JT_CLASS_NEW | JT_EXTENDS)) == 0) {
ProducedType typeArg = simplifyType(type).getTypeArgumentList().get(0);
ProducedType seqType = typeFact.getSequenceType(typeArg);
if (typeFact.isOptionalType(type)) {
type = typeFact.getOptionalType(seqType);
} else {
type = seqType;
}
}
} else if ((flags & (JT_SATISFIES | JT_EXTENDS | JT_NO_PRIMITIVES | JT_CLASS_NEW)) == 0
&& ((isCeylonBasicType(type) && !isOptional(type)) || isJavaString(type))) {
if (isCeylonString(type) || isJavaString(type)) {
return make().Type(syms().stringType);
} else if (isCeylonBoolean(type)) {
return make().TypeIdent(TypeTags.BOOLEAN);
} else if (isCeylonInteger(type)) {
if ("short".equals(type.getUnderlyingType())) {
return make().TypeIdent(TypeTags.SHORT);
} else if ((flags & JT_SMALL) != 0 || "int".equals(type.getUnderlyingType())) {
return make().TypeIdent(TypeTags.INT);
} else {
return make().TypeIdent(TypeTags.LONG);
}
} else if (isCeylonFloat(type)) {
if ((flags & JT_SMALL) != 0 || "float".equals(type.getUnderlyingType())) {
return make().TypeIdent(TypeTags.FLOAT);
} else {
return make().TypeIdent(TypeTags.DOUBLE);
}
} else if (isCeylonCharacter(type)) {
if ("char".equals(type.getUnderlyingType())) {
return make().TypeIdent(TypeTags.CHAR);
} else {
return make().TypeIdent(TypeTags.INT);
}
} else if (isCeylonByte(type)) {
return make().TypeIdent(TypeTags.BYTE);
}
} else if (isCeylonBoolean(type)
&& !isTypeParameter(type)) {
//&& (flags & TYPE_ARGUMENT) == 0){
// special case to get rid of $true and $false types
type = typeFact.getBooleanDeclaration().getType();
} else if ((flags & JT_VALUE_TYPE) == 0 && isJavaArray(type)){
return getJavaArrayElementType(type, flags);
}
JCExpression jt = null;
ProducedType simpleType;
if((flags & JT_CLASS_LITERAL) == 0)
simpleType = simplifyType(type);
else
simpleType = type;
// see if we need to cross methods when looking up container types
// this is required to properly collect all the type parameters for local interfaces
// which we pull up to the toplevel and capture all the container type parameters
boolean needsQualifyingTypeArgumentsFromLocalContainers =
Decl.isCeylon(simpleType.getDeclaration())
&& simpleType.getDeclaration() instanceof Interface
// this is only valid for interfaces, not for their companion which stay where they are
&& (flags & JT_COMPANION) == 0;
java.util.List<ProducedType> qualifyingTypes = null;
ProducedType qType = simpleType;
boolean hasTypeParameters = false;
while (qType != null) {
hasTypeParameters |= !qType.getTypeArguments().isEmpty();
if(qualifyingTypes != null)
qualifyingTypes.add(qType);
TypeDeclaration typeDeclaration = qType.getDeclaration();
// local interfaces that are pulled to the toplevel need to cross containing methods to find
// all the containing type parameters that it captures
if(Decl.isLocal(typeDeclaration)
&& needsQualifyingTypeArgumentsFromLocalContainers
&& typeDeclaration instanceof ClassOrInterface){
ClassOrInterface container = Decl.getClassOrInterfaceContainer(typeDeclaration, false);
qType = container == null ? null : container.getType();
}else{
qType = qType.getQualifyingType();
if(qType != null && qType.getDeclaration() instanceof ClassOrInterface == false){
// sometimes the typechecker throws qualifying intersections at us and
// we can't make anything of them, since some members may be unrelated to
// the qualified declaration. This happens with "extends super.Foo()"
// for example. See https://github.com/ceylon/ceylon-compiler/issues/1478
qType = qType.getSupertype((TypeDeclaration) typeDeclaration.getContainer());
}
}
// delayed allocation if we have a qualifying type
if(qualifyingTypes == null && qType != null){
qualifyingTypes = new java.util.ArrayList<ProducedType>();
qualifyingTypes.add(simpleType);
}
}
int firstQualifyingTypeWithTypeParameters = qualifyingTypes != null ? qualifyingTypes.size() - 1 : 0;
// find the first static one, from the right to the left
if(qualifyingTypes != null){
for(ProducedType pt : qualifyingTypes){
TypeDeclaration declaration = pt.getDeclaration();
if(Decl.isStatic(declaration)){
break;
}
firstQualifyingTypeWithTypeParameters--;
}
if(firstQualifyingTypeWithTypeParameters < 0)
firstQualifyingTypeWithTypeParameters = 0;
// put them in outer->inner order
Collections.reverse(qualifyingTypes);
}
if (((flags & JT_RAW) == 0) && hasTypeParameters) {
// special case for interfaces because we pull them into toplevel types
if(Decl.isCeylon(simpleType.getDeclaration())
&& qualifyingTypes != null
&& qualifyingTypes.size() > 1
&& simpleType.getDeclaration() instanceof Interface
// this is only valid for interfaces, not for their companion which stay where they are
&& (flags & JT_COMPANION) == 0){
JCExpression baseType;
TypeDeclaration tdecl = simpleType.getDeclaration();
// collect all the qualifying type args we'd normally have
java.util.List<TypeParameter> qualifyingTypeParameters = new java.util.ArrayList<TypeParameter>();
java.util.Map<TypeParameter, ProducedType> qualifyingTypeArguments = new java.util.HashMap<TypeParameter, ProducedType>();
collectQualifyingTypeArguments(qualifyingTypeParameters, qualifyingTypeArguments, qualifyingTypes);
ListBuffer<JCExpression> typeArgs = makeTypeArgs(isCeylonCallable(simpleType),
flags,
qualifyingTypeArguments, qualifyingTypeParameters, simpleType);
if (isCeylonCallable(type) &&
(flags & JT_CLASS_NEW) != 0) {
baseType = makeIdent(syms().ceylonAbstractCallableType);
} else {
baseType = naming.makeDeclarationName(tdecl, DeclNameFlag.QUALIFIED);
}
if (typeArgs != null && typeArgs.size() > 0) {
jt = make().TypeApply(baseType, typeArgs.toList());
} else {
jt = baseType;
}
}else if((flags & JT_NON_QUALIFIED) == 0){
int index = 0;
if(qualifyingTypes != null){
for (ProducedType qualifyingType : qualifyingTypes) {
jt = makeParameterisedType(qualifyingType, type, flags, jt, qualifyingTypes, firstQualifyingTypeWithTypeParameters, index);
index++;
}
}else{
jt = makeParameterisedType(simpleType, type, flags, jt, qualifyingTypes, firstQualifyingTypeWithTypeParameters, index);
}
}else{
jt = makeParameterisedType(type, type, flags, jt, qualifyingTypes, 0, 0);
}
} else {
TypeDeclaration tdecl = simpleType.getDeclaration();
// For an ordinary class or interface type T:
// - The Ceylon type T results in the Java type T
if(tdecl instanceof TypeParameter)
jt = makeQuotedIdent(tdecl.getName());
// don't use underlying type if we want no primitives
else if((flags & (JT_SATISFIES | JT_NO_PRIMITIVES)) != 0 || simpleType.getUnderlyingType() == null){
jt = naming.makeDeclarationName(tdecl, jtFlagsToDeclNameOpts(flags));
}else
jt = makeQuotedFQIdent(simpleType.getUnderlyingType());
}
return (jt != null) ? jt : makeErroneous(null, "compiler bug: the java type corresponding to " + ceylonType + " could not be computed");
}
/**
* Collects all the type parameters and arguments required for an interface that's been pulled up to the
* toplevel, including its containing type and method type parameters.
*/
private void collectQualifyingTypeArguments(java.util.List<TypeParameter> qualifyingTypeParameters,
Map<TypeParameter, ProducedType> qualifyingTypeArguments,
java.util.List<ProducedType> qualifyingTypes) {
// make sure we only add type parameters with the same name once, as duplicates are erased from the target interface
// since they cannot be accessed
Set<String> names = new HashSet<String>();
// walk the qualifying types backwards to make sure we only add a TP with the same name once and the outer one wins
for (int i = qualifyingTypes.size()-1 ; i >= 0 ; i--) {
ProducedType qualifiedType = qualifyingTypes.get(i);
Map<TypeParameter, ProducedType> tas = qualifiedType.getTypeArguments();
java.util.List<TypeParameter> tps = qualifiedType.getDeclaration().getTypeParameters();
// add any type params for this type
if (tps != null) {
int index = 0;
for(TypeParameter tp : tps){
// add it only once
if(names.add(tp.getName())){
// start putting all these type parameters at 0 and then in order
// so that outer type params end up before inner type params but
// order is preserved within each type
qualifyingTypeParameters.add(index++, tp);
qualifyingTypeArguments.put(tp, tas.get(tp));
}
}
}
// add any container method TP
TypeDeclaration declaration = qualifiedType.getDeclaration();
if(Decl.isLocal(declaration)){
Scope scope = declaration.getContainer();
// collect every container method until the next type or package
java.util.List<Method> methods = new LinkedList<Method>();
while(scope != null
&& scope instanceof ClassOrInterface == false
&& scope instanceof Package == false){
if(scope instanceof Method){
methods.add((Method) scope);
}
scope = scope.getContainer();
}
// methods are sorted inner to outer, which is the order we're following here for types
for(Method method : methods){
java.util.List<TypeParameter> methodTypeParameters = method.getTypeParameters();
if (methodTypeParameters != null) {
int index = 0;
for(TypeParameter tp : methodTypeParameters){
// add it only once
if(names.add(tp.getName())){
// start putting all these type parameters at 0 and then in order
// so that outer type params end up before inner type params but
// order is preserved within each type
qualifyingTypeParameters.add(index++, tp);
qualifyingTypeArguments.put(tp, tp.getType());
}
}
}
}
}
}
}
protected static final class MultidimensionalArray {
public final int dimension;
public final ProducedType type;
MultidimensionalArray(int dimension, ProducedType type){
this.dimension = dimension;
this.type = type;
}
}
protected MultidimensionalArray getMultiDimensionalArrayInfo(ProducedType type) {
int dimension = 0;
while(isJavaObjectArray(type)){
type = type.getTypeArgumentList().get(0);
dimension++;
}
if(dimension == 0)
return null;
return new MultidimensionalArray(dimension, type);
}
public boolean isJavaArray(ProducedType type) {
if(type == null)
return false;
type = simplifyType(type);
if(type == null)
return false;
return isJavaArray(type.getDeclaration());
}
public static boolean isJavaArray(TypeDeclaration decl) {
return Decl.isJavaArray(decl);
}
public boolean isJavaObjectArray(ProducedType type) {
if(type == null)
return false;
type = simplifyType(type);
if(type == null)
return false;
return isJavaObjectArray(type.getDeclaration());
}
public static boolean isJavaObjectArray(TypeDeclaration decl) {
return Decl.isJavaObjectArray(decl);
}
private JCExpression getJavaArrayElementType(ProducedType type, int flags) {
if(type == null)
return makeErroneous(null, "compiler bug: "+ type + " is not a java array");
type = simplifyType(type);
if(type == null || type.getDeclaration() instanceof Class == false)
return makeErroneous(null, "compiler bug: " + type + " is not a java array");
Class c = (Class) type.getDeclaration();
String name = c.getQualifiedNameString();
if(name.equals("java.lang::ObjectArray")){
// fetch its type parameter
if(type.getTypeArgumentList().size() != 1)
return makeErroneous(null, "compiler bug: " + type + " is missing parameter type to java ObjectArray");
ProducedType elementType = type.getTypeArgumentList().get(0);
if(elementType == null)
return makeErroneous(null, "compiler bug: " + type + " has null parameter type to java ObjectArray");
return make().TypeArray(makeJavaType(elementType, flags | JT_TYPE_ARGUMENT));
}else if(name.equals("java.lang::ByteArray")){
return make().TypeArray(make().TypeIdent(TypeTags.BYTE));
}else if(name.equals("java.lang::ShortArray")){
return make().TypeArray(make().TypeIdent(TypeTags.SHORT));
}else if(name.equals("java.lang::IntArray")){
return make().TypeArray(make().TypeIdent(TypeTags.INT));
}else if(name.equals("java.lang::LongArray")){
return make().TypeArray(make().TypeIdent(TypeTags.LONG));
}else if(name.equals("java.lang::FloatArray")){
return make().TypeArray(make().TypeIdent(TypeTags.FLOAT));
}else if(name.equals("java.lang::DoubleArray")){
return make().TypeArray(make().TypeIdent(TypeTags.DOUBLE));
}else if(name.equals("java.lang::BooleanArray")){
return make().TypeArray(make().TypeIdent(TypeTags.BOOLEAN));
}else if(name.equals("java.lang::CharArray")){
return make().TypeArray(make().TypeIdent(TypeTags.CHAR));
}else {
return makeErroneous(null, "compiler bug: " + type + " is an unknown java array type");
}
}
boolean isJavaEnumType(ProducedType type) {
Module jdkBaseModule = loader().getJDKBaseModule();
Package javaLang = jdkBaseModule.getPackage("java.lang");
TypeDeclaration enumDecl = (TypeDeclaration)javaLang.getDirectMember("Enum", null, false);
if (type.getDeclaration().isAnonymous()) {
type = type.getDeclaration().getExtendedType();
}
return type.isSubtypeOf(enumDecl.getProducedType(null, Collections.singletonList(type)));
}
public JCExpression makeParameterisedType(ProducedType type, ProducedType generalType, final int flags,
JCExpression qualifyingExpression, java.util.List<ProducedType> qualifyingTypes,
int firstQualifyingTypeWithTypeParameters, int index) {
JCExpression baseType;
TypeDeclaration tdecl = type.getDeclaration();
ListBuffer<JCExpression> typeArgs = null;
if(index >= firstQualifyingTypeWithTypeParameters) {
int taFlags = flags;
if (qualifyingTypes != null && index < qualifyingTypes.size()) {
// The qualifying types before the main one should
// have type parameters with proper variance
taFlags &= ~(JT_EXTENDS | JT_SATISFIES);
}
typeArgs = makeTypeArgs(
type,
taFlags);
}
if (isCeylonCallable(generalType) &&
(flags & JT_CLASS_NEW) != 0) {
baseType = makeIdent(syms().ceylonAbstractCallableType);
} else if (index == 0) {
// in Ceylon we'd move the nested decl to a companion class
// but in Java we just don't have type params to the qualifying type if the
// qualified type is static
if (tdecl instanceof Interface
&& qualifyingTypes != null
&& qualifyingTypes.size() > 1
&& firstQualifyingTypeWithTypeParameters == 0
&& (flags & JT_NON_QUALIFIED) == 0) {
baseType = naming.makeCompanionClassName(tdecl);
} else {
baseType = naming.makeDeclarationName(tdecl, jtFlagsToDeclNameOpts(flags));
}
} else {
baseType = naming.makeTypeDeclarationExpression(qualifyingExpression, tdecl,
jtFlagsToDeclNameOpts(flags
| JT_NON_QUALIFIED
| (type.getDeclaration() instanceof Interface ? JT_COMPANION : 0)));
}
if (typeArgs != null && typeArgs.size() > 0) {
qualifyingExpression = make().TypeApply(baseType, typeArgs.toList());
} else {
qualifyingExpression = baseType;
}
return qualifyingExpression;
}
private ListBuffer<JCExpression> makeTypeArgs(
ProducedType simpleType,
int flags) {
Map<TypeParameter, ProducedType> tas = simpleType.getTypeArguments();
java.util.List<TypeParameter> tps = simpleType.getDeclaration().getTypeParameters();
return makeTypeArgs(isCeylonCallable(simpleType), flags, tas, tps, simpleType);
}
private ListBuffer<JCExpression> makeTypeArgs(boolean isCeylonCallable,
int flags, Map<TypeParameter, ProducedType> tas,
java.util.List<TypeParameter> tps, ProducedType simpleType) {
boolean onlyErasedUnions = true;
ListBuffer<JCExpression> typeArgs = new ListBuffer<JCExpression>();
for (TypeParameter tp : tps) {
ProducedType ta = tas.get(tp);
// error handling
if(ta == null)
continue;
boolean isDependedOn = hasDependentTypeParameters(tps, tp);
// record whether we were initially working with Anything, because getNonNullType turns it into Object
// and we need to treat "in Anything" specially below
boolean isAnything = isAnything(ta);
// Null will claim to be optional, but if we get its non-null type we will land with Nothing, which is not what
// we want, so we make sure it's not Null
if (isOptional(ta) && !isNull(ta)) {
// For an optional type T?:
// - The Ceylon type Foo<T?> results in the Java type Foo<T>.
ta = getNonNullType(ta);
}
if (typeFact().isUnion(ta) || typeFact().isIntersection(ta)) {
// For any other union type U|V (U nor V is Optional):
// - The Ceylon type Foo<U|V> results in the raw Java type Foo.
// For any other intersection type U&V:
// - The Ceylon type Foo<U&V> results in the raw Java type Foo.
// use raw types if:
// - we're not in a type argument (when used as type arguments raw types have more constraint than at the toplevel)
// or we're in an extends or satisfies and the type parameter is a self type
// Note: it used to be we used raw types when calling constructors, but that was wrong as it did not
// conform with where raw types would be used between expressions and constructors
if(((flags & (JT_EXTENDS | JT_SATISFIES)) != 0 && tp.getSelfTypedDeclaration() != null)){
// A bit ugly, but we need to escape from the loop and create a raw type, no generics
typeArgs = null;
break;
} else if((flags & (__JT_TYPE_ARGUMENT | JT_EXTENDS | JT_SATISFIES)) != 0) {
onlyErasedUnions = false;
}
// otherwise just go on
} else {
onlyErasedUnions = false;
}
if (isCeylonBoolean(ta)
&& !isTypeParameter(ta)) {
ta = typeFact.getBooleanDeclaration().getType();
}
JCExpression jta;
if(!tp.getSatisfiedTypes().isEmpty()){
boolean needsCastForBounds = false;
for(ProducedType bound : tp.getSatisfiedTypes()){
bound = bound.substitute(tas);
needsCastForBounds |= expressionGen().needsCast(ta, bound, false, false, false);
}
if(needsCastForBounds){
// replace with the first bound
ta = tp.getSatisfiedTypes().get(0).substitute(tas);
if(tp.getSatisfiedTypes().size() > 1
|| isBoundsSelfDependant(tp)
|| willEraseToObject(ta)
// we should reject it for all non-covariant types, unless we're in satisfies/extends
|| ((flags & (JT_SATISFIES | JT_EXTENDS)) == 0 && !simpleType.isCovariant(tp))){
// A bit ugly, but we need to escape from the loop and create a raw type, no generics
typeArgs = null;
break;
}
}
}
if (ta.getDeclaration() instanceof NothingType
// if we're in a type argument, extends or satisfies already, union and intersection types should
// use the same erasure rules as bottom: prefer wildcards
|| ((flags & (__JT_TYPE_ARGUMENT | JT_EXTENDS | JT_SATISFIES)) != 0
&& (typeFact().isUnion(ta) || typeFact().isIntersection(ta)))) {
// For the bottom type Bottom:
if ((flags & (JT_CLASS_NEW)) != 0) {
// - The Ceylon type Foo<Bottom> or Foo<erased_type> appearing in an instantiation
// clause results in the Java raw type Foo
// A bit ugly, but we need to escape from the loop and create a raw type, no generics
typeArgs = null;
break;
} else {
// - The Ceylon type Foo<Bottom> appearing in an extends or satisfies location results in the Java type
// Foo<Object> (see https://github.com/ceylon/ceylon-compiler/issues/633 for why)
if((flags & (JT_SATISFIES | JT_EXTENDS)) != 0){
if (ta.getDeclaration() instanceof NothingType) {
jta = make().Type(syms().objectType);
} else {
if (!tp.getSatisfiedTypes().isEmpty()) {
// union or intersection: Use the common upper bound of the types
jta = makeJavaType(tp.getSatisfiedTypes().get(0), JT_TYPE_ARGUMENT);
} else {
jta = make().Type(syms().objectType);
}
}
}else if (ta.getDeclaration() instanceof NothingType){
// - The Ceylon type Foo<Bottom> appearing anywhere else results in the Java type
// - Foo if Foo is contravariant in T (see https://github.com/ceylon/ceylon-compiler/issues/1042), or
// - Foo<? extends Object> if Foo is covariant in T and not depended on by other type params
// - Foo<Object> otherwise
// this is more correct than Foo<?> because a method returning Foo<?> could never override a method returning Foo<Object>
// see https://github.com/ceylon/ceylon-compiler/issues/1003
if (simpleType.isContravariant(tp)) {
typeArgs = null;
break;
} else if (tp.isCovariant() && !isDependedOn) {
// DO NOT trust use-site covariance for Nothing, because we consider "out Nothing" to be the same
// as "Nothing". Only look at declaration-site covariance
jta = make().Wildcard(make().TypeBoundKind(BoundKind.EXTENDS), make().Type(syms().objectType));
} else {
jta = make().Type(syms().objectType);
}
}else{
// - The Ceylon type Foo<T> appearing anywhere else results in the Java type
// - Foo<T> if Foo is invariant in T,
// - Foo<? extends T> if Foo is covariant in T, or
// - Foo<? super T> if Foo is contravariant in T
if (((flags & JT_CLASS_NEW) == 0) && simpleType.isContravariant(tp)) {
jta = make().Wildcard(make().TypeBoundKind(BoundKind.SUPER), makeJavaType(ta, JT_TYPE_ARGUMENT));
} else if (((flags & JT_CLASS_NEW) == 0) && simpleType.isCovariant(tp) && !isDependedOn) {
jta = make().Wildcard(make().TypeBoundKind(BoundKind.EXTENDS), makeJavaType(ta, JT_TYPE_ARGUMENT));
} else {
jta = makeJavaType(ta, JT_TYPE_ARGUMENT);
}
}
}
} else {
// For an ordinary class or interface type T:
if ((flags & (JT_SATISFIES | JT_EXTENDS)) != 0) {
// - The Ceylon type Foo<T> appearing in an extends or satisfies clause
// results in the Java type Foo<T>
jta = makeJavaType(ta, JT_TYPE_ARGUMENT);
} else {
// - The Ceylon type Foo<T> appearing anywhere else results in the Java type
// - Foo<T> if Foo is invariant in T,
// - Foo<? extends T> if Foo is covariant in T, or
// - Foo<? super T> if Foo is contravariant in T
if (((flags & JT_CLASS_NEW) == 0)
&& simpleType.isContravariant(tp)
&& (!isAnything || tp.isContravariant())) {
// DO NOT trust use-site contravariance for Anything, because we consider "in Anything" to be the same
// as "Anything". Only look at declaration-site contravariance
jta = make().Wildcard(make().TypeBoundKind(BoundKind.SUPER), makeJavaType(ta, JT_TYPE_ARGUMENT));
} else if (((flags & JT_CLASS_NEW) == 0) && simpleType.isCovariant(tp) && !isDependedOn) {
jta = make().Wildcard(make().TypeBoundKind(BoundKind.EXTENDS), makeJavaType(ta, JT_TYPE_ARGUMENT));
} else {
jta = makeJavaType(ta, JT_TYPE_ARGUMENT);
}
}
}
typeArgs.add(jta);
if (isCeylonCallable) {
// In the runtime Callable only has a single type param
break;
}
}
if (onlyErasedUnions) {
typeArgs = null;
}
return typeArgs;
}
boolean hasSubstitutedBounds(ProducedType pt){
TypeDeclaration declaration = pt.getDeclaration();
java.util.List<TypeParameter> tps = declaration.getTypeParameters();
Map<TypeParameter, ProducedType> tas = pt.getTypeArguments();
boolean isCallable = isCeylonCallable(pt);
for(TypeParameter tp : tps){
ProducedType ta = tas.get(tp);
// error recovery
if(ta == null)
continue;
if(!tp.getSatisfiedTypes().isEmpty()){
for(ProducedType bound : tp.getSatisfiedTypes()){
bound = bound.substitute(tas);
if(expressionGen().needsCast(ta, bound, false, false, false))
return true;
}
}
if(hasSubstitutedBounds(ta))
return true;
// Callable ignores type parameters after the first
if(isCallable)
break;
}
return false;
}
protected ProducedType getNonNullType(ProducedType pt) {
// typeFact().getDefiniteType() intersects with Object, which isn't
// always right for working with the java type system.
if (typeFact().getAnythingDeclaration().equals(pt.getDeclaration())) {
pt = typeFact().getObjectDeclaration().getType();
}
else {
pt = pt.eliminateNull();
}
return pt;
}
private boolean isJavaString(ProducedType type) {
return "java.lang.String".equals(type.getUnderlyingType());
}
private ClassDefinitionBuilder ccdb;
public ClassDefinitionBuilder current() {
return ((AbstractTransformer)gen()).ccdb;
}
ClassDefinitionBuilder replace(ClassDefinitionBuilder ccdb) {
ClassDefinitionBuilder result = ((AbstractTransformer)gen()).ccdb;
((AbstractTransformer)gen()).ccdb = ccdb;
return result;
}
private DeclNameFlag[] jtFlagsToDeclNameOpts(int flags) {
java.util.List<DeclNameFlag> args = new LinkedList<DeclNameFlag>();
if ((flags & JT_COMPANION) != 0) {
args.add(DeclNameFlag.COMPANION);
}
if ((flags & JT_ANNOTATION) != 0) {
args.add(DeclNameFlag.ANNOTATION);
}
if ((flags & JT_ANNOTATIONS) != 0) {
args.add(DeclNameFlag.ANNOTATIONS);
}
if ((flags & JT_NON_QUALIFIED) == 0) {
args.add(DeclNameFlag.QUALIFIED);
}
DeclNameFlag[] opts = args.toArray(new DeclNameFlag[args.size()]);
return opts;
}
/**
* Gets the first type parameter from the type model representing a
* {@code ceylon.language.Callable<Result, ParameterTypes...>}.
* @param typeModel A {@code ceylon.language.Callable<Result, ParameterTypes...>}.
* @return The result type of the {@code Callable}.
*/
ProducedType getReturnTypeOfCallable(ProducedType typeModel) {
if (!isCeylonCallableSubtype(typeModel)) {
throw new BugException("expected Callable<...>, but was " + typeModel);
}
ProducedType ct = typeModel.getSupertype(typeFact().getCallableDeclaration());
return ct.getTypeArgumentList().get(0);
}
ProducedType getParameterTypeOfCallable(ProducedType callableType, int parameter) {
if (!isCeylonCallableSubtype(callableType)) {
throw new BugException("expected Callable<...>, but was " + callableType);
}
ProducedType tuple = typeFact().getCallableTuple(callableType);
if(tuple != null){
java.util.List<ProducedType> elementTypes = typeFact().getTupleElementTypes(tuple);
if(elementTypes.size() > parameter){
return elementTypes.get(parameter);
}
}
return typeFact().getUnknownType();
}
/**
* Returns true if any part of the given Callable is unknown, like Callable<Ret,Args>
*/
boolean isUnknownArgumentsCallable(ProducedType callableType) {
ProducedType args = typeFact().getCallableTuple(callableType);
return isUnknownTuple(args);
}
private boolean isUnknownTuple(ProducedType args) {
TypeDeclaration declaration = args.getDeclaration();
if (declaration instanceof TypeParameter) {
return true;
} else if (declaration instanceof UnionType){
/* Callable<R,A>&Callable<R,B> is the same as Callable<R,A|B> so
* for a union if either A or B is known then the union is known
*/
java.util.List<ProducedType> caseTypes = declaration.getCaseTypes();
if(caseTypes == null || caseTypes.size() < 2)
return true;
for (int ii = 0; ii < caseTypes.size(); ii++) {
if (!isUnknownTuple(caseTypes.get(ii))) {
return false;
}
}// all unknown
return true;
} else if (declaration instanceof IntersectionType) {
/* Callable<R,A>|Callable<R,B> is the same as Callable<R,A&B> so
* for an intersection if both A and B are known then the intersection is known
*/
java.util.List<ProducedType> caseTypes = declaration.getSatisfiedTypes();
if(caseTypes == null || caseTypes.size() < 2)
return true;
for (int ii = 0; ii < caseTypes.size(); ii++) {
if (isUnknownTuple(caseTypes.get(ii))) {
return true;
}
}
return false;
} else if (declaration instanceof NothingType) {
return true;
} else if(declaration instanceof ClassOrInterface) {
String name = declaration.getQualifiedNameString();
if(name.equals("ceylon.language::Tuple")){
ProducedType rest = args.getTypeArgumentList().get(2);
return isUnknownTuple(rest);
}
if(name.equals("ceylon.language::Empty")){
return false;
}
if(name.equals("ceylon.language::Sequential")
|| name.equals("ceylon.language::Sequence")){
return false;
}
} else if (declaration instanceof TypeAlias) {
return isUnknownTuple(args.resolveAliases());
}
return true;
}
int getNumParametersOfCallable(ProducedType callableType) {
ProducedType tuple = typeFact().getCallableTuple(callableType);
int simpleNumParametersOfCallable = getSimpleNumParametersOfCallable(tuple);
if(simpleNumParametersOfCallable != -1)
return simpleNumParametersOfCallable;
int count = 0;
while (tuple != null) {
ProducedType tst = typeFact().nonemptyArgs(tuple).getSupertype(typeFact().getTupleDeclaration());
if (tst!=null) {
java.util.List<ProducedType> tal = tst.getTypeArgumentList();
if (tal.size()>=3) {
tuple = tal.get(2);
count++;
continue;
}
}
else if (typeFact().isEmptyType(tuple)) {
// do nothing
}
else if (typeFact().isSequentialType(tuple)) {
count++; // we count variadic params as one
}
break;
}
return count;
}
private int getSimpleNumParametersOfCallable(ProducedType args) {
// can be a defaulted tuple of Empty|Tuple
TypeDeclaration declaration = args.getDeclaration();
if(declaration instanceof UnionType){
java.util.List<ProducedType> caseTypes = declaration.getCaseTypes();
if(caseTypes == null || caseTypes.size() != 2)
return -1;
ProducedType caseA = caseTypes.get(0);
TypeDeclaration caseADecl = caseA.getDeclaration();
ProducedType caseB = caseTypes.get(1);
TypeDeclaration caseBDecl = caseB.getDeclaration();
if(caseADecl instanceof ClassOrInterface == false
|| caseBDecl instanceof ClassOrInterface == false)
return -1;
if(caseADecl.getQualifiedNameString().equals("ceylon.language::Empty")
&& caseBDecl.getQualifiedNameString().equals("ceylon.language::Tuple"))
return getSimpleNumParametersOfCallable(caseB);
if(caseBDecl.getQualifiedNameString().equals("ceylon.language::Empty")
&& caseADecl.getQualifiedNameString().equals("ceylon.language::Tuple"))
return getSimpleNumParametersOfCallable(caseA);
return -1;
}
// can be Tuple, Empty, Sequence or Sequential
if(declaration instanceof ClassOrInterface == false)
return -1;
String name = declaration.getQualifiedNameString();
if(name.equals("ceylon.language::Tuple")){
ProducedType rest = args.getTypeArgumentList().get(2);
int ret = getSimpleNumParametersOfCallable(rest);
if(ret == -1)
return -1;
return ret + 1;
}
if(name.equals("ceylon.language::Empty")){
return 0;
}
if(name.equals("ceylon.language::Sequential")
|| name.equals("ceylon.language::Sequence")){
return 1;
}
return -1;
}
boolean isVariadicCallable(ProducedType callableType) {
ProducedType tuple = typeFact().getCallableTuple(callableType);
return typeFact().isTupleLengthUnbounded(tuple);
}
public int getMinimumParameterCountForCallable(ProducedType callableType) {
ProducedType tuple = typeFact().getCallableTuple(callableType);
return typeFact().getTupleMinimumLength(tuple);
}
/**
* Return the upper bound of any type parameter, instead of the type
* parameter itself
*/
static final int TP_TO_BOUND = 1<<0;
/**
* Return the type of the sequenced parameter (T[]) rather than its element type (T)
*/
static final int TP_SEQUENCED_TYPE = 1<<1;
ProducedType getTypeForParameter(Parameter parameter, ProducedReference producedReference, int flags) {
/* this method is bogus: It's really trying to answer
* "what's the type of the java declaration of the given parameter",
* but using the ceylon type system to do so.
*/
boolean functional = parameter.getModel() instanceof Method;
if (producedReference == null) {
return parameter.getType();
}
final ProducedTypedReference producedTypedReference = producedReference.getTypedParameter(parameter);
final ProducedType type = functional ? producedTypedReference.getFullType() : producedTypedReference.getType();
final TypedDeclaration producedParameterDecl = producedTypedReference.getDeclaration();
final ProducedType declType = producedParameterDecl.getType();
// be more resilient to upstream errors
if(declType == null)
return typeFact.getUnknownType();
final TypeDeclaration declTypeDecl = declType.getDeclaration();
if(isJavaVariadic(parameter) && (flags & TP_SEQUENCED_TYPE) == 0){
// type of param must be Iterable<T>
ProducedType elementType = typeFact.getIteratedType(type);
if(elementType == null){
log.error("ceylon", "Invalid type for Java variadic parameter: "+type.getProducedTypeQualifiedName());
return type;
}
return elementType;
}
if (declTypeDecl instanceof ClassOrInterface) {
return type;
} else if ((declTypeDecl instanceof TypeParameter)
&& (flags & TP_TO_BOUND) != 0) {
if(!declTypeDecl.getSatisfiedTypes().isEmpty()){
// use upper bound
ProducedType upperBound = declTypeDecl.getSatisfiedTypes().get(0);
// make sure we apply the type arguments
upperBound = substituteTypeArgumentsForTypeParameterBound(producedReference, upperBound);
ProducedType self = upperBound.getDeclaration().getSelfType();
if (self != null) {
// make sure we apply the type arguments
ProducedType selfUpperBound = self.substitute(upperBound.getTypeArguments());
if (!willEraseToObject(selfUpperBound)
&& (willEraseToObject(type) || expressionGen().needsCast(type, selfUpperBound, false, false, false))) {
return selfUpperBound;
}
}
if (!willEraseToObject(upperBound)
&& (willEraseToObject(type) || expressionGen().needsCast(type, upperBound, false, false, false))) {
return upperBound;
}
}
}
return type;
}
protected ProducedType substituteTypeArgumentsForTypeParameterBound(
ProducedReference target, ProducedType bound) {
Declaration declaration = target.getDeclaration();
if(declaration.getContainer() instanceof ClassOrInterface){
ProducedType targetType = target.getQualifyingType();
// static methods have a container but do not capture type parameters
if(targetType != null
&& !declaration.isStaticallyImportable()){
ClassOrInterface methodContainer = (ClassOrInterface) declaration.getContainer();
Map<TypeParameter, ProducedType> typeArguments = targetType.getSupertype(methodContainer).getTypeArguments();
// we need type arguments that may come from the method container
bound = bound.substitute(typeArguments);
}
}
// and those that may come from the method call itself
return bound.substitute(target.getTypeArguments());
}
private boolean isJavaVariadic(Parameter parameter) {
return parameter.isSequenced()
&& parameter.getDeclaration() instanceof Method
&& isJavaMethod((Method) parameter.getDeclaration());
}
boolean isJavaMethod(Method method) {
ClassOrInterface container = Decl.getClassOrInterfaceContainer(method);
return container != null && !Decl.isCeylon(container);
}
boolean isJavaCtor(Class cls) {
return !Decl.isCeylon(cls);
}
ProducedType getTypeForFunctionalParameter(Method fp) {
return fp.getProducedTypedReference(null, java.util.Collections.<ProducedType>emptyList()).getFullType();
}
/*
* Annotation generation
*/
List<JCAnnotation> makeAtCompileTimeError() {
return List.of(make().Annotation(makeIdent(syms().ceylonAtCompileTimeErrorType), List.<JCExpression> nil()));
}
List<JCAnnotation> makeAtOverride() {
return List.<JCAnnotation> of(make().Annotation(makeIdent(syms().overrideType), List.<JCExpression> nil()));
}
int checkCompilerAnnotations(Tree.Declaration decl, ListBuffer<JCTree> result){
int old = gen().disableAnnotations;
if(CodegenUtil.hasCompilerAnnotation(decl, "noanno")) {
gen().disableAnnotations = CeylonTransformer.DISABLE_MODEL_ANNOS | CeylonTransformer.DISABLE_USER_ANNOS;
}
if(CodegenUtil.hasCompilerAnnotation(decl, "nomodel"))
gen().disableAnnotations = CeylonTransformer.DISABLE_MODEL_ANNOS;
if(CodegenUtil.hasCompilerAnnotation(decl, "erroneous")) {
String message = CodegenUtil.getCompilerAnnotationArgument(decl, "erroneous");
result.append(gen().makeErroneous(decl, message));
}
return old;
}
void resetCompilerAnnotations(int value){
gen().disableAnnotations = value;
}
private List<JCAnnotation> makeModelAnnotation(Type annotationType, List<JCExpression> annotationArgs) {
if ((gen().disableAnnotations & CeylonTransformer.DISABLE_MODEL_ANNOS) != 0)
return List.nil();
return List.of(make().Annotation(makeIdent(annotationType), annotationArgs));
}
private List<JCAnnotation> makeAnnoAnnotation(Type annotationType, List<JCExpression> annotationArgs) {
return List.of(make().Annotation(makeIdent(annotationType), annotationArgs));
}
private List<JCAnnotation> makeModelAnnotation(Type annotationType) {
return makeModelAnnotation(annotationType, List.<JCExpression>nil());
}
List<JCAnnotation> makeAtCeylon() {
JCExpression majorAttribute = make().Assign(naming.makeUnquotedIdent("major"), make().Literal(Versions.JVM_BINARY_MAJOR_VERSION));
List<JCExpression> annotationArgs;
if(Versions.JVM_BINARY_MINOR_VERSION != 0){
JCExpression minorAttribute = make().Assign(naming.makeUnquotedIdent("minor"), make().Literal(Versions.JVM_BINARY_MINOR_VERSION));
annotationArgs = List.<JCExpression>of(majorAttribute, minorAttribute);
}else{
// keep the minor implicit value of 0 to reduce bytecode size
annotationArgs = List.<JCExpression>of(majorAttribute);
}
return makeModelAnnotation(syms().ceylonAtCeylonType, annotationArgs);
}
List<JCAnnotation> makeAtDynamic() {
return makeModelAnnotation(syms().ceylonAtDynamicType);
}
/** Returns a ListBuffer with assignment expressions for the doc, license and by arguments, as well as name,
* to be used in an annotation which requires them (such as Module and Package) */
ListBuffer<JCExpression> getLicenseAuthorsDocAnnotationArguments(String name, java.util.List<Annotation> anns) {
ListBuffer<JCExpression> authors = new ListBuffer<JCTree.JCExpression>();
ListBuffer<JCExpression> res = new ListBuffer<JCExpression>();
res.add(make().Assign(naming.makeUnquotedIdent("name"), make().Literal(name)));
for (Annotation a : anns) {
if (a.getPositionalArguments() != null && !a.getPositionalArguments().isEmpty()) {
if (a.getName().equals("doc")) {
res.add(make().Assign(naming.makeUnquotedIdent("doc"),
make().Literal(a.getPositionalArguments().get(0))));
} else if (a.getName().equals("license")) {
res.add(make().Assign(naming.makeUnquotedIdent("license"),
make().Literal(a.getPositionalArguments().get(0))));
} else if (a.getName().equals("by")) {
for (String author : a.getPositionalArguments()) {
authors.add(make().Literal(author));
}
}
}
}
if (!authors.isEmpty()) {
res.add(make().Assign(naming.makeUnquotedIdent("by"), make().NewArray(null, null, authors.toList())));
}
return res;
}
List<JCAnnotation> makeAtModule(Module module) {
ListBuffer<JCExpression> imports = new ListBuffer<JCTree.JCExpression>();
for(ModuleImport dependency : module.getImports()){
Module dependencyModule = dependency.getModule();
// do not include the implicit language module as a dependency
if(dependencyModule.getNameAsString().equals(AbstractModelLoader.CEYLON_LANGUAGE))
continue;
JCExpression dependencyName = make().Assign(naming.makeUnquotedIdent("name"),
make().Literal(dependencyModule.getNameAsString()));
JCExpression dependencyVersion = null;
if(dependencyModule.getVersion() != null)
dependencyVersion = make().Assign(naming.makeUnquotedIdent("version"),
make().Literal(dependencyModule.getVersion()));
List<JCExpression> spec;
if(dependencyVersion != null)
spec = List.<JCExpression>of(dependencyName, dependencyVersion);
else
spec = List.<JCExpression>of(dependencyName);
if (Util.getAnnotation(dependency, "shared") != null) {
JCExpression exported = make().Assign(naming.makeUnquotedIdent("export"), make().Literal(true));
spec = spec.append(exported);
}
if (Util.getAnnotation(dependency, "optional") != null) {
JCExpression exported = make().Assign(naming.makeUnquotedIdent("optional"), make().Literal(true));
spec = spec.append(exported);
}
JCAnnotation atImport = make().Annotation(makeIdent(syms().ceylonAtImportType), spec);
imports.add(atImport);
}
ListBuffer<JCExpression> annotationArgs = getLicenseAuthorsDocAnnotationArguments(
module.getNameAsString(), module.getAnnotations());
annotationArgs.add(make().Assign(naming.makeUnquotedIdent("version"), make().Literal(module.getVersion())));
annotationArgs.add(make().Assign(naming.makeUnquotedIdent("dependencies"),
make().NewArray(null, null, imports.toList())));
return makeModelAnnotation(syms().ceylonAtModuleType, annotationArgs.toList());
}
List<JCAnnotation> makeAtPackage(Package pkg) {
ListBuffer<JCExpression> annotationArgs = getLicenseAuthorsDocAnnotationArguments(
pkg.getNameAsString(), pkg.getAnnotations());
annotationArgs.add(make().Assign(naming.makeUnquotedIdent("shared"), makeBoolean(pkg.isShared())));
return makeModelAnnotation(syms().ceylonAtPackageType, annotationArgs.toList());
}
List<JCAnnotation> makeAtName(String name) {
return makeModelAnnotation(syms().ceylonAtNameType, List.<JCExpression>of(make().Literal(name)));
}
List<JCAnnotation> makeAtAlias(ProducedType type) {
String name = serialiseTypeSignature(type);
return makeModelAnnotation(syms().ceylonAtAliasType, List.<JCExpression>of(make().Literal(name)));
}
List<JCAnnotation> makeAtTypeAlias(ProducedType type) {
String name = serialiseTypeSignature(type);
return makeModelAnnotation(syms().ceylonAtTypeAliasType, List.<JCExpression>of(make().Literal(name)));
}
final JCAnnotation makeAtTypeParameter(String name, java.util.List<ProducedType> satisfiedTypes, java.util.List<ProducedType> caseTypes,
boolean covariant, boolean contravariant, ProducedType defaultValue) {
ListBuffer<JCExpression> attributes = new ListBuffer<JCExpression>();
// name
attributes.add(make().Assign(naming.makeUnquotedIdent("value"), make().Literal(name)));
// variance
String variance = "NONE";
if(covariant)
variance = "OUT";
else if(contravariant)
variance = "IN";
JCExpression varianceAttribute = make().Assign(naming.makeUnquotedIdent("variance"),
make().Select(makeIdent(syms().ceylonVarianceType), names().fromString(variance)));
attributes.add(varianceAttribute);
// upper bounds
ListBuffer<JCExpression> upperBounds = new ListBuffer<JCTree.JCExpression>();
for(ProducedType satisfiedType : satisfiedTypes){
String type = serialiseTypeSignature(satisfiedType);
upperBounds.append(make().Literal(type));
}
JCExpression satisfiesAttribute = make().Assign(naming.makeUnquotedIdent("satisfies"),
make().NewArray(null, null, upperBounds.toList()));
attributes.add(satisfiesAttribute);
// case types
ListBuffer<JCExpression> caseTypesExpressions = new ListBuffer<JCTree.JCExpression>();
if(caseTypes != null){
for(ProducedType caseType : caseTypes){
String type = serialiseTypeSignature(caseType);
caseTypesExpressions.append(make().Literal(type));
}
}
JCExpression caseTypeAttribute = make().Assign(naming.makeUnquotedIdent("caseTypes"),
make().NewArray(null, null, caseTypesExpressions.toList()));
attributes.add(caseTypeAttribute);
if(defaultValue != null){
attributes.add(make().Assign(naming.makeUnquotedIdent("defaultValue"), make().Literal(serialiseTypeSignature(defaultValue))));
}
// all done
return make().Annotation(makeIdent(syms().ceylonAtTypeParameter), attributes.toList());
}
List<JCAnnotation> makeAtTypeParameters(List<JCExpression> typeParameters) {
JCExpression value = make().NewArray(null, null, typeParameters);
return makeModelAnnotation(syms().ceylonAtTypeParameters, List.of(value));
}
List<JCAnnotation> makeAtSequenced() {
return makeModelAnnotation(syms().ceylonAtSequencedType);
}
List<JCAnnotation> makeAtFunctionalParameter(String value) {
return makeModelAnnotation(syms().ceylonAtFunctionalParameterType,
List.<JCExpression>of(make().Literal(value)));
}
List<JCAnnotation> makeAtDefaulted() {
return makeModelAnnotation(syms().ceylonAtDefaultedType);
}
List<JCAnnotation> makeAtAttribute(JCExpression setterClass) {
List<JCExpression> attributes = List.nil();
if (setterClass != null) {
JCExpression setterClassAttribute = make().Assign(naming.makeUnquotedIdent("setterClass"), setterClass);
attributes = attributes.prepend(setterClassAttribute);
}
return makeModelAnnotation(syms().ceylonAtAttributeType, attributes);
}
List<JCAnnotation> makeAtSetter(JCExpression setterClass) {
List<JCExpression> attributes = List.nil();
if (setterClass != null) {
JCExpression setterClassAttribute = make().Assign(naming.makeUnquotedIdent("getterClass"), setterClass);
attributes = attributes.prepend(setterClassAttribute);
}
return makeModelAnnotation(syms().ceylonAtSetterType, attributes);
}
List<JCAnnotation> makeAtAttribute() {
return makeModelAnnotation(syms().ceylonAtAttributeType);
}
List<JCAnnotation> makeAtMethod() {
return makeModelAnnotation(syms().ceylonAtMethodType);
}
List<JCAnnotation> makeAtObject() {
return makeModelAnnotation(syms().ceylonAtObjectType);
}
List<JCAnnotation> makeAtClass(ProducedType thisType, ProducedType extendedType) {
List<JCExpression> attributes = List.nil();
JCExpression extendsValue = null;
if (extendedType == null) {
extendsValue = make().Literal("");
} else if (!extendedType.isExactly(typeFact.getBasicDeclaration().getType())){
extendsValue = make().Literal(serialiseTypeSignature(extendedType));
}
if (extendsValue != null) {
JCExpression extendsAttribute = make().Assign(naming.makeUnquotedIdent("extendsType"), extendsValue);
attributes = attributes.prepend(extendsAttribute);
}
boolean isBasic = true;
boolean isIdentifiable = true;
if(extendedType == null){
// special for Anything
isBasic = isIdentifiable = false;
}else if(thisType != null){
isBasic = thisType.getSupertype(typeFact.getBasicDeclaration()) != null;
// if isBasic, then isIdentifiable remains true
if(!isBasic)
isIdentifiable = thisType.getSupertype(typeFact.getIdentifiableDeclaration()) != null;
}
if (!isBasic) {
JCExpression basicAttribute = make().Assign(naming.makeUnquotedIdent("basic"), makeBoolean(false));
attributes = attributes.prepend(basicAttribute);
}
if (!isIdentifiable) {
JCExpression identifiableAttribute = make().Assign(naming.makeUnquotedIdent("identifiable"), makeBoolean(false));
attributes = attributes.prepend(identifiableAttribute);
}
return makeModelAnnotation(syms().ceylonAtClassType, attributes);
}
List<JCAnnotation> makeAtSatisfiedTypes(java.util.List<ProducedType> satisfiedTypes) {
JCExpression attrib = makeTypesListAttr(satisfiedTypes);
if (attrib != null) {
return makeModelAnnotation(syms().ceylonAtSatisfiedTypes, List.of(attrib));
} else {
return List.nil();
}
}
List<JCAnnotation> makeAtCaseTypes(java.util.List<ProducedType> caseTypes, ProducedType ofType) {
List<JCExpression> attribs = List.nil();
if (ofType != null) {
JCExpression ofAttr = makeOfTypeAttr(ofType);
attribs = attribs.append(ofAttr);
} else {
if (caseTypes != null && !caseTypes.isEmpty()) {
JCExpression casesAttr = makeTypesListAttr(caseTypes);
attribs = attribs.append(casesAttr);
}
}
if (!attribs.isEmpty()) {
return makeModelAnnotation(syms().ceylonAtCaseTypes, attribs);
} else {
return List.nil();
}
}
private JCExpression makeTypesListAttr(java.util.List<ProducedType> types) {
if(types.isEmpty())
return null;
ListBuffer<JCExpression> upperBounds = new ListBuffer<JCTree.JCExpression>();
for(ProducedType type : types){
String typeSig = serialiseTypeSignature(type);
upperBounds.append(make().Literal(typeSig));
}
JCExpression caseAttribute = make().Assign(naming.makeUnquotedIdent("value"),
make().NewArray(null, null, upperBounds.toList()));
return caseAttribute;
}
private JCExpression makeOfTypeAttr(ProducedType ofType) {
if(ofType == null)
return null;
String typeSig = serialiseTypeSignature(ofType);
JCExpression ofAttribute = make().Assign(naming.makeUnquotedIdent("of"),
make().Literal(typeSig));
return ofAttribute;
}
List<JCAnnotation> makeAtIgnore() {
return makeModelAnnotation(syms().ceylonAtIgnore);
}
List<JCAnnotation> makeAtTransient() {
return makeModelAnnotation(syms().ceylonAtTransientType);
}
List<JCAnnotation> makeAtAnnotations(java.util.List<Annotation> annotations) {
if(annotations == null || annotations.isEmpty())
return List.nil();
ListBuffer<JCExpression> array = new ListBuffer<JCTree.JCExpression>();
for(Annotation annotation : annotations){
array.append(makeAtAnnotation(annotation));
}
JCExpression annotationsAttribute = make().Assign(naming.makeUnquotedIdent("value"),
make().NewArray(null, null, array.toList()));
return makeModelAnnotation(syms().ceylonAtAnnotationsType, List.of(annotationsAttribute));
}
private JCExpression makeAtAnnotation(Annotation annotation) {
JCExpression valueAttribute = make().Assign(naming.makeUnquotedIdent("value"),
make().Literal(annotation.getName()));
List<JCExpression> attributes;
if(!annotation.getPositionalArguments().isEmpty()){
java.util.List<String> positionalArguments = annotation.getPositionalArguments();
ListBuffer<JCExpression> array = new ListBuffer<JCTree.JCExpression>();
for(String val : positionalArguments)
array.add(make().Literal(val));
JCExpression argumentsAttribute = make().Assign(naming.makeUnquotedIdent("arguments"),
make().NewArray(null, null, array.toList()));
attributes = List.of(valueAttribute, argumentsAttribute);
}else if(!annotation.getNamedArguments().isEmpty()){
Map<String, String> namedArguments = annotation.getNamedArguments();
ListBuffer<JCExpression> array = new ListBuffer<JCTree.JCExpression>();
for(Entry<String, String> entry : namedArguments.entrySet()){
JCExpression argNameAttribute = make().Assign(naming.makeUnquotedIdent("name"),
make().Literal(entry.getKey()));
JCExpression argValueAttribute = make().Assign(naming.makeUnquotedIdent("value"),
make().Literal(entry.getValue()));
JCAnnotation namedArg = make().Annotation(makeIdent(syms().ceylonAtNamedArgumentType),
List.of(argNameAttribute, argValueAttribute));
array.add(namedArg);
}
JCExpression argumentsAttribute = make().Assign(naming.makeUnquotedIdent("namedArguments"),
make().NewArray(null, null, array.toList()));
attributes = List.of(valueAttribute, argumentsAttribute);
}else
attributes = List.of(valueAttribute);
return make().Annotation(makeIdent(syms().ceylonAtAnnotationType), attributes);
}
List<JCAnnotation> makeAtContainer(ProducedType type) {
JCExpression classAttribute = make().Assign(naming.makeUnquotedIdent("klass"),
makeClassLiteral(type));
List<JCExpression> attributes = List.of(classAttribute);
return makeModelAnnotation(syms().ceylonAtContainerType, attributes);
}
List<JCAnnotation> makeAtLocalDeclaration(String qualifier, boolean skipContainerClass) {
List<JCExpression> attributes = List.nil();
if(qualifier != null && !qualifier.isEmpty()){
JCExpression scopeAttribute = make().Assign(naming.makeUnquotedIdent("qualifier"),
make().Literal(qualifier));
attributes = List.of(scopeAttribute);
}
if(skipContainerClass){
JCExpression skipAttribute = make().Assign(naming.makeUnquotedIdent("isPackageLocal"),
make().Literal(true));
attributes = attributes.prepend(skipAttribute);
}
return makeModelAnnotation(syms().ceylonAtLocalDeclarationType, attributes);
}
JCAnnotation makeAtMember(ProducedType type) {
JCExpression classAttribute = make().Assign(naming.makeUnquotedIdent("klass"),
makeClassLiteral(type));
List<JCExpression> attributes = List.of(classAttribute);
return make().Annotation(makeIdent(syms().ceylonAtMemberType), attributes);
}
List<JCAnnotation> makeAtMembers(List<JCExpression> members) {
if(members.isEmpty())
return List.nil();
JCExpression attr = make().Assign(naming.makeUnquotedIdent("value"),
make().NewArray(null, null, members));
return makeModelAnnotation(syms().ceylonAtMembersType, List.of(attr));
}
private List<JCAnnotation> makeAtLocalDeclarations(Set<String> localDeclarations, Set<Interface> localInterfaces) {
if(localDeclarations.isEmpty() && localInterfaces.isEmpty())
return List.nil();
ListBuffer<JCExpression> array = new ListBuffer<JCTree.JCExpression>();
// sort them to get the same behaviour on every JDK
SortedSet<String> sortedNames = new TreeSet<String>();
sortedNames.addAll(localDeclarations);
for(Interface iface : localInterfaces){
sortedNames.add("::"+naming.makeTypeDeclarationName(iface));
}
for(String val : sortedNames)
array.add(make().Literal(val));
JCExpression attr = make().Assign(naming.makeUnquotedIdent("value"),
make().NewArray(null, null, array.toList()));
return makeModelAnnotation(syms().ceylonAtLocalDeclarationsType, List.of(attr));
}
protected List<JCAnnotation> makeAtLocalContainer(List<String> path, String companionClassName) {
if(path.isEmpty())
return List.nil();
ListBuffer<JCExpression> array = new ListBuffer<JCTree.JCExpression>();
for(String val : path)
array.add(make().Literal(val));
JCExpression pathAttr = make().Assign(naming.makeUnquotedIdent("path"),
make().NewArray(null, null, array.toList()));
JCExpression companionAttr = make().Assign(naming.makeUnquotedIdent("companionClassName"),
make().Literal(companionClassName == null ? "" : companionClassName));
return makeModelAnnotation(syms().ceylonAtLocalContainerType, List.of(pathAttr, companionAttr));
}
protected List<JCAnnotation> makeAtLocalDeclarations(Node tree) {
return makeAtLocalDeclarations(tree, null);
}
protected List<JCAnnotation> makeAtLocalDeclarations(Node tree1, Node tree2) {
LocalTypeVisitor visitor = new LocalTypeVisitor();
tree1.visitChildren(visitor);
if(tree2 != null)
tree2.visitChildren(visitor);
java.util.Set<String> locals = visitor.getLocals();
java.util.Set<Interface> localInterfaces = visitor.getLocalInterfaces();
return makeAtLocalDeclarations(locals, localInterfaces);
}
private List<JCAnnotation> makeAtAnnotationValue(Type annotationType, String name, JCExpression values) {
if (name == null) {
return makeAnnoAnnotation(annotationType, List.<JCExpression>of(values));
} else {
return makeAnnoAnnotation(annotationType, List.<JCExpression>of(
make().Assign(naming.makeUnquotedIdent("name"), make().Literal(name)),
make().Assign(naming.makeUnquotedIdent("value"), values)));
}
}
private List<JCAnnotation> makeAtAnnotationExprs(Type annotationType, List<JCExpression> value) {
return makeAnnoAnnotation(annotationType, value);
}
List<JCAnnotation> makeAtObjectValue(String name, JCExpression values) {
return makeAtAnnotationValue(syms().ceylonAtObjectValueType, name, values);
}
List<JCAnnotation> makeAtObjectExprs(JCExpression values) {
return makeAtAnnotationExprs(syms().ceylonAtObjectExprsType, List.<JCExpression>of(values));
}
List<JCAnnotation> makeAtStringValue(String name, JCExpression values) {
return makeAtAnnotationValue(syms().ceylonAtStringValueType, name, values);
}
List<JCAnnotation> makeAtStringExprs(JCExpression values) {
return makeAtAnnotationExprs(syms().ceylonAtStringExprsType, List.<JCExpression>of(values));
}
List<JCAnnotation> makeAtCharacterValue(String name, JCExpression values) {
return makeAtAnnotationValue(syms().ceylonAtCharacterValueType, name, values);
}
List<JCAnnotation> makeAtCharacterExprs(JCExpression values) {
return makeAtAnnotationExprs(syms().ceylonAtCharacterExprsType, List.<JCExpression>of(values));
}
List<JCAnnotation> makeAtBooleanValue(String name, JCExpression value) {
return makeAtAnnotationValue(syms().ceylonAtBooleanValueType, name, value);
}
List<JCAnnotation> makeAtBooleanExprs(JCExpression value) {
return makeAtAnnotationExprs(syms().ceylonAtBooleanExprsType, List.<JCExpression>of(value));
}
List<JCAnnotation> makeAtFloatValue(String name, JCExpression value) {
return makeAtAnnotationValue(syms().ceylonAtFloatValueType, name, value);
}
List<JCAnnotation> makeAtFloatExprs(JCExpression value) {
return makeAtAnnotationExprs(syms().ceylonAtFloatExprsType, List.<JCExpression>of(value));
}
List<JCAnnotation> makeAtIntegerValue(String name, JCExpression value) {
return makeAtAnnotationValue(syms().ceylonAtIntegerValueType, name, value);
}
List<JCAnnotation> makeAtIntegerExprs(JCExpression value) {
return makeAtAnnotationExprs(syms().ceylonAtIntegerExprsType, List.<JCExpression>of(value));
}
List<JCAnnotation> makeAtDeclarationValue(String name, JCExpression value) {
return makeAtAnnotationValue(syms().ceylonAtDeclarationValueType, name, value);
}
List<JCAnnotation> makeAtDeclarationExprs(JCExpression value) {
return makeAtAnnotationExprs(syms().ceylonAtDeclarationExprsType, List.<JCExpression>of(value));
}
List<JCAnnotation> makeAtParameterValue(JCExpression value) {
return makeAnnoAnnotation(syms().ceylonAtParameterValueType, List.<JCExpression>of(value));
}
/** Determine whether the given declaration requires a
* {@code @TypeInfo} annotation
*/
private boolean needsJavaTypeAnnotations(Declaration decl) {
Declaration reqdecl = decl;
if (reqdecl instanceof MethodOrValue
&& ((MethodOrValue)reqdecl).isParameter()) {
reqdecl = CodegenUtil.getParameterized(((MethodOrValue)reqdecl));
}
if (reqdecl instanceof TypeDeclaration) {
return true;
} else { // TypedDeclaration
return !Decl.isLocal(reqdecl);
}
}
List<JCTree.JCAnnotation> makeJavaTypeAnnotations(TypedDeclaration decl) {
return makeJavaTypeAnnotations(decl, true);
}
List<JCTree.JCAnnotation> makeJavaTypeAnnotations(TypedDeclaration decl, boolean handleFunctionalParameter) {
if(decl == null || decl.getType() == null)
return List.nil();
ProducedType type;
if (decl instanceof Method && ((Method)decl).isParameter() && handleFunctionalParameter) {
type = getTypeForFunctionalParameter((Method)decl);
} else if (decl instanceof Functional && Decl.isMpl((Functional)decl)) {
type = getReturnTypeOfCallable(decl.getProducedTypedReference(null, Collections.<ProducedType>emptyList()).getFullType());
} else {
type = decl.getType();
}
boolean declaredVoid = decl instanceof Method && Strategy.useBoxedVoid((Method)decl) && Decl.isUnboxedVoid(decl);
return makeJavaTypeAnnotations(type, declaredVoid,
CodegenUtil.hasTypeErased(decl),
CodegenUtil.hasUntrustedType(decl),
needsJavaTypeAnnotations(decl));
}
private List<JCTree.JCAnnotation> makeJavaTypeAnnotations(ProducedType type, boolean declaredVoid,
boolean hasTypeErased, boolean untrusted, boolean required) {
if (!required)
return List.nil();
String name = serialiseTypeSignature(type);
boolean erased = hasTypeErased || hasErasure(type);
// Add the original type to the annotations
ListBuffer<JCExpression> annotationArgs = ListBuffer.<JCExpression>lb();
annotationArgs.add(
make().Assign(naming.makeUnquotedIdent("value"), make().Literal(name)));
if (erased) {
annotationArgs.add(
make().Assign(naming.makeUnquotedIdent("erased"), make().Literal(erased)));
}
if (declaredVoid) {
annotationArgs.add(
make().Assign(naming.makeUnquotedIdent("declaredVoid"), make().Literal(declaredVoid)));
}
if (untrusted) {
annotationArgs.add(
make().Assign(naming.makeUnquotedIdent("untrusted"), make().Literal(untrusted)));
}
return makeModelAnnotation(syms().ceylonAtTypeInfoType, annotationArgs.toList());
}
private String serialiseTypeSignature(ProducedType type){
// resolve aliases
type = type.resolveAliases();
return typeSerialiser.getProducedTypeName(type, typeFact);
}
/*
* Boxing
*/
public enum BoxingStrategy {
UNBOXED, BOXED, INDIFFERENT;
}
public boolean canUnbox(ProducedType type){
// all the rest is boxed
return isCeylonBasicType(type) || isJavaString(type);
}
JCExpression boxUnboxIfNecessary(JCExpression javaExpr, Tree.Term expr,
ProducedType exprType,
BoxingStrategy boxingStrategy) {
boolean exprBoxed = !CodegenUtil.isUnBoxed(expr);
return boxUnboxIfNecessary(javaExpr, exprBoxed, exprType, boxingStrategy);
}
JCExpression boxUnboxIfNecessary(JCExpression javaExpr, boolean exprBoxed,
ProducedType exprType,
BoxingStrategy boxingStrategy) {
if(boxingStrategy == BoxingStrategy.INDIFFERENT)
return javaExpr;
boolean targetBoxed = boxingStrategy == BoxingStrategy.BOXED;
// only box if the two differ
if(targetBoxed == exprBoxed)
return javaExpr;
if (targetBoxed) {
// box
javaExpr = boxType(javaExpr, exprType);
} else {
// unbox
javaExpr = unboxType(javaExpr, exprType);
}
return javaExpr;
}
boolean isTypeParameter(ProducedType type) {
if(type == null)
return false;
if (typeFact().isOptionalType(type)) {
type = type.eliminateNull();
}
return type.getDeclaration() instanceof TypeParameter;
}
JCExpression unboxType(JCExpression expr, ProducedType exprType) {
if (isCeylonInteger(exprType)) {
expr = unboxInteger(expr);
} else if (isCeylonFloat(exprType)) {
expr = unboxFloat(expr);
} else if (isCeylonString(exprType)) {
expr = unboxString(expr);
} else if (isCeylonCharacter(exprType)) {
boolean isJavaCharacter = exprType.getUnderlyingType() != null;
expr = unboxCharacter(expr, isJavaCharacter);
} else if (isCeylonByte(exprType)) {
expr = unboxByte(expr);
} else if (isCeylonBoolean(exprType)) {
expr = unboxBoolean(expr);
} else if (isOptional(exprType)) {
exprType = typeFact().getDefiniteType(exprType);
if (isCeylonString(exprType)){
expr = unboxOptionalString(expr);
}
}
return expr;
}
JCExpression boxType(JCExpression expr, ProducedType exprType) {
if (isCeylonInteger(exprType)) {
expr = boxInteger(expr);
} else if (isCeylonFloat(exprType)) {
expr = boxFloat(expr);
} else if (isCeylonString(exprType)) {
expr = boxString(expr);
} else if (isCeylonCharacter(exprType)) {
expr = boxCharacter(expr);
} else if (isCeylonByte(exprType)) {
expr = boxByte(expr);
} else if (isCeylonBoolean(exprType)) {
expr = boxBoolean(expr);
} else if (isAnything(exprType)) {
expr = make().LetExpr(List.<JCStatement>of(make().Exec(expr)), makeNull());
} else if (isOptional(exprType)) {
// sometimes, due to interop we will get an unboxed java.lang.String whose Ceylon type
// is String? or passes for a boxed thing, and if we need to box it well we do
exprType = typeFact().getDefiniteType(exprType);
if (isCeylonString(exprType)){
expr = boxOptionalJavaString(expr);
}
}
return expr;
}
private JCTree.JCMethodInvocation boxInteger(JCExpression value) {
return makeBoxType(value, syms().ceylonIntegerType);
}
private JCTree.JCMethodInvocation boxFloat(JCExpression value) {
return makeBoxType(value, syms().ceylonFloatType);
}
private JCTree.JCMethodInvocation boxString(JCExpression value) {
return makeBoxType(value, syms().ceylonStringType);
}
private JCTree.JCMethodInvocation boxCharacter(JCExpression value) {
return makeBoxType(value, syms().ceylonCharacterType);
}
private JCTree.JCMethodInvocation boxByte(JCExpression value) {
return makeBoxType(value, syms().ceylonByteType);
}
private JCTree.JCMethodInvocation boxBoolean(JCExpression value) {
return makeBoxType(value, syms().ceylonBooleanType);
}
private JCTree.JCMethodInvocation makeBoxType(JCExpression value, Type type) {
return make().Apply(null, makeSelect(makeIdent(type), "instance"), List.<JCExpression>of(value));
}
private JCTree.JCMethodInvocation unboxInteger(JCExpression value) {
return makeUnboxType(value, "longValue");
}
private JCTree.JCMethodInvocation unboxFloat(JCExpression value) {
return makeUnboxType(value, "doubleValue");
}
private JCExpression unboxString(JCExpression value) {
if (isStringLiteral(value)) {
// If it's already a String literal, why call .toString on it?
return value;
}
return makeUnboxType(value, "toString");
}
private boolean isStringLiteral(JCExpression value) {
return value instanceof JCLiteral
&& ((JCLiteral)value).value instanceof String;
}
private JCExpression unboxOptionalString(JCExpression value){
if (isStringLiteral(value)) {
// If it's already a String literal, why call .toString on it?
return value;
}
Naming.SyntheticName name = naming.temp();
JCExpression type = makeJavaType(typeFact().getStringDeclaration().getType(), JT_NO_PRIMITIVES);
JCExpression expr = make().Conditional(make().Binary(JCTree.NE, name.makeIdent(), makeNull()),
unboxString(name.makeIdent()),
makeNull());
return makeLetExpr(name, null, type, value, expr);
}
private JCExpression boxOptionalJavaString(JCExpression value){
Naming.SyntheticName name = naming.temp();
JCExpression type = makeJavaType(typeFact().getStringDeclaration().getType());
JCExpression expr = make().Conditional(make().Binary(JCTree.NE, name.makeIdent(), makeNull()),
boxString(name.makeIdent()),
makeNull());
return makeLetExpr(name, null, type, value, expr);
}
private JCTree.JCMethodInvocation unboxCharacter(JCExpression value, boolean isJava) {
return makeUnboxType(value, isJava ? "charValue" : "intValue");
}
private JCTree.JCMethodInvocation unboxByte(JCExpression value) {
return makeUnboxType(value, "byteValue");
}
private JCTree.JCMethodInvocation unboxBoolean(JCExpression value) {
return makeUnboxType(value, "booleanValue");
}
private JCTree.JCMethodInvocation makeUnboxType(JCExpression value, String unboxMethodName) {
return make().Apply(null, makeSelect(value, unboxMethodName), List.<JCExpression>nil());
}
/*
* Sequences
*/
/**
* Turns a <tt>ceylon.language.Iterable</tt> to a <tt>ceylon.language.Sequential</tt> by invoking
* its <tt>getSequence()</tt> method.
*/
JCExpression iterableToSequential(JCExpression iterable){
return make().Apply(null, makeSelect(iterable, "sequence"), List.<JCExpression>nil());
}
/**
* Returns a JCExpression along the lines of
* {@code new ArraySequence<seqElemType>(list...)}
* @param elems The elements in the sequence
* @param seqElemType The sequence type parameter
* @param makeJavaTypeOpts The option flags to pass to makeJavaType().
* @return a JCExpression
* @see #makeSequenceRaw(java.util.List)
*/
JCExpression makeSequence(List<JCExpression> elems, ProducedType seqElemType, int makeJavaTypeOpts) {
return make().TypeCast(makeJavaType(typeFact().getSequenceType(seqElemType), JT_RAW),
utilInvocation().sequentialInstance(null,
makeReifiedTypeArgument(seqElemType),
makeEmptyAsSequential(false),
elems));
}
/**
* Makes a lazy iterable literal, for a sequenced argument to a named invocation
* (<code>f{foo=""; expr1, expr2, *expr3}</code>) or
* for an iterable instantiation (<code>{expr1, expr2, *expr3}</code>)
*/
JCExpression makeLazyIterable(Tree.SequencedArgument sequencedArgument,
ProducedType seqElemType, ProducedType absentType,
int flags) {
java.util.List<PositionalArgument> list = sequencedArgument.getPositionalArguments();
int i = 0;
ListBuffer<JCExpression> expressions = new ListBuffer<JCExpression>();
boolean spread = false;
for (Tree.PositionalArgument arg : list) {
at(arg);
JCExpression jcExpression;
// last expression can be an Iterable<seqElemType>
if(arg instanceof Tree.SpreadArgument || arg instanceof Tree.Comprehension){
// make sure we only have spread/comprehension as last
if(i != list.size()-1){
jcExpression = makeErroneous(arg, "compiler bug: spread or comprehension argument is not last in sequence literal");
}else{
ProducedType type = typeFact().getIterableType(seqElemType);
spread = true;
if(arg instanceof Tree.SpreadArgument){
Tree.Expression expr = ((Tree.SpreadArgument) arg).getExpression();
// always boxed since it is a sequence
jcExpression = expressionGen().transformExpression(expr, BoxingStrategy.BOXED, type);
}else{
jcExpression = expressionGen().transformComprehension((Comprehension) arg, type);
}
}
}else if(arg instanceof Tree.ListedArgument){
Tree.Expression expr = ((Tree.ListedArgument) arg).getExpression();
// always boxed since we stuff them into a sequence
jcExpression = expressionGen().transformExpression(expr, BoxingStrategy.BOXED, seqElemType);
}else{
jcExpression = makeErroneous(arg, "compiler bug: " + arg.getNodeType() + " is not a supported sequenced argument");
}
// the last iterable goes first if spread
expressions.add(jcExpression);
i++;
}
boolean old = expressionGen().withinSyntheticClassBody(true);
try (SavedPosition p = noPosition()) {
if (Strategy.preferLazySwitchingIterable(sequencedArgument.getPositionalArguments())) {
// use a LazySwitchingIterable
MethodDefinitionBuilder mdb = MethodDefinitionBuilder.systemMethod(this, Unfix.$evaluate$.toString());
mdb.isOverride(true);
mdb.modifiers(PROTECTED | FINAL);
mdb.resultType(null, make().Type(syms().objectType));
mdb.parameter(ParameterDefinitionBuilder.systemParameter(this, Unfix.$index$.toString())
.type(make().Type(syms().intType), null));
ListBuffer<JCCase> cases = ListBuffer.<JCCase>lb();
i = 0;
for (JCExpression e : expressions) {
cases.add(make().Case(make().Literal(i++), List.<JCStatement>of(make().Return(e))));
}
cases.add(make().Case(null, List.<JCStatement>of(make().Return(makeNull()))));
mdb.body(make().Switch(naming.makeUnquotedIdent(Unfix.$index$), cases.toList()));
return make().NewClass(null,
List.<JCExpression>nil(),//of(makeJavaType(seqElemType), makeJavaType(absentType)),
make().TypeApply(make().QualIdent(syms.ceylonLazyIterableType.tsym),
List.<JCExpression>of(makeJavaType(seqElemType, JT_TYPE_ARGUMENT), makeJavaType(absentType, JT_TYPE_ARGUMENT))),
List.of(makeReifiedTypeArgument(seqElemType),// td,
makeReifiedTypeArgument(absentType),//td
make().Literal(list.size()),// numMethods
make().Literal(spread)),// spread),
make().AnonymousClassDef(make().Modifiers(FINAL),
List.<JCTree>of(mdb.build())));
} else {
// use a LazyInvokingIterable
ListBuffer<JCTree> methods = new ListBuffer<JCTree>();
MethodDefinitionBuilder mdb = MethodDefinitionBuilder.systemMethod(this, Unfix.$lookup$.toString());
mdb.isOverride(true);
mdb.modifiers(PROTECTED | FINAL);
mdb.resultType(null, naming.makeQualIdent(make().Type(syms().methodHandlesType), "Lookup"));
mdb.body(make().Return(make().Apply(List.<JCExpression>nil(),
naming.makeQualIdent(make().Type(syms().methodHandlesType), "lookup"),
List.<JCExpression>nil())));
methods.add(mdb.build());
mdb = MethodDefinitionBuilder.systemMethod(this, Unfix.$invoke$.toString());
mdb.isOverride(true);
mdb.modifiers(PROTECTED | FINAL);
mdb.resultType(null, make().Type(syms().objectType));
mdb.parameter(ParameterDefinitionBuilder.systemParameter(this, "handle")
.type(make().Type(syms().methodHandleType), null));
mdb.body(make().Return(make().Apply(List.<JCExpression>nil(),
naming.makeQualIdent(naming.makeUnquotedIdent("handle"), "invokeExact"),
List.<JCExpression>of(naming.makeThis()))));
methods.add(mdb.build());
i = 0;
for (JCExpression expr : expressions) {
mdb = MethodDefinitionBuilder.systemMethod(this, "$"+i);
i++;
mdb.modifiers(PRIVATE | FINAL);
mdb.resultType(null, make().Type(syms().objectType));
mdb.body(make().Return(expr));
methods.add(mdb.build());
}
return make().NewClass(null,
List.<JCExpression>nil(),//of(makeJavaType(seqElemType), makeJavaType(absentType)),
make().TypeApply(make().QualIdent(syms.ceylonLazyInvokingIterableType.tsym),
List.<JCExpression>of(makeJavaType(seqElemType, JT_TYPE_ARGUMENT), makeJavaType(absentType, JT_TYPE_ARGUMENT))),
List.of(makeReifiedTypeArgument(seqElemType),// td,
makeReifiedTypeArgument(absentType),//td
make().Literal(list.size()),// numMethods
make().Literal(spread)),// spread),
make().AnonymousClassDef(make().Modifiers(FINAL),
methods.toList()));
}
} finally {
expressionGen().withinSyntheticClassBody(old);
}
}
/**
* Makes an iterable literal, where the first element of elems is an Iterable, and the rest are the start of the
* iterable.
*/
JCExpression makeIterable(List<JCExpression> elems, ProducedType seqElemType, int makeJavaTypeOpts) {
JCExpression elemTypeExpr = makeJavaType(seqElemType, makeJavaTypeOpts);
elems = elems.prepend(makeReifiedTypeArgument(seqElemType));
// we delegate to ArrayIterable.instance() so that we can filter out empty Iterables
return make().Apply(List.<JCExpression>of(elemTypeExpr), makeSelect(makeIdent(syms().ceylonArrayIterableType), "instance"), elems);
}
JCExpression makeEmptyAsSequential(boolean needsCast){
if(needsCast)
return make().TypeCast(makeJavaType(typeFact().getSequentialDeclaration().getType(), JT_RAW), makeEmpty());
return makeEmpty();
}
JCExpression makeLanguageValue(String valueName) {
return make().Apply(
List.<JCTree.JCExpression>nil(),
naming.makeLanguageValue(valueName),
List.<JCTree.JCExpression>nil());
}
JCExpression makeEmpty() {
return makeLanguageValue("empty");
}
JCExpression makeFinished() {
return makeLanguageValue("finished");
}
/**
* Turns a sequence into a Java array
* @param expr the sequence
* @param sequenceType the (destination) sequence type
* @param boxingStrategy the boxing strategy for expr
* @param exprType the (source) expression type
* @param initialElements the elements to place at the beginning of the Java array
*/
JCExpression sequenceToJavaArray(JCExpression expr, ProducedType sequenceType,
BoxingStrategy boxingStrategy, ProducedType exprType,
List<JCTree.JCExpression> initialElements) {
// find the sequence element type
ProducedType type = typeFact().getIteratedType(sequenceType);
if(boxingStrategy == BoxingStrategy.UNBOXED){
if(isCeylonInteger(type)){
if("short".equals(type.getUnderlyingType()))
return utilInvocation().toShortArray(expr, initialElements);
else if("int".equals(type.getUnderlyingType()))
return utilInvocation().toIntArray(expr, initialElements);
else
return utilInvocation().toLongArray(expr, initialElements);
}else if(isCeylonFloat(type)){
if("float".equals(type.getUnderlyingType()))
return utilInvocation().toFloatArray(expr, initialElements);
else
return utilInvocation().toDoubleArray(expr, initialElements);
} else if (isCeylonCharacter(type)) {
if ("char".equals(type.getUnderlyingType()))
return utilInvocation().toCharArray(expr, initialElements);
// else it must be boxed, right?
} else if (isCeylonByte(type)) {
return utilInvocation().toByteArray(expr, initialElements);
} else if (isCeylonBoolean(type)) {
return utilInvocation().toBooleanArray(expr, initialElements);
} else if (isJavaString(type)) {
return utilInvocation().toJavaStringArray(expr, initialElements);
} else if (isCeylonString(type)) {
return objectVariadicToJavaArray(type, sequenceType, expr, exprType, initialElements);
}
return objectVariadicToJavaArray(type, sequenceType, expr, exprType, initialElements);
}else{
return objectVariadicToJavaArray(type, sequenceType, expr, exprType, initialElements);
}
}
private JCExpression objectVariadicToJavaArray(ProducedType type,
ProducedType sequenceType, JCExpression expr, ProducedType exprType, List<JCExpression> initialElements) {
if(typeFact().getSequentialType(exprType) != null){
return objectSequentialToJavaArray(type, expr, initialElements);
}
return objectIterableToJavaArray(type, typeFact().getIterableType(sequenceType), expr, initialElements);
}
// This can't be reached anymore since we can't spread iterables anymore ATM
private JCExpression objectIterableToJavaArray(ProducedType type,
ProducedType iterableType, JCExpression expr, List<JCExpression> initialElements) {
JCExpression klass = makeJavaType(type, JT_CLASS_NEW | JT_NO_PRIMITIVES);
JCExpression klassLiteral = make().Select(klass, names().fromString("class"));
return utilInvocation().toArray(expr, klassLiteral, initialElements);
}
private JCExpression objectSequentialToJavaArray(ProducedType type, JCExpression expr, List<JCExpression> initialElements) {
JCExpression klass1 = makeJavaType(type, JT_RAW | JT_NO_PRIMITIVES);
JCExpression klass2 = makeJavaType(type, JT_CLASS_NEW | JT_NO_PRIMITIVES);
Naming.SyntheticName seqName = naming.temp().suffixedBy(0);
ProducedType fixedSizedType = typeFact().getSequentialDeclaration().getProducedType(null, Arrays.asList(type));
JCExpression seqTypeExpr1 = makeJavaType(fixedSizedType);
//JCExpression seqTypeExpr2 = makeJavaType(fixedSizedType);
JCExpression sizeExpr = make().Apply(List.<JCExpression>nil(),
make().Select(seqName.makeIdent(), names().fromString("getSize")),
List.<JCExpression>nil());
sizeExpr = utilInvocation().toInt(sizeExpr);
// add initial elements if required
if(!initialElements.isEmpty())
sizeExpr = make().Binary(JCTree.PLUS,
sizeExpr,
makeInteger(initialElements.size()));
JCExpression newArrayExpr = make().NewArray(klass1, List.of(sizeExpr), null);
JCExpression sequenceToArrayExpr = utilInvocation().toArray(
seqName.makeIdent(), newArrayExpr, initialElements,
klass2);
// since T[] is erased to Sequential<T> we probably need a cast to FixedSized<T>
//JCExpression castedExpr = make().TypeCast(seqTypeExpr2, expr);
return makeLetExpr(seqName, List.<JCStatement>nil(), seqTypeExpr1, expr, sequenceToArrayExpr);
}
/**
* Abstraction over how we transform a {@code is} type test
*/
interface TypeTestTransformation<R> {
/**
* Combine the results of two other type tests using AND or OR,
* depending on the {@code op} parameter
*/
public R andOr(R a, R b, int op);
public R not(R a);
/** Make a type test using that just evaluates as the given result */
public R eval(JCExpression varExpr, boolean result);
/**
* Make a type test using {@code == null} or {@code != null},
* depending on the {@code op} parameter
*/
public R nullTest(JCExpression varExpr, int op);
/** Make a type test using {@code Util.isIdentifiable()} */
public R isIdentifiable(JCExpression varExpr);
/** Make a type test using {@code Util.isBasic()} */
public R isBasic(JCExpression varExpr);
/** Make a type test using {@code instanceof} */
public R isInstanceof(JCExpression varExpr, ProducedType testedType);
/** Make a type test using {@code Util.isReified()} */
public R isReified(JCExpression varExpr, ProducedType testedType);
}
/**
* A type test transformation that builds a tree for evaluating the type test
* @see PerfTypeTestTransformation
*/
class JavacTypeTestTransformation implements TypeTestTransformation<JCExpression> {
@Override
public JCExpression andOr(JCExpression a, JCExpression b, int op) {
return make().Binary(op, a, b);
}
@Override
public JCExpression not(JCExpression a) {
return make().Unary(JCTree.NOT, a);
}
@Override
public JCExpression eval(JCExpression varExpr, boolean result) {
return makeIgnoredEvalAndReturn(varExpr, makeBoolean(result));
}
@Override
public JCExpression nullTest(JCExpression varExpr, int op) {
return make().Binary(op, varExpr, makeNull());
}
@Override
public JCExpression isIdentifiable(JCExpression varExpr) {
return utilInvocation().isIdentifiable(varExpr);
}
@Override
public JCExpression isBasic(JCExpression varExpr) {
return utilInvocation().isBasic(varExpr);
}
@Override
public JCExpression isInstanceof(JCExpression varExpr,
ProducedType testedType) {
JCExpression rawTypeExpr = makeJavaType(testedType, JT_NO_PRIMITIVES | JT_RAW);
return make().TypeTest(varExpr, rawTypeExpr);
}
@Override
public JCExpression isReified(JCExpression varExpr,
ProducedType testedType) {
return utilInvocation().isReified(varExpr, testedType);
}
}
JavacTypeTestTransformation javacTypeTester = null;
JavacTypeTestTransformation javacTypeTester() {
if (this.javacTypeTester == null) {
this.javacTypeTester = new JavacTypeTestTransformation();
}
return this.javacTypeTester;
}
/**
* A type test transformation that estimates whether the real type test
* transformation (@link JavacTypeTester} produce a test which is
* expensive (anything involving reification of inspecting annotations)
* or cheap (just involving instanceof, != null, == null, and similar).
*/
class PerfTypeTestTransformation implements TypeTestTransformation<Boolean> {
@Override
public Boolean andOr(Boolean aIsCheap, Boolean bIsCheap, int op) {
// cheap only if both halves are cheap
return aIsCheap.booleanValue() && bIsCheap.booleanValue() ? Boolean.TRUE : Boolean.FALSE;
}
@Override
public Boolean not(Boolean a) {
return a;
}
@Override
public Boolean eval(JCExpression varExpr, boolean result) {
return Boolean.TRUE;
}
@Override
public Boolean nullTest(JCExpression varExpr, int op) {
// != null and == null are always cheap
return Boolean.TRUE;
}
@Override
public Boolean isIdentifiable(JCExpression varExpr) {
// Util.isIdentifiable() is expensive
return Boolean.FALSE;
}
@Override
public Boolean isBasic(JCExpression varExpr) {
// Util.isBasic() is expensive
return Boolean.FALSE;
}
@Override
public Boolean isInstanceof(JCExpression varExpr,
ProducedType testedType) {
// instanceof is cheap
return Boolean.TRUE;
}
@Override
public Boolean isReified(JCExpression varExpr, ProducedType testedType) {
// Util.isReified() is expensive
return Boolean.FALSE;
}
}
PerfTypeTestTransformation perfTypeTester = null;
PerfTypeTestTransformation perfTypeTester() {
if (this.perfTypeTester == null) {
this.perfTypeTester = new PerfTypeTestTransformation();
}
return this.perfTypeTester;
}
/**
* Creates comparisons of expressions against types, used for {@code is}
* conditions ({@code is X e}), the {@code is} operator ({@code e is X})
* and {@code is} cases ({@code case (is X)})
*/
JCExpression makeTypeTest(JCExpression firstTimeExpr, Naming.CName varName, ProducedType testedType, ProducedType expressionType) {
return makeTypeTest(javacTypeTester(), firstTimeExpr, varName, testedType, expressionType);
}
/**
* Determines whether the given type test generated by
* {@link #makeTypeTest(JCExpression, com.redhat.ceylon.compiler.java.codegen.Naming.CName, ProducedType, ProducedType)}
* will be "cheap" or "expensive"
*/
boolean isTypeTestCheap(JCExpression firstTimeExpr, Naming.CName varName, ProducedType testedType, ProducedType expressionType) {
return makeTypeTest(perfTypeTester(), firstTimeExpr, varName, testedType, expressionType);
}
JCExpression makeOptimizedTypeTest(
JCExpression firstTimeExpr, Naming.CName varName, ProducedType testedType, ProducedType expressionType) {
// If the type test is expensive and we can figure out a
// "complement type" whose type test is cheap we can invert the test.
//TypeDeclaration widerDeclaration = expressionType.getDeclaration();
if (!isTypeTestCheap(firstTimeExpr, varName, testedType, expressionType)) {
//if (widerDeclaration instanceof UnionType
// || widerDeclaration instanceof ClassOrInterface) {
// we've got a X|Y and we're testing for X
// or parhaps a A|B|C|D and we're testing for C|D
java.util.List<ProducedType> cases = expressionType.getCaseTypes();
if (cases != null) {
if ((testedType.getDeclaration() instanceof ClassOrInterface
|| testedType.getDeclaration() instanceof TypeParameter)
&& cases.remove(testedType)) {
} else if (testedType.getDeclaration() instanceof UnionType) {
for (ProducedType ct : testedType.getCaseTypes()) {
if (!cases.remove(ct)) {
cases = null;
break;
}
}
} else {
cases = null;
}
if (cases != null) {
ProducedType complementType = typeFact().getNothingDeclaration().getType();
for (ProducedType ct : cases) {
complementType = com.redhat.ceylon.compiler.typechecker.model.Util.unionType(complementType, ct, typeFact());
}
if (/*typeFact().getLanguageModuleDeclaration("Finished").equals(complementType.getDeclaration())
||*/ com.redhat.ceylon.compiler.typechecker.model.Util.intersectionType(complementType, testedType, typeFact()).isNothing()) {
return make().Unary(JCTree.NOT, makeTypeTest(firstTimeExpr, varName, complementType, expressionType));
}
}
}
//}
}
return makeTypeTest(firstTimeExpr, varName, testedType, expressionType);
}
private <R> R makeTypeTest(TypeTestTransformation<R> typeTester,
JCExpression firstTimeExpr, Naming.CName varName,
ProducedType testedType, ProducedType expressionType) {
R result = null;
// make sure aliases are resolved
testedType = testedType.resolveAliases();
// optimisation when all we're doing is making sure it is not null
if(expressionType != null
&& testedType.getSupertype(typeFact().getObjectDeclaration()) != null
&& expressionType.isExactly(typeFact().getOptionalType(testedType))){
JCExpression varExpr = firstTimeExpr != null ? firstTimeExpr : varName.makeIdent();
return typeTester.nullTest(varExpr, JCTree.NE);
}
TypeDeclaration declaration = testedType.getDeclaration();
if (declaration instanceof ClassOrInterface) {
JCExpression varExpr = firstTimeExpr != null ? firstTimeExpr : varName.makeIdent();
if (isAnything(testedType)){
// everything is Void, it's the root of the hierarchy
return typeTester.eval(varExpr, true);
} else if (testedType.isExactly(typeFact().getNullDeclaration().getType())){
// is Null => is null
return typeTester.nullTest(varExpr, JCTree.EQ);
} else if (testedType.isExactly(typeFact().getObjectDeclaration().getType())){
// is Object => is not null
return typeTester.nullTest(varExpr, JCTree.NE);
} else if (testedType.isExactly(typeFact().getIdentifiableDeclaration().getType())){
// it's erased
return typeTester.isIdentifiable(varExpr);
} else if (testedType.isExactly(typeFact().getBasicDeclaration().getType())){
// it's erased
return typeTester.isBasic(varExpr);
} else if (testedType.getDeclaration().getQualifiedNameString().equals("java.lang::Error")){
// need to exclude AssertionError
return typeTester.andOr(typeTester.isInstanceof(varExpr, testedType),
typeTester.not(typeTester.isInstanceof(varName.makeIdent(), typeFact().getAssertionErrorDeclaration().getType())),
JCTree.AND);
} else if (testedType.getTypeArguments().isEmpty()) {
// non-generic Class or interface, use instanceof
return typeTester.isInstanceof(varExpr, testedType);
} else {// generic class or interface...
if (declaration.getSelfType() != null
&& declaration.getSelfType().getDeclaration() instanceof TypeParameter // of TypeArg
&& declaration.getSelfType().isSubtypeOf(declaration.getType()) // given TypeArg satisfies SelfType<TypeArg>
&& testedType.getTypeArguments().get(declaration.getSelfType().getDeclaration()).getDeclaration() instanceof ClassOrInterface) {
// "is SelfType<ClassOrInterface>" can be written "is ClassOrInterface"
return makeTypeTest(typeTester, firstTimeExpr, varName, testedType.getTypeArguments().get(declaration.getSelfType().getDeclaration()), expressionType);
} else if (canOptimiseReifiedTypeTest(testedType)) {
// Use an instanceof
return typeTester.isInstanceof(varExpr, testedType);
} else {
// Have to use a reified test
if (!Decl.equal(declaration, expressionType.getDeclaration())) {
// do a cheap instanceof test to try to shortcircuit the expensive
// Util.isReified()
// XXX Possible future optimization: When the `is` is a condition
// in an `assert` we expect the result to be true, so
// instanceof shortcircuit doesn't achieve anything
return typeTester.andOr(
typeTester.isInstanceof(varExpr, testedType),
typeTester.isReified(varName.makeIdent(), testedType), JCTree.AND);
} else {
return typeTester.isReified(varExpr, testedType);
}
}
}
} else if (typeFact().isUnion(testedType)) {
UnionType union = (UnionType)declaration;
for (ProducedType pt : union.getCaseTypes()) {
R partExpr = makeTypeTest(typeTester, firstTimeExpr, varName, pt, expressionType);
firstTimeExpr = null;
if (result == null) {
result = partExpr;
} else {
result = typeTester.andOr(result, partExpr, JCTree.OR);
}
}
return result;
} else if (typeFact().isIntersection(testedType)) {
IntersectionType union = (IntersectionType)declaration;
for (ProducedType pt : union.getSatisfiedTypes()) {
R partExpr = makeTypeTest(typeTester, firstTimeExpr, varName, pt, expressionType);
firstTimeExpr = null;
if (result == null) {
result = partExpr;
} else {
result = typeTester.andOr(result, partExpr, JCTree.AND);
}
}
return result;
} else if (declaration instanceof NothingType){
// nothing is Bottom
JCExpression varExpr = firstTimeExpr != null ? firstTimeExpr : varName.makeIdent();
return typeTester.eval(varExpr, false);
} else if (declaration instanceof TypeParameter) {
JCExpression varExpr = firstTimeExpr != null ? firstTimeExpr : varName.makeIdent();
if (!reifiableUpperBounds((TypeParameter)declaration, expressionType).isEmpty()) {
// If we're testing against a type parameter with
// class or interface upper bounds we can again shortcircuit the
// Util.isReified() using instanceof against the bounds
result = typeTester.isReified(varName.makeIdent(), testedType);
Iterator<ProducedType> iterator = reifiableUpperBounds((TypeParameter)declaration, expressionType).iterator();
while (iterator.hasNext()) {
ProducedType type = iterator.next();
ClassOrInterface c = ((ClassOrInterface)type.resolveAliases().getDeclaration());
result = typeTester.andOr(
typeTester.isInstanceof(iterator.hasNext() ? varName.makeIdent() : varExpr, c.getType()),
result, JCTree.AND);
}
return result;
} else {
return typeTester.isReified(varExpr, testedType);
}
} else {
throw BugException.unhandledDeclarationCase(declaration);
}
}
/**
* Returns the upper bounds of the given type parameter which are
* java reifiable types (and can thus be used in an {@code instanceof})
*/
private java.util.List<ProducedType> reifiableUpperBounds(
TypeParameter testedType, ProducedType expressionType) {
ArrayList<ProducedType> result = new ArrayList<ProducedType>();
for (ProducedType type: testedType.getSatisfiedTypes()) {
if (type.getDeclaration() instanceof ClassOrInterface // reified, so we can use instanceof
&& !willEraseToObject(type) // no point doing instanceof Object
&& !type.isSupertypeOf(expressionType)) { // no point doing instanceof
result.add(type);
}
}
return result;
}
/**
* Determine whether we can use a plain {@code instanceof} instead of
* a full {@code Util.isReified()} for a {@code is} test
*/
private boolean canOptimiseReifiedTypeTest(ProducedType type) {
if(isJavaArray(type)){
if(isJavaObjectArray(type)){
MultidimensionalArray multiArray = getMultiDimensionalArrayInfo(type);
// we can test, even if not fully reified in Java
return multiArray.type.getDeclaration() instanceof ClassOrInterface;
}else{
// primitive array we can test
return true;
}
}
// we can optimise it if we've got a ClassOrInterface with only Anything type parameters
if(type.getDeclaration() instanceof ClassOrInterface == false)
return false;
for(Entry<TypeParameter, ProducedType> entry : type.getTypeArguments().entrySet()){
TypeParameter tp = entry.getKey();
if(!type.isCovariant(tp)) {
return false;
}
java.util.List<ProducedType> bounds = tp.getSatisfiedTypes();
ProducedType ta = entry.getValue();
if ((bounds == null || bounds.isEmpty()) && !isAnything(ta)) {
return false;
}
for (ProducedType bound : bounds) {
if (!ta.isSupertypeOf(bound)) {
return false;
}
}
}
// they're all Anything (or supertypes of their upper bound) we can optimise
return true;
}
JCExpression makeNonEmptyTest(JCExpression firstTimeExpr) {
Interface sequence = typeFact().getSequenceDeclaration();
JCExpression sequenceType = makeJavaType(sequence.getType(), JT_NO_PRIMITIVES | JT_RAW);
return make().TypeTest(firstTimeExpr, sequenceType);
}
private RuntimeUtil utilInvocation = null;
RuntimeUtil utilInvocation() {
if (utilInvocation == null) {
utilInvocation = new RuntimeUtil(this);
}
return utilInvocation;
}
/**
* Invokes a static method of the Metamodel helper class
* @param methodName name of the method
* @param arguments The arguments to the invocation
* @param typeArguments The arguments to the method
* @return the invocation AST
*/
public JCExpression makeMetamodelInvocation(String methodName, List<JCExpression> arguments, List<JCExpression> typeArguments) {
return make().Apply(typeArguments,
make().Select(make().QualIdent(syms().ceylonMetamodelType.tsym),
names().fromString(methodName)),
arguments);
}
public JCExpression makeTypeDescriptorType(){
return makeJavaType(syms().ceylonTypeDescriptorType.tsym);
}
public JCExpression makeReifiedTypeType(){
return makeJavaType(syms().ceylonReifiedTypeType.tsym);
}
public JCExpression makeSerializableType(){
return makeJavaType(syms().ceylonSerializableType.tsym);
}
public JCExpression makeNothingTypeDescriptor() {
return make().Select(makeTypeDescriptorType(),
names().fromString("NothingType"));
}
private LetExpr makeIgnoredEvalAndReturn(JCExpression toEval, JCExpression toReturn){
// define a variable of type j.l.Object to hold the result of the evaluation
JCVariableDecl def = makeVar(naming.temp(), make().Type(syms().objectType), toEval);
// then ignore this result and return something else
return make().LetExpr(def, toReturn);
}
/**
* Makes an 'erroneous' AST node with a message to be logged as an error
* and (eventually) treated as a compiler bug
*
* @see BugException
*/
JCExpression makeErroneous(Node node, String message) {
return makeErroneous(node, message, List.<JCTree>nil());
}
/**
* Makes an 'erroneous' AST node with a message to be logged as an error
* and (eventually) treated as a compiler bug.
*
* @see BugException
*/
JCExpression makeErroneous(Node node, String message, List<? extends JCTree> errs) {
return makeErr(node, "ceylon.codegen.erroneous", message, errs);
}
private JCExpression makeErr(Node node, String key, String message, List<? extends JCTree> errs) {
if (node != null) {
at(node);
}
if (message != null) {
if (node != null) {
node.addError(new CodeGenError(node, message, null));
} else {
log.error(key, message);
}
}
return make().Erroneous(errs);
}
List<JCExpression> makeTypeParameterBounds(java.util.List<ProducedType> satisfiedTypes){
ListBuffer<JCExpression> bounds = new ListBuffer<JCExpression>();
for (ProducedType t : satisfiedTypes) {
if (!willEraseToObject(t)) {
JCExpression bound = makeJavaType(t, AbstractTransformer.JT_NO_PRIMITIVES);
// if it's a class, we need to move it first as per JLS http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.4
if(t.getDeclaration() instanceof Class)
bounds.prepend(bound);
else
bounds.append(bound);
}
}
return bounds.toList();
}
/**
* Determines whether any of the given type parameters
* (not including {@code tp}) has contraints dependent on {@code tp}.
*
* Partial hack for https://github.com/ceylon/ceylon-compiler/issues/920
* We need to find if a covariant param has other type parameters with bounds to this one
* For example if we have "Foo<out A, out B>() given B satisfies A" then we can't generate
* the following signature: "Foo<? extends Object, ? extends String" because the subtype of
* String that can satisfy B is not necessarily the subtype of Object that we used for A.
*/
boolean hasDependentTypeParameters(java.util.List<TypeParameter> tps, TypeParameter tp) {
boolean isDependedOn = false;
for(TypeParameter otherTypeParameter : tps){
// skip this very type parameter
if(Decl.equal(otherTypeParameter, tp))
continue;
if(dependsOnTypeParameter(otherTypeParameter, tp)){
isDependedOn = true;
break;
}
}
return isDependedOn;
}
/**
* Returns true if the bounds of the type parameter depend on the type parameter itself,
* like Element given Element satisfies Foo<Element> for example.
*/
boolean isBoundsSelfDependant(TypeParameter tp){
return dependsOnTypeParameter(tp, tp);
}
private boolean dependsOnTypeParameter(TypeParameter tpToCheck, TypeParameter tpToDependOn) {
for(ProducedType pt : tpToCheck.getSatisfiedTypes()){
if(dependsOnTypeParameter(pt, tpToDependOn))
return true;
}
return false;
}
private boolean dependsOnTypeParameter(ProducedType t, TypeParameter tp) {
TypeDeclaration decl = t.getDeclaration();
if(decl instanceof UnionType){
for(ProducedType pt : decl.getCaseTypes()){
if(dependsOnTypeParameter(pt, tp)){
return true;
}
}
}else if(decl instanceof IntersectionType){
for(ProducedType pt : decl.getSatisfiedTypes()){
if(dependsOnTypeParameter(pt, tp)){
return true;
}
}
}else if(decl instanceof TypeParameter){
if (tp == null || Decl.equal(tp, decl))
return true;
}else if(decl instanceof ClassOrInterface){
for(ProducedType ta : t.getTypeArgumentList()){
if(dependsOnTypeParameter(ta, tp)){
return true;
}
}
}
return false;
}
boolean hasConstrainedTypeParameters(ProducedType type) {
TypeDeclaration declaration = type.getDeclaration();
if(declaration instanceof TypeParameter){
TypeParameter tp = (TypeParameter) declaration;
return !tp.getSatisfiedTypes().isEmpty();
}
if(declaration instanceof UnionType){
for(ProducedType m : declaration.getCaseTypes())
if(hasConstrainedTypeParameters(m))
return true;
return false;
}
if(declaration instanceof IntersectionType){
for(ProducedType m : declaration.getSatisfiedTypes())
if(hasConstrainedTypeParameters(m))
return true;
return false;
}
// check its type arguments
// special case for Callable which has only a single type param in Java
boolean isCallable = isCeylonCallable(type);
for(ProducedType typeArg : type.getTypeArgumentList()){
if(hasConstrainedTypeParameters(typeArg))
return true;
// stop after the first type arg for Callable
if(isCallable)
break;
}
return false;
}
boolean containsTypeParameter(ProducedType type) {
TypeDeclaration declaration = type.getDeclaration();
if(declaration instanceof TypeParameter){
return true;
}
if(declaration instanceof UnionType){
for(ProducedType m : declaration.getCaseTypes())
if(containsTypeParameter(m))
return true;
return false;
}
if(declaration instanceof IntersectionType){
for(ProducedType m : declaration.getSatisfiedTypes())
if(containsTypeParameter(m))
return true;
return false;
}
// check its type arguments
// special case for Callable which has only a single type param in Java
boolean isCallable = isCeylonCallable(type);
for(ProducedType typeArg : type.getTypeArgumentList()){
if(containsTypeParameter(typeArg))
return true;
// stop after the first type arg for Callable
if(isCallable)
break;
}
return false;
}
boolean hasDependentCovariantTypeParameters(ProducedType type) {
TypeDeclaration declaration = type.getDeclaration();
if(declaration instanceof UnionType){
for(ProducedType m : declaration.getCaseTypes())
if(hasDependentCovariantTypeParameters(m))
return true;
return false;
}
if(declaration instanceof IntersectionType){
for(ProducedType m : declaration.getSatisfiedTypes())
if(hasDependentCovariantTypeParameters(m))
return true;
return false;
}
// check its type arguments
// special case for Callable which has only a single type param in Java
boolean isCallable = isCeylonCallable(type);
// check if any type parameter is dependent on and covariant
java.util.List<TypeParameter> typeParams = declaration.getTypeParameters();
Map<TypeParameter, ProducedType> typeArguments = type.getTypeArguments();
for(TypeParameter typeParam : typeParams){
ProducedType typeArg = typeArguments.get(typeParam);
if(type.isCovariant(typeParam)
&& hasDependentTypeParameters(typeParams, typeParam)){
// see if the type argument in question contains type parameters and is erased to Object
if(containsTypeParameter(typeArg) && willEraseToObject(typeArg))
return true;
}
// now check if we the type argument has the same problem
if(hasDependentCovariantTypeParameters(typeArg))
return true;
// stop after the first type arg for Callable
if(isCallable)
break;
}
return false;
}
private JCTypeParameter makeTypeParameter(String name, java.util.List<ProducedType> satisfiedTypes, boolean covariant, boolean contravariant) {
return make().TypeParameter(names().fromString(name), makeTypeParameterBounds(satisfiedTypes));
}
JCTypeParameter makeTypeParameter(TypeParameter declarationModel, java.util.List<ProducedType> satisfiedTypesForBounds) {
TypeParameter typeParameterForBounds = declarationModel;
if (satisfiedTypesForBounds == null) {
satisfiedTypesForBounds = declarationModel.getSatisfiedTypes();
}
// special case for method refinenement where Java doesn't let us refine the parameter bounds
if(declarationModel.getContainer() instanceof Method){
Method method = (Method) declarationModel.getContainer();
Method refinedMethod = (Method) method.getRefinedDeclaration();
if (!Decl.equal(method, refinedMethod)) {
// find the param index
int index = method.getTypeParameters().indexOf(declarationModel);
if(index == -1){
log.error("Failed to find type parameter index: "+declarationModel.getName());
}else if(refinedMethod.getTypeParameters().size() > index){
// ignore smaller index than size since the typechecker would have found the error
TypeParameter refinedTP = refinedMethod.getTypeParameters().get(index);
if(!haveSameBounds(declarationModel, refinedTP)){
// find the right instantiation of that type parameter
TypeDeclaration methodContainer = (TypeDeclaration) method.getContainer();
TypeDeclaration refinedMethodContainer = (TypeDeclaration) refinedMethod.getContainer();
// find the supertype that gave us that method and its type arguments
Map<TypeParameter, ProducedType> typeArguments = methodContainer.getType().getSupertype(refinedMethodContainer).getTypeArguments();
satisfiedTypesForBounds = new ArrayList<ProducedType>(refinedTP.getSatisfiedTypes().size());
for(ProducedType satisfiedType : refinedTP.getSatisfiedTypes()){
// substitute the refined type parameter bounds with the right type arguments
satisfiedTypesForBounds.add(satisfiedType.substitute(typeArguments));
}
typeParameterForBounds = refinedTP;
}
}
}
}
return makeTypeParameter(declarationModel.getName(),
satisfiedTypesForBounds,
typeParameterForBounds.isCovariant(),
typeParameterForBounds.isContravariant());
}
JCTypeParameter makeTypeParameter(Tree.TypeParameterDeclaration param) {
at(param);
return makeTypeParameter(param.getDeclarationModel(), null);
}
JCAnnotation makeAtTypeParameter(TypeParameter declarationModel) {
return makeAtTypeParameter(declarationModel.getName(),
declarationModel.getSatisfiedTypes(),
declarationModel.getCaseTypes(),
declarationModel.isCovariant(),
declarationModel.isContravariant(),
declarationModel.getDefaultTypeArgument());
}
JCAnnotation makeAtTypeParameter(Tree.TypeParameterDeclaration param) {
at(param);
return makeAtTypeParameter(param.getDeclarationModel());
}
final List<JCExpression> typeArguments(Functional method) {
return typeArguments(method.getTypeParameters(), method.getType().getTypeArguments());
}
final List<JCExpression> typeArguments(Tree.ClassOrInterface type) {
return typeArguments(type.getDeclarationModel().getTypeParameters(), type.getDeclarationModel().getType().getTypeArguments());
}
final List<JCExpression> typeArguments(java.util.List<TypeParameter> typeParameters, Map<TypeParameter, ProducedType> typeArguments) {
ListBuffer<JCExpression> typeArgs = ListBuffer.<JCExpression>lb();
for (TypeParameter tp : typeParameters) {
ProducedType type = typeArguments.get(tp);
if (type != null) {
typeArgs.append(makeJavaType(type, JT_TYPE_ARGUMENT));
} else {
typeArgs.append(makeJavaType(tp.getType(), JT_TYPE_ARGUMENT));
}
}
return typeArgs.toList();
}
/**
* Returns the name of the field in classes which holds the companion
* instance.
*/
final String getCompanionFieldName(Interface def) {
return naming.getCompanionFieldName(def);
}
protected int getPosition(Node node) {
int pos = getMap().getStartPosition(node.getToken().getLine())
+ node.getToken().getCharPositionInLine();
log.useSource(gen().getFileObject());
return pos;
}
public JCExpression makeClassLiteral(ProducedType type) {
return makeSelect(makeJavaType(type, JT_NO_PRIMITIVES | JT_RAW | JT_CLASS_LITERAL), "class");
}
/**
* Same as makeClassLiteral but does not use erasure rules
*/
public JCExpression makeUnerasedClassLiteral(TypeDeclaration declaration) {
JCExpression className = naming.makeDeclarationName(declaration, DeclNameFlag.QUALIFIED);
return makeSelect(className, "class");
}
public java.util.List<JCExpression> makeReifiedTypeArguments(ProducedReference ref){
ref = resolveAliasesForReifiedTypeArguments(ref);
Declaration declaration = ref.getDeclaration();
if(!supportsReified(declaration))
return Collections.emptyList();
return makeReifiedTypeArguments(getTypeArguments(ref));
}
ProducedReference resolveAliasesForReifiedTypeArguments(ProducedReference ref) {
// this is a bit tricky:
// - for method references (ProducedTypedReference) it's all good
// - for classes we get a ProducedType which we use to resolve aliases
// -- UNLESS it's a class with an instantiator, in which case we should not resolve aliases
// because the instantiator has the right set of type parameters
if(ref instanceof ProducedType && !Strategy.generateInstantiator(ref.getDeclaration()))
return ((ProducedType)ref).resolveAliases();
return ref;
}
public static boolean supportsReified(Declaration declaration){
if(declaration instanceof ClassOrInterface){
// Java constructors don't support reified type arguments
return Decl.isCeylon((TypeDeclaration) declaration);
}else if(declaration instanceof Method){
if (((Method)declaration).isParameter()) {
// those can never be parameterised
return false;
}
if(Decl.isToplevel(declaration))
return true;
// Java methods don't support reified type arguments
Method m = (Method) CodegenUtil.getTopmostRefinedDeclaration(declaration);
// See what its container is
ClassOrInterface container = Decl.getClassOrInterfaceContainer(m);
// a method which is not a toplevel and is not a class method, must be a method within method and
// that must be Ceylon so it supports it
if(container == null)
return true;
return supportsReified(container);
}else{
throw BugException.unhandledDeclarationCase(declaration);
}
}
private java.util.List<ProducedType> getTypeArguments(
ProducedReference producedReference) {
java.util.List<TypeParameter> typeParameters = getTypeParameters(producedReference);
java.util.List<ProducedType> typeArguments = new ArrayList<ProducedType>(typeParameters.size());
for(TypeParameter tp : typeParameters)
typeArguments.add(producedReference.getTypeArguments().get(tp));
return typeArguments;
}
private java.util.List<ProducedType> getTypeArguments(
Method method) {
java.util.List<TypeParameter> typeParameters = method.getTypeParameters();
java.util.List<ProducedType> typeArguments = new ArrayList<ProducedType>(typeParameters.size());
for(TypeParameter tp : typeParameters)
typeArguments.add(tp.getType());
return typeArguments;
}
java.util.List<TypeParameter> getTypeParameters(
ProducedReference producedReference) {
Declaration declaration = producedReference.getDeclaration();
if(declaration instanceof ClassOrInterface)
return ((ClassOrInterface)declaration).getTypeParameters();
else
return ((Method)declaration).getTypeParameters();
}
public List<JCExpression> makeReifiedTypeArguments(
java.util.List<ProducedType> typeArguments) {
// same as makeReifiedTypeArgumentsResolved(typeArguments, false) but resolve each element
List<JCExpression> ret = List.nil();
for(int i=typeArguments.size()-1;i>=0;i--){
ret = ret.prepend(makeReifiedTypeArgumentResolved(typeArguments.get(i).resolveAliases(), false));
}
return ret;
}
private List<JCExpression> makeReifiedTypeArgumentsResolved(
java.util.List<ProducedType> typeArguments, boolean qualified) {
List<JCExpression> ret = List.nil();
for(int i=typeArguments.size()-1;i>=0;i--){
ret = ret.prepend(makeReifiedTypeArgumentResolved(typeArguments.get(i), qualified));
}
return ret;
}
public JCExpression makeReifiedTypeArgument(ProducedType pt) {
return makeReifiedTypeArgumentResolved(pt.resolveAliases(), false);
}
private JCExpression makeReifiedTypeArgumentResolved(ProducedType pt, boolean qualified) {
TypeDeclaration declaration = pt.getDeclaration();
if(declaration instanceof ClassOrInterface){
// see if we have an alias for it
if(supportsReifiedAlias((ClassOrInterface) declaration)){
JCExpression qualifier = naming.makeDeclarationName(declaration, DeclNameFlag.QUALIFIED);
return makeSelect(qualifier, naming.getTypeDescriptorAliasName());
}
// no alias, must build it
List<JCExpression> typeTestArguments = makeReifiedTypeArgumentsResolved(pt.getTypeArgumentList(), qualified);
JCExpression thisType = makeUnerasedClassLiteral(declaration);
// do we have variance overrides?
Map<TypeParameter, SiteVariance> varianceOverrides = pt.getVarianceOverrides();
if(!varianceOverrides.isEmpty()){
// we need to pass them as second argument then, in an array
ListBuffer<JCExpression> varianceElements = new ListBuffer<JCExpression>();
for(TypeParameter typeParameter : declaration.getTypeParameters()){
SiteVariance useSiteVariance = varianceOverrides.get(typeParameter);
String selector;
if(useSiteVariance != null){
switch(useSiteVariance){
case IN:
selector = "IN";
break;
case OUT:
selector = "OUT";
break;
default:
selector = "NONE";
break;
}
}else{
selector = "NONE";
}
JCExpression varianceElement = make().Select(makeIdent(syms().ceylonVarianceType), names().fromString(selector));
varianceElements.append(varianceElement);
}
JCNewArray varianceArray = make().NewArray(makeIdent(syms().ceylonVarianceType), List.<JCExpression>nil(), varianceElements.toList());
typeTestArguments = typeTestArguments.prepend(varianceArray);
}
typeTestArguments = typeTestArguments.prepend(thisType);
JCExpression classDescriptor = make().Apply(null, makeSelect(makeTypeDescriptorType(), "klass"), typeTestArguments);
ProducedType qualifyingType = pt.getQualifyingType();
JCExpression containerType = null;
if(qualifyingType == null){
// it may be contained in a function or value, and we want its type
Declaration enclosingDeclaration = getDeclarationContainer(declaration);
if(enclosingDeclaration instanceof TypedDeclaration)
containerType = makeTypedDeclarationTypeDescriptorResolved((TypedDeclaration) enclosingDeclaration);
else if(enclosingDeclaration instanceof TypeDeclaration)
qualifyingType = ((TypeDeclaration) enclosingDeclaration).getType();
}
if(qualifyingType != null){
containerType = makeReifiedTypeArgumentResolved(qualifyingType, true);
}
if(containerType == null){
return classDescriptor;
}else{
return make().Apply(null, makeSelect(makeTypeDescriptorType(), "member"),
List.of(containerType, classDescriptor));
}
} else if(declaration instanceof TypeParameter){
TypeParameter tp = (TypeParameter) declaration;
String name = naming.getTypeArgumentDescriptorName(tp);
if(!qualified)
return makeUnquotedIdent(name);
Scope container = tp.getContainer();
JCExpression qualifier = null;
if(container instanceof Class){
qualifier = naming.makeQualifiedThis(makeJavaType(((Class)container).getType(), JT_RAW));
}else if(container instanceof Interface){
qualifier = naming.makeQualifiedThis(makeJavaType(((Interface)container).getType(), JT_COMPANION | JT_RAW));
}else if(container instanceof Method){
// name must be a unique name, as returned by getTypeArgumentDescriptorName
return makeUnquotedIdent(name);
}else{
throw BugException.unhandledCase(container);
}
return makeSelect(qualifier, name);
} else if(declaration instanceof UnionType){
// FIXME: refactor this shite
List<JCExpression> typeTestArguments = List.nil();
java.util.List<ProducedType> typeParameters = ((UnionType)declaration).getCaseTypes();
for(int i=typeParameters.size()-1;i>=0;i--){
typeTestArguments = typeTestArguments.prepend(makeReifiedTypeArgument(typeParameters.get(i)));
}
return make().Apply(null, makeSelect(makeTypeDescriptorType(), "union"), typeTestArguments);
} else if(declaration instanceof IntersectionType){
List<JCExpression> typeTestArguments = List.nil();
java.util.List<ProducedType> typeParameters = ((IntersectionType)declaration).getSatisfiedTypes();
for(int i=typeParameters.size()-1;i>=0;i--){
typeTestArguments = typeTestArguments.prepend(makeReifiedTypeArgument(typeParameters.get(i)));
}
return make().Apply(null, makeSelect(makeTypeDescriptorType(), "intersection"), typeTestArguments);
} else if(declaration instanceof NothingType){
return makeNothingTypeDescriptor();
} else {
throw BugException.unhandledDeclarationCase(declaration);
}
}
private JCExpression makeTypedDeclarationTypeDescriptorResolved(TypedDeclaration declaration) {
// figure out the method name
String methodName = declaration.getPrefixedName();
List<JCExpression> arguments;
if(declaration instanceof Method)
arguments = makeReifiedTypeArgumentsResolved(getTypeArguments((Method)declaration), true);
else
arguments = List.nil();
if(declaration.isToplevel()){
JCExpression getterClassNameExpr;
if(declaration instanceof Method){
getterClassNameExpr = naming.makeName(declaration, Naming.NA_FQ | Naming.NA_WRAPPER);
}else{
String getterClassName = Naming.getAttrClassName(declaration, 0);
getterClassNameExpr = naming.makeUnquotedIdent(getterClassName);
}
arguments = arguments.prepend(makeSelect(getterClassNameExpr, "class"));
}else
arguments = arguments.prepend(make().Literal(methodName));
JCMethodInvocation typedDeclarationDescriptor = make().Apply(null, makeSelect(makeTypeDescriptorType(), "functionOrValue"),
arguments);
// see if the declaration has a container too
Declaration enclosingDeclaration = getDeclarationContainer(declaration);
JCExpression containerType = null;
if(enclosingDeclaration instanceof TypedDeclaration)
containerType = makeTypedDeclarationTypeDescriptorResolved((TypedDeclaration) enclosingDeclaration);
else if(enclosingDeclaration instanceof TypeDeclaration){
ProducedType qualifyingType = ((TypeDeclaration) enclosingDeclaration).getType();
containerType = makeReifiedTypeArgumentResolved(qualifyingType, true);
}
if(containerType == null){
return typedDeclarationDescriptor;
}else{
return make().Apply(null, makeSelect(makeTypeDescriptorType(), "member"),
List.of(containerType, typedDeclarationDescriptor));
}
}
JCExpressionStatement makeReifiedTypeParameterAssignment(
TypeParameter param) {
String descriptorName = naming.getTypeArgumentDescriptorName(param);
return make().Exec(make().Assign(
naming.makeQualIdent(naming.makeThis(), descriptorName),
naming.makeQualIdent(null, descriptorName)));
}
JCVariableDecl makeReifiedTypeParameterVarDecl(TypeParameter param, boolean isCompanion) {
String descriptorName = naming.getTypeArgumentDescriptorName(param);
long flags = PRIVATE;
if(!isCompanion)
flags |= FINAL;
List<JCAnnotation> annotations = makeAtIgnore();
JCVariableDecl localVar = make().VarDef(make().Modifiers(flags, annotations), names().fromString(descriptorName),
makeTypeDescriptorType(), null);
return localVar;
}
protected Declaration getDeclarationContainer(Declaration declaration) {
// Here we can use getContainer, we don't care about scopes
Scope container = declaration.getContainer();
while(container != null){
if(container instanceof Package)
return null;
if(container instanceof Declaration
// skip anonymous methods
&& (container instanceof Method == false || !((Declaration) container).isAnonymous()))
return (Declaration) container;
container = container.getContainer();
}
// did not find anything
return null;
}
public boolean supportsReifiedAlias(ClassOrInterface decl){
return !decl.isAlias()
&& decl.getTypeParameters().isEmpty()
&& supportsReified(decl)
&& Decl.isToplevel(decl);
}
boolean isSequencedAnnotation(Class klass) {
TypeDeclaration meta = typeFact().getSequencedAnnotationDeclaration();
return meta != null && klass.getType().isSubtypeOf(
meta.getProducedType(null,
Arrays.asList(typeFact().getAnythingDeclaration().getType(),
typeFact().getNothingDeclaration().getType())));
}
private Module getLanguageModule() {
return loader.getLanguageModule();
}
private Module getJDKBaseModule() {
return loader.getJDKBaseModule();
}
ProducedType getGetterInterfaceType(TypedDeclaration attrTypedDecl) {
ProducedTypedReference typedRef = getTypedReference(attrTypedDecl);
ProducedTypedReference nonWideningTypedRef = nonWideningTypeDecl(typedRef);
ProducedType nonWideningType = nonWideningType(typedRef, nonWideningTypedRef);
ProducedType type;
boolean unboxed = CodegenUtil.isUnBoxed(attrTypedDecl);
if (unboxed && isCeylonBoolean(nonWideningType)) {
type = javacCeylonTypeToProducedType(syms().ceylonGetterBooleanType);
} else if (unboxed && isCeylonInteger(nonWideningType)) {
type = javacCeylonTypeToProducedType(syms().ceylonGetterLongType);
} else if (unboxed && isCeylonFloat(nonWideningType)) {
type = javacCeylonTypeToProducedType(syms().ceylonGetterDoubleType);
} else if (unboxed && isCeylonCharacter(nonWideningType)) {
type = javacCeylonTypeToProducedType(syms().ceylonGetterIntType);
} else if (unboxed && isCeylonByte(nonWideningType)) {
type = javacCeylonTypeToProducedType(syms().ceylonGetterByteType);
} else {
type = javacCeylonTypeToProducedType(syms().ceylonGetterType);
ProducedType typeArg = nonWideningType;
if (unboxed && isCeylonString(typeArg)) {
typeArg = javacJavaTypeToProducedType(syms().stringType);
}
type = producedType(type.getDeclaration(), typeArg);
}
return type;
}
/**
* Makes a {@code java.lang.Class<? extends UPPERBOUND>}
*/
JCExpression makeJavaClassTypeBounded(ProducedType upperBound) {
return make().TypeApply(make().QualIdent(syms().classType.tsym),
List.<JCExpression>of(make().Wildcard(make().TypeBoundKind(BoundKind.EXTENDS),
makeJavaType(upperBound))));
}
/**
* If this value is for the hash attribute, turn its long value into an int value by applying (int)(e ^ (e >>> 32))
*/
public JCExpression convertToIntIfHashAttribute(Declaration model, JCExpression value) {
if(CodegenUtil.isHashAttribute(model)){
return convertToIntForHashAttribute(value);
}
return value;
}
/**
* Turn this long value into an int value by applying (int)(e ^ (e >>> 32))
*/
public JCExpression convertToIntForHashAttribute(JCExpression value) {
SyntheticName tempName = naming.temp("hash");
JCExpression type = make().Type(syms().longType);
JCBinary combine = make().Binary(JCTree.BITXOR, makeUnquotedIdent(tempName.asName()),
make().Binary(JCTree.USR, makeUnquotedIdent(tempName.asName()), makeInteger(32)));
return make().TypeCast(syms().intType, makeLetExpr(tempName, null, type, value, combine));
}
/**
* If we satisfy a Foo<T> and T is variant, we implement Foo<T> but our impl
* has type Foo<? extends T> or Foo<? super T> (to allow for refining it more than once).
* So if we call a method which includes this Foo type in an invariant location, we will
* think we get a Foo<? extends T> but in reality we get a Foo<? extends capture#X of ? extends T> which
* is not the same thing and does not work in invariant locations.
* Note that this can't happen for real invariant locations since the typechecker will prevent it, but it
* happens when we must fix variant locations due to type parameter dependences like in Tuple.
*
* See https://github.com/ceylon/ceylon-compiler/issues/1550
*
* @param declaration the type declaration which we should check has variant type parameters and appears in an invariant
* position in the given type.
* @param type the type in which to check for the given declaration, in an invariant position.
* @return true if all these conditions are met.
*/
public boolean needsRawCastForMixinSuperCall(TypeDeclaration declaration, ProducedType type) {
return !declaration.getTypeParameters().isEmpty()
&& hasVariantTypeParameters(declaration)
&& declarationAppearsInInvariantPosition(declaration, type.resolveAliases());
}
private boolean declarationAppearsInInvariantPosition(TypeDeclaration declaration, ProducedType type) {
TypeDeclaration typeDeclaration = type.getDeclaration();
if(typeDeclaration instanceof UnionType){
for(ProducedType pt : typeDeclaration.getCaseTypes()){
if(declarationAppearsInInvariantPosition(declaration, pt))
return true;
}
return false;
}
if(typeDeclaration instanceof IntersectionType){
for(ProducedType pt : typeDeclaration.getSatisfiedTypes()){
if(declarationAppearsInInvariantPosition(declaration, pt))
return true;
}
return false;
}
if(typeDeclaration instanceof ClassOrInterface){
java.util.List<TypeParameter> typeParameters = typeDeclaration.getTypeParameters();
Map<TypeParameter, ProducedType> typeArguments = type.getTypeArguments();
for(TypeParameter tp : typeParameters){
ProducedType typeArgument = typeArguments.get(tp);
if(tp.isInvariant()
|| hasDependentTypeParameters(typeParameters, tp)){
if(Decl.equal(typeArgument.getDeclaration(), declaration)){
return true;
}
}
if(declarationAppearsInInvariantPosition(declaration, typeArgument))
return true;
}
}
return false;
}
private boolean hasVariantTypeParameters(TypeDeclaration declaration) {
for(TypeParameter tp : declaration.getTypeParameters()){
if(!tp.isInvariant())
return true;
}
return false;
}
protected ProducedType getOptionalTypeForInteropIfAllowed(ProducedType expectedType, ProducedType termType, Term term){
// make sure we do not insert null checks if we're going to allow testing for null
if(expectedType != null
&& hasUncheckedNulls(term)
&& isOptional(expectedType)
&& !isOptional(termType)){
// get rid of null-check if we accept an optional type on the LHS
return typeFact().getOptionalType(termType);
}
return termType;
}
public JCThrow makeThrowUnresolvedCompilationError(String exceptionMessage) {
return make().Throw(make().NewClass(null,
List.<JCExpression>nil(),
make().QualIdent(syms().ceylonUnresolvedCompilationErrorType.tsym),
List.<JCExpression>of(make().Literal(exceptionMessage)), null));
}
public JCThrow makeThrowUnresolvedCompilationError(LocalizedError error) {
String errorMessage = error.getErrorMessage().getMessage();
at(error.getNode());
return makeThrowUnresolvedCompilationError(errorMessage != null ? errorMessage : "compiler bug: error with unknown message");
}
JCThrow makeThrowAssertionException(JCExpression messageExpr) {
JCExpression exception = make().NewClass(null, null,
makeIdent(syms().ceylonAssertionErrorType),
List.<JCExpression>of(messageExpr),
null);
return make().Throw(exception);
}
}