Hana 9e216da9ef go.mod: add go.mod and move pygments to third_party
After go1.16, go will use module mode by default,
even when the repository is checked out under GOPATH
or in a one-off directory. Add go.mod, go.sum to keep
this repo buildable without opting out of the module
mode.

> go mod init github.com/mmcgrana/gobyexample
> go mod tidy
> go mod vendor

In module mode, the 'vendor' directory is special
and its contents will be actively maintained by the
go command. pygments aren't the dependency the go will
know about, so it will delete the contents from vendor
directory. Move it to `third_party` directory now.

And, vendor the blackfriday package.

Note: the tutorial contents are not affected by the
change in go1.16 because all the examples in this
tutorial ask users to run the go command with the
explicit list of files to be compiled (e.g.
`go run hello-world.go` or `go build command-line-arguments.go`).
When the source list is provided, the go command does
not have to compute the build list and whether it's
running in GOPATH mode or module mode becomes irrelevant.
2021-02-15 16:45:26 -05:00

818 lines
25 KiB
Plaintext

//
// 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
}