From 7a6ee0c2006b0924d4f6c1a739e84ce9c8c6144a Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Mon, 26 Sep 2022 18:01:39 +0200 Subject: [PATCH] Implement `type_of` (#3722) This change implements a simple `type_of` method that returns a type of a given value, including for polyglot objects. The change also allows for pattern matching on various time-related instances. It is a nice-to-have on its own, but it was primarily needed here to write some tests. For equality checks on types we currently can't use `==` due to a known _feature_ which essentially does wrong dispatching. This will be improved in the upcoming statics PR so we agreed that there is no point in duplicating that work and we can replace it later. Also, note that this PR changes `Meta.is_same_object`. Comparing types revealed that it was wrong when comparing polyglot wrappers over the same value. --- CHANGELOG.md | 3 + .../Base/0.0.0-dev/src/Data/Time/Date.enso | 7 +- .../0.0.0-dev/src/Data/Time/Date_Time.enso | 8 +- .../0.0.0-dev/src/Data/Time/Time_Of_Day.enso | 8 +- .../0.0.0-dev/src/Data/Time/Time_Zone.enso | 5 +- .../lib/Standard/Base/0.0.0-dev/src/Meta.enso | 41 +++---- .../Database/0.0.0-dev/src/Data/Table.enso | 2 +- .../Table/0.0.0-dev/src/Excel/Range.enso | 2 +- .../lib/Standard/Test/0.0.0-dev/src/Main.enso | 44 +++++++ .../controlflow/caseexpr/DateBranchNode.java | 59 ++++++++++ .../caseexpr/DateTimeBranchNode.java | 63 ++++++++++ .../caseexpr/TimeOfDayBranchNode.java | 59 ++++++++++ .../caseexpr/TimeZoneBranchNode.java | 60 ++++++++++ .../builtin/meta/IsSameObjectNode.java | 21 +++- .../expression/builtin/meta/TypeOfNode.java | 108 ++++++++++++++++++ .../runtime/type/TypesFromProxy.java | 10 ++ .../enso/compiler/codegen/IrToTruffle.scala | 12 ++ test/Table_Tests/src/Aggregate_Spec.enso | 4 +- test/Tests/src/Data/Time/Date_Spec.enso | 7 ++ test/Tests/src/Data/Time/Date_Time_Spec.enso | 7 ++ .../Tests/src/Data/Time/Time_Of_Day_Spec.enso | 7 ++ test/Tests/src/Data/Time/Time_Zone_Spec.enso | 7 ++ test/Tests/src/Semantic/Case_Spec.enso | 36 +++++- test/Tests/src/Semantic/Meta_Spec.enso | 90 ++++++++++++--- 24 files changed, 617 insertions(+), 53 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/DateBranchNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/DateTimeBranchNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TimeOfDayBranchNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TimeZoneBranchNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNode.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0580ebe59fd9..b6c019a1c695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -198,6 +198,8 @@ - [Added `databases`, `schemas`, `tables` support to database Connection.][3632] - [Implemented `start_of` and `end_of` methods for date/time types allowing to find start and end of a period of time containing the provided time.][3695] +- [Implemented `type_of` and `is_of_type` methods for getting the type of a + value and comparing types, respectively.][3722] - [Implemented `work_days_until` for counting work dys between dates and `add_work_days` which allows to shift a date by a number of work days.][3726] - [Added `query` and `read` functions to Database connections.][3727] @@ -320,6 +322,7 @@ [3684]: https://github.com/enso-org/enso/pull/3684 [3691]: https://github.com/enso-org/enso/pull/3691 [3695]: https://github.com/enso-org/enso/pull/3695 +[3722]: https://github.com/enso-org/enso/pull/3722 [3726]: https://github.com/enso-org/enso/pull/3726 [3727]: https://github.com/enso-org/enso/pull/3727 [3733]: https://github.com/enso-org/enso/pull/3733 diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso index c08c7b3cde48..4f5520203066 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso @@ -502,8 +502,11 @@ type Date ## Compares two Dates for equality. == : Date -> Boolean == self that = - sign = Time_Utils.compare_to_localdate self that - 0 == sign + case that of + Date -> + sign = Time_Utils.compare_to_localdate self that + 0 == sign + _ -> False ## PRIVATE week_days_between start end = diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index 6626de84d837..82ab926be374 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -564,5 +564,9 @@ type Date_Time ## Compares two Date_Time for equality. == : Date_Time -> Boolean == self that = - sign = Time_Utils.compare_to_zoneddatetime self that - 0 == sign + case that of + Date_Time -> + sign = Time_Utils.compare_to_zoneddatetime self that + 0 == sign + _ -> + False diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso index 4020e65e48a0..169c4a91dea4 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso @@ -330,5 +330,9 @@ type Time_Of_Day ## Compares two Time_Of_Day for equality. == : Date -> Boolean == self that = - sign = Time_Utils.compare_to_localtime self that - 0 == sign + case that of + Time_Of_Day -> + sign = Time_Utils.compare_to_localtime self that + 0 == sign + _ -> + False diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Zone.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Zone.enso index 51e558187cda..4d17f2d7cd99 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Zone.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Zone.enso @@ -140,4 +140,7 @@ type Time_Zone ## Compares two Zones for equality. == : Time_Zone -> Boolean - == self that = Time_Utils.equals_zone self that + == self that = + case that of + Time_Zone -> Time_Utils.equals_zone self that + _ -> False diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso index 4f1124cadcbb..c80cf47b0a79 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso @@ -253,12 +253,13 @@ is_same_object value_1 value_2 = @Builtin_Method "Meta.is_same_object" ## UNSTABLE ADVANCED - Checks if `self` is an instance of `typ`. + Checks whether `self` represents the same underlying reference as `value`. Arguments: - - typ: The type to check `self` against. -Any.is_a : Any -> Boolean -Any.is_a self typ = is_a self typ + - value_1: The first value. + - value_2: The second value. +Any.is_same_object_as : Any -> Boolean +Any.is_same_object_as self value = is_same_object self value ## UNSTABLE ADVANCED @@ -267,8 +268,8 @@ Any.is_a self typ = is_a self typ Arguments: - typ: The type to check `self` against. -Any.is_an : Any -> Boolean -Any.is_an self typ = is_a self typ +Any.is_a : Any -> Boolean +Any.is_a self typ = is_a self typ ## UNSTABLE ADVANCED @@ -278,17 +279,7 @@ Any.is_an self typ = is_a self typ Arguments: - typ: The type to check `self` against. Base.Error.is_a : Any -> Boolean -Base.Error.is_a self typ = self.is_an typ - -## UNSTABLE - ADVANCED - - Checks if `self` is an instance of `typ`. - - Arguments: - - typ: The type to check `self` against. -Base.Error.is_an : Any -> Boolean -Base.Error.is_an self typ = typ==Any || typ==Base.Error +Base.Error.is_a self typ = typ==Any || typ==Base.Error ## UNSTABLE ADVANCED @@ -303,12 +294,17 @@ is_a value typ = if is_same_object value typ then True else if typ == Any then True else if is_error value then typ == Base.Error else case value of - Array -> typ == Array + Vector.Vector -> typ.is_same_object_as Vector.Vector + Array -> typ.is_same_object_as Array Boolean -> if typ == Boolean then True else value == typ Text -> typ == Text Number -> if typ == Number then True else case value of Integer -> typ == Integer Decimal -> typ == Decimal + Date_Time.Date_Time -> typ.is_same_object_as Date_Time.Date_Time + Date.Date -> typ.is_same_object_as Date.Date + Time_Of_Day.Time_Of_Day -> typ.is_same_object_as Time_Of_Day.Time_Of_Day + Time_Zone.Time_Zone -> typ.is_same_object_as Time_Zone.Time_Zone Base.Polyglot -> typ==Base.Polyglot || java_instance_check value typ _ -> @@ -335,13 +331,12 @@ java_instance_check value typ = ## UNSTABLE ADVANCED - Checks if `value` is an instance of `typ`. + Returns the type of the given value. Arguments: - - value: The value to check for being an instance of `typ`. - - typ: The type to check `self` against. -is_an : Any -> Any -> Boolean -is_an value typ = is_a value typ + - value: the value to get the type of. +type_of : Any -> Any +type_of value = @Builtin_Method "Meta.type_of_builtin" ## Represents a polyglot language. type Language diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso index 50e2bb831866..abc147df8255 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso @@ -705,7 +705,7 @@ type Table agg = p.second new_name = p.first Aggregate_Helper.make_aggregate_column self agg new_name . catch - partitioned = results.partition (_.is_an Internal_Column_Data) + partitioned = results.partition (_.is_a Internal_Column_Data) ## When working on join we may encounter further issues with having aggregate columns exposed directly, it may be useful to re-use the `lift_aggregate` method to push the aggregates into a diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Excel/Range.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Excel/Range.enso index ff3d5b7a4d50..e945601095d2 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Excel/Range.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Excel/Range.enso @@ -79,7 +79,7 @@ type Excel_Range unchanged. column_index : (Text|Integer) -> Integer column_index column = - if column.is_an Integer then column else Java_Range.parseA1Column column + if column.is_a Integer then column else Java_Range.parseA1Column column ## Creates a Range from an address. from_address : Text -> Excel_Range diff --git a/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso index 1c99434ca78c..3d62f4518dd9 100644 --- a/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso @@ -286,6 +286,28 @@ Any.should_equal self that frames_to_skip=0 = case self == that of msg = self.to_text + " did not equal " + that.to_text + " (at " + loc + ")." fail msg +## Asserts that `self` value is equal to the expected type value. + + Arguments: + - that: The type to check `self` for equality with. + - frames_to_skip (optional, advanced): used to alter the location which is + displayed as the source of this error. + + > Example + Assert that some type is equal to another., + + import Standard.Examples + import Standard.Test + + example_should_equal = Examples.some_tpe . should_equal_tpe Vector.Vector +Any.should_equal_type : Any -> Integer -> Assertion +Any.should_equal_type self that frames_to_skip=0 = case (self.is_same_object_as that) of + True -> Success + False -> + loc = Meta.get_source_location 2+frames_to_skip + msg = self.to_text + " did not equal type " + that.to_text + " (at " + loc + ")." + fail msg + ## Asserts that `self` value is not equal to the expected value. Arguments: @@ -308,6 +330,28 @@ Any.should_not_equal self that frames_to_skip=0 = case self != that of msg = self.to_text + " did equal " + that.to_text + " (at " + loc + ")." fail msg +## Asserts that `self` value is not equal to the expected type value. + + Arguments: + - that: The type to check `self` for equality with. + - frames_to_skip (optional, advanced): used to alter the location which is + displayed as the source of this error. + + > Example + Assert that some type is equal to another., + + import Standard.Examples + import Standard.Test + + example_should_not_equal = Examples.some_tpe . should_not_equal_tpe Vector.Vector +Any.should_not_equal_type : Any -> Integer -> Assertion +Any.should_not_equal_type self that frames_to_skip=0 = case (self.is_same_object_as that . not) of + True -> Success + False -> + loc = Meta.get_source_location 2+frames_to_skip + msg = self.to_text + " did equal type " + that.to_text + " (at " + loc + ")." + fail msg + ## Asserts that `self` value is equal to the expected value. Arguments: diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/DateBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/DateBranchNode.java new file mode 100644 index 000000000000..f07f2ae840bd --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/DateBranchNode.java @@ -0,0 +1,59 @@ +package org.enso.interpreter.node.controlflow.caseexpr; + +import com.oracle.truffle.api.RootCallTarget; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.NodeInfo; +import com.oracle.truffle.api.profiles.ConditionProfile; +import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.runtime.data.EnsoDate; + +/** An implementation of the case expression specialised to working on Date. */ +@NodeInfo(shortName = "DateMatch") +public abstract class DateBranchNode extends BranchNode { + private final Type date; + private final ConditionProfile profile = ConditionProfile.createCountingProfile(); + + DateBranchNode(Type vector, RootCallTarget branch) { + super(branch); + this.date = vector; + } + + /** + * Creates a new node for handling matching on a case expression. + * + * @param date the expression to use for matching + * @param branch the expression to be executed if (@code matcher} matches + * @return a node for matching in a case expression + */ + public static DateBranchNode build(Type date, RootCallTarget branch) { + return DateBranchNodeGen.create(date, branch); + } + + @Specialization + void doType(VirtualFrame frame, Object state, Type target) { + if (profile.profile(date == target)) { + accept(frame, state, new Object[0]); + } + } + + @Specialization + void doEnsoDate(VirtualFrame frame, Object state, EnsoDate date) { + accept(frame, state, new Object[0]); + } + + @Specialization(guards = {"interop.isDate(date)", "!interop.isTime(date)"}) + void doDate( + VirtualFrame frame, + Object state, + Object date, + @CachedLibrary(limit = "10") InteropLibrary interop) { + accept(frame, state, new Object[0]); + } + + @Fallback + void doFallback(VirtualFrame frame, Object state, Object target) {} +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/DateTimeBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/DateTimeBranchNode.java new file mode 100644 index 000000000000..07475b5b11cb --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/DateTimeBranchNode.java @@ -0,0 +1,63 @@ +package org.enso.interpreter.node.controlflow.caseexpr; + +import com.oracle.truffle.api.RootCallTarget; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.NodeInfo; +import com.oracle.truffle.api.profiles.ConditionProfile; +import org.enso.interpreter.runtime.data.EnsoDateTime; +import org.enso.interpreter.runtime.data.Type; + +/** An implementation of the case expression specialised to working on Date_Time. */ +@NodeInfo(shortName = "DateTimeMatch") +public abstract class DateTimeBranchNode extends BranchNode { + private final Type dateTime; + private final ConditionProfile profile = ConditionProfile.createCountingProfile(); + + DateTimeBranchNode(Type vector, RootCallTarget branch) { + super(branch); + this.dateTime = vector; + } + + /** + * Creates a new node for handling matching on a case expression. + * + * @param dateTime the expression to use for matching + * @param branch the expression to be executed if (@code matcher} matches + * @return a node for matching in a case expression + */ + public static DateTimeBranchNode build(Type dateTime, RootCallTarget branch) { + return DateTimeBranchNodeGen.create(dateTime, branch); + } + + @Specialization + void doType(VirtualFrame frame, Object state, Type target) { + if (profile.profile(dateTime == target)) { + accept(frame, state, new Object[0]); + } + } + + @Specialization + void doEnsoDateTime(VirtualFrame frame, Object state, EnsoDateTime dateTime) { + accept(frame, state, new Object[0]); + } + + @Specialization( + guards = { + "interop.isDate(dateTime)", + "interop.isTime(dateTime)", + }) + void doDateTime( + VirtualFrame frame, + Object state, + Object dateTime, + @CachedLibrary(limit = "10") InteropLibrary interop) { + accept(frame, state, new Object[0]); + } + + @Fallback + void doFallback(VirtualFrame frame, Object state, Object target) {} +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TimeOfDayBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TimeOfDayBranchNode.java new file mode 100644 index 000000000000..453f87586bb9 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TimeOfDayBranchNode.java @@ -0,0 +1,59 @@ +package org.enso.interpreter.node.controlflow.caseexpr; + +import com.oracle.truffle.api.RootCallTarget; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.NodeInfo; +import com.oracle.truffle.api.profiles.ConditionProfile; +import org.enso.interpreter.runtime.data.EnsoTimeOfDay; +import org.enso.interpreter.runtime.data.Type; + +/** An implementation of the case expression specialised to working on Time_Of_Day. */ +@NodeInfo(shortName = "TimeOfDayMatch") +public abstract class TimeOfDayBranchNode extends BranchNode { + private final Type timeOfDay; + private final ConditionProfile profile = ConditionProfile.createCountingProfile(); + + TimeOfDayBranchNode(Type vector, RootCallTarget branch) { + super(branch); + this.timeOfDay = vector; + } + + /** + * Creates a new node for handling matching on a case expression. + * + * @param timeOfDay the expression to use for matching + * @param branch the expression to be executed if (@code matcher} matches + * @return a node for matching in a case expression + */ + public static TimeOfDayBranchNode build(Type timeOfDay, RootCallTarget branch) { + return TimeOfDayBranchNodeGen.create(timeOfDay, branch); + } + + @Specialization + void doType(VirtualFrame frame, Object state, Type target) { + if (profile.profile(timeOfDay == target)) { + accept(frame, state, new Object[0]); + } + } + + @Specialization + void doEnsoTimeOfDay(VirtualFrame frame, Object state, EnsoTimeOfDay timeOfDay) { + accept(frame, state, new Object[0]); + } + + @Specialization(guards = {"!interop.isDate(timeOfDay)", "interop.isTime(timeOfDay)"}) + void doTime( + VirtualFrame frame, + Object state, + Object timeOfDay, + @CachedLibrary(limit = "10") InteropLibrary interop) { + accept(frame, state, new Object[0]); + } + + @Fallback + void doFallback(VirtualFrame frame, Object state, Object target) {} +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TimeZoneBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TimeZoneBranchNode.java new file mode 100644 index 000000000000..dbcfc9bf9e36 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TimeZoneBranchNode.java @@ -0,0 +1,60 @@ +package org.enso.interpreter.node.controlflow.caseexpr; + +import com.oracle.truffle.api.RootCallTarget; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.NodeInfo; +import com.oracle.truffle.api.profiles.ConditionProfile; +import org.enso.interpreter.runtime.data.EnsoTimeZone; +import org.enso.interpreter.runtime.data.Type; + +/** An implementation of the case expression specialised to working on Time_Zone. */ +@NodeInfo(shortName = "TimeZoneMatch") +public abstract class TimeZoneBranchNode extends BranchNode { + private final Type timeZone; + private final ConditionProfile profile = ConditionProfile.createCountingProfile(); + + TimeZoneBranchNode(Type vector, RootCallTarget branch) { + super(branch); + this.timeZone = vector; + } + + /** + * Creates a new node for handling matching on a case expression. + * + * @param timeZone the expression to use for matching + * @param branch the expression to be executed if (@code matcher} matches + * @return a node for matching in a case expression + */ + public static TimeZoneBranchNode build(Type timeZone, RootCallTarget branch) { + return TimeZoneBranchNodeGen.create(timeZone, branch); + } + + @Specialization + void doType(VirtualFrame frame, Object state, Type target) { + if (profile.profile(timeZone == target)) { + accept(frame, state, new Object[0]); + } + } + + @Specialization + void doEnsoZone(VirtualFrame frame, Object state, EnsoTimeZone timeZone) { + accept(frame, state, new Object[0]); + } + + @Specialization( + guards = {"!interop.isDate(zone)", "!interop.isTime(zone)", "interop.isTimeZone(zone)"}) + void doZone( + VirtualFrame frame, + Object state, + Object zone, + @CachedLibrary(limit = "10") InteropLibrary interop) { + accept(frame, state, new Object[0]); + } + + @Fallback + void doFallback(VirtualFrame frame, Object state, Object target) {} +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsSameObjectNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsSameObjectNode.java index e14d899c7d6d..329935d958d6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsSameObjectNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsSameObjectNode.java @@ -1,5 +1,8 @@ package org.enso.interpreter.node.expression.builtin.meta; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.dsl.AcceptsError; import org.enso.interpreter.dsl.BuiltinMethod; @@ -8,8 +11,20 @@ type = "Meta", name = "is_same_object", description = "Checks if the two arguments share an underlying reference.") -public class IsSameObjectNode extends Node { - boolean execute(@AcceptsError Object value_1, @AcceptsError Object value_2) { - return value_1 == value_2; +public abstract class IsSameObjectNode extends Node { + + static IsSameObjectNode build() { + return IsSameObjectNodeGen.create(); + } + + abstract boolean execute(@AcceptsError Object left, @AcceptsError Object right); + + @Specialization(limit = "3") + boolean doExecute( + Object left, + Object right, + @CachedLibrary("left") InteropLibrary leftInterop, + @CachedLibrary("right") InteropLibrary rightInteropp) { + return (left == right) || leftInterop.isIdentical(left, right, rightInteropp); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNode.java new file mode 100644 index 000000000000..c4fec7c7bfb2 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNode.java @@ -0,0 +1,108 @@ +package org.enso.interpreter.node.expression.builtin.meta; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Fallback; +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.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.Constants; +import org.enso.interpreter.dsl.AcceptsError; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.builtin.Builtins; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; +import org.enso.interpreter.runtime.number.EnsoBigInteger; +import org.enso.interpreter.runtime.type.TypesGen; + +@BuiltinMethod( + type = "Meta", + name = "type_of_builtin", + description = "Returns the type of a value.") +public abstract class TypeOfNode extends Node { + + abstract Object execute(@AcceptsError Object value); + + static TypeOfNode build() { + return TypeOfNodeGen.create(); + } + + @Specialization + Object doDouble(double value) { + Context ctx = Context.get(this); + return ctx.getBuiltins().number().getDecimal(); + } + + @Specialization + Object doLong(long value) { + Context ctx = Context.get(this); + return ctx.getBuiltins().number().getInteger(); + } + + @Specialization + Object doBigInteger(EnsoBigInteger value) { + Context ctx = Context.get(this); + return ctx.getBuiltins().number().getInteger(); + } + + @Specialization(guards = {"interop.isTime(value)", "interop.isDate(value)"}) + Object doDateTime(Object value, @CachedLibrary(limit = "3") InteropLibrary interop) { + Context ctx = Context.get(this); + return ctx.getBuiltins().dateTime(); + } + + @Specialization( + guards = {"interop.isTimeZone(value)", "!interop.isDate(value)", "!interop.isTime(value)"}) + Object doTimeZone(Object value, @CachedLibrary(limit = "3") InteropLibrary interop) { + Context ctx = Context.get(this); + return ctx.getBuiltins().timeZone(); + } + + @Specialization(guards = {"interop.isDate(value)", "!interop.isTime(value)"}) + Object doDate(Object value, @CachedLibrary(limit = "3") InteropLibrary interop) { + Context ctx = Context.get(this); + return ctx.getBuiltins().date(); + } + + @Specialization(guards = {"interop.isTime(value)", "!interop.isDate(value)"}) + Object doTime(Object value, @CachedLibrary(limit = "3") InteropLibrary interop) { + Context ctx = Context.get(this); + return ctx.getBuiltins().timeOfDay(); + } + + @Specialization( + guards = { + "interop.hasMetaObject(value)", + "!types.hasType(value)", + "!interop.isDate(value)", + "!interop.isTime(value)", + "!interop.isTimeZone(value)" + }) + Object doMetaObject( + Object value, + @CachedLibrary(limit = "3") InteropLibrary interop, + @CachedLibrary(limit = "3") TypesLibrary types) { + try { + return interop.getMetaObject(value); + } catch (UnsupportedMessageException e) { + CompilerDirectives.transferToInterpreter(); + Builtins builtins = Context.get(this).getBuiltins(); + throw new PanicException(builtins.error().makeCompileError("invalid meta object"), this); + } + } + + @Specialization(guards = {"types.hasType(value)", "!interop.isNumber(value)"}) + Object doType( + Object value, + @CachedLibrary(limit = "3") InteropLibrary interop, + @CachedLibrary(limit = "3") TypesLibrary types) { + return types.getType(value); + } + + @Fallback + Object doAny(Object value) { + return Context.get(this).getBuiltins().error().makeCompileError("unknown type_of for " + value); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/TypesFromProxy.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/TypesFromProxy.java index b673e4142151..87b91904090c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/TypesFromProxy.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/TypesFromProxy.java @@ -34,6 +34,10 @@ public static Type fromTypeSystem(Builtins builtins, String typeName) { return builtins.array(); case ConstantsGen.BOOLEAN: return builtins.bool().getType(); + case ConstantsGen.DATE: + return builtins.date(); + case ConstantsGen.DATE_TIME: + return builtins.dateTime(); case ConstantsGen.DECIMAL: return builtins.number().getDecimal(); case ConstantsGen.ERROR: @@ -56,6 +60,12 @@ public static Type fromTypeSystem(Builtins builtins, String typeName) { return builtins.ref(); case ConstantsGen.TEXT: return builtins.text(); + case ConstantsGen.TIME_OF_DAY: + return builtins.timeOfDay(); + case ConstantsGen.TIME_ZONE: + return builtins.timeZone(); + case ConstantsGen.VECTOR: + return builtins.vector(); default: throw new CompilerError("Invalid builtin type " + typeName); } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index 77125559c11c..c8c6befc2b26 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -951,6 +951,10 @@ class IrToTruffle( val any = context.getBuiltins.any val array = context.getBuiltins.array val vector = context.getBuiltins.vector + val date = context.getBuiltins.date + val dateTime = context.getBuiltins.dateTime + val timeOfDay = context.getBuiltins.timeOfDay + val timeZone = context.getBuiltins.timeZone val file = context.getBuiltins.file val builtinBool = context.getBuiltins.bool.getType val number = context.getBuiltins.number @@ -976,6 +980,14 @@ class IrToTruffle( ArrayBranchNode.build(tpe, branchCodeNode.getCallTarget) } else if (tpe == vector) { VectorBranchNode.build(tpe, branchCodeNode.getCallTarget) + } else if (tpe == date) { + DateBranchNode.build(tpe, branchCodeNode.getCallTarget) + } else if (tpe == dateTime) { + DateTimeBranchNode.build(tpe, branchCodeNode.getCallTarget) + } else if (tpe == timeOfDay) { + TimeOfDayBranchNode.build(tpe, branchCodeNode.getCallTarget) + } else if (tpe == timeZone) { + TimeZoneBranchNode.build(tpe, branchCodeNode.getCallTarget) } else if (tpe == file) { FileBranchNode.build(tpe, branchCodeNode.getCallTarget) } else if (tpe == polyglot) { diff --git a/test/Table_Tests/src/Aggregate_Spec.enso b/test/Table_Tests/src/Aggregate_Spec.enso index 2c2ffe6b4800..38e786ba9fa4 100644 --- a/test/Table_Tests/src/Aggregate_Spec.enso +++ b/test/Table_Tests/src/Aggregate_Spec.enso @@ -1326,14 +1326,14 @@ aggregate_spec prefix table empty_table table_builder materialize is_database te new_table = table.aggregate [Group_By "Key", Concatenate "Value"] problems = Warning.get_all new_table . map .value problems.length . should_equal 1 - problems.at 0 . is_an Invalid_Aggregation_Data . should_be_true + problems.at 0 . is_a Invalid_Aggregation_Data . should_be_true problems.at 0 . rows . length . should_equal 15 Test.specify "should merge Floating Point Grouping warnings" <| new_table = table.aggregate [Group_By "Float", Count Nothing] problems = Warning.get_all new_table . map .value problems.length . should_equal 1 - problems.at 0 . is_an Floating_Point_Grouping_Data . should_be_true + problems.at 0 . is_a Floating_Point_Grouping_Data . should_be_true problems.at 0 . rows . length . should_equal 15 if is_database then diff --git a/test/Tests/src/Data/Time/Date_Spec.enso b/test/Tests/src/Data/Time/Date_Spec.enso index 54e16493ffc8..f8d0aa61d222 100644 --- a/test/Tests/src/Data/Time/Date_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Spec.enso @@ -133,6 +133,13 @@ spec_with name create_new_date parse_date = date_1>date_2 . should_be_true date_1time_2 . should_be_true time_1time_2 . should_be_true time_1 Test.fail ("Unexpected result: " + result.to_text) + Test.specify "should correctly determine the type of zone" <| + zone = Time_Zone.parse "Europe/Warsaw" + Meta.type_of zone . should_equal_type Time_Zone.Time_Zone Test.group "JavaZoneId" <| Test.specify "should get system zone id" <| defaultZone = ZoneId.systemDefault @@ -61,5 +65,8 @@ spec = Json.from_pairs [["type", "Time_Zone"], ["id", "+01:02:03"]] (ZoneId.of "UTC").to_json.should_equal <| Json.from_pairs [["type", "Time_Zone"], ["id", "UTC"]] + Test.specify "should correctly determine the type of zone" <| + zone = ZoneId.systemDefault + Meta.type_of zone . should_equal_type Time_Zone.Time_Zone main = Test.Suite.run_main spec diff --git a/test/Tests/src/Semantic/Case_Spec.enso b/test/Tests/src/Semantic/Case_Spec.enso index 2c513ccc537d..69ac27aed71b 100644 --- a/test/Tests/src/Semantic/Case_Spec.enso +++ b/test/Tests/src/Semantic/Case_Spec.enso @@ -88,6 +88,40 @@ spec = Test.group "Pattern Matches" <| case Any of Any -> Nothing _ -> Test.fail "Expected the Any constructor to match." + Test.specify "should be able to match on date/time values" <| + new_date = Date.new 2020 6 1 + new_date_time = Date_Time.new 2020 6 1 + new_time = Time_Of_Day.new 11 11 + new_zone = Time_Zone.system + + case new_date of + Date_Time.Date_Time -> Test.fail "Expected date value to match Date." + Time_Of_Day.Time_Of_Day -> Test.fail "Expected date value to match Date." + Time_Zone.Time_Zone -> Test.fail "Expected date value to match Date." + Date.Date -> Nothing + _ -> Test.fail "Expected date value to match Date." + + case new_date_time of + Date.Date -> Test.fail "Expected datetime value to match Date_Time." + Time_Of_Day.Time_Of_Day -> Test.fail "Expected datetime value to match Date_Time." + Time_Zone.Time_Zone -> Test.fail "Expected datetime value to match Date_Time." + Date_Time.Date_Time -> Nothing + _ -> Test.fail "Expected datetime value to match Date_Time." + + case new_time of + Date.Date -> Test.fail "Expected time value to match Time_Of_Day." + Date_Time.Date_Time -> Test.fail "Expected time value to match Time_Of_Day." + Time_Zone.Time_Zone -> Test.fail "Expected time value to match Time_Of_Day." + Time_Of_Day.Time_Of_Day -> Nothing + _ -> Test.fail "Expected time value to match Time_Of_Day." + + case new_zone of + Date.Date -> Test.fail "Expected timezone value to match Time_Zone." + Date_Time.Date_Time -> Test.fail "Expected timezone to match Time_Zone." + Time_Of_Day.Time_Of_Day -> Test.fail "Expected timezone to match Time_Zone." + Time_Zone.Time_Zone -> Nothing + _ -> Test.fail "Expected timezone value to match Time_Zone." + Test.specify "should be able to match on literal values" <| value_1 = 42 value_2 = "foo" @@ -112,8 +146,6 @@ spec = Test.group "Pattern Matches" <| "ę" -> Test.fail "Expected value to match constant." '\u0065\u{301}' -> Nothing _ -> Test.fail "Expected value to match constant." - - Test.specify "should be able to match on literal values nested in constructors" <| value_1 = Cons 42 Nil value_2 = Cons (Cons 42 Nil) Nil diff --git a/test/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index cceb7e36eba8..1dd189d3ba0c 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -3,6 +3,11 @@ import Standard.Base import Standard.Base.System.Platform +polyglot java import java.lang.Object as JObject +polyglot java import java.lang.Long as JLong +polyglot java import java.lang.Exception as JException +polyglot java import java.io.IOException +polyglot java import java.util.ArrayList polyglot java import java.util.Random polyglot java import java.util.Locale as JavaLocale @@ -39,53 +44,110 @@ spec = Test.group "Meta-Value Manipulation" <| meta_err.is_a Meta.Error_Data . should_be_true meta_err.value . should_equal "My Error" Test.specify "should allow checking if a value is of a certain type" <| - 1.is_an Any . should_be_true - 1.2.is_an Any . should_be_true - (My_Type_Data 1 "foo" Nothing).is_an Any . should_be_true + 1.is_a Any . should_be_true + 1.2.is_a Any . should_be_true + (My_Type_Data 1 "foo" Nothing).is_a Any . should_be_true - Array.is_an Array . should_be_true - [].to_array.is_an Array . should_be_true + Array.is_a Array . should_be_true + [].to_array.is_a Array . should_be_true [].to_array.is_a Decimal . should_be_false + [1,2,3].is_a Vector.Vector . should_be_true + [1,2,3].is_a Array . should_be_false Boolean.is_a Boolean . should_be_true True.is_a Boolean . should_be_true False.is_a Boolean . should_be_true - True.is_an Integer . should_be_false + True.is_a Integer . should_be_false "".is_a Text . should_be_true "".is_a Decimal . should_be_false - 1.is_an Array . should_be_false - 1.is_an Integer . should_be_true + 1.is_a Array . should_be_false + 1.is_a Integer . should_be_true 1.is_a Number . should_be_true 1.is_a Decimal . should_be_false 1.0.is_a Number . should_be_true 1.0.is_a Decimal . should_be_true - 1.0.is_an Integer . should_be_false + 1.0.is_a Integer . should_be_false 1.0.is_a Text . should_be_false random_gen = Random.new Meta.is_a random_gen Polyglot . should_be_true - Meta.is_an random_gen Integer . should_be_false + Meta.is_a random_gen Integer . should_be_false (My_Type_Data 1 "foo" Nothing).is_a My_Type_Data . should_be_true (My_Type_Data 1 "foo" Nothing).is_a Test_Type_Data . should_be_false (My_Type_Data 1 "foo" Nothing).is_a Number . should_be_false err = Error.throw "Error Value" - 1.is_an Error . should_be_false - err.is_an Error . should_be_true + 1.is_a Error . should_be_false + err.is_a Error . should_be_true err.is_a Text . should_be_false - Meta.is_an err Error . should_be_true + Meta.is_a err Error . should_be_true Meta.is_a err Text . should_be_false + Meta.is_a Date.now Date.Date . should_be_true + Meta.is_a Date_Time.now Date_Time.Date_Time . should_be_true + Meta.is_a Date_Time.now Date.Date . should_be_false + Meta.is_a Time_Of_Day.now Time_Of_Day.Time_Of_Day . should_be_true + Meta.is_a Time_Of_Day.now Date.Date . should_be_false + Meta.is_a Date_Time.now.zone Time_Zone.Time_Zone . should_be_true + Meta.is_a Date_Time.now.zone Date.Date . should_be_false + + Test.specify "should allow for returning the type of a value" <| + n_1 = Meta.type_of 42 + n_1 . should_equal_type Integer + n_1 . should_not_equal_type Decimal + + n_2 = Meta.type_of 2.81 + n_2 . should_equal_type Decimal + n_2 . should_not_equal_type Integer + + n_3 = Meta.type_of (Long.MAX_VALUE * 2) + n_3 . should_equal_type Integer + n_3 . should_not_equal_type Decimal + + v_tpe = Meta.type_of [1,2,3] + v_tpe . should_equal_type Vector.Vector + v_tpe . should_not_equal_type Array + Meta.type_of [1,2,3].to_array . should_equal_type Array + + Meta.type_of "foo" . should_equal_type Text + Meta.type_of "0" . should_not_equal_type Integer + + Meta.type_of True . should_equal_type Boolean + Meta.type_of False . should_not_equal_type Any + + (Meta.type_of Date.now) . should_equal_type Date.Date + (Meta.type_of Date.now) . should_not_equal_type Date_Time.Date_Time + (Meta.type_of Date_Time.now) . should_equal_type Date_Time.Date_Time + (Meta.type_of Date_Time.now) . should_not_equal_type Date.Date + (Meta.type_of Time_Of_Day.now) . should_equal_type Time_Of_Day.Time_Of_Day + (Meta.type_of Time_Of_Day.now) . should_not_equal_type Date.Date + (Meta.type_of Date_Time.now.zone) . should_equal_type Time_Zone.Time_Zone + (Meta.type_of Date_Time.now.zone) . should_not_equal_type Date.Date + (Meta.type_of Time_Zone.local) . should_equal_type Time_Zone.Time_Zone + (Meta.type_of Time_Zone.system) . should_equal_type Time_Zone.Time_Zone + + list = ArrayList.new + list.add 123 + list_tpe = Meta.type_of list + list_tpe . should_not_equal_type JObject + list_tpe . should_equal_type ArrayList + + e = IOException.new "meh" + e_tpe = Meta.type_of e + e_tpe . should_equal_type IOException + e_tpe . should_not_equal_type JException + location_pending = case Platform.os of Platform.Windows -> "This test is disabled on Windows until issue 1561 is fixed." _ -> Nothing + Test.specify "should allow to get the source location of a frame" pending=location_pending <| src = Meta.get_source_location 0 - loc = "Meta_Spec.enso:87:15-40" + loc = "Meta_Spec.enso:149:15-40" src.take (Last loc.length) . should_equal loc Test.specify "should allow to get qualified type names of values" <|