Skip to content

Commit

Permalink
Every builtin type has a common super class (#11861)
Browse files Browse the repository at this point in the history
This PR is separated from #11589. It only introduces [BuiltinObject](https://github.com/enso-org/enso/blob/8feab15290c45a485815619972a93ab69f34e78a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/BuiltinObject.java), a common supertype for all the builtin types. It does not change any behavior.

`BuiltinObject` defines `hasMetaObject`, `getMetaObject`, `hasType` and `getType` messages, so they no longer have to be implemented in subclasses.

# Important Notes
- Introduce also test [BuiltinsJavaInteropTest](https://github.com/enso-org/enso/blob/0d92891b8eecd3071f0f4f1d8c55524b637d14a8/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/builtins/BuiltinsJavaInteropTest.java)
- Builtin annotation processor [enforces](1fe2f3e) that every builtin class extend `BuiltinObject` class.
  • Loading branch information
Akirathan authored Dec 24, 2024
1 parent 910d5a7 commit 6fe253f
Show file tree
Hide file tree
Showing 21 changed files with 366 additions and 391 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.enso.example;

import java.time.LocalDate;
import org.graalvm.polyglot.Value;

public final class PolyglotTestClass {
private PolyglotTestClass() {}

public static boolean isPolyglotDate_Object(Object obj) {
return obj instanceof Value polyglotVal && polyglotVal.isDate();
}

public static boolean isPolyglotDate_LocalDate(LocalDate date) {
return date != null;
}

public static boolean isPolyglotDate_Value(Value val) {
return val != null && val.isDate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.enso.interpreter.test.builtins;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

import java.io.ByteArrayOutputStream;
import org.enso.test.utils.ContextUtils;
import org.graalvm.polyglot.Context;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

/**
* In these tests, we call Java methods from Enso. Java methods have different signatures that
* accept Enso values in different ways.
*/
public class BuiltinsJavaInteropTest {
private static Context ctx;
private static final ByteArrayOutputStream out = new ByteArrayOutputStream();

@BeforeClass
public static void prepareCtx() {
ctx = ContextUtils.createDefaultContext(out);
}

@AfterClass
public static void disposeCtx() {
ctx.close();
ctx = null;
}

@After
public void resetOutput() {
out.reset();
}

/**
* This test reflects the state of many Java methods in stdlibs that accept Enso values as {@link
* java.lang.Object}. If the Java method has a single argument of type {@link java.lang.Object},
* and we pass {@code Date_Time} in it, we expect the host interop conversion to convert it to
* {@link java.time.LocalDateTime}.
*/
@Test
public void javaMethodAcceptsEnsoTimeOfDay_AsObject() {
var src =
"""
from Standard.Base import Date_Time
polyglot java import org.enso.example.PolyglotTestClass
main =
dt = Date_Time.now
PolyglotTestClass.isPolyglotDate_Object dt
""";
var result = ContextUtils.evalModule(ctx, src);
assertThat(result.asBoolean(), is(true));
}

@Test
public void javaMethodAcceptsEnsoTimeOfDay_AsLocalDate() {
var src =
"""
from Standard.Base import Date_Time
polyglot java import org.enso.example.PolyglotTestClass
main =
dt = Date_Time.now
PolyglotTestClass.isPolyglotDate_LocalDate dt
""";
var result = ContextUtils.evalModule(ctx, src);
assertThat(result.asBoolean(), is(true));
}

@Test
public void javaMethodAcceptsEnsoTimeOfDay_AsValue() {
var src =
"""
from Standard.Base import Date_Time
polyglot java import org.enso.example.PolyglotTestClass
main =
dt = Date_Time.now
PolyglotTestClass.isPolyglotDate_Value dt
""";
var result = ContextUtils.evalModule(ctx, src);
assertThat(result.asBoolean(), is(true));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package org.enso.interpreter.runtime.builtin;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Idempotent;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.node.expression.builtin.Builtin;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.data.EnsoObject;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;

/**
* Base class for every Enso builtin object. Not type. Note that base class for a builtin type is
* {@link Builtin}.
*
* <p>In other words, this class represents an object of builtin type in a similar way that {@link
* org.enso.interpreter.runtime.data.atom.Atom} represents an object of a non-builtin type.
*/
@ExportLibrary(InteropLibrary.class)
@ExportLibrary(TypesLibrary.class)
public abstract class BuiltinObject extends EnsoObject {

@ExportMessage
public final boolean hasType() {
return true;
}

/**
* Returns the name of the builtin as saved inside {@link Builtins#builtinsByName}. Not fully
* qualified.
*
* @return
*/
protected abstract String builtinName();

protected final Type getBuiltinType(Node node) {
return GetType.uncached(this, node);
}

/**
* Must return false, otherwise if a builtin object is passed to a host method that has a single
* {@code Object} argument, host interop would convert the builtin object to a {@code Map} with
* all its members. Even if the builtin object is, e.g., a number of a date.
*
* <p>Must return false as long as all our stdlib Java methods accept {@code Object} and not
* {@link org.graalvm.polyglot.Value} as arguments comming from Enso.
*/
@ExportMessage
public final boolean hasMembers() {
return false;
}

@ExportMessage
public final Object getMembers(boolean includeInternal) throws UnsupportedMessageException {
throw UnsupportedMessageException.create();
}

@ExportMessage
public final boolean hasMetaObject() {
return true;
}

@ExportMessage(name = "getType", library = TypesLibrary.class)
@ExportMessage(name = "getMetaObject", library = InteropLibrary.class)
public static final class GetType {

GetType() {}

/**
* Caching on class of the receiver - as long as there is the same class, its {@link
* #builtinName()} method will return the same value. Note that we don't want to cache on the
* builtin name, as that would create a separate polymorph cache for every instance of the
* receiver.
*/
@Specialization(
guards = {"cachedReceiverClass == receiver.getClass()", "getCtx(node) == cachedCtx"},
limit = "1")
public static Type doItCached(
BuiltinObject receiver,
@Bind("$node") Node node,
@Cached("receiver.getClass()") Class<? extends BuiltinObject> cachedReceiverClass,
@Cached(value = "getCtx(node)", allowUncached = true) EnsoContext cachedCtx,
@Cached(value = "getBuiltinType(receiver, cachedCtx)", allowUncached = true)
Builtin cachedBuiltinType) {
return cachedBuiltinType.getType();
}

@Specialization(replaces = "doItCached")
public static Type uncached(BuiltinObject receiver, @Bind("$node") Node node) {
var ctx = getCtx(node);
return getBuiltinType(receiver, ctx).getType();
}

@TruffleBoundary
public static Builtin getBuiltinType(BuiltinObject receiver, EnsoContext ctx) {
return ctx.getBuiltins().getBuiltinType(receiver.builtinName());
}

@Idempotent
public static EnsoContext getCtx(Node node) {
return EnsoContext.get(node);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
package org.enso.interpreter.runtime.data;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalTime;
import org.enso.interpreter.dsl.Builtin;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
import org.enso.interpreter.runtime.builtin.BuiltinObject;
import org.enso.polyglot.common_utils.Core_Date_Utils;

@ExportLibrary(InteropLibrary.class)
@ExportLibrary(TypesLibrary.class)
@Builtin(pkg = "date", name = "Date", stdlibName = "Standard.Base.Data.Time.Date.Date")
public final class EnsoDate extends EnsoObject {
public final class EnsoDate extends BuiltinObject {
private final LocalDate date;

public EnsoDate(LocalDate date) {
this.date = date;
}

@Override
protected String builtinName() {
return "Date";
}

@Builtin.Method(description = "Return current Date", autoRegister = false)
@CompilerDirectives.TruffleBoundary
public static EnsoDate today() {
Expand Down Expand Up @@ -77,26 +78,6 @@ LocalTime asTime() throws UnsupportedMessageException {
throw UnsupportedMessageException.create();
}

@ExportMessage
Type getMetaObject(@Bind("$node") Node node) {
return EnsoContext.get(node).getBuiltins().date();
}

@ExportMessage
boolean hasMetaObject() {
return true;
}

@ExportMessage
boolean hasType() {
return true;
}

@ExportMessage
Type getType(@Bind("$node") Node node) {
return EnsoContext.get(node).getBuiltins().date();
}

@CompilerDirectives.TruffleBoundary
@ExportMessage
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
package org.enso.interpreter.runtime.data;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.enso.interpreter.dsl.Builtin;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.builtin.BuiltinObject;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
import org.enso.polyglot.common_utils.Core_Date_Utils;

@ExportLibrary(InteropLibrary.class)
@ExportLibrary(TypesLibrary.class)
@Builtin(
pkg = "date",
name = "DateTime",
stdlibName = "Standard.Base.Data.Time.Date_Time.Date_Time")
public final class EnsoDateTime extends EnsoObject {
public final class EnsoDateTime extends BuiltinObject {
private final ZonedDateTime dateTime;

public EnsoDateTime(ZonedDateTime dateTime) {
this.dateTime = dateTime;
}

@Override
protected String builtinName() {
return "Date_Time";
}

@Builtin.Method(
name = "epoch_start",
description = "Return the Enso start of the Epoch",
Expand Down Expand Up @@ -205,26 +205,6 @@ ZoneId asTimeZone() {
return dateTime.getZone();
}

@ExportMessage
Type getMetaObject(@CachedLibrary("this") InteropLibrary thisLib) {
return EnsoContext.get(thisLib).getBuiltins().dateTime();
}

@ExportMessage
boolean hasMetaObject() {
return true;
}

@ExportMessage
boolean hasType() {
return true;
}

@ExportMessage
Type getType(@Bind("$node") Node node) {
return EnsoContext.get(node).getBuiltins().dateTime();
}

@ExportMessage
@CompilerDirectives.TruffleBoundary
@Override
Expand Down
Loading

0 comments on commit 6fe253f

Please sign in to comment.