diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index e47567953a2a..1ec64b90cb1c 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -376,6 +376,10 @@ interface ExpressionUpdate { * - array with multiple values represents an intersetion type */ type: string[]; + /** + * The list of types this expression can be converted to. + */ + hiddenType: string[]; /** The updated method call info. */ methodCall?: MethodCall; /** Profiling information about the expression. */ diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala index e6596b450334..d1b73c18d7ce 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala @@ -200,7 +200,8 @@ final class ContextEventsListener( val computedExpressions = expressionUpdates.map { update => ContextRegistryProtocol.ExpressionUpdate( update.expressionId, - update.expressionTypes.getOrElse(Vector()), + update.expressionType.map(_.visibleType).getOrElse(Vector()), + update.expressionType.map(_.hiddenType).getOrElse(Vector()), update.methodCall.map(toProtocolMethodCall), update.profilingInfo.map(toProtocolProfilingInfo), update.fromCache, diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala index aef9df5d3167..ff2ff5e909b4 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala @@ -177,6 +177,7 @@ object ContextRegistryProtocol { * * @param expressionId the id of updated expression * @param type the updated type of expression + * @param hiddenType the list of types this expression can be converted to * @param methodCall the updated method call * @param profilingInfo profiling information about the expression * @param fromCache whether the expression's value came from the cache @@ -185,6 +186,7 @@ object ContextRegistryProtocol { case class ExpressionUpdate( expressionId: UUID, `type`: Vector[String], + hiddenType: Vector[String], methodCall: Option[MethodCall], profilingInfo: Vector[ProfilingInfo], fromCache: Boolean, diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala b/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala index b1371fd6a6d2..badd7fd5be10 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala @@ -64,7 +64,12 @@ class ContextEventsListenerSpec Set( Api.ExpressionUpdate( Suggestions.method.externalId.get, - Some(Vector(Suggestions.method.returnType)), + Some( + Api.ExpressionType( + Vector(Suggestions.method.returnType), + Vector(Suggestions.method.selfType) + ) + ), Some(methodCall), Vector(), false, @@ -87,6 +92,7 @@ class ContextEventsListenerSpec ContextRegistryProtocol.ExpressionUpdate( Suggestions.method.externalId.get, Vector(Suggestions.method.returnType), + Vector(Suggestions.method.selfType), Some(toProtocolMethodCall(methodCall)), Vector(), false, @@ -136,6 +142,7 @@ class ContextEventsListenerSpec ContextRegistryProtocol.ExpressionUpdate( Suggestions.method.externalId.get, Vector(), + Vector(), None, Vector(), false, @@ -174,6 +181,7 @@ class ContextEventsListenerSpec ContextRegistryProtocol.ExpressionUpdate( Suggestions.method.externalId.get, Vector(), + Vector(), None, Vector(), false, @@ -230,6 +238,7 @@ class ContextEventsListenerSpec ContextRegistryProtocol.ExpressionUpdate( Suggestions.method.externalId.get, Vector(), + Vector(), None, Vector(), false, @@ -239,6 +248,7 @@ class ContextEventsListenerSpec ContextRegistryProtocol.ExpressionUpdate( Suggestions.local.externalId.get, Vector(), + Vector(), None, Vector(), false, diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index cec87900947b..9f8768a77925 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -73,6 +73,16 @@ object Runtime { notAppliedArguments: Vector[Int] ) + /** The type of the expression. + * + * @param visibleType the public type of the expression visible to the user + * @param hiddenType the list of types this expression can be converted to + */ + case class ExpressionType( + visibleType: Vector[String], + hiddenType: Vector[String] + ) + /** A representation of an executable position in code. */ sealed trait StackItem @@ -108,7 +118,7 @@ object Runtime { /** An update about the computed expression. * * @param expressionId the expression id - * @param expressionTypes the type of expression + * @param expressionType the type of expression * @param methodCall the underlying method call of this expression * @param profilingInfo profiling information about the execution of this expression * @param fromCache whether the value for this expression came from the cache @@ -119,7 +129,7 @@ object Runtime { @named("expressionUpdate") case class ExpressionUpdate( expressionId: ExpressionId, - expressionTypes: Option[Vector[String]], + expressionType: Option[ExpressionType], methodCall: Option[MethodCall], profilingInfo: Vector[ProfilingInfo], fromCache: Boolean, diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java index 7787878b47ca..95e6539748f9 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java @@ -17,7 +17,7 @@ public final class RuntimeCache implements java.util.function.Function { private final Map> cache = new HashMap<>(); private final Map> expressions = new HashMap<>(); - private final Map types = new HashMap<>(); + private final Map types = new HashMap<>(); private final Map calls = new HashMap<>(); private CachePreferences preferences = CachePreferences.empty(); private Consumer observer; @@ -107,15 +107,15 @@ public Set clear(CachePreferences.Kind kind) { * @return the previously cached type. */ @CompilerDirectives.TruffleBoundary - public String[] putType(UUID key, String[] typeNames) { - return types.put(key, typeNames); + public TypeInfo putType(UUID key, TypeInfo typeInfo) { + return types.put(key, typeInfo); } /** * @return the cached type of the expression */ @CompilerDirectives.TruffleBoundary - public String[] getType(UUID key) { + public TypeInfo getType(UUID key) { return types.get(key); } diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/TypeInfo.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/TypeInfo.java new file mode 100644 index 000000000000..8cfeb793520a --- /dev/null +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/TypeInfo.java @@ -0,0 +1,28 @@ +package org.enso.interpreter.instrument; + +import java.util.Arrays; + +/** + * The type information observed by the instrumentation. + * + *

The list in the type definition represents an intersection type. A list with a single element + * represents a simple type. + * + * @param visibleType the public type of the value visible to the user + * @param hiddenType the list of types the value can be converted to + */ +public record TypeInfo(String[] visibleType, String[] hiddenType) { + + public static TypeInfo ofType(String typeName) { + return new TypeInfo(new String[] {typeName}, new String[0]); + } + + public static TypeInfo ofIntersectionType(String[] intersectionType) { + return new TypeInfo(intersectionType, new String[0]); + } + + @Override + public String toString() { + return "TypeInfo(" + Arrays.toString(visibleType) + "," + Arrays.toString(hiddenType) + ")"; + } +} diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java index 43adb215bd69..6f194a608f68 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java @@ -1,6 +1,7 @@ package org.enso.interpreter.service; import com.oracle.truffle.api.CompilerDirectives; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -9,6 +10,7 @@ import org.enso.interpreter.instrument.MethodCallsCache; import org.enso.interpreter.instrument.OneshotExpression; import org.enso.interpreter.instrument.RuntimeCache; +import org.enso.interpreter.instrument.TypeInfo; import org.enso.interpreter.instrument.UpdatesSynchronizationState; import org.enso.interpreter.instrument.VisualizationHolder; import org.enso.interpreter.instrument.profiling.ExecutionTime; @@ -98,16 +100,16 @@ public Object findCachedResult(IdExecutionService.Info info) { @Override public void updateCachedResult(IdExecutionService.Info info) { Object result = info.getResult(); - String[] resultTypes = typeOf(result); + TypeInfo resultType = typeOf(result); UUID nodeId = info.getId(); - String[] cachedTypes = cache.getType(nodeId); + TypeInfo cachedType = cache.getType(nodeId); FunctionCallInfo call = functionCallInfoById(nodeId); FunctionCallInfo cachedCall = cache.getCall(nodeId); ProfilingInfo[] profilingInfo = new ProfilingInfo[] {new ExecutionTime(info.getElapsedTime())}; ExpressionValue expressionValue = new ExpressionValue( - nodeId, result, resultTypes, cachedTypes, call, cachedCall, profilingInfo, false); + nodeId, result, resultType, cachedType, call, cachedCall, profilingInfo, false); syncState.setExpressionUnsync(nodeId); syncState.setVisualizationUnsync(nodeId); @@ -119,7 +121,7 @@ public void updateCachedResult(IdExecutionService.Info info) { cache.offer(nodeId, result); cache.putCall(nodeId, call); } - cache.putType(nodeId, resultTypes); + cache.putType(nodeId, resultType); callOnComputedCallback(expressionValue); executeOneshotExpressions(nodeId, result, info); @@ -214,19 +216,32 @@ private FunctionCallInfo functionCallInfoById(UUID nodeId) { return calls.get(nodeId); } - private String[] typeOf(Object value) { + private TypeInfo typeOf(Object value) { if (value instanceof UnresolvedSymbol) { - return new String[] {Constants.UNRESOLVED_SYMBOL}; + return TypeInfo.ofType(Constants.UNRESOLVED_SYMBOL); } - var typeOfNode = TypeOfNode.getUncached(); - 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++) { - result[i] = getTypeQualifiedName(allTypes[i]); + if (value != null) { + final TypeOfNode typeOfNode = TypeOfNode.getUncached(); + final Type[] publicTypes = typeOfNode.findAllTypesOrNull(value, false); + + if (publicTypes != null) { + final Type[] allTypes = typeOfNode.findAllTypesOrNull(value, true); + assert Arrays.equals(publicTypes, Arrays.copyOfRange(allTypes, 0, publicTypes.length)); + final Type[] hiddenTypes = + Arrays.copyOfRange(allTypes, publicTypes.length, allTypes.length); + + final String[] publicTypeNames = new String[publicTypes.length]; + for (var i = 0; i < publicTypes.length; i++) { + publicTypeNames[i] = getTypeQualifiedName(publicTypes[i]); + } + final String[] hiddenTypeNames = new String[hiddenTypes.length]; + for (var i = 0; i < hiddenTypeNames.length; i++) { + hiddenTypeNames[i] = getTypeQualifiedName(hiddenTypes[i]); + } + + return new TypeInfo(publicTypeNames, hiddenTypeNames); } - return result; } return null; diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java index e1036b5681d5..3dc436734518 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java @@ -32,6 +32,7 @@ import org.enso.interpreter.instrument.ExpressionExecutionState; import org.enso.interpreter.instrument.MethodCallsCache; import org.enso.interpreter.instrument.RuntimeCache; +import org.enso.interpreter.instrument.TypeInfo; import org.enso.interpreter.instrument.UpdatesSynchronizationState; import org.enso.interpreter.instrument.VisualizationHolder; import org.enso.interpreter.instrument.profiling.ProfilingInfo; @@ -662,8 +663,8 @@ public FunctionCallInstrumentationNode.FunctionCall getCall() { public static final class ExpressionValue { private final UUID expressionId; private final Object value; - private final String[] types; - private final String[] cachedTypes; + private final TypeInfo typeInfo; + private final TypeInfo cachedTypeInfo; private final FunctionCallInfo callInfo; private final FunctionCallInfo cachedCallInfo; private final ProfilingInfo[] profilingInfo; @@ -674,8 +675,8 @@ public static final class ExpressionValue { * * @param expressionId the id of the expression being computed. * @param value the value returned by computing the expression. - * @param types the type of the returned value. - * @param cachedTypes the cached type of the value. + * @param typeInfo the type info of the returned value. + * @param cachedTypeInfo the cached type info of the value. * @param callInfo the function call data. * @param cachedCallInfo the cached call data. * @param profilingInfo the profiling information associated with this node @@ -684,16 +685,16 @@ public static final class ExpressionValue { public ExpressionValue( UUID expressionId, Object value, - String[] types, - String[] cachedTypes, + TypeInfo typeInfo, + TypeInfo cachedTypeInfo, FunctionCallInfo callInfo, FunctionCallInfo cachedCallInfo, ProfilingInfo[] profilingInfo, boolean wasCached) { this.expressionId = expressionId; this.value = value; - this.types = types; - this.cachedTypes = cachedTypes; + this.typeInfo = typeInfo; + this.cachedTypeInfo = cachedTypeInfo; this.callInfo = callInfo; this.cachedCallInfo = cachedCallInfo; this.profilingInfo = profilingInfo; @@ -708,11 +709,11 @@ public String toString() { + expressionId + ", value=" + (value == null ? "null" : new MaskedString(value.toString()).applyMasking()) - + ", types='" - + Arrays.toString(types) + + ", typeInfo='" + + typeInfo + '\'' - + ", cachedTypes='" - + Arrays.toString(cachedTypes) + + ", cachedTypeInfo='" + + cachedTypeInfo + '\'' + ", callInfo=" + callInfo @@ -735,15 +736,15 @@ public UUID getExpressionId() { /** * @return the type of the returned value. */ - public String[] getTypes() { - return types; + public TypeInfo getType() { + return typeInfo; } /** * @return the cached type of the value. */ - public String[] getCachedTypes() { - return cachedTypes; + public TypeInfo getCachedType() { + return cachedTypeInfo; } /** @@ -785,7 +786,22 @@ public boolean wasCached() { * @return {@code true} when the type differs from the cached value. */ public boolean isTypeChanged() { - return !Arrays.equals(types, cachedTypes); + String[] visibleType = null; + String[] hiddenType = null; + if (typeInfo != null) { + visibleType = typeInfo.visibleType(); + hiddenType = typeInfo.hiddenType(); + } + + String[] cachedVisibleType = null; + String[] cachedHiddenType = null; + if (cachedTypeInfo != null) { + cachedVisibleType = cachedTypeInfo.visibleType(); + cachedHiddenType = cachedTypeInfo.hiddenType(); + } + + return !Arrays.equals(visibleType, cachedVisibleType) + || !Arrays.equals(hiddenType, cachedHiddenType); } /** diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 04857b8c9d94..c1291ce6b7dd 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -5,6 +5,7 @@ import org.enso.interpreter.instrument.{ InstrumentFrame, MethodCallsCache, RuntimeCache, + TypeInfo, UpdatesSynchronizationState, Visualization, WarningPreview @@ -414,7 +415,7 @@ object ProgramExecutionSupport { Set( Api.ExpressionUpdate( value.getExpressionId, - Option(value.getTypes).map(_.toVector), + Option(value.getType).map(toExpressionType), methodCall, value.getProfilingInfo.map { case e: ExecutionTime => Api.ProfilingInfo.ExecutionTime(e.getNanoTimeElapsed) @@ -450,7 +451,7 @@ object ProgramExecutionSupport { expressionId ) ) || - Types.isPanic(value.getTypes) + Types.isPanic(value.getType.visibleType()) ) { val payload = value.getValue match { case sentinel: PanicSentinel => @@ -562,7 +563,7 @@ object ProgramExecutionSupport { Set( Api.ExpressionUpdate( value.getExpressionId, - Option(value.getTypes).map(_.toVector), + Option(value.getType).map(toExpressionType), methodCall, value.getProfilingInfo.map { case e: ExecutionTime => Api.ProfilingInfo.ExecutionTime(e.getNanoTimeElapsed) @@ -816,8 +817,10 @@ object ProgramExecutionSupport { !value.wasCached() && !value.getValue.isInstanceOf[DataflowError] for { call <- - if (Types.isPanic(value.getTypes) || notCachedAndNotDataflowError) - Option(value.getCallInfo) + if ( + Types.isPanic(value.getType.visibleType()) || + notCachedAndNotDataflowError + ) Option(value.getCallInfo) else Option(value.getCallInfo).orElse(Option(value.getCachedCallInfo)) methodPointer <- toMethodPointer(call.functionPointer) } yield { @@ -844,6 +847,17 @@ object ProgramExecutionSupport { functionName ) + /** Extract the expression type information from the provided type info. + * + * @param typeInfo the runtime type info + * @return the appropriate expression type + */ + private def toExpressionType(typeInfo: TypeInfo): Api.ExpressionType = + Api.ExpressionType( + typeInfo.visibleType().toVector, + typeInfo.hiddenType().toVector + ) + /** Find source file path by the module name. * * @param module the module name diff --git a/engine/runtime-instrument-common/src/test/java/org/enso/interpreter/instrument/RuntimeCacheTest.java b/engine/runtime-instrument-common/src/test/java/org/enso/interpreter/instrument/RuntimeCacheTest.java index ead3e2ea802b..521afedecac7 100644 --- a/engine/runtime-instrument-common/src/test/java/org/enso/interpreter/instrument/RuntimeCacheTest.java +++ b/engine/runtime-instrument-common/src/test/java/org/enso/interpreter/instrument/RuntimeCacheTest.java @@ -1,6 +1,5 @@ package org.enso.interpreter.instrument; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -9,6 +8,7 @@ import java.lang.ref.Reference; import java.lang.ref.WeakReference; +import java.util.Arrays; import java.util.HashSet; import java.util.UUID; import org.enso.common.CachePreferences; @@ -46,10 +46,10 @@ public void removeItems() { public void cacheTypes() { var cache = new RuntimeCache(); var key = UUID.randomUUID(); - var obj = new String[] {"Number"}; + var obj = TypeInfo.ofType("Number"); assertNull(cache.putType(key, obj)); - assertArrayEquals(obj, cache.putType(key, obj)); + assertTypesEquals(obj, cache.putType(key, obj)); cache.removeType(key); assertNull(cache.putType(key, obj)); @@ -166,6 +166,11 @@ private static void assertGC(String msg, boolean expectGC, Reference ref) { } } + private static boolean assertTypesEquals(TypeInfo a, TypeInfo b) { + return Arrays.equals(a.visibleType(), b.visibleType()) + && Arrays.equals(a.hiddenType(), b.hiddenType()); + } + private static CachePreferences of(UUID key, CachePreferences.Kind value) { var preferences = CachePreferences.empty(); preferences.set(key, value); diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala index 029d60809f73..4dad8729497a 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala @@ -53,7 +53,7 @@ object TestMessages { Set( Api.ExpressionUpdate( expressionId, - Some(Vector(expressionType)), + Some(Api.ExpressionType(Vector(expressionType), Vector())), None, Vector(Api.ProfilingInfo.ExecutionTime(0)), false, @@ -120,7 +120,7 @@ object TestMessages { Set( Api.ExpressionUpdate( expressionId, - Some(expressionTypes), + Some(Api.ExpressionType(expressionTypes, Vector())), methodCall, Vector(Api.ProfilingInfo.ExecutionTime(0)), fromCache, @@ -171,7 +171,7 @@ object TestMessages { Set( Api.ExpressionUpdate( expressionId, - Some(Vector(expressionType)), + Some(Api.ExpressionType(Vector(expressionType), Vector())), Some(methodCall), Vector(Api.ProfilingInfo.ExecutionTime(0)), fromCache, @@ -282,7 +282,7 @@ object TestMessages { Set( Api.ExpressionUpdate( expressionId, - Some(Vector(ConstantsGen.ERROR)), + Some(Api.ExpressionType(Vector(ConstantsGen.ERROR), Vector())), methodCallOpt, Vector(Api.ProfilingInfo.ExecutionTime(0)), fromCache, @@ -441,7 +441,9 @@ object TestMessages { Set( Api.ExpressionUpdate( expressionId, - builtin.map(Vector(_)), + builtin.map(builtinType => + Api.ExpressionType(Vector(builtinType), Vector()) + ), methodCall, Vector(Api.ProfilingInfo.ExecutionTime(0)), false, @@ -496,7 +498,7 @@ object TestMessages { expressionIds.toSet.map { expressionId => Api.ExpressionUpdate( expressionId, - Some(Vector(ConstantsGen.PANIC)), + Some(Api.ExpressionType(Vector(ConstantsGen.PANIC), Vector())), methodCall, Vector(Api.ProfilingInfo.ExecutionTime(0)), false, diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiType.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiType.java index afe2adbd7eb0..84a82b3ba794 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiType.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiType.java @@ -12,9 +12,9 @@ import org.enso.interpreter.runtime.EnsoContext; /** - * Internal representation of {@code Type[]} that supports identity comparision with {@code ==} to + * Internal representation of {@code Type[]} that supports identity comparison with {@code ==} to * support inline caching of {@link EnsoMultiValue}. This is a separate and hidden concept from - * {@link Type} which is used thru out the Enso codebase. + * {@link Type} which is used throughout the codebase. * *

Think twice before opening this type to public! */