-
-
Notifications
You must be signed in to change notification settings - Fork 0
Java Interop
java fun Abs(Value: Int): Int = java.lang.Math.abs
java type JavaStringBuilder = java.lang.StringBuilder
java new NewBuilder(Capacity: Int): JavaStringBuilder owned nonnull =
java.lang.StringBuilder
java method BuilderText(Builder: JavaStringBuilder borrowed): String nonnull =
java.lang.StringBuilder.toString
java fun Separator(): String nonnull = java.lang.System.lineSeparator
type LoadError = | Failed(Message: Option<String>)
java fun Read(): String nullable
throws java.io.IOException as LoadError.Failed(Message = message) =
app.Reader.read
java fun ApplyTwice(Mapper: fun(Int): Int sam app.IntMapper, Value: Int): Int =
app.Callbacks.applyTwice
Status: Interpreter No. JVM Yes. CLI run No. CLI compile Yes.
java fun declares a static Java method boundary that compiled Meris code can
call through an ordinary Meris function name. java type, java new, and
java method add the first opaque Java object-handle boundary for compiled
Meris code.
Function-value parameters may opt into a narrow Java SAM adapter with explicit
sam <java.interface.BinaryName> metadata.
java fun Abs(Value: Int): Int = java.lang.Math.abs
fun Magnitude(Value: Int): Int {
Abs(Value)
}
The declaration shape is:
java fun Name(Parameters...): Return = java.binary.Owner.method
java fun Name(Parameters...): Return nullability = java.binary.Owner.method
java fun Name(...): Return throws Java.Exception as Error.Variant = target
java fun Name(...): Return throws
Java.Exception as Error.Variant(Field = message) = target
java type JavaHandle = java.binary.Owner
java new Name(Parameters...): JavaHandle owned nonnull = java.binary.Owner
java method Name(Receiver: JavaHandle borrowed, Parameters...): Return =
java.binary.Owner.method
The target uses a Java binary owner name with dots, followed by the method name.
For example, java.lang.Math.abs targets owner java.lang.Math and method
abs. Static methods declared on Java interfaces are valid when the owner is
available to the compiler; java.util.List.of emits as an interface static
method reference.
The owner and exact public static method must resolve on the compiler
classpath. If the owner is missing, Meris reports M7016. If the owner exists
but no static method matches the declared Meris parameters and Java return
shape, Meris reports M7017.
For Java classes outside the Meris distribution, pass the compile-time classpath to the CLI:
meris check src/main.meris --classpath build/java-classes
meris compile src/main.meris --out build/meris-classes \
--classpath build/java-classesClasspath entries use the host platform path separator and may point to class
directories or jar files. The same Java classes must be present on the runtime
java -cp command that runs the generated Meris bytecode.
Project manifests can hold the same classpath:
[project]
source = "src"
out = "build/classes"
classpath = ["build/java-classes", "lib/helper.jar"]Use meris check --project . or meris compile --project . when the project
has meris.toml.
This first subset is intentionally small:
| Position | Supported types |
|---|---|
| Static parameters |
Int, Bool, String
|
| SAM callback parameters |
fun(...) values marked with explicit sam metadata |
| Instance receiver | visible java type handle marked borrowed
|
| Constructor returns | matching java type owned nonnull
|
| Returns |
Int, Bool, Unit, String nonnull, String nullable, String platform unsafe, declared Java handles with nullability markers |
Reference returns must mark the Java nullability boundary:
| Marker | Meaning | Status |
|---|---|---|
nonnull |
Java method is known not to return null. | JVM executable for String. |
nullable |
Java method may return null. | JVM executable as Meris Option<String>. |
platform unsafe |
Java method nullability is unknown. | JVM executable as Meris Option<String>. |
Unmarked Java reference returns are rejected. String nullable keeps the Java
method descriptor as String and exposes Meris Option<String>: null becomes
Option.none(), non-null becomes Option.some(value). String platform is
rejected unless it includes unsafe; the exposed type is still
Option<String>.
unsafe is not a general modifier. Int unsafe, String nonnull unsafe, and
String nullable unsafe are rejected before lowering.
Declared Java exceptions may translate to Meris typed-error variants:
type LoadError = | Failed
java fun Read(): String nonnull
throws java.io.IOException as LoadError.Failed =
app.Reader.read
Payload variants require explicit field mappings:
type LoadError = | Failed(Message: Option<String>)
java fun Read(): String nullable
throws java.io.IOException as LoadError.Failed(Message = message) =
app.Reader.read
The JVM backend catches only the declared Java exception class around the
interop call. It rethrows JvmRuntimeError with the mapped error type and
variant, so existing handle expressions continue to dispatch by variant name
and bind payload fields.
The first supported payload source is message. It reads
Throwable.getMessage() and exposes it as Option<String>, because Java
permits a null exception message. Mapping message into a String field is a
type error. A payload translation must map every target variant field; partial
payloads are rejected before lowering.
Each Java exception class may appear once per java fun; duplicate mappings are
rejected before lowering.
java type declares a nominal Meris handle for one Java binary owner. The
handle is opaque. Meris code cannot select Java fields, compare by Java object
identity, construct the owner directly, or call arbitrary Java methods. It can
only pass the handle through declared interop functions.
java new exposes a public constructor. The return must be the matching handle
type marked owned nonnull; nullable and platform-unsafe constructor returns
are rejected. Constructor overload selection is exact against checked Meris
parameter types.
java method exposes a public instance method as an ordinary Meris function.
The receiver is the first parameter and must be a visible Java handle marked
borrowed. Instance-call sugar such as Builder.toString() remains outside
the Java interop boundary.
Java collection copy adapters are documented separately. They copy Java List
or object-array returns into Meris-owned immutable snapshots. The copy adapter
is valid only on List returns in Meris source; array backing is inferred from
the Java method signature. List copy Option<Element> preserves nullable Java
elements when Element is Int, Bool, or String. Nested List elements
are copied recursively when their leaves are supported scalar or explicit
Option scalar elements. List nullable copy Element and
List platform unsafe copy Element expose Option<List<Element>> for Java
collection returns; array-backed nullable collection returns are not supported
yet.
Meris adapts function values to Java single-abstract-method interfaces only when the Java interop declaration marks the parameter explicitly:
java fun ApplyTwice(Mapper: fun(Int): Int sam app.IntMapper, Value: Int): Int =
app.Callbacks.applyTwice
The Meris-facing parameter remains fun(Int): Int. Ordinary function-value
semantics do not change; the JVM backend binds the current Meris runtime to the
function-value method handle and wraps that handle with the declared Java
interface before the Java call.
The adapter target must be a public Java interface with exactly one abstract
method. The current executable subset accepts SAM method parameters only for
primitive-safe Int and Bool; returns may use Int, Bool, Unit, or
String. Reference callback parameters are rejected until Meris has explicit
Java-to-Meris callback nullability syntax.
SAM-adapted function types must not declare raises. If a callback can raise a
typed Meris error, handle it before crossing into Java because the Java
interface has no typed-error channel.
The interpreter does not execute Java interop declarations. A script that
declares Java interop and is passed to meris run fails during CLI validation
with M7003. Compile the script with meris compile when it uses Java
interop.
The conformance suite includes an object interop fixture that exercises the
declared Java handle path end to end: java type, java new, borrowed
java method, checked visibility, and JVM execution through the compile lane.
That fixture proves the boundary shape without opening Java objects to
arbitrary field access or reflection.
The SAM adapter tests cover parser metadata, type-checker diagnostics, and JVM execution through an explicit Java functional interface. Generic erased Java functional interfaces and callback-specific typed-error mapping remain staged.
The JVM backend derives the method descriptor from checked Meris types and
emits INVOKESTATIC against the declared Java owner and method name. java new
emits NEW, DUP, and INVOKESPECIAL <init>. java method emits
INVOKEVIRTUAL or INVOKEINTERFACE after evaluating the receiver and
arguments. Target resolution happens before lowering, so bytecode emission does
not guess at missing Java owners, constructors, or methods. If the owner
resolves to an interface on the compiler classpath, the emitted call uses an
interface method reference.
No JVM descriptor appears in source code or frontend type references.