com.redhat.ceylon.compiler.java.codegen.CallableBuilder Maven / Gradle / Ivy
/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.redhat.ceylon.compiler.java.codegen;
import static com.redhat.ceylon.compiler.java.codegen.AbstractTransformer.JT_CLASS_NEW;
import static com.redhat.ceylon.compiler.java.codegen.AbstractTransformer.JT_COMPANION;
import static com.redhat.ceylon.compiler.java.codegen.AbstractTransformer.JT_EXTENDS;
import static com.redhat.ceylon.compiler.java.codegen.AbstractTransformer.JT_NO_PRIMITIVES;
import java.util.ArrayList;
import java.util.Collections;
import com.redhat.ceylon.compiler.java.codegen.AbstractTransformer.BoxingStrategy;
import com.redhat.ceylon.compiler.java.codegen.Naming.SyntheticName;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.langtools.tools.javac.code.Flags;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCAnnotation;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCClassDecl;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCExpression;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCMethodDecl;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCMethodInvocation;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCNewClass;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree.JCStatement;
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.util.List;
import com.redhat.ceylon.langtools.tools.javac.util.ListBuffer;
import com.redhat.ceylon.langtools.tools.javac.util.Name;
import com.redhat.ceylon.model.loader.NamingBase.Suffix;
import com.redhat.ceylon.model.loader.NamingBase.Unfix;
import com.redhat.ceylon.model.loader.model.FieldValue;
import com.redhat.ceylon.model.typechecker.model.Class;
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.Interface;
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.Type;
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.model.Value;
/**
* Constructs anonymous subclasses of AbstractCallable, required for higher
* order functions.
*
*
* - If the Callable does not need to encode defaulted parameter values, and
* is not variadic we just generate the relevent {@code $call()}
* method(s). This includes the case of a method reference to something
* with defaulted parameters, because the Callable itself doesn't need to
* encode the default parameter values.
*
- If the Callable needs to encode defaulted parameter values itself (for
* example if it's a Callable for an anonymous function,
* or a method argument with defaulted parameters) then:
*
* - a {@code private $call$typed()} method is generated which encodes
* the actual method code,
* - {@code private $$paramName()} methods are generated for each
* defaulted parameter, and
* - the {@code public $call()} methods downcast their argument(s),
* invoke the necessary {@code $$paramName()} and delegate to
* {@code $call$typed()}.
*
* - If the Callable is variadic then:
*
* - a {@code private $call$typed()} method is generated which encodes
* the actual method code,
* - {@code public $call$variadic()} methods are generated for each
* arity greater than the number of non-defaulted parameters and less
* than or equal to 3, plus one which uses Javac varargs, and
* - the {@code $call()} methods delegate to {@code $call$variadic()} methods,
* downcasting and obtaining default arguments if required,
*
*
*
*/
public class CallableBuilder {
private static final int CALLABLE_MAX_FIZED_ARITY = 3;
static interface DefaultValueMethodTransformation {
public JCExpression makeDefaultValueMethod(AbstractTransformer gen,
Parameter defaultedParam, List defaultMethodArgs);
}
public static final DefaultValueMethodTransformation DEFAULTED_PARAM_METHOD = new DefaultValueMethodTransformation() {
@Override
public JCExpression makeDefaultValueMethod(AbstractTransformer gen,
Parameter defaultedParam,
List defaultMethodArgs) {
return gen.make().Apply(null,
gen.naming.makeDefaultedParamMethod(null, defaultedParam),
defaultMethodArgs);
}
};
DefaultValueMethodTransformation defaultValueCall = DEFAULTED_PARAM_METHOD;
private final AbstractTransformer gen;
private final Node node;
private final Type typeModel;
private final ParameterList paramLists;
private final int numParams;
private final int minimumParams;
private final int minimumArguments;
private final boolean hasOptionalParameters;
private final boolean isVariadic;
/**
* For deferred declarations the default parameter value methods are
* generated on the wrapper class, not on the Callable (#1177)
*/
private boolean delegateDefaultedCalls = true;
private java.util.List parameterTypes;
private ListBuffer parameterDefaultValueMethods;
private CallableTransformation transformation;
private boolean companionAccess = false;
private Naming.Substitution instanceSubstitution;
private List annotations;
private CallableBuilder(CeylonTransformer gen, Node node, Type typeModel, ParameterList paramLists) {
this.gen = gen;
this.node = node;
this.typeModel = typeModel;
this.paramLists = paramLists;
this.numParams = paramLists.getParameters().size();
int minimumParams = 0;
int minimumArguments = 0;
for(Parameter p : paramLists.getParameters()){
if(!p.isDefaulted())
minimumParams++;
if (!Strategy.hasDefaultParameterOverload(p)) {
minimumArguments++;
}
}
this.minimumParams = minimumParams;
this.minimumArguments = minimumArguments;
this.isVariadic = gen.isVariadicCallable(typeModel);
this.hasOptionalParameters = minimumParams != numParams;
}
/**
* Constructs an {@code AbstractCallable} suitable for wrapping a
* method reference. For example:
*
* void someMethod() { ... }
* Anything() ref = someMethod;
*
*/
public static JCExpression methodReference(CeylonTransformer gen,
final Tree.StaticMemberOrTypeExpression forwardCallTo, ParameterList parameterList) {
ListBuffer letStmts = ListBuffer.lb();
CallableBuilder cb = new CallableBuilder(gen, forwardCallTo, forwardCallTo.getTypeModel(), parameterList);
cb.parameterTypes = cb.getParameterTypesFromCallableModel();
Naming.SyntheticName instanceFieldName;
boolean instanceFieldIsBoxed = false;
if (forwardCallTo instanceof Tree.QualifiedMemberOrTypeExpression
&& !ExpressionTransformer.isSuperOrSuperOf(((Tree.QualifiedMemberOrTypeExpression) forwardCallTo).getPrimary())
&& !ExpressionTransformer.isPackageQualified((Tree.QualifiedMemberOrTypeExpression)forwardCallTo)) {
if ((((Tree.QualifiedMemberOrTypeExpression)forwardCallTo).getMemberOperator() instanceof Tree.SpreadOp)) {
instanceFieldIsBoxed = true;
instanceFieldName = null;
} else {
Tree.QualifiedMemberOrTypeExpression qmte = (Tree.QualifiedMemberOrTypeExpression)forwardCallTo;
boolean prevCallableInv = gen.expressionGen().withinSyntheticClassBody(true);
try {
instanceFieldName = gen.naming.synthetic(Unfix.$instance$);
int varTypeFlags = Decl.isPrivateAccessRequiringCompanion(qmte) ? JT_COMPANION : 0;
Type primaryType;
if (Decl.isValueTypeDecl(qmte.getPrimary().getTypeModel())) {
primaryType = qmte.getPrimary().getTypeModel();
} else {
primaryType = qmte.getTarget().getQualifyingType();
}
if (((Tree.QualifiedMemberOrTypeExpression)forwardCallTo).getMemberOperator() instanceof Tree.SafeMemberOp) {
primaryType = gen.typeFact().getOptionalType(primaryType);
}
JCExpression primaryExpr = gen.expressionGen().transformQualifiedMemberPrimary(qmte);
if (Decl.isPrivateAccessRequiringCompanion(qmte)) {
primaryExpr = gen.naming.makeCompanionAccessorCall(primaryExpr, (Interface)qmte.getDeclaration().getContainer());
}
Type varType = qmte.getDeclaration().isShared() ? primaryType : Decl.getPrivateAccessType(qmte);
if (qmte.getPrimary().getUnboxed() == false) {
varTypeFlags |= JT_NO_PRIMITIVES;
instanceFieldIsBoxed = true;
}
letStmts.add(gen.makeVar(Flags.FINAL,
instanceFieldName,
gen.makeJavaType(varType, varTypeFlags),
primaryExpr));
if (qmte.getPrimary() instanceof Tree.MemberOrTypeExpression
&& ((Tree.MemberOrTypeExpression)qmte.getPrimary()).getDeclaration() instanceof TypedDeclaration) {
cb.instanceSubstitution = gen.naming.addVariableSubst((TypedDeclaration)((Tree.MemberOrTypeExpression)qmte.getPrimary()).getDeclaration(), instanceFieldName.getName());
}
} finally {
gen.expressionGen().withinSyntheticClassBody(prevCallableInv);
}
}
} else {
instanceFieldName = null;
}
CallableTransformation tx;
cb.defaultValueCall = new DefaultValueMethodTransformation() {
@Override
public JCExpression makeDefaultValueMethod(AbstractTransformer gen,
Parameter defaultedParam,
List defaultMethodArgs) {
JCExpression fn = null;
if (forwardCallTo instanceof Tree.BaseMemberOrTypeExpression) {
fn = gen.naming.makeDefaultedParamMethod(null, defaultedParam);
} else if (forwardCallTo instanceof Tree.QualifiedMemberOrTypeExpression) {
JCExpression qualifier = gen.expressionGen().transformTermForInvocation(((Tree.QualifiedMemberOrTypeExpression)forwardCallTo).getPrimary(), null);
fn = gen.naming.makeDefaultedParamMethod(qualifier, defaultedParam);
}
return gen.make().Apply(null,
fn,
defaultMethodArgs);
}
};
if (cb.isVariadic) {
tx = cb.new VariadicCallableTransformation(
cb.new CallMethodWithForwardedBody(instanceFieldName, instanceFieldIsBoxed, forwardCallTo, false));
} else {
tx = cb.new FixedArityCallableTransformation(cb.new CallMethodWithForwardedBody(instanceFieldName, instanceFieldIsBoxed, forwardCallTo, true), null);
}
cb.useTransformation(tx);
return letStmts.isEmpty() ? cb.build() : gen.make().LetExpr(letStmts.toList(), cb.build());
}
/**
* Used for "static" method or class references. For example:
*
* value x = Integer.plus;
* value y = Foo.method;
* value z = Outer.Inner;
*
*/
public static CallableBuilder unboundFunctionalMemberReference(
CeylonTransformer gen,
Tree.QualifiedMemberOrTypeExpression qmte,
Type typeModel,
final Functional methodClassOrCtor,
Reference producedReference) {
final ParameterList parameterList = methodClassOrCtor.getFirstParameterList();
Type type = typeModel;
JCExpression target;
boolean hasOuter = !(Decl.isConstructor((Declaration)methodClassOrCtor)
&& gen.getNumParameterLists(typeModel) == 1);
if (!hasOuter) {
type = typeModel;
target = null;
} else {
type = gen.getReturnTypeOfCallable(type);
Type qualifyingType = qmte.getTarget().getQualifyingType();
target = gen.naming.makeUnquotedIdent(Unfix.$instance$);
target = gen.expressionGen().applyErasureAndBoxing(target, producedReference.getQualifyingType(), true, BoxingStrategy.BOXED, qualifyingType);
}
CallableBuilder inner = new CallableBuilder(gen, null, type, parameterList);
inner.parameterTypes = inner.getParameterTypesFromCallableModel();//FromParameterModels();
if (hasOuter) {
inner.defaultValueCall = inner.new MemberReferenceDefaultValueCall(methodClassOrCtor);
}
CallBuilder callBuilder = CallBuilder.instance(gen);
Type accessType = gen.getParameterTypeOfCallable(typeModel, 0);
if (Decl.isConstructor((Declaration)methodClassOrCtor)) {
Constructor ctor = Decl.getConstructor((Declaration)methodClassOrCtor);
Class cls = Decl.getConstructedClass(ctor);
if (Strategy.generateInstantiator(ctor)) {
callBuilder.invoke(gen.naming.makeInstantiatorMethodName(target, cls));
} else {
callBuilder.instantiate(
gen.makeJavaType(gen.getReturnTypeOfCallable(typeModel), JT_CLASS_NEW));
if (!ctor.isShared()) {
accessType = Decl.getPrivateAccessType(qmte);
}
}
} else if (methodClassOrCtor instanceof Function
&& ((Function)methodClassOrCtor).isParameter()) {
callBuilder.invoke(gen.naming.makeQualifiedName(target, (Function)methodClassOrCtor, Naming.NA_MEMBER));
} else if (methodClassOrCtor instanceof Function) {
callBuilder.invoke(gen.naming.makeQualifiedName(target, (Function)methodClassOrCtor, Naming.NA_MEMBER));
if (!((TypedDeclaration)methodClassOrCtor).isShared()) {
accessType = Decl.getPrivateAccessType(qmte);
}
} else if (methodClassOrCtor instanceof Class) {
Class cls = (Class)methodClassOrCtor;
if (Strategy.generateInstantiator(cls)) {
callBuilder.invoke(gen.naming.makeInstantiatorMethodName(target, cls));
} else {
callBuilder.instantiate(new ExpressionAndType(target, null),
gen.makeJavaType(cls.getType(), JT_CLASS_NEW | AbstractTransformer.JT_NON_QUALIFIED));
if (!cls.isShared()) {
accessType = Decl.getPrivateAccessType(qmte);
}
}
} else {
throw BugException.unhandledDeclarationCase((Declaration)methodClassOrCtor, qmte);
}
ListBuffer reified = ListBuffer.lb();
DirectInvocation.addReifiedArguments(gen, producedReference, reified);
for (ExpressionAndType reifiedArgument : reified) {
callBuilder.argument(reifiedArgument.expression);
}
if (Decl.isConstructor((Declaration)methodClassOrCtor)
&& !Decl.isDefaultConstructor(Decl.getConstructor((Declaration)methodClassOrCtor))) {
// invoke the param class ctor
Constructor ctor = Decl.getConstructor((Declaration)methodClassOrCtor);
callBuilder.argument(gen.naming.makeNamedConstructorName(ctor, false));
}
for (Parameter parameter : parameterList.getParameters()) {
callBuilder.argument(gen.naming.makeQuotedIdent(Naming.getAliasedParameterName(parameter)));
}
JCExpression innerInvocation = callBuilder.build();
// Need to worry about boxing for Function and FunctionalParameter
if (methodClassOrCtor instanceof TypedDeclaration
&& !Decl.isConstructor((Declaration)methodClassOrCtor)) {
// use the method return type since the function is actually applied
Type returnType = gen.getReturnTypeOfCallable(type);
innerInvocation = gen.expressionGen().applyErasureAndBoxing(innerInvocation,
returnType,
// make sure we use the type erased info as it has not been passed to the expression since the
// expression is a Callable
CodegenUtil.hasTypeErased((TypedDeclaration)methodClassOrCtor),
!CodegenUtil.isUnBoxed((TypedDeclaration)methodClassOrCtor),
BoxingStrategy.BOXED, returnType, 0);
} else if (methodClassOrCtor instanceof Class
&& Strategy.isInstantiatorUntyped((Class)methodClassOrCtor)) {
// $new method declared to return Object, so needs typecast
innerInvocation = gen.make().TypeCast(gen.makeJavaType(
((Class)methodClassOrCtor).getType()), innerInvocation);
}
List innerBody = List.of(gen.make().Return(innerInvocation));
inner.useDefaultTransformation(innerBody);
if (!hasOuter) {
return inner;
}
ParameterList outerPl = new ParameterList();
Parameter instanceParameter = new Parameter();
instanceParameter.setName(Naming.name(Unfix.$instance$));
Value valueModel = new Value();
instanceParameter.setModel(valueModel);
valueModel.setName(instanceParameter.getName());
valueModel.setInitializerParameter(instanceParameter);
valueModel.setType(accessType);
valueModel.setUnboxed(false);
outerPl.getParameters().add(instanceParameter);
CallableBuilder outer = new CallableBuilder(gen, null, typeModel, outerPl);
outer.parameterTypes = outer.getParameterTypesFromParameterModels();
List outerBody = List.of(gen.make().Return(inner.build()));
outer.useDefaultTransformation(outerBody);
outer.companionAccess = Decl.isPrivateAccessRequiringCompanion(qmte);
return outer;
}
class MemberReferenceDefaultValueCall implements DefaultValueMethodTransformation {
private Functional methodOrClass;
MemberReferenceDefaultValueCall(final Functional methodOrClass) {
this.methodOrClass = methodOrClass;
}
@Override
public JCExpression makeDefaultValueMethod(AbstractTransformer gen, Parameter defaultedParam, List defaultMethodArgs) {
JCExpression fn = gen.naming.makeDefaultedParamMethod(gen.naming.makeUnquotedIdent(Unfix.$instance$),
defaultedParam);
return gen.make().Apply(null,
fn,
defaultMethodArgs);
}
}
public static CallableBuilder javaStaticMethodReference(CeylonTransformer gen,
Type typeModel,
final Functional methodOrClass,
Reference producedReference) {
final ParameterList parameterList = methodOrClass.getFirstParameterList();
CallableBuilder inner = new CallableBuilder(gen, null, typeModel, parameterList);
ArrayList pt = new ArrayList<>();
for (Parameter p : methodOrClass.getFirstParameterList().getParameters()) {
pt.add(p.getType());
}
inner.parameterTypes = pt;
inner.defaultValueCall = inner.new MemberReferenceDefaultValueCall(methodOrClass);
JCExpression innerInvocation = gen.expressionGen().makeJavaStaticInvocation(gen,
methodOrClass, producedReference, parameterList);
// Need to worry about boxing for Function and FunctionalParameter
if (methodOrClass instanceof TypedDeclaration) {
innerInvocation = gen.expressionGen().applyErasureAndBoxing(innerInvocation,
methodOrClass.getType(),
!CodegenUtil.isUnBoxed((TypedDeclaration)methodOrClass),
BoxingStrategy.BOXED, methodOrClass.getType());
} else if (Strategy.isInstantiatorUntyped((Class)methodOrClass)) {
// $new method declared to return Object, so needs typecast
innerInvocation = gen.make().TypeCast(gen.makeJavaType(
((Class)methodOrClass).getType()), innerInvocation);
}
List innerBody = List.of(gen.make().Return(innerInvocation));
inner.useDefaultTransformation(innerBody);
return inner;
}
/**
* Used for "static" value references. For example:
*
* value x = Integer.plus;
* value y = Foo.method;
* value z = Outer.Inner;
*
*/
public static CallableBuilder unboundValueMemberReference(
CeylonTransformer gen,
Tree.QualifiedMemberOrTypeExpression qmte,
Type typeModel,
final TypedDeclaration value) {
CallBuilder callBuilder = CallBuilder.instance(gen);
Type qualifyingType = qmte.getTarget().getQualifyingType();
JCExpression target = gen.naming.makeUnquotedIdent(Unfix.$instance$);
target = gen.expressionGen().applyErasureAndBoxing(target, qmte.getPrimary().getTypeModel(), true, BoxingStrategy.BOXED, qualifyingType);
if (gen.expressionGen().isThrowableMessage(qmte)) {
callBuilder.invoke(gen.utilInvocation().throwableMessage());
callBuilder.argument(target);
} else if (gen.expressionGen().isThrowableSuppressed(qmte)) {
callBuilder.invoke(gen.utilInvocation().suppressedExceptions());
callBuilder.argument(target);
} else {
JCExpression memberName = gen.naming.makeQualifiedName(target, value, Naming.NA_GETTER | Naming.NA_MEMBER);
if(value instanceof FieldValue){
callBuilder.fieldRead(memberName);
}else{
callBuilder.invoke(memberName);
}
}
JCExpression innerInvocation = callBuilder.build();
// use the return type since the value is actually applied
Type returnType = gen.getReturnTypeOfCallable(typeModel);
innerInvocation = gen.expressionGen().applyErasureAndBoxing(innerInvocation, returnType,
// make sure we use the type erased info as it has not been passed to the expression since the
// expression is a Callable
qmte.getTypeErased(), !CodegenUtil.isUnBoxed(value),
BoxingStrategy.BOXED, returnType, 0);
ParameterList outerPl = new ParameterList();
Parameter instanceParameter = new Parameter();
instanceParameter.setName(Naming.name(Unfix.$instance$));
Value valueModel = new Value();
instanceParameter.setModel(valueModel);
Type accessType = gen.getParameterTypeOfCallable(typeModel, 0);;
if (!value.isShared()) {
accessType = Decl.getPrivateAccessType(qmte);
}
valueModel.setName(instanceParameter.getName());
valueModel.setInitializerParameter(instanceParameter);
valueModel.setType(accessType);
valueModel.setUnboxed(false);
outerPl.getParameters().add(instanceParameter);
CallableBuilder outer = new CallableBuilder(gen, null, typeModel, outerPl);
outer.parameterTypes = outer.getParameterTypesFromParameterModels();
List innerBody = List.of(gen.make().Return(innerInvocation));
outer.useDefaultTransformation(innerBody);
outer.companionAccess = Decl.isPrivateAccessRequiringCompanion(qmte);
return outer;
}
/**
* Constructs an {@code AbstractCallable} suitable for an anonymous function.
*/
public static CallableBuilder anonymous(
CeylonTransformer gen,
Node node,
FunctionOrValue model,
Tree.Expression expr,
java.util.List parameterListTree,
Type callableTypeModel, boolean delegateDefaultedCalls) {
boolean prevSyntheticClassBody = gen.expressionGen().withinSyntheticClassBody(true);
JCExpression transformedExpr = gen.expressionGen().transformExpression(expr, BoxingStrategy.BOXED, gen.getReturnTypeOfCallable(callableTypeModel));
gen.expressionGen().withinSyntheticClassBody(prevSyntheticClassBody);
final List stmts = List.of(gen.make().Return(transformedExpr));
return methodArgument(gen, null, model, callableTypeModel, parameterListTree, stmts, delegateDefaultedCalls);
}
public static CallableBuilder methodArgument(
CeylonTransformer gen,
Node node,
Function model,
Type callableTypeModel,
java.util.List parameterListTree,
List stmts) {
return methodArgument(gen, node, model,callableTypeModel, parameterListTree, stmts, true);
}
/**
* Transforms either a method argument in a named invocation, or an
* anonymous method. For example:
*
* namedInvocation(
* methodArgument() {
* }
* );
*
* or
*
* Integer() x;
* x => () => 1;
*
*
*/
private static CallableBuilder methodArgument(
CeylonTransformer gen,
Node node,
FunctionOrValue model,
Type callableTypeModel,
java.util.List parameterListTree,
List stmts, boolean delegateDefaultedCalls) {
for (int ii = parameterListTree.size()-1; ii > 0; ii-- ) {
Tree.ParameterList pl = parameterListTree.get(ii);
Type t = callableTypeModel;
for (int jj = 0; jj < ii; jj++) {
t = gen.getReturnTypeOfCallable(t);
}
CallableBuilder cb = new CallableBuilder(gen, node, t, pl.getModel());
cb.parameterTypes = cb.getParameterTypesFromParameterModels();
cb.parameterDefaultValueMethods(pl);
cb.delegateDefaultedCalls = delegateDefaultedCalls;
cb.useDefaultTransformation(stmts);
stmts = List.of(gen.make().Return(cb.build()));
}
CallableBuilder cb = new CallableBuilder(gen, node, callableTypeModel, parameterListTree.get(0).getModel());
cb.parameterTypes = cb.getParameterTypesFromParameterModels();
cb.parameterDefaultValueMethods(parameterListTree.get(0));
cb.delegateDefaultedCalls = delegateDefaultedCalls;
cb.useDefaultTransformation(stmts);
cb.annotations = gen.makeAtMethod().prependList(gen.makeAtName(model.getName())).prependList(gen.makeAtLocalDeclaration(model.getQualifier(), false));
return cb;
}
/**
* Constructs an {@code AbstractCallable} suitable for use in a method
* definition with a multiple parameter lists (i.e. an intermediate result
* from method with multiple parameter lists). For example
*
* String name(String first)(String second) => first + " " + second;
*
*/
public static CallableBuilder mpl(
CeylonTransformer gen,
Type typeModel,
ParameterList parameterList,
Tree.ParameterList parameterListTree,
List body) {
CallableBuilder cb = new CallableBuilder(gen, parameterListTree, typeModel, parameterList);
if (body == null) {
body = List.nil();
}
cb.parameterTypes = cb.getParameterTypesFromParameterModels();
cb.parameterDefaultValueMethods(parameterListTree);
cb.useDefaultTransformation(body);
return cb;
}
public int getMinimumParameters() {
return minimumParams;
}
public int getMinimumArguments() {
return minimumArguments;
}
/**
* Abstraction over things which can generate a method to add to
* the AbstractCallable we're building.
*/
abstract class MethodWithArity {
/**
* Makes a method with the given (Java) arity.
*/
abstract MethodDefinitionBuilder makeMethod(int arity);
/**
* Makes a variable declaration for a variable which will be used as an
* argument to a call. The variable is initialized to either the
* corresponding parameter,
*
* Foo foo = (Foo)arg0;
*
* or the default value for the corresponding parameter
*
* Bar bar = $$bar(**previous-args**);
*
*/
protected void makeDowncastOrDefaultVar(
ListBuffer stmts,
Naming.SyntheticName name,
boolean boxed,
final Parameter param,
final int a, final int arity) {
JCExpression varInitialExpression = makeDowncastOrDefault(param, boxed, a, arity);
makeVarForParameter(stmts, param, parameterTypes.get(a),
name, boxed, varInitialExpression);
}
/**
* Makes an expression, (appropriately downcasted or unboxed) for the
* given parameter of the {@code $call()} method.
* @param boxed
*/
private JCExpression makeCallParameterExpr(Parameter param, boolean boxed, int argIndex, boolean varargs) {
Type paramType = gen.typeFact().denotableType(parameterTypes.get(Math.min(argIndex, numParams-1)));
return makeParameterExpr(param, argIndex, paramType, boxed, varargs);
}
/**
* Makes an expression, (appropriately downcasted or unboxed) for the
* given parameter of the {@code $call()} method.
*/
protected JCExpression makeParameterExpr(Parameter param, int argIndex, Type paramType, boolean boxed, boolean ellipsis){
JCExpression argExpr;
if (!ellipsis) {
// The Callable has overridden one of the non-ellipsis $call$()
// methods
argExpr = makeParamIdent(gen, argIndex);
} else {
// The Callable has overridden the ellipsis $call$() method
// so we need to index into the ellipsis array
argExpr = gen.make().Indexed(
makeParamIdent(gen, 0),
gen.make().Literal(argIndex));
}
int ebFlags = ExpressionTransformer.EXPR_DOWN_CAST; // we're effectively downcasting it from Object
BoxingStrategy boxingStrategy;
if(!boxed && isValueTypeCall(param, paramType))
boxingStrategy = BoxingStrategy.UNBOXED;
else
boxingStrategy = CodegenUtil.getBoxingStrategy(param.getModel());
argExpr = gen.expressionGen().applyErasureAndBoxing(argExpr,
paramType, // it came in as Object, but we need to pretend its type
// is the parameter type because that's how unboxing determines how it has to unbox
true, // it's completely erased
true, // it came in boxed
boxingStrategy, // see if we need to box
paramType, // see what type we need
ebFlags);
if (companionAccess) {
argExpr = gen.naming.makeCompanionAccessorCall(argExpr, (Interface)paramType.getType().getDeclaration());
}
return argExpr;
}
protected JCExpression makeDowncastOrDefault(final Parameter param,
boolean boxed, final int a, final int arity) {
// read the value
JCExpression paramExpression = makeCallParameterExpr(param, boxed, a, arity > CALLABLE_MAX_FIZED_ARITY);
JCExpression varInitialExpression;
// TODO Suspicious
if(param.isDefaulted() || param.isSequenced()){
if(arity > CALLABLE_MAX_FIZED_ARITY){
// must check if it's defined
JCExpression test = gen.make().Binary(JCTree.GT, gen.makeSelect(getParamName(0), "length"), gen.makeInteger(a));
JCExpression elseBranch = makeDefaultValueCall(param, a);
varInitialExpression = gen.make().Conditional(test, paramExpression, elseBranch);
}else if(a >= arity && Strategy.hasDefaultParameterValueMethod(param)){
// get its default value because we don't have it
varInitialExpression = makeDefaultValueCall(param, a);
}else{
// we must have it
varInitialExpression = paramExpression;
}
}else{
varInitialExpression = paramExpression;
}
return varInitialExpression;
}
/**
* Makes an invocation of a default parameter value method (if the
* parameter is defaulted), or empty (if it's *variadic)
*/
private JCExpression makeDefaultValueCall(Parameter defaultedParam, int i){
if (Strategy.hasDefaultParameterValueMethod(defaultedParam)) {
// add the default value
List defaultMethodArgs = List.nil();
// pass all the previous values
for(int a=i-1;a>=0;a--){
Parameter param = paramLists.getParameters().get(a);
JCExpression previousValue = getCallableTempVarName(param).makeIdent();
defaultMethodArgs = defaultMethodArgs.prepend(previousValue);
}
// now call the default value method
return defaultValueCall.makeDefaultValueMethod(gen, defaultedParam, defaultMethodArgs);
} else if (Strategy.hasEmptyDefaultArgument(defaultedParam)) {
return gen.makeEmptyAsSequential(true);
}
throw new BugException(defaultedParam.getName() + " is not a defaulted parameter");
}
protected Naming.SyntheticName getCallableTempVarName(Parameter param) {
return gen.naming.synthetic(Naming.getAliasedParameterName(param));
}
protected final void makeVarForParameter(ListBuffer stmts,
final Parameter param, Type parameterType,
Naming.SyntheticName name, boolean boxed, JCExpression expr) {
// store it in a local var
int flags = 0;
boolean castRequired = false;
if ((parameterType.isExactlyNothing()
|| gen.willEraseToObject(parameterType))) {
Type et = param.getType();
while (et.getDeclaration() instanceof TypeParameter
&& !et.getSatisfiedTypes().isEmpty()) {
et = et.getSatisfiedTypes().get(0);
}
if (param.getType() != et) {
parameterType = et;
castRequired = true;
flags |= AbstractTransformer.JT_RAW;
}
}
flags |= jtFlagsForParameter(param, parameterType, boxed);
if (castRequired && !gen.willEraseToObject(parameterType)) {
expr = gen.make().TypeCast(gen.makeJavaType(parameterType, flags), expr);
}
JCVariableDecl var = gen.make().VarDef(gen.make().Modifiers(param.getModel().isVariable() ? 0 : Flags.FINAL),
name.asName(),
gen.makeJavaType(parameterType, flags),
expr);
stmts.append(var);
if (ParameterDefinitionBuilder.isBoxedVariableParameter(param)) {
stmts.append(gen.makeVariableBoxDecl(name.makeIdent(), param.getModel()));
}
}
protected int jtFlagsForParameter(final Parameter param, Type parameterType, boolean boxed) {
int flags = 0;
if(!CodegenUtil.isUnBoxed(param.getModel()) && (!isValueTypeCall(param, parameterType) || boxed)){
flags |= AbstractTransformer.JT_NO_PRIMITIVES;
}
if (companionAccess) {
flags |= AbstractTransformer.JT_COMPANION;
}
return flags;
}
protected boolean isValueTypeCall(Parameter param, Type parameterType) {
return false;
}
}
class CallMethodWithGivenBody extends MethodWithArity {
private List body;
private boolean usedBody = false;
CallMethodWithGivenBody(List body) {
this.body = body;
}
@Override
MethodDefinitionBuilder makeMethod(int arity) {
if (arity < Math.min(getMinimumArguments(), CALLABLE_MAX_FIZED_ARITY+1)) {
return null;
}
if (usedBody) {
body = List.of(gen.make().Exec(gen.makeErroneous(null, "compiler bug: tree reuse detected")));
}
usedBody = true;
ListBuffer stmts = new ListBuffer();
int a = 0;
for(Parameter param : paramLists.getParameters()){
// don't read default parameter values for forwarded calls
makeDowncastOrDefaultVar(stmts, getCallableTempVarName(param), false, param, a, arity);
a++;
}
return makeCallMethod(stmts.appendList(body).toList(), arity);
}
}
class CallMethodWithForwardedBody extends MethodWithArity {
final boolean isCallMethod;
private final Tree.Term forwardCallTo;
private final Naming.SyntheticName instanceFieldName;
private final boolean instanceFieldIsBoxed;
CallMethodWithForwardedBody(Naming.SyntheticName instanceFieldName, boolean instanceFieldIsBoxed, Tree.Term forwardCallTo, boolean isCallMethod) {
this.instanceFieldName = instanceFieldName;
this.instanceFieldIsBoxed = instanceFieldIsBoxed;
this.forwardCallTo = forwardCallTo;
this.isCallMethod = isCallMethod;
}
@Override
protected Naming.SyntheticName getCallableTempVarName(Parameter param) {
// prefix them with $$ if we only forward, otherwise we need them to have the proper names
return gen.naming.synthetic(Naming.getCallableTempVarName(param));
}
@Override
protected int jtFlagsForParameter(final Parameter param, Type parameterType, boolean boxed) {
int flags = super.jtFlagsForParameter(param, parameterType, boxed);
// Always go raw if we're forwarding, because we're building the call ourselves and we don't get a chance to apply erasure and
// casting to parameter expressions when we pass them to the forwarded method. Ideally we could set it up correctly so that
// the proper erasure is done when we read from the Callable.call Object param, but since we store it in a variable defined here,
// we'd need to duplicate some of the erasure logic here to make or not the type raw, and that would be worse.
// Besides, named parameter invocation does the same.
// See https://github.com/ceylon/ceylon-compiler/issues/1005
flags |= AbstractTransformer.JT_RAW;
return flags;
}
@Override
protected boolean isValueTypeCall(Parameter param, Type parameterType) {
if(!param.isSequenced()
&& forwardCallTo instanceof Tree.QualifiedMemberExpression){
Tree.Primary primary = ((Tree.QualifiedMemberExpression) forwardCallTo).getPrimary();
return Decl.isValueTypeDecl(primary.getTypeModel())
&& Decl.isValueTypeDecl(parameterType);
}
return false;
}
@Override
MethodDefinitionBuilder makeMethod(int arity) {
if (arity < Math.min(getMinimumArguments(), CALLABLE_MAX_FIZED_ARITY+1)) {
return null;
}
ListBuffer stmts = new ListBuffer();
if (isCallMethod) {
int a = 0;
for(Parameter param : paramLists.getParameters()){
// don't read default parameter values for forwarded calls
// if we are in a call method below the variadic one,
// otherwise consume every parameter
if(arity <= CALLABLE_MAX_FIZED_ARITY
/*&& forwardCallTo != null */&& arity == a)
break;
makeDowncastOrDefaultVar(stmts, getCallableTempVarName(param), instanceFieldIsBoxed, param, a, arity);
a++;
}
}
JCExpression invocation = makeInvocation(arity, isCallMethod);
stmts.append(gen.make().Return(invocation));
return isCallMethod ? makeCallMethod(stmts.toList(), arity) : makeCallTypedMethod(stmts.toList());
}
private JCExpression makeInvocation(int arity, boolean isCallMethod) {
Reference target = appliedReference();
CallableInvocation invocationBuilder = new CallableInvocation (
gen,
instanceFieldName,
instanceFieldIsBoxed,
forwardCallTo,
target.getDeclaration(),
target,
gen.getReturnTypeOfCallable(getTypeModel()),
forwardCallTo,
paramLists,
// if we are in a call method below the variadic one, respect arity, otherwise use the parameter list
// size to forward all the arguments
arity <= CALLABLE_MAX_FIZED_ARITY ? arity : paramLists.getParameters().size(),
isCallMethod);
boolean prevCallableInv = gen.expressionGen().withinSyntheticClassBody(true);
JCExpression invocation;
try {
invocation = gen.expressionGen().transformInvocation(invocationBuilder);
} finally {
gen.expressionGen().withinSyntheticClassBody(prevCallableInv);
}
return invocation;
}
private Type getTypeModel() {
return forwardCallTo.getTypeModel();
}
private Reference appliedReference() {
Reference target;
if (forwardCallTo instanceof Tree.MemberOrTypeExpression) {
target = ((Tree.MemberOrTypeExpression)forwardCallTo).getTarget();
} else if (forwardCallTo instanceof Tree.FunctionArgument) {
Function method = ((Tree.FunctionArgument) forwardCallTo).getDeclarationModel();
target = method.appliedReference(null, Collections.emptyList());
} else {
throw new RuntimeException(forwardCallTo.getNodeType());
}
return target;
}
}
/**
* Abstraction over various kinds of {@code $call()} method
*/
abstract class CallableTransformation {
final void appendMethods(ListBuffer classBody) {
if (delegateDefaultedCalls) {
// now generate a method for each supported minimum number of parameters below 4
// which delegates to the $call$typed method if required
for(int javaArity=0; javaArity <= CALLABLE_MAX_FIZED_ARITY+1; javaArity++){
for (MethodDefinitionBuilder mdb : makeMethodsForArity(javaArity)) {
if (mdb != null) {
classBody.append(mdb.build());
}
}
}
} else {
// generate the $call method for the max number of parameters,
// (which delegates to the $call$typed method if required)
for (MethodDefinitionBuilder mdb : makeMethodsForArity(numParams)) {
classBody.append(mdb.build());
}
}
// if required, generate the $call$typed method
MethodDefinitionBuilder callTypedMethod = makeCallTypedMethod();
if (callTypedMethod != null) {
JCMethodDecl callTyped = callTypedMethod.build();
if (callTyped.params.size() != numParams) {
throw new BugException();
}
classBody.append(callTyped);
}
}
/**
* Make the public {@code $call$()} and {@code $call$variadic()} methods
* with the given arity.
* @param arity
* @return
*/
protected abstract Iterable makeMethodsForArity(int arity);
/**
* Make the private {@code $call$typed()} method, whose arity must
* be {@link #numParams}, or return null if
* no {@code $call$typed()} is required
* @return
*/
abstract MethodDefinitionBuilder makeCallTypedMethod();
}
protected final MethodDefinitionBuilder makeCallMethod(List body, int arity) {
MethodDefinitionBuilder callMethod = MethodDefinitionBuilder.callable(gen);
callMethod.isOverride(true);
callMethod.modifiers(Flags.PUBLIC);
Type returnType = gen.getReturnTypeOfCallable(typeModel);
callMethod.resultType(gen.makeJavaType(returnType, JT_NO_PRIMITIVES), null);
// Now append formal parameters
switch (arity) {
case 3:
callMethod.parameter(makeCallableCallParam(0, arity-3));
// fall through
case 2:
callMethod.parameter(makeCallableCallParam(0, arity-2));
// fall through
case 1:
callMethod.parameter(makeCallableCallParam(0, arity-1));
break;
case 0:
break;
default: // use varargs
callMethod.parameter(makeCallableCallParam(Flags.VARARGS, 0));
}
// Return the call result, or null if a void method
callMethod.body(body);
return callMethod;
}
/**
* Builds a {@code $call()} method that
*
* - downcasts the parameters to their correct type,
* - adds any default arguments, and
-
*
- delegates to the {@code $call$typed()} method
*
*/
class FixedArityCallableTransformation extends CallableTransformation {
private final MethodWithArity call;
private final MethodWithArity callTyped;
FixedArityCallableTransformation(MethodWithArity call, MethodWithArity callTyped) {
this.call = call;
this.callTyped = callTyped;
}
@Override
protected List makeMethodsForArity(int arity) {
if (arity > numParams) {
return List.nil();
}
return List.of(call.makeMethod(arity));
}
@Override
MethodDefinitionBuilder makeCallTypedMethod() {
return callTyped != null ? callTyped.makeMethod(numParams) : null;
}
}
abstract class VariadicMethodWithArity extends MethodWithArity {
protected final JCExpression makeRespread(List arguments) {
JCExpression invocation = gen.make().Apply(null,
gen.naming.makeUnquotedIdent(Naming.name(Unfix.$spreadVarargs$)),
arguments);
if (getVariadicParameter().isAtLeastOne()) {
invocation = gen.make().TypeCast(
gen.makeJavaType(getVariadicType(), AbstractTransformer.JT_RAW),
invocation);
}
return invocation;
}
protected final SyntheticName parameterName(int a) {
Parameter param = paramLists.getParameters().get(a);
SyntheticName name = getCallableTempVarName(param);
return name;
}
protected final boolean parameterSequenced(int a) {
return paramLists.getParameters().get(a).isSequenced();
}
protected Parameter getVariadicParameter() {
return paramLists.getParameters().get(numParams - 1);
}
protected Type getVariadicType() {
return parameterTypes.get(numParams - 1);
}
protected Type getVariadicIteratedType() {
return gen.typeFact().getIteratedType(getVariadicType());
}
/** Makes an argument that's just the ident of the parameter {@code $param$n}*/
protected final void makeParameterArgument(final int arity,
ListBuffer stmts, ListBuffer args,
int a) {
SyntheticName name = parameterName(a);
Parameter param = paramLists.getParameters().get(a);
makeDowncastOrDefaultVar(stmts,
name, false, param, a, arity);
args.append(name.makeIdent());
}
/**
* Makes a call to {@code $call$typed()} if required, otherwise uses the
* given body.
* @return
*/
protected final JCMethodInvocation makeCallTypedCall(final int arity, List args) {
JCMethodInvocation chain = gen.make().Apply(null, gen.makeUnquotedIdent(Naming.getCallableTypedMethodName()), args);
return chain;
}
/** Appends a single argument out of */
protected final int makeSequencedArgument(final int arity,
ListBuffer stmts, ListBuffer args,
int a) {
ListBuffer varargs = ListBuffer.lb();
for (; a < arity; a++) {
if (arity < numParams - 1) {
Parameter param1 = paramLists.getParameters().get(Math.min(a, numParams-1));
makeDowncastOrDefaultVar(stmts,
parameterName(Math.min(a, numParams-1)), false, param1, a, arity);
} else {
varargs.append(gen.make().Ident(makeParamName(gen, a)));
}
}
JCExpression varargsSequence;
if (varargs.isEmpty()) {
varargsSequence = gen.makeEmptyAsSequential(true);
} else {
varargsSequence = gen.makeSequence(varargs.toList(),
getVariadicIteratedType(), 0);
}
SyntheticName vname = getCallableTempVarName(getVariadicParameter()).suffixedBy(Suffix.$variadic$);
args.append(vname.makeIdent());
makeVarForParameter(stmts, getVariadicParameter(), getVariadicType(),
vname, false, varargsSequence);
return a;
}
/**
* Constructs an argument list for the target method as follows:
*
* - uses the declared parameters {@code $param$0}, {@code $param$1}, ...,
*
- if the declared target parameter is sequenced collects all remaining
* parameters into a sequential target argument.
*
- TODO Does some other shit
*
*/
protected final void case3(final int arity, ListBuffer stmts,
ListBuffer args) {
int a = 0;
for (; a <= arity; a++) {
if (parameterSequenced(a)) {
// wrap this and all remaining parameters into a sequential
a = makeSequencedArgument(arity, stmts, args, a);
break;
}
if (a < getMinimumArguments()) {
makeParameterArgument(arity, stmts, args, a);
} else {
break;
}
}
for (; args.size() < numParams; a++) {
if (parameterSequenced(a)) {
a = makeSequencedArgument(arity, stmts, args, a);
} else {
makeParameterArgument(arity, stmts, args, a);
}
}
}
}
/**
* Generates {@code $call()} methods for variadic Callables
*/
class CallMethodForVariadic extends VariadicMethodWithArity {
/**
* Make the {@code $call()} method, which delegates to the $call$typed()
*/
@Override
MethodDefinitionBuilder makeMethod(final int arity) {
if (arity < Math.min(getMinimumArguments(), CALLABLE_MAX_FIZED_ARITY+1)) {
return null;
}
ListBuffer stmts = new ListBuffer();
ListBuffer args = ListBuffer.lb();
if (arity <= CALLABLE_MAX_FIZED_ARITY) {
case3(arity, stmts, args);
} else {
makeEllipsisMethod(arity, stmts, args);
}
stmts.append(gen.make().Return(makeCallTypedCall(arity, args.toList())));
MethodDefinitionBuilder callMethod = CallableBuilder.this.makeCallMethod(stmts.toList(), arity);
return callMethod;
}
private void makeEllipsisMethod(final int arity,
ListBuffer stmts, ListBuffer args) {
// we're generating the $call(Object...) method
// pass along the parameters
int a = 0;
for(;a < paramLists.getParameters().size(); a++){
// don't read default parameter values for forwarded calls
if(parameterSequenced(a)) {
break;
}
makeParameterArgument(arity, stmts, args, a);
}
if (a < paramLists.getParameters().size()) {
// there are still parameters needing arguments...
args.append(makeRespread(
List.of(
gen.makeReifiedTypeArgument(getVariadicIteratedType()),
gen.make().Literal(numParams-1),
gen.make().Binary(JCTree.MINUS, gen.naming.makeQualIdent(makeParamIdent(gen, 0), "length"), gen.make().Literal(numParams-1)),
makeParamIdent(gen, 0),
gen.makeEmpty())));
}
}
}
/**
* Generates {@code $call$variadic()} methods for variadic Callables
*/
class CallVariadicMethodForVariadic extends VariadicMethodWithArity {
private void makeEllipsisMethod(final int arity1,
ListBuffer stmts, ListBuffer args) {
int a = 0;
for(Parameter param : paramLists.getParameters()){
if (param.isSequenced()) {
break;
}
makeParameterArgument(arity1, stmts, args, a);
a++;
}
ListBuffer lb = ListBuffer.lb();
for (; a < arity1-1 && a < CALLABLE_MAX_FIZED_ARITY; a++) {
Parameter param = paramLists.getParameters().get(Math.min(a, numParams-1));
lb.append(makeParameterExpr(param, a, getVariadicIteratedType(), false, false));
}
ListBuffer spreadCallArgs = ListBuffer.lb();
spreadCallArgs.append(gen.makeReifiedTypeArgument(getVariadicIteratedType()));
if (arity1 > CALLABLE_MAX_FIZED_ARITY+1) {
spreadCallArgs.append(gen.make().Literal(getMinimumArguments()));
spreadCallArgs.append(makeParamIdent(gen, 0));
} else {
spreadCallArgs.append(gen.make().Literal(0));
spreadCallArgs.append(gen.make().Literal(lb.size()));
spreadCallArgs.append(gen.make().NewArray(gen.make().QualIdent(gen.syms().objectType.tsym), List.nil(), lb.toList()));
spreadCallArgs.append(makeParamIdent(gen, a));
}
args.append(makeRespread(spreadCallArgs.toList()));
}
@Override
MethodDefinitionBuilder makeMethod(final int arity) {
final int arity1 = arity+1;// The arity including the Sequential parameter
if (arity1 < getMinimumArguments()) {
return null;
}
ListBuffer stmts = new ListBuffer();
ListBuffer args = ListBuffer.lb();
if (arity <= CALLABLE_MAX_FIZED_ARITY) {
if (arity1 < getMinimumParameters()) {
// We need to extract some arguments from the sequential parameter
destructureSequential(arity, stmts, args);
} else if (arity1 < numParams) {
case3(arity, stmts, args);
} else if (arity1 == numParams) {
// The arity of the $callvariadic$ method matches what we're calling
useDeclaredParameters(arity, stmts, args);
} else { // arity1 > numParams
// We need to pack some of the last parameters into a sequential argument
respread(arity, stmts, args);
}
} else {
makeEllipsisMethod(arity1, stmts, args);
}
MethodDefinitionBuilder callVaryMethod = MethodDefinitionBuilder.systemMethod(gen, Naming.getCallableVariadicMethodName());
callVaryMethod.isOverride(true);
callVaryMethod.modifiers(Flags.PUBLIC);
Type returnType = gen.getReturnTypeOfCallable(typeModel);
callVaryMethod.resultType(gen.makeJavaType(returnType, JT_NO_PRIMITIVES), null);
// Now append formal parameters
switch (arity1) {
case 4:
callVaryMethod.parameter(makeCallableCallParam(0, arity1-4));
callVaryMethod.parameter(makeCallableCallParam(0, arity1-3));
callVaryMethod.parameter(makeCallableCallParam(0, arity1-2));
callVaryMethod.parameter(makeCallableVaryParam(0, arity1-1));
break;
case 3:
callVaryMethod.parameter(makeCallableCallParam(0, arity1-3));
callVaryMethod.parameter(makeCallableCallParam(0, arity1-2));
callVaryMethod.parameter(makeCallableVaryParam(0, arity1-1));
break;
case 2:
callVaryMethod.parameter(makeCallableCallParam(0, arity1-2));
callVaryMethod.parameter(makeCallableVaryParam(0, arity1-1));
break;
case 1:
callVaryMethod.parameter(makeCallableVaryParam(0, arity1-1));
break;
case 0:
break;
default: // use varargs
callVaryMethod.parameter(makeCallableCallParam(Flags.VARARGS, 0));
}
// Return the call result, or null if a void method
stmts.append(gen.make().Return(makeCallTypedCall(arity1, args.toList())));
callVaryMethod.body(stmts.toList());
return callVaryMethod;
}
/**
* Constructs an argument list for the target method as follows:
*
* - uses the declared parameters {@code $param$0}, {@code $param$1}, ...,
* so long as the parameter is not sequenced.
*
- if the declared target parameter is sequenced collects all remaining
* parameters into a sequential target argument.
*
- TODO Does some other shit
*
- Calls {@code AbstractCallable.$spreadVarargs$} to construct a
* Sequential argument from the remaining parameters.
*
*/
private void respread(final int arity, ListBuffer stmts,
ListBuffer args) {
// respread
int a = 0;
for (; a < numParams; a++) {
if (parameterSequenced(a)) {
break;
}
makeParameterArgument(arity, stmts, args, a);
}
ListBuffer variadicElements = ListBuffer.lb();
for (; a < arity; a++) {
Parameter param = paramLists.getParameters().get(Math.min(a, numParams-1));
variadicElements.append(makeParameterExpr(param, a, getVariadicIteratedType(), false, false));
}
ListBuffer spreadCallArgs = ListBuffer.lb();
spreadCallArgs.append(gen.makeReifiedTypeArgument(getVariadicIteratedType()));
if (arity > CALLABLE_MAX_FIZED_ARITY) {
spreadCallArgs.append(gen.make().Literal(getMinimumArguments()));
spreadCallArgs.append(makeParamIdent(gen, 0));
} else {
spreadCallArgs.append(gen.make().Literal(0));
spreadCallArgs.append(gen.make().Literal(variadicElements.size()));
spreadCallArgs.append(gen.make().NewArray(gen.make().QualIdent(gen.syms().objectType.tsym), List.nil(), variadicElements.toList()));
spreadCallArgs.append(makeParamIdent(gen, a));
}
args.append(makeRespread(spreadCallArgs.toList()));
}
/**
* Constructs an argument list for the target method as follows:
*
* - uses the declared parameters {@code $param$0}, {@code $param$1}, ...,
*
*/
private void useDeclaredParameters(final int arity, ListBuffer stmts,
ListBuffer args) {
for (int a= 0; a < numParams; a++) {
makeParameterArgument(arity, stmts, args, a);
}
}
/**
* Constructs an argument list for the target method as follows:
*
* - uses the declared parameters {@code $param$0}, {@code $param$1}, ...,
*
- then pulls further arguments from the sequential parameter
* using {@code $param$n.get()}
*
- finally then uses {@code $param$n.spanFrom()} on the sequential parameter
* to get the sequential argument to the target method
*
*/
private void destructureSequential(final int arity, ListBuffer stmts,
ListBuffer args) {
// destructuring
int a = 0;
for (; a < getMinimumArguments()-1; a++) {
makeParameterArgument(arity, stmts, args, a);
}
for (; a < numParams-1; a++) {
// Extract from the sequential
SyntheticName name = parameterName(a);
JCExpression get = gen.make().Apply(null,
gen.makeQualIdent(makeParamIdent(gen, arity), "get"),
List.of(gen.expressionGen().applyErasureAndBoxing(gen.make().Literal(a),
gen.typeFact().getIntegerType(), false, BoxingStrategy.BOXED, gen.typeFact().getIntegerType())));
Parameter param = paramLists.getParameters().get(a);
get = gen.expressionGen().applyErasureAndBoxing(get,
parameterTypes.get(a),
true, true,
(jtFlagsForParameter(param, parameterTypes.get(a), false) & JT_NO_PRIMITIVES) == 0 ? BoxingStrategy.UNBOXED : BoxingStrategy.BOXED ,
parameterTypes.get(a), 0);
makeVarForParameter(stmts, param, parameterTypes.get(a),
name, false, get);
args.append(name.makeIdent());
}
// Get the rest of the sequential using spanFrom()
SyntheticName name = parameterName(numParams-1);
JCExpression spanFrom = gen.make().Apply(null,
gen.makeQualIdent(makeParamIdent(gen, arity), "spanFrom"),
List.of(gen.expressionGen().applyErasureAndBoxing(gen.make().Literal(a),
gen.typeFact().getIntegerType(), false, BoxingStrategy.BOXED, gen.typeFact().getIntegerType())));
spanFrom = gen.expressionGen().applyErasureAndBoxing(spanFrom,
parameterTypes.get(a),
true, true, BoxingStrategy.UNBOXED,
parameterTypes.get(a), 0);
Parameter param = paramLists.getParameters().get(numParams-1);
makeVarForParameter(stmts, param, parameterTypes.get(a),
name, false, spanFrom);
args.append(name.makeIdent());
}
}
/**
*
* - a {@code private $call$typed()} method is generated which encodes
* the actual method code,
* - the {@code public $call$variadic()} methods delegate to the
* {@code $call$typed()} method,
* A {@code public $call$variadic()} method is generated for each
* arity greater than the number of non-defaulted parameters and less
* than or equal to {@link #CALLABLE_MAX_FIZED_ARITY}, plus one
* which uses Javac varargs
* - the {@code $call()} methods delegate to the {@code $call$typed()} method,
* downcasting and obtaining default arguments if required,
*
*/
class VariadicCallableTransformation extends CallableTransformation {
private final CallMethodForVariadic call;
private final CallVariadicMethodForVariadic callVariadic;
private final MethodWithArity callTyped;
VariadicCallableTransformation(MethodWithArity callTyped) {
this.call = new CallMethodForVariadic();
this.callVariadic = new CallVariadicMethodForVariadic();
this.callTyped = callTyped;
}
@Override
protected List makeMethodsForArity(int arity) {
List result = List.nil();
MethodDefinitionBuilder callVaryMethod = callVariadic.makeMethod(arity);
if (callVaryMethod != null) {
result = result.prepend(callVaryMethod);
}
MethodDefinitionBuilder callMethod = call.makeMethod(arity);
if (callMethod != null) {
result = result.prepend(callMethod);
}
return result;
}
@Override
MethodDefinitionBuilder makeCallTypedMethod() {
return callTyped.makeMethod(numParams);
}
}
private CallableBuilder useTransformation(CallableTransformation transformation) {
this.transformation = transformation;
return this;
}
private CallableBuilder useDefaultTransformation(List stmts) {
CallableTransformation tx;
if (isVariadic) {
MethodWithArity callTyped = new CallTypedMethod(stmts);
tx = new VariadicCallableTransformation(callTyped);
} else if (hasOptionalParameters) {
MethodWithArity call = new CallMethodForVariadic();
MethodWithArity callTyped = new CallTypedMethod(stmts);
tx = new FixedArityCallableTransformation(call, callTyped);
} else {
MethodWithArity call = new CallMethodWithGivenBody(stmts);
MethodWithArity callTyped = null;
tx = new FixedArityCallableTransformation(call, callTyped);
}
useTransformation(tx);
return this;
}
private CallableBuilder parameterDefaultValueMethods(Tree.ParameterList parameterListTree) {
if (parameterDefaultValueMethods == null) {
parameterDefaultValueMethods = ListBuffer.lb();
}
for(Tree.Parameter p : parameterListTree.getParameters()){
if(Decl.getDefaultArgument(p) != null){
MethodDefinitionBuilder methodBuilder = gen.classGen().makeParamDefaultValueMethod(false, null, parameterListTree, p);
this.parameterDefaultValueMethods.append(methodBuilder);
}
}
return this;
}
public JCExpression build() {
// Generate a subclass of Callable
ListBuffer classBody = new ListBuffer();
gen.at(node);
if (parameterDefaultValueMethods != null) {
for (MethodDefinitionBuilder mdb : parameterDefaultValueMethods) {
classBody.append(mdb.build());
}
}
transformation.appendMethods(classBody);
JCClassDecl classDef = gen.make().AnonymousClassDef(gen.make().Modifiers(0, annotations != null ? annotations : List.nil()), classBody.toList());
int variadicIndex = isVariadic ? numParams - 1 : -1;
Type callableType;
if (typeModel.isTypeConstructor()) {
callableType = typeModel.getDeclaration().getExtendedType();
} else {
callableType = typeModel;
}
JCNewClass callableInstance = gen.make().NewClass(null,
null,
gen.makeJavaType(callableType, JT_EXTENDS | JT_CLASS_NEW),
List.of(gen.makeReifiedTypeArgument(callableType.getTypeArgumentList().get(0)),
gen.makeReifiedTypeArgument(callableType.getTypeArgumentList().get(1)),
gen.make().Literal(callableType.asString(true)),
gen.make().TypeCast(gen.syms().shortType, gen.makeInteger(variadicIndex))),
classDef);
JCExpression result;
if (typeModel.isTypeConstructor()) {
result = buildTypeConstructor(callableType, callableInstance);
} else {
result = callableInstance;
}
gen.at(null);
if (instanceSubstitution != null) {
instanceSubstitution.close();
}
return result;
}
protected JCExpression buildTypeConstructor(Type callableType,
JCNewClass callableInstance) {
JCExpression result;
// Wrap in an anonymous TypeConstructor subcla
MethodDefinitionBuilder rawApply = MethodDefinitionBuilder.systemMethod(gen, Naming.Unfix.apply.toString());
rawApply.modifiers(Flags.PUBLIC);
rawApply.isOverride(true);
//for (TypeParameter tp : typeModel.getDeclaration().getTypeParameters()) {
// apply.typeParameter(tp);
//}
rawApply.resultType(null, gen.makeJavaType(callableType, AbstractTransformer.JT_RAW));
{
ParameterDefinitionBuilder pdb = ParameterDefinitionBuilder.systemParameter(gen, "applied");
pdb.modifiers(Flags.FINAL);
pdb.type(gen.make().TypeArray(gen.make().Type(gen.syms().ceylonTypeDescriptorType)), null);
rawApply.parameter(pdb);
}
rawApply.body(List.of(
gen.make().Return(gen.make().Apply(null,
gen.naming.makeUnquotedIdent(Naming.Unfix.$apply$.toString()),
List.of(gen.naming.makeUnquotedIdent("applied"))))));
MethodDefinitionBuilder typedApply = MethodDefinitionBuilder.systemMethod(gen, Naming.Unfix.$apply$.toString());
typedApply.modifiers(Flags.PRIVATE);
//for (TypeParameter tp : typeModel.getDeclaration().getTypeParameters()) {
// apply.typeParameter(tp);
//}
typedApply.resultType(null, gen.makeJavaType(callableType));
{
ParameterDefinitionBuilder pdb = ParameterDefinitionBuilder.systemParameter(gen, "applied");
pdb.modifiers(Flags.FINAL);
pdb.type(gen.make().TypeArray(gen.make().Type(gen.syms().ceylonTypeDescriptorType)), null);
typedApply.parameter(pdb);
}
ListBuffer typeParameters = ListBuffer.lb();
for (TypeParameter typeParameter : typeModel.getDeclaration().getTypeParameters()) {
Type typeArgument = typeModel.getTypeArguments().get(typeParameter);
typeParameters.add(gen.makeTypeParameter(typeParameter, null));
typedApply.body(gen.makeVar(Flags.FINAL,
gen.naming.getTypeArgumentDescriptorName(typeParameter),
gen.make().Type(gen.syms().ceylonTypeDescriptorType),
gen.make().Indexed(gen.makeUnquotedIdent("applied"),
gen.make().Literal(typeModel.getTypeArgumentList().indexOf(typeArgument)))));
}
typedApply.body(gen.make().Return(callableInstance));
//typedApply.body(body.toList());
MethodDefinitionBuilder ctor = MethodDefinitionBuilder.constructor(gen);
ctor.body(gen.make().Exec(gen.make().Apply(null, gen.naming.makeSuper(), List.of(gen.make().Literal(typeModel.asString(true))))));
SyntheticName n = gen.naming.synthetic(typeModel.getDeclaration().getName());
JCClassDecl classDef = gen.make().ClassDef(
gen.make().Modifiers(0, List.nil()),
n.asName(),//name,
typeParameters.toList(),
gen.make().QualIdent(gen.syms().ceylonAbstractTypeConstructorType.tsym),//extending
List.nil(),//implementing,
List.of(ctor.build(), rawApply.build(), typedApply.build()));
result = gen.make().LetExpr(
List.of(classDef),
gen.make().NewClass(null,
null,
n.makeIdent(),
List.nil(),
//List.of(gen.make().Literal(typeModel.asString(true))),
null));
return result;
}
private java.util.List getParameterTypesFromCallableModel() {
java.util.List parameterTypes = new ArrayList(numParams);
for(int i=0;i getParameterTypesFromParameterModels() {
java.util.List parameterTypes = new ArrayList(numParams);
// get them from our declaration
for(Parameter p : paramLists.getParameters()){
Type pt;
FunctionOrValue pm = p.getModel();
if(pm instanceof Function
&& ((Function)pm).isParameter())
pt = gen.getTypeForFunctionalParameter((Function) pm);
else
pt = p.getType();
parameterTypes.add(pt);
}
return parameterTypes;
}
class CallTypedMethod extends MethodWithArity {
private final List stmts;
public CallTypedMethod(List stmts) {
this.stmts = stmts;
}
@Override
MethodDefinitionBuilder makeMethod(int arity) {
return makeCallTypedMethod(stmts);
}
}
/**
* Makes the {@code call$typed()} method, using the given body.
*/
private MethodDefinitionBuilder makeCallTypedMethod(List body) {
// make the method
MethodDefinitionBuilder methodBuilder = MethodDefinitionBuilder.systemMethod(gen, Naming.getCallableTypedMethodName());
methodBuilder.noAnnotations();
methodBuilder.modifiers(Flags.PRIVATE);
Type returnType = gen.getReturnTypeOfCallable(typeModel);
methodBuilder.resultType(gen.makeJavaType(returnType, JT_NO_PRIMITIVES), null);
// add all parameters
int i=0;
for(Parameter param : paramLists.getParameters()){
ParameterDefinitionBuilder parameterBuilder = ParameterDefinitionBuilder.systemParameter(gen, Naming.getAliasedParameterName(param));
JCExpression paramType = gen.makeJavaType(parameterTypes.get(i));
parameterBuilder.modifiers(Flags.FINAL);
parameterBuilder.type(paramType, null);
methodBuilder.parameter(parameterBuilder);
i++;
}
// Return the call result, or null if a void method
methodBuilder.body(body);
return methodBuilder;
}
private static Name makeParamName(AbstractTransformer gen, int paramIndex) {
return gen.names().fromString(getParamName(paramIndex));
}
private static JCExpression makeParamIdent(AbstractTransformer gen, int paramIndex) {
return gen.make().Ident(gen.names().fromString(getParamName(paramIndex)));
}
private static String getParamName(int paramIndex) {
return "$param$"+paramIndex;
}
private ParameterDefinitionBuilder makeCallableCallParam(long flags, int ii) {
JCExpression type = gen.makeIdent(gen.syms().objectType);
if ((flags & Flags.VARARGS) != 0) {
type = gen.make().TypeArray(type);
}
ParameterDefinitionBuilder pdb = ParameterDefinitionBuilder.systemParameter(gen, getParamName(ii));
pdb.modifiers(Flags.FINAL | flags);
pdb.type(type, null);
return pdb;
}
private ParameterDefinitionBuilder makeCallableVaryParam(long flags, int ii) {
Type iteratedType = gen.typeFact().getIteratedType(parameterTypes.get(parameterTypes.size()-1));
// $call$var()'s variadic parameter is *always* erasred to Sequential
// even if it's a Variadic+ parameter
JCExpression type = gen.makeJavaType(gen.typeFact().getSequentialType(iteratedType), AbstractTransformer.JT_RAW);
ParameterDefinitionBuilder pdb = ParameterDefinitionBuilder.systemParameter(gen, getParamName(ii));
pdb.modifiers(Flags.FINAL | flags);
pdb.type(type, null);
return pdb;
}
//Target target = new Target(null);
class Target {
Tree.Term forwardCallTo;
Target(Tree.Term forwardCallTo) {
this.forwardCallTo = forwardCallTo;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy