Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.tree.TreeUtil.hasUncheckedNulls;
import static com.redhat.ceylon.langtools.tools.javac.code.Flags.FINAL;
import static com.redhat.ceylon.langtools.tools.javac.code.Flags.PRIVATE;
import static com.redhat.ceylon.langtools.tools.javac.code.Flags.PROTECTED;
import static com.redhat.ceylon.model.typechecker.model.ModelUtil.appliedType;
import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isForBackend;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
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.Stack;
import java.util.TreeSet;
import org.antlr.runtime.Token;
import com.redhat.ceylon.ceylondoc.Util;
import com.redhat.ceylon.common.Backend;
import com.redhat.ceylon.common.Backends;
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.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.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.ModuleDescriptor;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.PositionalArgument;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.Term;
import com.redhat.ceylon.langtools.tools.javac.code.BoundKind;
import com.redhat.ceylon.langtools.tools.javac.code.Symtab;
import com.redhat.ceylon.langtools.tools.javac.code.TypeTags;
import com.redhat.ceylon.langtools.tools.javac.code.Symbol.TypeSymbol;
import com.redhat.ceylon.langtools.tools.javac.main.OptionName;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree;
import com.redhat.ceylon.langtools.tools.javac.tree.TreeMaker;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.Factory;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCAnnotation;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCBinary;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCBlock;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCCase;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCExpression;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCExpressionStatement;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCFieldAccess;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCIdent;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCLiteral;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCMethodInvocation;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCNewArray;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCStatement;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCSwitch;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCThrow;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCTypeParameter;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCVariableDecl;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.LetExpr;
import com.redhat.ceylon.langtools.tools.javac.util.Context;
import com.redhat.ceylon.langtools.tools.javac.util.List;
import com.redhat.ceylon.langtools.tools.javac.util.ListBuffer;
import com.redhat.ceylon.langtools.tools.javac.util.Log;
import com.redhat.ceylon.langtools.tools.javac.util.Name;
import com.redhat.ceylon.langtools.tools.javac.util.Names;
import com.redhat.ceylon.langtools.tools.javac.util.Options;
import com.redhat.ceylon.langtools.tools.javac.util.Position;
import com.redhat.ceylon.langtools.tools.javac.util.Position.LineMap;
import com.redhat.ceylon.compiler.typechecker.tree.TreeUtil;
import com.redhat.ceylon.model.loader.AbstractModelLoader;
import com.redhat.ceylon.model.loader.LanguageAnnotation;
import com.redhat.ceylon.model.loader.NamingBase.Unfix;
import com.redhat.ceylon.model.typechecker.model.Annotation;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.model.typechecker.model.Constructor;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Function;
import com.redhat.ceylon.model.typechecker.model.FunctionOrValue;
import com.redhat.ceylon.model.typechecker.model.Functional;
import com.redhat.ceylon.model.typechecker.model.Generic;
import com.redhat.ceylon.model.typechecker.model.Interface;
import com.redhat.ceylon.model.typechecker.model.Module;
import com.redhat.ceylon.model.typechecker.model.ModuleImport;
import com.redhat.ceylon.model.typechecker.model.Package;
import com.redhat.ceylon.model.typechecker.model.Parameter;
import com.redhat.ceylon.model.typechecker.model.ParameterList;
import com.redhat.ceylon.model.typechecker.model.Reference;
import com.redhat.ceylon.model.typechecker.model.Scope;
import com.redhat.ceylon.model.typechecker.model.SiteVariance;
import com.redhat.ceylon.model.typechecker.model.Type;
import com.redhat.ceylon.model.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.model.typechecker.model.TypeParameter;
import com.redhat.ceylon.model.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.model.typechecker.model.TypedReference;
import com.redhat.ceylon.model.typechecker.util.TypePrinter;
/**
* Base class for all delegating transformers
*/
public abstract class AbstractTransformer implements Transformation {
private final static TypePrinter typeSerialiser = new TypePrinter(
true,//printAbbreviated
true,//printTypeParameters
false,//printTypeParameterDetail
true,//printQualifyingType
false,//escapeLowercased
true,//printFullyQualified
true);//printQualifier
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;
private Stack> typeParameterSubstitutions = new Stack>();
protected Map omittedModelAnnotations;
public boolean simpleAnnotationModels;
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);
simpleAnnotationModels = Options.instance(context).get(OptionName.BOOTSTRAPCEYLON) != null;
}
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());
}
}
boolean blocked = false;
int block() {
gen().blocked = true;
return make.pos;
}
int unblock() {
gen().blocked = false;
return make.pos;
}
protected void _at(int pos) {
if (!gen().blocked) {
make.at(pos);
}
}
@Override
public Factory at(Node node) {
if (node == null) {
_at(Position.NOPOS);
}
else {
Token token = node.getToken();
if (token != null) {
int tokenStartPosition = getMap().getStartPosition(token.getLine()) + token.getCharPositionInLine();
_at(tokenStartPosition);
if (javaPositionsRetriever != null) {
javaPositionsRetriever.addCeylonNode(tokenStartPosition, node);
}
}
}
return make();
}
public Factory at(Node node, Token token) {
if (token == null) {
_at(Position.NOPOS);
}
else {
if (token != null) {
int tokenStartPosition = getMap().getStartPosition(token.getLine()) + token.getCharPositionInLine();
_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() {
_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);
_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);
_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 unquoted simple identifier
* @param ident The identifier
* @return The ident
*/
JCExpression makeUnquotedIdent(String ident) {
return naming.makeUnquotedIdent(ident);
}
/**
* Makes an unquoted simple identifier
* @param ident The identifier
* @return The ident
*/
JCExpression makeUnquotedIdent(Name ident) {
return naming.makeUnquotedIdent(ident);
}
/**
* Makes an quoted simple identifier
* @param ident The identifier
* @return The ident
*/
JCIdent makeQuotedIdent(String ident) {
// TODO Only 3 callers
return naming.makeQuotedIdent(ident);
}
/**
* Makes a quoted 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 unquoted 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(com.redhat.ceylon.langtools.tools.javac.code.Type type) {
return naming.makeIdent(type);
}
/**
* Makes a unquoted 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 unquoted 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(Type type) {
if (canUnbox(type)) {
if (isCeylonBoolean(type)) {
return makeBoolean(false);
} else if (isCeylonFloat(type) && type.getUnderlyingType() == null) {
return make().Literal(0.0);
} else if ("float".equals(type.getUnderlyingType())) {
return make().Literal((float)0.0);
} else if (isCeylonInteger(type) && type.getUnderlyingType() == null) {
return makeLong(0);
} else if ("long".equals(type.getUnderlyingType())) {
return makeLong(0);
} else if ("int".equals(type.getUnderlyingType())) {
return make().Literal(0);
} else if ("short".equals(type.getUnderlyingType())) {
return make().TypeCast(make().Type(syms().shortType), make().Literal(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 params = parameter != null ? List.of(parameter) : List.nil();
return makeNewClass(name, params);
}
/** Creates a "new foo();" */
JCTree.JCNewClass makeSyntheticInstance(Declaration decl) {
JCExpression clazz = naming.makeSyntheticClassname(decl);
return makeNewClass(clazz, List.nil());
}
JCTree.JCNewClass makeNewClass(JCExpression clazz) {
return makeNewClass(clazz, null);
}
// Creates a "new foo(arg1, arg2, ...);"
JCTree.JCNewClass makeNewClass(JCExpression clazz, List args) {
if (args == null) {
args = List.nil();
}
return make().NewClass(null, null, clazz, args, null);
}
JCBlock makeGetterBlock(TypedDeclaration declarationModel,
final Tree.Block block,
final Tree.SpecifierOrInitializerExpression expression) {
List stats;
if (block != null) {
stats = statementGen().transformBlock(block);
} else {
BoxingStrategy boxing = CodegenUtil.getBoxingStrategy(declarationModel);
Type 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.of(transStat);
}
JCBlock getterBlock = make().Block(0, stats);
return getterBlock;
}
JCBlock makeGetterBlock(final JCExpression expression) {
List stats = List.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 stats;
if (block != null) {
stats = statementGen().transformBlock(block);
} else {
Type 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.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}, {@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.of(
makeJavaType(declarationModel.getType(), flags)));
}
return boxClass;
}
/**
* Makes a final {@code VariableBox} (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 args = init != null ? List.of(init) : List.nil();
JCExpression newBox = make().NewClass(
null, List.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 statements, JCExpression... args) {
return makeLetExpr(varBaseName.getName(), statements, args);
}
private JCExpression makeLetExpr(String varBaseName, List statements, JCExpression... args) {
String varName = null;
ListBuffer 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(Type 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(Type type) {
return type.getSupertype(typeFact.getNullDeclaration()) != null;
}
boolean isNullValue(Type type) {
return type.getSupertype(typeFact.getNullValueDeclaration().getTypeDeclaration()) != null;
}
public static boolean isAnything(Type type) {
return CodegenUtil.isVoid(type);
}
private boolean isObject(Type type) {
return typeFact.getObjectType().isExactly(type);
}
private boolean isBasic(Type type) {
return typeFact.getBasicType().isExactly(type);
}
private boolean isIdentifiable(Type type) {
return typeFact.getIdentifiableType().isExactly(type);
}
public boolean isAlias(Type type) {
return type.getDeclaration().isAlias() || typeFact.getDefiniteType(type).getDeclaration().isAlias();
}
/**
* Simplifies a Ceylon type, eliminating certain things which Java cannot be represented in a Java type.
* The following simplifications are made:
*
*
Ceylon Type
result
*
T?
T
*
T&Object
T
*
T&Identifiable
T
*
T&Basic
T
*
*/
Type simplifyType(Type orgType) {
if(orgType == null)
return null;
Type 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();
}
}
if (type.isUnion() && type.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 = type.getCaseTypes().get(0);
} else if (type.isIntersection()) {
java.util.List satisfiedTypes = type.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 (isObject(satisfiedTypes.get(1)) || isBasic(satisfiedTypes.get(1)) || isIdentifiable(satisfiedTypes.get(0))) {
type = satisfiedTypes.get(0);
} else if (isObject(satisfiedTypes.get(0)) || isBasic(satisfiedTypes.get(0)) || isIdentifiable(satisfiedTypes.get(0))) {
type = satisfiedTypes.get(1);
}
}
}
if(isTrueFalseUnion(type))
type = typeFact().getBooleanType();
else if(containsJavaEnumInUnion(type))
type = typeFact().denotableType(type);
if (type.getDeclaration() instanceof Constructor) {
type = type.getExtendedType();
}
return type;
}
private boolean containsJavaEnumInUnion(Type type) {
if(!type.isUnion())
return false;
for(Type caseType : type.getCaseTypes()){
if(caseType.isClass()
&& caseType.getDeclaration().isJavaEnum())
return true;
}
return false;
}
TypedReference getTypedReference(TypedDeclaration decl){
java.util.List typeArgs = Collections.emptyList();
if (decl instanceof Function) {
// For methods create type arguments for any type parameters it might have
Function m = (Function)decl;
if (!m.getTypeParameters().isEmpty()) {
typeArgs = new ArrayList(m.getTypeParameters().size());
for (TypeParameter p: m.getTypeParameters()) {
Type pt = p.getType();
typeArgs.add(pt);
}
}
}
if(decl.getContainer() instanceof TypeDeclaration){
TypeDeclaration containerDecl = (TypeDeclaration) decl.getContainer();
return containerDecl.getType().getTypedMember(decl, typeArgs);
}
return decl.appliedTypedReference(null, typeArgs);
}
TypedReference nonWideningTypeDecl(TypedReference typedReference) {
return nonWideningTypeDecl(typedReference, typedReference.getQualifyingType());
}
TypedReference nonWideningTypeDecl(TypedReference typedReference, Type currentType) {
TypedReference 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
*/
Type declType = typedReference.getType();
Type 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);
if (!typedReference.getTypeArguments().containsKey(simplifyType(refinedDeclType).getDeclaration())) {
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) {
TypedReference typedReference = getTypedReference(typedDeclaration);
return isWideningTypeDecl(typedReference, typedReference.getQualifyingType());
}
public boolean isWideningTypeDecl(TypedReference typedReference, Type currentType) {
TypedReference 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
*/
Type declType = typedReference.getType();
Type 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 TypedReference getRefinedDeclaration(TypedReference typedReference, Type currentType) {
TypedDeclaration decl = typedReference.getDeclaration();
TypedDeclaration modelRefinedDecl = (TypedDeclaration)decl.getRefinedDeclaration();
Type 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 refinedMembers = getRefinedMembers(declaringType, decl.getName(),
com.redhat.ceylon.model.typechecker.model.ModelUtil.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
TypedReference 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
TypedReference 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
Type extendedType = declaringType.getExtendedType();
if(extendedType != null){
TypedReference refinedTypedReference = getRefinedTypedReference(extendedType, modelRefinedDecl);
Type refinedType = refinedTypedReference.getType();
if(!isTypeParameter(refinedType)
&& !willEraseToObject(refinedType))
return refinedTypedReference;
}
// then satisfied interfaces
for(Type satisfiedType : declaringType.getSatisfiedTypes()){
TypedReference refinedTypedReference = getRefinedTypedReference(satisfiedType, modelRefinedDecl);
Type refinedType = refinedTypedReference.getType();
if(!isTypeParameter(refinedType)
&& !willEraseToObject(refinedType))
return refinedTypedReference;
}
}
}
}
/*
* Now there's another crazy case:
*
* interface Top {
* Top ret => nothing;
* }
* interface Left satisfies Top {}
* interface Right satisfies Top {}
* 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.ret from Left, and that's the one we want
* to use for determining widening.
* See https://github.com/ceylon/ceylon-compiler/issues/1765
*/
Type firstInstantiation = isInheritedWithDifferentTypeArguments(modelRefinedDecl.getContainer(), currentType);
if(firstInstantiation != null){
TypedReference firstInstantiationTypedReference = getRefinedTypedReference(firstInstantiation, modelRefinedDecl);
Type firstInstantiationType = firstInstantiationTypedReference.getType();
if(isWidening(decl.getType(), firstInstantiationType)
|| isWideningTypeArguments(decl.getType(), firstInstantiationType, true))
return firstInstantiationTypedReference;
}
}
return getRefinedTypedReference(typedReference, modelRefinedDecl);
}
protected Type isInheritedWithDifferentTypeArguments(Scope container, Type 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;
Type[] arg = new Type[1];
return findFirstInheritedTypeIfInheritedTwiceWithDifferentTypeArguments(iface, currentType, arg);
}
private Type findFirstInheritedTypeIfInheritedTwiceWithDifferentTypeArguments(Interface iface, Type currentType, Type[] 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
Type extendedType = currentType.getExtendedType();
if(extendedType != null){
Type ret = findFirstInheritedTypeIfInheritedTwiceWithDifferentTypeArguments(iface, extendedType, found);
// stop there if we found a result
if(ret != null)
return ret;
}
// then satisfied interfaces
for(Type satisfiedType : currentType.getSatisfiedTypes()){
Type 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 signature = com.redhat.ceylon.model.typechecker.model.ModelUtil.getSignature(decl);
boolean overloaded = decl.isOverloaded() || decl.isAbstraction();
Declaration refinedDeclaration = decl.getRefinedDeclaration();
if(refinedDeclaration != null){
overloaded |= refinedDeclaration.isOverloaded() || refinedDeclaration.isAbstraction();
}
ClassOrInterface container = (ClassOrInterface) decl.getContainer();
HashSet visited = new HashSet();
// start looking for it, but skip this type, only lookup upwards of it
TypedDeclaration firstRefinedDeclaration = getFirstRefinedDeclaration(container, decl, signature, visited, true, overloaded);
// 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 signature, HashSet visited,
boolean skipType, boolean isOverloaded) {
if(!visited.add(typeDecl))
return null;
if(!skipType){
TypedDeclaration member = (TypedDeclaration) typeDecl.getDirectMember(decl.getName(), signature, false, isOverloaded);
if(member != null)
return member;
}
// look up
// first look in super types
if(typeDecl.getExtendedType() != null){
TypedDeclaration refinedDecl = getFirstRefinedDeclaration(typeDecl.getExtendedType().getDeclaration(), decl, signature, visited, false, isOverloaded);
if(refinedDecl != null && refinedDecl.isShared())
return refinedDecl;
}
// look in interfaces
for(Type interf : typeDecl.getSatisfiedTypes()){
TypedDeclaration refinedDecl = getFirstRefinedDeclaration(interf.getDeclaration(), decl, signature, visited, false, isOverloaded);
if(refinedDecl != null && refinedDecl.isShared())
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 getRefinedMembers(TypeDeclaration decl,
String name,
java.util.List signature, boolean ellipsis) {
Set ret = new HashSet();
collectRefinedMembers(decl.getType(), name, signature, ellipsis,
new HashSet(), ret, true);
return ret;
}
private void collectRefinedMembers(Type currentType, String name,
java.util.List signature, boolean ellipsis,
java.util.Set visited, Set ret,
boolean ignoreFirst) {
TypeDeclaration decl = currentType.getDeclaration();
if (visited.contains(decl)) {
return;
}
else {
visited.add(decl);
Type et = currentType.getExtendedType();
if (et!=null) {
collectRefinedMembers(et, name, signature, ellipsis, visited, ret, false);
}
for (Type st: currentType.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 Function){
// 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 typedSignature = getTypedSignature(currentType, found);
if(typedSignature != null
&& hasMatchingSignature(signature, typedSignature))
ret.add(found);
}
}
}
}
private java.util.List getTypedSignature(Type currentType, TypedDeclaration found) {
// check that its signature is compatible
java.util.List parameterLists = ((Function) found).getParameterLists();
if(parameterLists == null || parameterLists.isEmpty())
return null;
// only consider first param list
java.util.List parameters = parameterLists.get(0).getParameters();
if(parameters == null)
return null;
TypedReference typedMember = currentType.getTypedMember(found, Collections.emptyList());
if(typedMember == null)
return null;
java.util.List typedSignature = new ArrayList(parameters.size());
for(Parameter p : parameters){
Type parameterType = typedMember.getTypedParameter(p).getFullType();
typedSignature.add(parameterType);
}
return typedSignature;
}
private boolean hasMatchingSignature(java.util.List signature, java.util.List typedSignature) {
if(signature.size() != typedSignature.size())
return false;
for(int i=0;i typeArgs = new ArrayList();
if (typedReference.getDeclaration() instanceof Generic) {
for (TypeParameter tp : ((Generic)typedReference.getDeclaration()).getTypeParameters()) {
typeArgs.add(typedReference.getTypeArguments().get(tp));
}
}
return refinedDeclaration.appliedTypedReference(refinedContainerType, typeArgs);
}
private TypedReference getRefinedTypedReference(Type qualifyingType,
TypedDeclaration refinedDeclaration) {
TypeDeclaration refinedContainer = (TypeDeclaration)refinedDeclaration.getContainer();
Type refinedContainerType = qualifyingType.getSupertype(refinedContainer);
return refinedDeclaration.appliedTypedReference(refinedContainerType, Collections.emptyList());
}
public boolean isWidening(Type declType, Type refinedDeclType) {
return !isCeylonObject(declType)
&& willEraseToObject(declType)
&& !willEraseToObject(refinedDeclType);
}
private boolean isWideningTypeArguments(Type declType, Type 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
Type 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 typeArguments = declType.getTypeArguments();
Map refinedTypeArguments = refinedDeclType.getTypeArguments();
java.util.List typeParameters = declType.getDeclaration().getTypeParameters();
for(TypeParameter tp : typeParameters){
Type typeArgument = typeArguments.get(tp);
if(typeArgument == null)
return true; // something fishy here
Type 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 satTP = tp.getSatisfiedTypes();
java.util.List satRefinedTP = new LinkedList();
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(Type satisfiedType : satTP){
for(Type 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;
}
Type nonWideningType(TypedReference declaration, TypedReference refinedDeclaration){
final Reference pr;
if (declaration.equals(refinedDeclaration)) {
pr = declaration;
} else {
Type 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 Function){
// find its index in the refined declaration
TypeParameter refinedTypeParameter = (TypeParameter) refinedType.getDeclaration();
Function refinedMethod = (Function) 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 Function == false)
throw new BugException("refining declaration is not a method: "+declaration);
Function refiningMethod = (Function) 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 Type javacCeylonTypeToProducedType(com.redhat.ceylon.langtools.tools.javac.code.Type t) {
return loader().getType(getLanguageModule(), t.tsym.packge().getQualifiedName().toString(), t.tsym.getQualifiedName().toString(), null);
}
Type javacJavaTypeToProducedType(com.redhat.ceylon.langtools.tools.javac.code.Type t) {
return loader().getType(getJDKBaseModule(), t.tsym.packge().getQualifiedName().toString(), t.tsym.getQualifiedName().toString(), null);
}
Declaration javacJavaTypeDeclaration(com.redhat.ceylon.langtools.tools.javac.code.Type t) {
return loader().getDeclaration(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(Type type) {
if(type == null)
return false;
type = simplifyType(type);
if (type.isUnion() || type.isIntersection()) {
// Any Unions and Intersections erase to Object as well
// except for the ones that erase to Sequential
return true;
}
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())
|| type.isNothing()) {
return true;
}
return false;
}
boolean willEraseToPrimitive(Type type) {
return (isCeylonBoolean(type)
|| isCeylonInteger(type)
|| isCeylonFloat(type)
|| isCeylonCharacter(type)
|| isCeylonByte(type));
}
boolean willEraseToAnnotation(Type type) {
type = simplifyType(type);
return type != null
&& (type.isExactly(typeFact.getAnnotationDeclaration().getType())
|| (type.getDeclaration() instanceof ClassOrInterface
&& type.getDeclaration().equals(typeFact.getConstrainedAnnotationDeclaration())));
}
boolean willEraseToException(Type type) {
type = simplifyType(type);
return type != null && type.isExactly(typeFact.getExceptionType());
}
boolean willEraseToThrowable(Type type) {
type = simplifyType(type);
TypeDeclaration decl = type.getDeclaration();
return Decl.equal(decl, typeFact.getThrowableDeclaration());
}
boolean willEraseToSequence(Type 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) {
return willEraseToBestBounds(param.getModel());
}
public boolean willEraseToBestBounds(FunctionOrValue paramModel) {
Type type = paramModel.getType();
if (typeFact().isUnion(type)
|| typeFact().isIntersection(type)) {
final Type refinedType = ((TypedDeclaration)CodegenUtil.getTopmostRefinedDeclaration(paramModel)).getType();
if (refinedType.isTypeParameter()
&& !refinedType.getSatisfiedTypes().isEmpty()) {
return true;
}
}
return false;
}
boolean hasErasure(Type type) {
return type==null ? false : hasErasureResolved(type.resolveAliases());
}
private boolean hasErasureResolved(Type type) {
if(type == null)
return false;
if(type.isUnion()){
java.util.List caseTypes = type.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(type.isIntersection()){
java.util.List satisfiedTypes = type.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(type.isTypeParameter()){
// consider type parameters with non-erased bounds as erased to force a cast
// see https://github.com/ceylon/ceylon-compiler/issues/1327
for(Type bound : type.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(Type 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(Type type){
return type==null ? false : isTurnedToRawResolved(type.resolveAliases());
}
private boolean isTurnedToRawResolved(Type type) {
// if we don't have type arguments we can't be raw
if(type.getTypeArguments().isEmpty())
return false;
// we only go raw if any type param is an erased union/intersection
// start with type but consider ever qualifying type
Type singleType = type;
do{
// special case for Callable where we stop after the first type param
boolean isCallable = isCeylonCallable(singleType);
TypeDeclaration declaration = singleType.getDeclaration();
Map typeArguments = singleType.getTypeArguments();
for(TypeParameter tp : declaration.getTypeParameters()){
Type 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.isNothing())
return true;
if (isErasedUnionOrIntersection(ta)) {
return true;
}
// 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 false;
}
private boolean isErasedUnionOrIntersection(Type producedType) {
if(producedType.isUnion()){
java.util.List caseTypes = producedType.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(producedType.isIntersection()){
java.util.List satisfiedTypes = producedType.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(Type type) {
return type != null && type.isExactly(typeFact.getStringType());
}
boolean isCeylonBoolean(Type type) {
TypeDeclaration declaration = type.getDeclaration();
return declaration != null
&& (type.isExactly(typeFact.getBooleanType())
|| isBooleanTrue(declaration)
|| Decl.equal(declaration, typeFact.getBooleanTrueClassDeclaration())
|| isBooleanFalse(declaration)
|| Decl.equal(declaration, typeFact.getBooleanFalseClassDeclaration())
|| isTrueFalseUnion(type));
}
private boolean isTrueFalseUnion(Type type) {
if(type.isUnion() && type.getCaseTypes().size() == 2){
for(Type t : type.getCaseTypes()){
if(!isCeylonBoolean(t))
return false;
}
return true;
}
return false;
}
boolean isCeylonInteger(Type type) {
return type != null && type.isExactly(typeFact.getIntegerType());
}
boolean isCeylonFloat(Type type) {
return type != null && type.isExactly(typeFact.getFloatType());
}
boolean isCeylonCharacter(Type type) {
return type != null && type.isExactly(typeFact.getCharacterType());
}
boolean isCeylonByte(Type type) {
return type != null && type.isExactly(typeFact.getByteType());
}
boolean isCeylonArray(Type type) {
return type.getSupertype(typeFact.getArrayDeclaration()) != null;
}
boolean isCeylonObject(Type type) {
return type != null && type.isExactly(typeFact.getObjectType());
}
boolean isCeylonBasicType(Type type) {
return (isCeylonString(type)
|| isCeylonBoolean(type)
|| isCeylonInteger(type)
|| isCeylonFloat(type)
|| isCeylonCharacter(type)
|| isCeylonByte(type));
}
boolean isCeylonCallable(Type 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(Type type) {
return typeFact().isCallableType(type);
}
boolean isExactlySequential(Type type) {
return typeFact().getDefiniteType(type).isSequential();
}
boolean isCeylonMetamodelDeclaration(Type type) {
return type.isSubtypeOf(typeFact().getMetamodelDeclarationDeclaration().getType());
}
boolean isCeylonSequentialMetamodelDeclaration(Type 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;
/* Do not perform any simplifications/reductions on the type */
private static final int __JT_FULL_TYPE = 1 << 10;
/** For use when generating a type argument. Implies {@code JT_NO_PRIMITIVES} */
static final int JT_TYPE_ARGUMENT = JT_NO_PRIMITIVES | __JT_FULL_TYPE;
/** 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;
/** For use when generating a narrower type for a refinement, for example when the
* parameter of an overriding method is of type T while the original was T
*/
static final int JT_NARROWED = __JT_FULL_TYPE;
static final int JT_IS = 1 << 15;
/**
* This function is used solely for method return types and parameters
*/
JCExpression makeJavaType(TypedDeclaration typeDecl, Type type, int flags) {
if (typeDecl instanceof Function
&& ((Function)typeDecl).isParameter()) {
Function p = (Function)typeDecl;
Type 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(Type producedType) {
return makeJavaType(producedType, 0);
}
public boolean rawParameters(Declaration d) {
if (d.isMember()
&& rawSupertype(((ClassOrInterface)d.getContainer()).getType(), JT_SATISFIES | JT_EXTENDS)) {
return true;
} else {
return false;
}
}
/**
* Determine whether the given type, when appearing in
* an {@code extends} or {@code implements} clause, should be made raw.
* This can happen because the type itself must be made raw, or because
* any of its supertypes were made raw.
* See #1875.
*/
public boolean rawSupertype(Type ceylonType, int flags) {
if (ceylonType == null
|| (flags & (JT_SATISFIES | JT_EXTENDS)) == 0) {
return false;
}
Type simpleType = simplifyType(ceylonType);
Map tas = simpleType.getTypeArguments();
java.util.List tps = simpleType.getDeclaration().getTypeParameters();
for (TypeParameter tp : tps) {
Type ta = tas.get(tp);
// error handling
if(ta == null)
continue;
// 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 results in the Java type Foo.
ta = getNonNullType(ta);
}
// In a type argument Foo or Foo transform to just Foo
ta = simplifyType(ta);
if (typeFact().isUnion(ta) || typeFact().isIntersection(ta)) {
// For any other union type U|V (U nor V is Optional):
// - The Ceylon type Foo results in the raw Java type Foo.
// For any other intersection type U&V:
// - The Ceylon type Foo 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
return true;
} else if ((flags & (__JT_FULL_TYPE | JT_EXTENDS | JT_SATISFIES)) == 0) {
return true;
}
// otherwise just go on
}
if(!tp.getSatisfiedTypes().isEmpty()){
boolean needsCastForBounds = false;
for(Type bound : tp.getSatisfiedTypes()){
bound = bound.substitute(simpleType);
needsCastForBounds |= expressionGen().needsCast(ta, bound, false, false, false);
}
if(needsCastForBounds){
// replace with the first bound
ta = tp.getSatisfiedTypes().get(0).substitute(simpleType);
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
return true;
}
}
}
if (ta.isNothing()
// 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_FULL_TYPE | 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 or Foo 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
return true;
}
}
}
// deal with supertypes which were raw
for (Type superType : ceylonType.getSatisfiedTypes()) {
if (rawSupertype(superType, flags)) {
return true;
}
}
return rawSupertype(ceylonType.getExtendedType(), flags);
}
JCExpression makeJavaType(final Type ceylonType, final int flags) {
Type type = ceylonType;
if(type == null || type.isUnknown())
return make().Erroneous();
if (type.getDeclaration() instanceof Constructor) {
type = type.getExtendedType();
}
// resolve aliases
if((flags & JT_CLASS_LITERAL) == 0)
type = type.resolveAliases();
if ((flags & __JT_RAW_TP_BOUND) != 0
&& type.isTypeParameter()) {
type = type.getExtendedType();
}
if(type.isUnion()){
for (Type pt : type.getCaseTypes()){
if(pt.getDeclaration().isAnonymous()){
// found one, let's try to make it simpler
Type simplerType = typeFact().denotableType(type);
if(!simplerType.isNothing()
&& !simplerType.isUnion()){
type = simplerType;
} else if (isCeylonBoolean(simplifyType(simplerType))) {
type = simplerType;
}
break;
}
}
}
if(type.getDeclaration().isJavaEnum()){
type = type.getExtendedType();
}
if (type.isTypeConstructor()) {
return make().QualIdent(syms().ceylonAbstractTypeConstructorType.tsym);
}
// 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 (willEraseToAnnotation(type)) {
return make().Type(syms().annotationType);
} 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 | JT_IS)) == 0) {
Type typeArg = simplifyType(type).getTypeArgumentList().get(0);
Type 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.getBooleanType();
} else if ((flags & JT_VALUE_TYPE) == 0 && isJavaArray(type)){
return getJavaArrayElementType(type, flags);
}
JCExpression jt = null;
Type 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 qualifyingTypes = null;
Reference qType = simpleType;
boolean hasTypeParameters = false;
while (qType != null) {
hasTypeParameters |= !qType.getTypeArguments().isEmpty();
if(qualifyingTypes != null)
qualifyingTypes.add(qType);
Declaration 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) || !typeDeclaration.isNamed()) // local or anonymous
&& needsQualifyingTypeArgumentsFromLocalContainers
&& typeDeclaration instanceof ClassOrInterface){
Declaration container = Decl.getDeclarationScope(typeDeclaration.getContainer());
while (container instanceof Function) {
qType = ((Function)container).getReference();
if (qualifyingTypes == null) {
qualifyingTypes = new java.util.ArrayList();
qualifyingTypes.add(simpleType);
}
hasTypeParameters = true;
qualifyingTypes.add(qType);
container = Decl.getDeclarationScope(container.getContainer());
}
if (container instanceof TypeDeclaration) {
qType = ((TypeDeclaration)container).getType();
}else {
qType = null;
}
}else if(typeDeclaration.isNamed()){ // avoid anonymous types which may pretend that they have a qualifying type
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 = ((Type)qType).getSupertype((TypeDeclaration) typeDeclaration.getContainer());
}
}else{
// skip local declaration containers
qType = null;
}
// delayed allocation if we have a qualifying type
if(qualifyingTypes == null && qType != null){
qualifyingTypes = new java.util.ArrayList();
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(Reference pt : qualifyingTypes){
Declaration declaration = pt.getDeclaration();
if(declaration instanceof TypeDeclaration && Decl.isStatic((TypeDeclaration)declaration)){
break;
}
firstQualifyingTypeWithTypeParameters--;
}
if(firstQualifyingTypeWithTypeParameters < 0)
firstQualifyingTypeWithTypeParameters = 0;
// put them in outer->inner order
Collections.reverse(qualifyingTypes);
}
if (((flags & JT_RAW) == 0) && hasTypeParameters
&& !rawSupertype(ceylonType, flags)) {
// 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 qualifyingTypeParameters = new java.util.ArrayList();
java.util.Map qualifyingTypeArguments = new java.util.HashMap();
collectQualifyingTypeArguments(qualifyingTypeParameters, qualifyingTypeArguments, qualifyingTypes);
ListBuffer 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 (Reference qualifyingType : qualifyingTypes) {
jt = makeParameterisedType(qualifyingType.getType(), 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 (isCeylonCallable(type) &&
(flags & JT_CLASS_NEW) != 0) {
jt = makeIdent(syms().ceylonAbstractCallableType);
} else 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 qualifyingTypeParameters,
Map qualifyingTypeArguments,
java.util.List 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 names = new HashSet();
// 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--) {
Reference qualifiedType = qualifyingTypes.get(i);
Map tas = qualifiedType.getTypeArguments();
java.util.List tps = ((Generic)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
Declaration declaration = qualifiedType.getDeclaration();
if(Decl.isLocal(declaration)){
Scope scope = declaration.getContainer();
// collect every container method until the next type or package
java.util.List methods = new LinkedList();
while(scope != null
&& scope instanceof ClassOrInterface == false
&& scope instanceof Package == false){
if(scope instanceof Function){
methods.add((Function) scope);
}
scope = scope.getContainer();
}
// methods are sorted inner to outer, which is the order we're following here for types
for(Function method : methods){
java.util.List 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 Type type;
MultidimensionalArray(int dimension, Type type){
this.dimension = dimension;
this.type = type;
}
}
protected MultidimensionalArray getMultiDimensionalArrayInfo(Type 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(Type 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(Type 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(Type 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");
Type 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(Type type) {
Module jdkBaseModule = loader().getJDKBaseModule();
Package javaLang = jdkBaseModule.getPackage("java.lang");
TypeDeclaration enumDecl = (TypeDeclaration)javaLang.getDirectMember("Enum", null, false);
if (type.isClass() && type.getDeclaration().isAnonymous()) {
type = type.getExtendedType();
}
return type.isSubtypeOf(enumDecl.appliedType(null, Collections.singletonList(type)));
}
public JCExpression makeParameterisedType(Type type, Type generalType, final int flags,
JCExpression qualifyingExpression, java.util.List qualifyingTypes,
int firstQualifyingTypeWithTypeParameters, int index) {
JCExpression baseType;
TypeDeclaration tdecl = type.getDeclaration();
ListBuffer 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 makeTypeArgs(
Type simpleType,
int flags) {
Map tas = simpleType.getTypeArguments();
java.util.List tps = simpleType.getDeclaration().getTypeParameters();
return makeTypeArgs(isCeylonCallable(simpleType), flags, tas, tps, simpleType);
}
private ListBuffer makeTypeArgs(boolean isCeylonCallable,
int flags, Map tas,
java.util.List tps, Type simpleType) {
ListBuffer typeArgs = new ListBuffer();
for (TypeParameter tp : tps) {
Type 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 results in the Java type Foo.
ta = getNonNullType(ta);
}
// In a type argument Foo or Foo transform to just Foo
ta = simplifyType(ta);
if (typeFact().isUnion(ta) || typeFact().isIntersection(ta)) {
// For any other union type U|V (U nor V is Optional):
// - The Ceylon type Foo results in the raw Java type Foo.
// For any other intersection type U&V:
// - The Ceylon type Foo 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
if ((flags & (JT_EXTENDS | JT_SATISFIES)) != 0) throw new BugException("rawSupertype() should prevent this method going raw when JT_EXTENDS | JT_SATISFIES");
typeArgs = null;
break;
} else if ((flags & (__JT_FULL_TYPE | JT_EXTENDS | JT_SATISFIES)) == 0) {
if ((flags & (JT_EXTENDS | JT_SATISFIES)) != 0) throw new BugException("rawSupertype() should prevent this method going raw when JT_EXTENDS | JT_SATISFIES");
typeArgs = null;
break;
}
// otherwise just go on
}
if (isCeylonBoolean(ta)
&& !isTypeParameter(ta)) {
ta = typeFact.getBooleanType();
}
JCExpression jta;
if(!tp.getSatisfiedTypes().isEmpty()){
boolean needsCastForBounds = false;
for(Type bound : tp.getSatisfiedTypes()){
bound = bound.substitute(tas, null);
needsCastForBounds |= expressionGen().needsCast(ta, bound, false, false, false);
}
if(needsCastForBounds){
// replace with the first bound
ta = tp.getSatisfiedTypes().get(0).substitute(tas, null);
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))){
if ((flags & (JT_EXTENDS | JT_SATISFIES)) != 0) throw new BugException("rawSupertype() should prevent this method going raw when JT_EXTENDS | JT_SATISFIES");
// A bit ugly, but we need to escape from the loop and create a raw type, no generics
typeArgs = null;
break;
}
}
}
if (ta.isExactlyNothing()
// 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_FULL_TYPE | 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 or Foo 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
if ((flags & (JT_EXTENDS | JT_SATISFIES)) != 0) throw new BugException("rawSupertype() should prevent this method going raw when JT_EXTENDS | JT_SATISFIES");
typeArgs = null;
break;
} else {
// - The Ceylon type Foo appearing in an extends or satisfies location results in the Java type
// Foo