Skip to content

Commit

Permalink
case of and intersection types (#11850)
Browse files Browse the repository at this point in the history
Also: Prefer `getURI` path when gathering location of a source
  • Loading branch information
JaroslavTulach authored Dec 13, 2024
1 parent e9b0ba9 commit 7998ed5
Show file tree
Hide file tree
Showing 18 changed files with 198 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ Error.should_equal self that frames_to_skip=0 =
Number.should_equal : Float -> Float -> Integer -> Spec_Result
Number.should_equal self that epsilon=0 frames_to_skip=0 =
matches = case that of
_ : Number -> self.equals that epsilon
n : Number -> self.equals n epsilon
_ -> self==that
case matches of
True -> Spec_Result.Success
Expand Down
20 changes: 8 additions & 12 deletions docs/types/intersection-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,14 @@ method calls will also only accept the value if it satisfies the type it
_has been cast to_. Any additional remaining _hidden_ types
can only be brought back through an _explicit_ cast.
To perform an explicit cast that can uncover the 'hidden' part of a type write
`f = c:Float`.

<!--
When #11828 is fixed.
- or inspect the types in a `case` expression, e.g.
```
case c of
f : Float -> f.sqrt
_ -> "Not a float"
```
-->
`f = c:Float` or inspect the types in a `case` expression, e.g.
```ruby
case c of
f : Float -> f.sqrt
_ -> "Not a float"
```
Remember to use `f.sqrt` and not `c.sqrt`. `f` in the case branch _has been cast to_ `Float` while
`c` in the case branch only _can be cast to_.

> [!WARNING]
> Keep in mind that while both argument type check in method definitions and a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ private String[] typeOf(Object value) {
}

var typeOfNode = TypeOfNode.getUncached();
Type[] allTypes = value == null ? null : typeOfNode.findAllTypesOrNull(value);
Type[] allTypes = value == null ? null : typeOfNode.findAllTypesOrNull(value, true);
if (allTypes != null) {
String[] result = new String[allTypes.length];
for (var i = 0; i < allTypes.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ private static Context ctx() {
new TestRootNode(
(frame) -> {
var arg = frame.getArguments()[0];
var allTypes = (boolean) frame.getArguments()[1];
var t = node.findTypeOrError(arg);
var all = node.findAllTypesOrNull(arg);
var all = node.findAllTypesOrNull(arg, allTypes);
return new Object[] {t, all};
});
root.insertChildren(node);
Expand Down Expand Up @@ -116,16 +117,22 @@ public static void disposeCtx() throws Exception {
}

@Test
public void typeOfCheck() {
assertType(value, type, typeIndex);
public void typeOfCheckAllTypes() {
assertType(value, type, typeIndex, true);
}

private static void assertType(Object value, String expectedTypeName, int typeIndex) {
@Test
public void typeOfCheckHasBeenCastToTypes() {
assertType(value, type, typeIndex, false);
}

private static void assertType(
Object value, String expectedTypeName, int typeIndex, boolean allTypes) {
assertNotNull("Value " + value + " should have a type", expectedTypeName);
ContextUtils.executeInContext(
ctx(),
() -> {
var pairResult = (Object[]) testTypesCall.call(value);
var pairResult = (Object[]) testTypesCall.call(value, allTypes);
var t = pairResult[0];
var all = (Object[]) pairResult[1];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import static org.junit.Assert.assertEquals;

import com.oracle.truffle.api.RootCallTarget;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.callable.UnresolvedConstructor;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.data.EnsoMultiValue;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.library.dispatch.TypeOfNode;
import org.enso.test.utils.ContextUtils;
Expand All @@ -28,8 +30,9 @@ private static Context ctx() {
new TestRootNode(
(frame) -> {
var arg = frame.getArguments()[0];
var allTypes = (boolean) frame.getArguments()[1];
var t = node.findTypeOrError(arg);
var all = node.findAllTypesOrNull(arg);
var all = node.findAllTypesOrNull(arg, allTypes);
return new Object[] {t, all};
});
root.insertChildren(node);
Expand All @@ -54,7 +57,7 @@ public void typeOfUnresolvedConstructor() {
ctx(),
() -> {
var cnstr = UnresolvedConstructor.build(null, "Unknown_Name");
var arr = (Object[]) testTypesCall.call(cnstr);
var arr = (Object[]) testTypesCall.call(cnstr, true);
var type = (Type) arr[0];
var allTypes = (Type[]) arr[1];
assertEquals("Function", type.getName());
Expand All @@ -70,7 +73,7 @@ public void typeOfUnresolvedSymbol() {
ctx(),
() -> {
var cnstr = UnresolvedSymbol.build("Unknown_Name", null);
var arr = (Object[]) testTypesCall.call(cnstr);
var arr = (Object[]) testTypesCall.call(cnstr, true);
var type = (Type) arr[0];
var allTypes = (Type[]) arr[1];
assertEquals("Function", type.getName());
Expand All @@ -79,4 +82,29 @@ public void typeOfUnresolvedSymbol() {
return null;
});
}

@Test
public void multiValueWithHiddenType() {
ContextUtils.executeInContext(
ctx(),
() -> {
var ensoCtx = EnsoContext.get(testTypesCall.getRootNode());
var types =
new Type[] {
ensoCtx.getBuiltins().number().getInteger(), ensoCtx.getBuiltins().text()
};
var multi = EnsoMultiValue.create(types, 1, new Object[] {42L, "Meaning"});
var arr = (Object[]) testTypesCall.call(multi, true);
var allTypes = (Type[]) arr[1];
assertEquals("Two types", 2, allTypes.length);
assertEquals("Integer", types[0], allTypes[0]);
assertEquals("Text", types[1], allTypes[1]);

var arr1 = (Object[]) testTypesCall.call(multi, false);
var allTypes1 = (Type[]) arr1[1];
assertEquals("Just one type", 1, allTypes1.length);
assertEquals("Integer", types[0], allTypes1[0]);
return null;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,13 @@ final Object doThatConversionUncached(
InvokeFunctionNode invokeNode) {
var selfType = findType(typeOfNode, self);
if (that instanceof EnsoMultiValue multi) {
var all = typeOfNode.findAllTypesOrNull(multi);
var all = typeOfNode.findAllTypesOrNull(multi, false);
assert all != null;
for (var thatType : all) {
var fn = findSymbol(symbol, thatType);
if (fn != null) {
var thatCasted =
EnsoMultiValue.CastToNode.getUncached()
.findTypeOrNull(thatType, multi, false, false);
if (thatCasted == null) {
continue;
}
var result =
doDispatch(
frame, self, thatCasted, selfType, thatType, fn, convertNode, invokeNode);
doDispatch(frame, self, multi, selfType, thatType, fn, convertNode, invokeNode);
if (result != null) {
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.profiles.CountingConditionProfile;
import org.enso.interpreter.node.expression.builtin.meta.IsValueOfTypeNode;
import org.enso.interpreter.runtime.data.EnsoMultiValue;
import org.enso.interpreter.runtime.data.Type;

/** An implementation of the case expression specialised to working on types. */
Expand Down Expand Up @@ -33,7 +34,13 @@ public static CatchTypeBranchNode build(
}

public void execute(VirtualFrame frame, Object state, Object value) {
if (profile.profile(isValueOfTypeNode.execute(expectedType, value))) {
if (profile.profile(isValueOfTypeNode.execute(expectedType, value, true))) {
if (value instanceof EnsoMultiValue multi) {
var replacement =
EnsoMultiValue.CastToNode.getUncached().findTypeOrNull(expectedType, multi, true, true);
assert replacement != null : "Must find the type, when isValueOfTypeNode is true";
value = replacement;
}
accept(frame, state, new Object[] {value});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private Object executeCallbackOrRethrow(
AbstractTruffleException originalException,
InteropLibrary interopLibrary) {

if (profile.profile(isValueOfTypeNode.execute(panicType, payload))) {
if (profile.profile(isValueOfTypeNode.execute(panicType, payload, true))) {
var builtins = EnsoContext.get(this).getBuiltins();
var cons = builtins.caughtPanic().getUniqueConstructor();
var caughtPanic =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.oracle.truffle.api.TruffleStackTrace;
import com.oracle.truffle.api.TruffleStackTraceElement;
import com.oracle.truffle.api.nodes.Node;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import org.enso.interpreter.dsl.BuiltinMethod;
Expand Down Expand Up @@ -76,6 +77,11 @@ String printStackTrace(Throwable throwable) {
var sourceLoc = errorFrame.getLocation().getEncapsulatingSourceSection();
if (sourceLoc != null) {
var path = sourceLoc.getSource().getPath();
try {
path = new File(sourceLoc.getSource().getURI()).getPath();
} catch (IllegalArgumentException | NullPointerException ignore) {
// keep original value of path
}
var ident = (path != null) ? path : sourceLoc.getSource().getName();
var loc =
(sourceLoc.getStartLine() == sourceLoc.getEndLine())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ EqualsAndInfo equalsMultiValue(
@Shared("multiCast") @Cached EnsoMultiValue.CastToNode castNode,
@Shared("multiType") @Cached TypeOfNode typesNode,
@Shared("multiEquals") @Cached EqualsSimpleNode delegate) {
var types = typesNode.findAllTypesOrNull(self);
var types = typesNode.findAllTypesOrNull(self, false);
assert types != null;
for (var t : types) {
var value = castNode.findTypeOrNull(t, self, false, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ public class IsANode extends Node {
private @Child IsValueOfTypeNode check = IsValueOfTypeNode.build();

public boolean execute(@AcceptsError Object value, Object type) {
return check.execute(type, value);
return check.execute(type, value, true);
}
}
Loading

0 comments on commit 7998ed5

Please sign in to comment.