818 lines
25 KiB
Plaintext
Executable File
818 lines
25 KiB
Plaintext
Executable File
//
|
|
// Copyright (c) 2008, Brian Frank and Andy Frank
|
|
// Licensed under the Academic Free License version 3.0
|
|
//
|
|
// History:
|
|
// 17 Nov 08 Brian Frank Creation
|
|
//
|
|
|
|
using compiler
|
|
|
|
**
|
|
** JavaBridge is the compiler plugin for bringing Java
|
|
** classes into the Fantom type system.
|
|
**
|
|
class JavaBridge : CBridge
|
|
{
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Constructor
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
**
|
|
** Construct a JavaBridge for current environment
|
|
**
|
|
new make(Compiler c, ClassPath cp := ClassPath.makeForCurrent)
|
|
: super(c)
|
|
{
|
|
this.cp = cp
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Namespace
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
**
|
|
** Map a FFI "podName" to a Java package.
|
|
**
|
|
override CPod resolvePod(Str name, Loc? loc)
|
|
{
|
|
// the empty package is used to represent primitives
|
|
if (name == "") return primitives
|
|
|
|
// look for package name in classpatch
|
|
classes := cp.classes[name]
|
|
if (classes == null)
|
|
throw CompilerErr("Java package '$name' not found", loc)
|
|
|
|
// map package to JavaPod
|
|
return JavaPod(this, name, classes)
|
|
}
|
|
|
|
**
|
|
** Map class meta-data and Java members to Fantom slots
|
|
** for the specified JavaType.
|
|
**
|
|
virtual Void loadType(JavaType type, Str:CSlot slots)
|
|
{
|
|
JavaReflect.loadType(type, slots)
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Call Resolution
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
**
|
|
** Resolve a construction call to a Java constructor.
|
|
**
|
|
override Expr resolveConstruction(CallExpr call)
|
|
{
|
|
// if the last argument is an it-block, then we know
|
|
// right away that we will not be passing it thru to Java,
|
|
// so strip it off to be appended as call to Obj.with
|
|
itBlock := call.args.last as ClosureExpr
|
|
if (itBlock != null && itBlock.isItBlock)
|
|
call.args.removeAt(-1)
|
|
else
|
|
itBlock = null
|
|
|
|
// if this is an interop array like IntArray/int[] use make
|
|
// factory otherwise look for Java constructor called <init>
|
|
JavaType base := call.target.ctype
|
|
if (base.isInteropArray)
|
|
call.method = base.method("make")
|
|
else
|
|
call.method = base.method("<init>")
|
|
|
|
// call resolution to deal with overloading
|
|
call = resolveCall(call)
|
|
|
|
// we need to create an implicit target for the Java runtime
|
|
// to perform the new opcode to ensure it is on the stack
|
|
// before the args (we don't do this for interop Array classes)
|
|
if (!base.isInteropArray)
|
|
{
|
|
loc := call.loc
|
|
call.target = CallExpr.makeWithMethod(loc, null, base.newMethod) { synthetic=true }
|
|
}
|
|
|
|
// if we stripped an it-block argument,
|
|
// add it as trailing call to Obj.with
|
|
if (itBlock != null) return itBlock.toWith(call)
|
|
return call
|
|
}
|
|
|
|
**
|
|
** Resolve a construction chain call where a Fantom constructor
|
|
** calls the super-class constructor. Type check the arguments
|
|
** and insert any conversions needed.
|
|
**
|
|
override Expr resolveConstructorChain(CallExpr call)
|
|
{
|
|
// we don't allow chaining to a this ctor for Java FFI
|
|
if (call.target.id !== ExprId.superExpr)
|
|
throw err("Must use super constructor call in Java FFI", call.loc)
|
|
|
|
// route to a superclass constructor
|
|
JavaType base := call.target.ctype.deref
|
|
call.method = base.method("<init>")
|
|
|
|
// call resolution to deal with overloading
|
|
return resolveCall(call)
|
|
}
|
|
|
|
**
|
|
** Given a dot operator slot access on the given foreign
|
|
** base type, determine the appopriate slot to use based on
|
|
** whether parens were used
|
|
** base.name => noParens = true
|
|
** base.name() => noParens = false
|
|
**
|
|
** In Java a given name could be bound to both a field and
|
|
** a method. In this case we only resolve the field if
|
|
** no parens are used. We also handle the special case of
|
|
** Java annotations here because their element methods are
|
|
** also mapped as Fantom fields (instance based mixin field).
|
|
**
|
|
override CSlot? resolveSlotAccess(CType base, Str name, Bool noParens)
|
|
{
|
|
// first try to resolve as a field
|
|
field := base.field(name)
|
|
if (field != null)
|
|
{
|
|
// if no () we used and this isn't an annotation field
|
|
if (noParens && (field.isStatic || !base.isMixin))
|
|
return field
|
|
|
|
// if we did find a field, then make sure we use that
|
|
// field's parent type to resolve a method (becuase the
|
|
// base type might be a sub-class of a Java type in which
|
|
// case it is unware of field/method overloads)
|
|
return field.parent.method(name)
|
|
}
|
|
|
|
// lookup method
|
|
return base.method(name)
|
|
}
|
|
|
|
**
|
|
** Resolve a method call: try to find the best match
|
|
** and apply any coercions needed.
|
|
**
|
|
override CallExpr resolveCall(CallExpr call)
|
|
{
|
|
// try to match against all the overloaded methods
|
|
matches := CallMatch[,]
|
|
CMethod? m := call.method
|
|
while (m != null)
|
|
{
|
|
match := matchCall(call, m)
|
|
if (match != null) matches.add(match)
|
|
m = m is JavaMethod ? ((JavaMethod)m).next : null
|
|
}
|
|
|
|
// if we have exactly one match use then use that one
|
|
if (matches.size == 1) return matches[0].apply(call)
|
|
|
|
// if we have multiple matches; resolve to
|
|
// most specific match according to JLS rules
|
|
// TODO: this does not correct resolve when using Fantom implicit casting
|
|
if (matches.size > 1)
|
|
{
|
|
best := resolveMostSpecific(matches)
|
|
if (best != null) return best.apply(call)
|
|
}
|
|
|
|
// zero or multiple ambiguous matches is a compiler error
|
|
s := StrBuf()
|
|
s.add(matches.isEmpty ? "Invalid args " : "Ambiguous call ")
|
|
s.add(call.name).add("(")
|
|
s.add(call.args.join(", ") |Expr arg->Str| { return arg.toTypeStr })
|
|
s.add(")")
|
|
throw err(s.toStr, call.loc)
|
|
}
|
|
|
|
**
|
|
** Check if the call matches the specified overload method.
|
|
** If so return method and coerced args otherwise return null.
|
|
**
|
|
internal CallMatch? matchCall(CallExpr call, CMethod m)
|
|
{
|
|
// first check if have matching numbers of args and params
|
|
args := call.args
|
|
if (m.params.size < args.size) return null
|
|
|
|
// check if each argument is ok or can be coerced
|
|
isErr := false
|
|
newArgs := args.dup
|
|
m.params.each |CParam p, Int i|
|
|
{
|
|
if (i >= args.size)
|
|
{
|
|
// param has a default value, then that is ok
|
|
if (!p.hasDefault) isErr = true
|
|
}
|
|
else
|
|
{
|
|
// ensure arg fits parameter type (or auto-cast)
|
|
newArgs[i] = coerce(args[i], p.paramType) |->| { isErr = true }
|
|
}
|
|
}
|
|
if (isErr) return null
|
|
return CallMatch { it.method = m; it.args = newArgs }
|
|
}
|
|
|
|
**
|
|
** Given a list of overloaed methods find the most specific method
|
|
** according to Java Language Specification 15.11.2.2. The "informal
|
|
** intuition" rule is that a method is more specific than another
|
|
** if the first could be could be passed onto the second one.
|
|
**
|
|
internal static CallMatch? resolveMostSpecific(CallMatch[] matches)
|
|
{
|
|
CallMatch? best := matches[0]
|
|
for (i:=1; i<matches.size; ++i)
|
|
{
|
|
x := matches[i]
|
|
if (isMoreSpecific(best, x)) { continue }
|
|
if (isMoreSpecific(x, best)) { best = x; continue }
|
|
return null
|
|
}
|
|
return best
|
|
}
|
|
|
|
**
|
|
** Is 'a' more specific than 'b' such that 'a' could be used
|
|
** passed to 'b' without a compile time error.
|
|
**
|
|
internal static Bool isMoreSpecific(CallMatch a, CallMatch b)
|
|
{
|
|
return a.method.params.all |CParam ap, Int i->Bool|
|
|
{
|
|
bp := b.method.params[i]
|
|
return ap.paramType.fits(bp.paramType)
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Overrides
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
**
|
|
** Called during Inherit step when a Fantom slot overrides a FFI slot.
|
|
** Log and throw compiler error if there is a problem.
|
|
**
|
|
override Void checkOverride(TypeDef t, CSlot base, SlotDef def)
|
|
{
|
|
// we don't allow Fantom to override Java methods with multiple
|
|
// overloaded versions since the Fantom type system can't actually
|
|
// override all the overloaded versions
|
|
jslot := base as JavaSlot
|
|
if (jslot?.next != null)
|
|
throw err("Cannot override Java overloaded method: '$jslot.name'", def.loc)
|
|
|
|
// route to method override checking
|
|
if (base is JavaMethod && def is MethodDef)
|
|
checkMethodOverride(t, base, def)
|
|
}
|
|
|
|
**
|
|
** Called on method/method overrides in the checkOverride callback.
|
|
**
|
|
private Void checkMethodOverride(TypeDef t, JavaMethod base, MethodDef def)
|
|
{
|
|
// bail early if we know things aren't going to work out
|
|
if (base.params.size != def.params.size) return
|
|
|
|
// if the return type is primitive or Java array and the
|
|
// Fantom declaration matches how it is inferred into the Fan
|
|
// type system, then just change the return type - the compiler
|
|
// will impliclty do all the return coercions
|
|
if (isOverrideInferredType(base.returnType, def.returnType))
|
|
{
|
|
def.ret = def.inheritedRet = base.returnType
|
|
}
|
|
|
|
// if any of the parameters is a primitive or Java array
|
|
// and the Fantom declaration matches how it is inferred into
|
|
// the Fantom type type, then change the parameter type to
|
|
// the Java override type and make the Fantom type a local
|
|
// variable:
|
|
// Java: void foo(int a) { ... }
|
|
// Fantom: Void foo(Int a) { ... }
|
|
// Result: Void foo(int a_$J) { Int a := a_$J; ... }
|
|
//
|
|
base.params.eachr |CParam bp, Int i|
|
|
{
|
|
dp := def.paramDefs[i]
|
|
if (!isOverrideInferredType(bp.paramType, dp.paramType)) return
|
|
|
|
// add local variable: Int bar := bar_$J
|
|
local := LocalDefStmt(def.loc)
|
|
local.ctype = dp.paramType
|
|
local.name = dp.name
|
|
local.init = UnknownVarExpr(def.loc, null, dp.name + "_\$J")
|
|
def.code.stmts.insert(0, local)
|
|
|
|
// rename parameter Int bar -> int bar_$J
|
|
dp.name = dp.name + "_\$J"
|
|
dp.paramType = bp.paramType
|
|
}
|
|
}
|
|
|
|
**
|
|
** When overriding a Java method check if the base type is
|
|
** is a Java primitive or array and the override definition is
|
|
** matches how the Java type is inferred in the Fantom type system.
|
|
** If we have a match return true and we'll swizzle things in
|
|
** checkMethodOverride.
|
|
**
|
|
static private Bool isOverrideInferredType(CType base, CType def)
|
|
{
|
|
// check if base class slot is a JavaType
|
|
java := base.toNonNullable as JavaType
|
|
if (java != null)
|
|
{
|
|
// allow primitives is it matches the inferred type
|
|
if (java.isPrimitive) return java.inferredAs == def
|
|
|
|
// allow arrays if mapped as Foo[] -> Foo?[]?
|
|
if (java.isArray) return java.inferredAs == def.toNonNullable && def.isNullable
|
|
}
|
|
return false
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// CheckErrors
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
**
|
|
** Called during CheckErrors step for a type which extends
|
|
** a FFI class or implements any FFI mixins.
|
|
**
|
|
override Void checkType(TypeDef def)
|
|
{
|
|
// can't subclass a primitive array like ByteArray/byte[]
|
|
if (def.base.deref is JavaType && def.base.deref->isInteropArray)
|
|
{
|
|
err("Cannot subclass from Java interop array: $def.base", def.loc)
|
|
return
|
|
}
|
|
|
|
// we don't allow deep inheritance of Java classes because
|
|
// the Fantom constructor and Java constructor model don't match
|
|
// up past one level of inheritance
|
|
// NOTE: that that when we remove this restriction we need to
|
|
// test how field initialization works because instance$init
|
|
// is almost certain to break with the current emit design
|
|
javaBase := def.base
|
|
while (javaBase != null && !javaBase.isForeign) javaBase = javaBase.base
|
|
if (javaBase != null && javaBase !== def.base)
|
|
{
|
|
err("Cannot subclass Java class more than one level: $javaBase", def.loc)
|
|
return
|
|
}
|
|
|
|
// ensure that when we map Fantom constructors to Java
|
|
// constructors that we don't have duplicate signatures
|
|
ctors := def.ctorDefs
|
|
ctors.each |MethodDef a, Int i|
|
|
{
|
|
ctors.each |MethodDef b, Int j|
|
|
{
|
|
if (i > j && areParamsSame(a, b))
|
|
err("Duplicate Java FFI constructor signatures: '$b.name' and '$a.name'", a.loc)
|
|
}
|
|
}
|
|
}
|
|
|
|
**
|
|
** Do the two methods have the exact same parameter types.
|
|
**
|
|
static Bool areParamsSame(CMethod a, CMethod b)
|
|
{
|
|
if (a.params.size != b.params.size) return false
|
|
for (i:=0; i<a.params.size; ++i)
|
|
{
|
|
if (a.params[i].paramType != b.params[i].paramType)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Coercion
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
**
|
|
** Return if we can make the actual type fit the expected
|
|
** type, potentially using a coercion.
|
|
**
|
|
Bool fits(CType actual, CType expected)
|
|
{
|
|
// use dummy expression and route to coerce code
|
|
dummy := UnknownVarExpr(Loc("dummy"), null, "dummy") { ctype = actual }
|
|
fits := true
|
|
coerce(dummy, expected) |->| { fits=false }
|
|
return fits
|
|
}
|
|
|
|
**
|
|
** Coerce expression to expected type. If not a type match
|
|
** then run the onErr function.
|
|
**
|
|
override Expr coerce(Expr expr, CType expected, |->| onErr)
|
|
{
|
|
// handle easy case
|
|
actual := expr.ctype
|
|
expected = expected.deref
|
|
if (actual == expected) return expr
|
|
|
|
// handle null literal
|
|
if (expr.id === ExprId.nullLiteral && expected.isNullable)
|
|
return expr
|
|
|
|
// handle Fantom to Java primitives
|
|
if (expected.pod == primitives)
|
|
return coerceToPrimitive(expr, expected, onErr)
|
|
|
|
// handle Java primitives to Fan
|
|
if (actual.pod == primitives)
|
|
return coerceFromPrimitive(expr, expected, onErr)
|
|
|
|
// handle Java array to Fantom list
|
|
if (actual.name[0] == '[')
|
|
return coerceFromArray(expr, expected, onErr)
|
|
|
|
// handle Fantom list to Java array
|
|
if (expected.name[0] == '[')
|
|
return coerceToArray(expr, expected, onErr)
|
|
|
|
// handle sys::Func -> Java interface
|
|
if (actual is FuncType && expected.isMixin && expected.toNonNullable is JavaType)
|
|
return coerceFuncToInterface(expr, expected.toNonNullable, onErr)
|
|
|
|
// handle special classes and interfaces for built-in Fantom
|
|
// classes which actually map directly to Java built-in types
|
|
if (actual.isBool && boolTypes.contains(expected.toNonNullable.signature)) return box(expr)
|
|
if (actual.isInt && intTypes.contains(expected.toNonNullable.signature)) return box(expr)
|
|
if (actual.isFloat && floatTypes.contains(expected.toNonNullable.signature)) return box(expr)
|
|
if (actual.isDecimal && decimalTypes.contains(expected.toNonNullable.signature)) return expr
|
|
if (actual.isStr && strTypes.contains(expected.toNonNullable.signature)) return expr
|
|
|
|
// use normal Fantom coercion behavior
|
|
return super.coerce(expr, expected, onErr)
|
|
}
|
|
|
|
**
|
|
** Ensure value type is boxed.
|
|
**
|
|
private Expr box(Expr expr)
|
|
{
|
|
if (expr.ctype.isVal)
|
|
return TypeCheckExpr.coerce(expr, expr.ctype.toNullable)
|
|
else
|
|
return expr
|
|
}
|
|
|
|
**
|
|
** Coerce a fan expression to a Java primitive (other
|
|
** than the ones we support natively)
|
|
**
|
|
Expr coerceToPrimitive(Expr expr, JavaType expected, |->| onErr)
|
|
{
|
|
actual := expr.ctype
|
|
|
|
// sys::Int (long) -> int, short, byte
|
|
if (actual.isInt && expected.isPrimitiveIntLike)
|
|
return TypeCheckExpr.coerce(expr, expected)
|
|
|
|
// sys::Float (double) -> float
|
|
if (actual.isFloat && expected.isPrimitiveFloat)
|
|
return TypeCheckExpr.coerce(expr, expected)
|
|
|
|
// no coercion - type error
|
|
onErr()
|
|
return expr
|
|
}
|
|
|
|
**
|
|
** Coerce a Java primitive to a Fantom type.
|
|
**
|
|
Expr coerceFromPrimitive(Expr expr, CType expected, |->| onErr)
|
|
{
|
|
actual := (JavaType)expr.ctype
|
|
|
|
// int, short, byte -> sys::Int (long)
|
|
if (actual.isPrimitiveIntLike)
|
|
{
|
|
if (expected.isInt || expected.isObj)
|
|
return TypeCheckExpr.coerce(expr, expected)
|
|
}
|
|
|
|
// float -> sys::Float (float)
|
|
if (actual.isPrimitiveFloat)
|
|
{
|
|
if (expected.isFloat || expected.isObj)
|
|
return TypeCheckExpr.coerce(expr, expected)
|
|
}
|
|
|
|
// no coercion - type error
|
|
onErr()
|
|
return expr
|
|
}
|
|
|
|
**
|
|
** Coerce a Java array to a Fantom list.
|
|
**
|
|
Expr coerceFromArray(Expr expr, CType expected, |->| onErr)
|
|
{
|
|
actual := (JavaType)expr.ctype.toNonNullable
|
|
|
|
// if expected is array type
|
|
if (expected is JavaType && ((JavaType)expected).isArray)
|
|
if (actual.arrayOf.fits(((JavaType)expected).arrayOf)) return expr
|
|
|
|
// if expected is Obj
|
|
if (expected.isObj) return arrayToList(expr, actual.inferredArrayOf)
|
|
|
|
// if expected is list type
|
|
if (expected.toNonNullable is ListType)
|
|
{
|
|
expectedOf := ((ListType)expected.toNonNullable).v
|
|
if (actual.inferredArrayOf.fits(expectedOf)) return arrayToList(expr, expectedOf)
|
|
}
|
|
|
|
// no coercion available
|
|
onErr()
|
|
return expr
|
|
}
|
|
|
|
**
|
|
** Generate List.make(of, expr) where expr is Object[]
|
|
**
|
|
private Expr arrayToList(Expr expr, CType of)
|
|
{
|
|
loc := expr.loc
|
|
ofExpr := LiteralExpr(loc, ExprId.typeLiteral, ns.typeType, of)
|
|
call := CallExpr.makeWithMethod(loc, null, listMakeFromArray, [ofExpr, expr])
|
|
call.synthetic = true
|
|
return call
|
|
}
|
|
|
|
**
|
|
** Coerce a Fantom list to Java array.
|
|
**
|
|
Expr coerceToArray(Expr expr, CType expected, |->| onErr)
|
|
{
|
|
loc := expr.loc
|
|
expectedOf := ((JavaType)expected.toNonNullable).inferredArrayOf
|
|
actual := expr.ctype
|
|
|
|
// if actual is list type
|
|
if (actual.toNonNullable is ListType)
|
|
{
|
|
actualOf := ((ListType)actual.toNonNullable).v
|
|
if (actualOf.fits(expectedOf))
|
|
{
|
|
// (Foo[])list.asArray(cls)
|
|
clsLiteral := CallExpr.makeWithMethod(loc, null, JavaType.classLiteral(this, expectedOf))
|
|
asArray := CallExpr.makeWithMethod(loc, expr, listAsArray, [clsLiteral])
|
|
return TypeCheckExpr.coerce(asArray, expected)
|
|
}
|
|
}
|
|
|
|
// no coercion available
|
|
onErr()
|
|
return expr
|
|
}
|
|
|
|
**
|
|
** Attempt to coerce a parameterized sys::Func expr to a Java
|
|
** interface if the interface supports exactly one matching method.
|
|
**
|
|
Expr coerceFuncToInterface(Expr expr, JavaType expected, |->| onErr)
|
|
{
|
|
// check if we have exactly one abstract method in the expected type
|
|
loc := expr.loc
|
|
abstracts := expected.methods.findAll |CMethod m->Bool| { return m.isAbstract }
|
|
if (abstracts.size != 1) { onErr(); return expr }
|
|
method := abstracts.first
|
|
|
|
// check if we have a match
|
|
FuncType funcType := (FuncType)expr.ctype
|
|
if (!isFuncToInterfaceMatch(funcType, method)) { onErr(); return expr }
|
|
|
|
// check if we've already generated a wrapper for this combo
|
|
key := "${funcType.signature}+${method.qname}"
|
|
ctor := funcWrappers[key]
|
|
if (ctor == null)
|
|
{
|
|
ctor = generateFuncToInterfaceWrapper(expr.loc, funcType, expected, method)
|
|
funcWrappers[key] = ctor
|
|
}
|
|
|
|
// replace expr with FuncWrapperX(expr)
|
|
call := CallExpr.makeWithMethod(loc, null, ctor, [expr])
|
|
call.synthetic = true
|
|
return call
|
|
}
|
|
|
|
**
|
|
** Return if the specified function type can be used to implement
|
|
** the specified interface method.
|
|
**
|
|
Bool isFuncToInterfaceMatch(FuncType funcType, CMethod method)
|
|
{
|
|
// sanity check to map to callX method - can't handle more than 8 args
|
|
if (method.params.size > 8) return false
|
|
|
|
// check if method is match for function; first check is that
|
|
// method must supply all the arguments required by the function
|
|
if (funcType.params.size > method.params.size) return false
|
|
|
|
// check that func return type fits method return
|
|
retOk := method.returnType.isVoid || fits(funcType.ret, method.returnType)
|
|
if (!retOk) return false
|
|
|
|
// check all the method parameters fit the function parameters
|
|
paramsOk := funcType.params.all |CType f, Int i->Bool| { return fits(f, method.params[i].paramType) }
|
|
if (!paramsOk) return false
|
|
|
|
return true
|
|
}
|
|
|
|
**
|
|
** Generate the wrapper which implements the specified expected interface
|
|
** and overrides the specified method which calls the function.
|
|
**
|
|
CMethod generateFuncToInterfaceWrapper(Loc loc, FuncType funcType, CType expected, CMethod method)
|
|
{
|
|
// Fantom: func typed as |Str|
|
|
// Java: interface Foo { void bar(String) }
|
|
// Result: FuncWrapperX(func)
|
|
//
|
|
// class FuncWrapperX : Foo
|
|
// {
|
|
// new make(Func f) { _func = f }
|
|
// override Void bar(Str a) { _func.call(a) }
|
|
// Func _func
|
|
// }
|
|
|
|
// generate FuncWrapper class
|
|
name := "FuncWrapper" + funcWrappers.size
|
|
cls := TypeDef(ns, loc, compiler.types[0].unit, name, FConst.Internal + FConst.Synthetic)
|
|
cls.base = ns.objType
|
|
cls.mixins = [expected]
|
|
addTypeDef(cls)
|
|
|
|
// generate FuncWrapper._func field
|
|
field := FieldDef(loc, cls)
|
|
((SlotDef)field).name = "_func"
|
|
((DefNode)field).flags = FConst.Private + FConst.Storage + FConst.Synthetic
|
|
field.fieldType = funcType
|
|
cls.addSlot(field)
|
|
|
|
// generate FuncWrapper.make constructor
|
|
ctor := MethodDef(loc, cls, "make", FConst.Internal + FConst.Ctor + FConst.Synthetic)
|
|
ctor.ret = ns.voidType
|
|
ctor.paramDefs = [ParamDef(loc, funcType, "f")]
|
|
ctor.code = Block.make(loc)
|
|
ctor.code.stmts.add(BinaryExpr.makeAssign(
|
|
FieldExpr(loc, ThisExpr(loc), field),
|
|
UnknownVarExpr(loc, null, "f")).toStmt)
|
|
ctor.code.stmts.add(ReturnStmt.make(loc))
|
|
cls.addSlot(ctor)
|
|
|
|
// generate FuncWrapper override of abstract method
|
|
over := MethodDef(loc, cls, method.name, FConst.Public + FConst.Override + FConst.Synthetic)
|
|
over.ret = method.returnType
|
|
over.paramDefs = ParamDef[,]
|
|
over.code = Block.make(loc)
|
|
callArity := "call"
|
|
call := CallExpr.makeWithMethod(loc, FieldExpr(loc, ThisExpr(loc), field), funcType.method(callArity))
|
|
method.params.each |CParam param, Int i|
|
|
{
|
|
paramName := "p$i"
|
|
over.params.add(ParamDef(loc, param.paramType, paramName))
|
|
if (i < funcType.params.size)
|
|
call.args.add(UnknownVarExpr(loc, null, paramName))
|
|
}
|
|
if (method.returnType.isVoid)
|
|
over.code.stmts.add(call.toStmt).add(ReturnStmt(loc))
|
|
else
|
|
over.code.stmts.add(ReturnStmt(loc, call))
|
|
cls.addSlot(over)
|
|
|
|
// return the ctor which we use for coercion
|
|
return ctor
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Reflection
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
**
|
|
** Get a CMethod representation for 'List.make(Type, Object[])'
|
|
**
|
|
once CMethod listMakeFromArray()
|
|
{
|
|
return JavaMethod(
|
|
this.ns.listType,
|
|
"make",
|
|
FConst.Public + FConst.Static,
|
|
this.ns.listType.toNullable,
|
|
[
|
|
JavaParam("of", this.ns.typeType),
|
|
JavaParam("array", objectArrayType)
|
|
])
|
|
}
|
|
|
|
**
|
|
** Get a CMethod representation for 'Object[] List.asArray()'
|
|
**
|
|
once CMethod listAsArray()
|
|
{
|
|
return JavaMethod(
|
|
this.ns.listType,
|
|
"asArray",
|
|
FConst.Public,
|
|
objectArrayType,
|
|
[JavaParam("cls", classType)])
|
|
}
|
|
|
|
**
|
|
** Get a CType representation for 'java.lang.Class'
|
|
**
|
|
once JavaType classType()
|
|
{
|
|
return ns.resolveType("[java]java.lang::Class")
|
|
}
|
|
|
|
**
|
|
** Get a CType representation for 'java.lang.Object[]'
|
|
**
|
|
once JavaType objectArrayType()
|
|
{
|
|
return ns.resolveType("[java]java.lang::[Object")
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Fields
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
const static Str[] boolTypes := Str[
|
|
"[java]java.io::Serializable",
|
|
"[java]java.lang::Comparable",
|
|
]
|
|
|
|
const static Str[] intTypes := Str[
|
|
"[java]java.lang::Number",
|
|
"[java]java.io::Serializable",
|
|
"[java]java.lang::Comparable",
|
|
]
|
|
|
|
const static Str[] floatTypes := Str[
|
|
"[java]java.lang::Number",
|
|
"[java]java.io::Serializable",
|
|
"[java]java.lang::Comparable",
|
|
]
|
|
|
|
const static Str[] decimalTypes := Str[
|
|
"[java]java.lang::Number",
|
|
"[java]java.io::Serializable",
|
|
"[java]java.lang::Comparable",
|
|
]
|
|
|
|
const static Str[] strTypes := Str[
|
|
"[java]java.io::Serializable",
|
|
"[java]java.lang::CharSequence",
|
|
"[java]java.lang::Comparable",
|
|
]
|
|
|
|
JavaPrimitives primitives := JavaPrimitives(this)
|
|
ClassPath cp
|
|
|
|
private Str:CMethod funcWrappers := Str:CMethod[:] // funcType+method:ctor
|
|
|
|
}
|
|
|
|
**************************************************************************
|
|
** CallMatch
|
|
**************************************************************************
|
|
|
|
internal class CallMatch
|
|
{
|
|
CallExpr apply(CallExpr call)
|
|
{
|
|
call.args = args
|
|
call.method = method
|
|
call.ctype = method.isCtor ? method.parent : method.returnType
|
|
return call
|
|
}
|
|
|
|
override Str toStr() { return method.signature }
|
|
|
|
CMethod? method // matched method
|
|
Expr[]? args // coerced arguments
|
|
} |