From aba7f00843a98a2bb7dcef9fc8310714ab157dc1 Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Mon, 19 Sep 2022 23:21:00 +0200 Subject: [PATCH] Implement `type_of` method 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 patterning 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. --- .../Base/0.0.0-dev/src/Data/Time/Date.enso | 8 ++- .../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 | 34 ++++++++++ .../controlflow/caseexpr/DateBranchNode.java | 59 +++++++++++++++++ .../caseexpr/DateTimeBranchNode.java | 64 +++++++++++++++++++ .../caseexpr/TimeOfDayBranchNode.java | 59 +++++++++++++++++ .../caseexpr/TimeZoneBranchNode.java | 59 +++++++++++++++++ .../expression/builtin/meta/TypeOfNode.java | 27 ++++++++ .../builtin/meta/TypeOfPolyglotNode.java | 37 +++++++++++ .../enso/compiler/codegen/IrToTruffle.scala | 12 ++++ test/Tests/src/Semantic/Meta_Spec.enso | 40 +++++++++++- 13 files changed, 412 insertions(+), 8 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 create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfPolyglotNode.java 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 4ca1bfccd9e27..b1b03685ddd5c 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 @@ -353,5 +353,9 @@ 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 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 68c471038be2f..66e745e8aaca1 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 @@ -527,5 +527,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 4020e65e48a0b..169c4a91dea41 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 51e558187cda5..4d17f2d7cd997 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 b9307a5ea44ee..a432c82f8c41d 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 @@ -342,6 +342,40 @@ java_instance_check value typ = is_an : Any -> Any -> Boolean is_an value typ = is_a value typ +## UNSTABLE + ADVANCED + + Returns the type of the given value. + + Arguments: + - value: the value to get the type of. +type_of : Any -> Any +type_of value = + if is_error value then Base.Error else + case value of + Base.Polyglot -> Meta.type_of_polyglot_builtin value + Integer -> Integer + Decimal -> Decimal + Number -> Number + _ -> + Meta.type_of_builtin value + +## PRIVATE + + Returns the type of the polyglot value. + + Arguments: + - value: the value to get the type of. +type_of_polyglot_builtin value = @Builtin_Method "Meta.type_of_polyglot_builtin" + +## PRIVATE + + A builtin method returning the type of a non-polyglot value. + + Arguments: + - value: the value to get the type of. +type_of_builtin value = @Builtin_Method "Meta.type_of_builtin" + ## Represents a polyglot language. type Language 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 0000000000000..5d58635548ef3 --- /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)") + 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 0000000000000..55f0ce2e70477 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/DateTimeBranchNode.java @@ -0,0 +1,64 @@ +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 = "DateMatch") +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)", + "interop.isTimeZone(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 0000000000000..204420352e18b --- /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.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 0000000000000..c7888683bf68c --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TimeZoneBranchNode.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.EnsoTimeZone; +import org.enso.interpreter.runtime.data.Type; + +/** An implementation of the case expression specialised to working on Time_Zone. */ +@NodeInfo(shortName = "DateMatch") +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.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/TypeOfNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNode.java new file mode 100644 index 0000000000000..436a9122e1853 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNode.java @@ -0,0 +1,27 @@ +package org.enso.interpreter.node.expression.builtin.meta; + +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.BranchProfile; +import org.enso.interpreter.Constants; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; + +@BuiltinMethod( + type = "Meta", + name = "type_of_builtin", + description = "Returns the type of a value.") +public class TypeOfNode extends Node { + private @Child TypesLibrary types = + TypesLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); + private final BranchProfile err = BranchProfile.create(); + + Object execute(Object value) { + if (types.hasType(value)) { + return types.getType(value); + } else { + Context ctx = Context.get(this); + return ctx.getBuiltins().any(); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfPolyglotNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfPolyglotNode.java new file mode 100644 index 0000000000000..3983e5472e9e9 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfPolyglotNode.java @@ -0,0 +1,37 @@ +package org.enso.interpreter.node.expression.builtin.meta; + +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.BranchProfile; +import org.enso.interpreter.Constants; +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; + +@BuiltinMethod( + type = "Meta", + name = "type_of_polyglot_builtin", + description = "Returns the type of a polyglot value.") +public class TypeOfPolyglotNode extends Node { + private @Child InteropLibrary library = + InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); + private final BranchProfile err = BranchProfile.create(); + + Object execute(Object value) { + if (library.hasMetaObject(value)) { + try { + return library.getMetaObject(value); + } catch (UnsupportedMessageException e) { + err.enter(); + Builtins builtins = Context.get(this).getBuiltins(); + throw new PanicException( + builtins.error().makeTypeError(builtins.any(), value, "object"), this); + } + } else { + Context ctx = Context.get(this); + return ctx.getBuiltins().any(); + } + } +} 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 77125559c11cd..c8c6befc2b26a 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/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index 1d7e637b2f931..6e5104d342e2b 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -2,6 +2,10 @@ from Standard.Base import all import Standard.Base.System.Platform +polyglot java import java.lang.Object as JObject +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 @@ -79,12 +83,46 @@ spec = Test.group "Meta-Value Manipulation" <| Meta.is_an err Error . should_be_true Meta.is_a err Text . should_be_false + Test.specify "should allow for returning the type of a value" <| + Meta.type_of 42 . should_equal Integer + Meta.type_of 2.81 . should_equal Decimal + Meta.type_of 2.81 . should_not_equal Integer + + # Can't compare like + # Meta.type_of [1,2,3] . should_equal Vector.Vector + # because it tries to compare lengths, where RHS is a type + Meta.is_a (Meta.type_of [1,2,3]) Vector.Vector . should_be_true + + Meta.type_of "foo" . should_equal Text + Meta.type_of "0" . should_not_equal Integer + + Meta.type_of True . should_equal Boolean + Meta.type_of False . should_not_equal Any + + Meta.is_a (Meta.type_of Date.now) Date.Date . should_be_true + Meta.is_a (Meta.type_of Date_Time.now) Date_Time.Date_Time . should_be_true + Meta.is_a (Meta.type_of Date_Time.now) Date.Date . should_be_false + Meta.is_a (Meta.type_of Time_Of_Day.now) Time_Of_Day.Time_Of_Day . should_be_true + Meta.is_a (Meta.type_of Time_Of_Day.now) Date.Date . should_be_false + Meta.is_a (Meta.type_of Date_Time.now.zone) Time_Zone.Time_Zone . should_be_true + Meta.is_a (Meta.type_of Date_Time.now.zone) Date.Date . should_be_false + + list = ArrayList.new + list.add 123 + Meta.type_of list . should_not_equal JObject + Meta.type_of list . should_equal ArrayList + + e = IOException.new "meh" + Meta.type_of list . should_not_equal JObject + Meta.type_of e . should_equal IOException + Meta.type_of e . should_not_equal 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:86:15-40" + loc = "Meta_Spec.enso:124:15-40" src.take (Last loc.length) . should_equal loc Test.specify "should allow to get qualified type names of values" <|