diff --git a/CHANGELOG.md b/CHANGELOG.md index 169f10cc8742..79a10f8d7d36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -748,6 +748,7 @@ finalizers][6335] - [Warning.get_all returns only unique warnings][6372] - [Reimplement `enso_project` as a proper builtin][6352] +- [Limit number of reported warnings per value][6577] [3227]: https://github.com/enso-org/enso/pull/3227 [3248]: https://github.com/enso-org/enso/pull/3248 @@ -857,6 +858,7 @@ [6335]: https://github.com/enso-org/enso/pull/6335 [6372]: https://github.com/enso-org/enso/pull/6372 [6352]: https://github.com/enso-org/enso/pull/6352 +[6577]: https://github.com/enso-org/enso/pull/6577 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso index 0b7d54ccea20..8032057a08df 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso @@ -56,6 +56,11 @@ type Warning get_all : Any -> Vector Warning get_all value = Vector.from_polyglot_array (get_all_array value) + ## ADVANCED + Returns `True` if the maximal number of reported warnings for a value has been reached, `False` otherwise. + limit_reached : Any -> Boolean + limit_reached value = @Builtin_Method "Warning.limit_reached" + ## PRIVATE ADVANCED diff --git a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Warnings.enso b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Warnings.enso index 3a71b3c32e8e..8bf55193145c 100644 --- a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Warnings.enso +++ b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Warnings.enso @@ -11,5 +11,8 @@ from Standard.Base import all process_to_json_text : Any -> Text process_to_json_text value = warnings = Warning.get_all value - text = warnings.map w->w.value.to_display_text - text.to_json + texts = warnings.map w->w.value.to_display_text + vec = case Warning.limit_reached value of + True -> texts + ["Warnings limit reached."] + False -> texts + vec.to_json 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 d5dafa970177..b5e58a04dc82 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 @@ -234,7 +234,7 @@ final class ContextEventsListener( payload: Api.ExpressionUpdate.Payload.Value.Warnings ): ContextRegistryProtocol.ExpressionUpdate.Payload.Value.Warnings = ContextRegistryProtocol.ExpressionUpdate.Payload.Value - .Warnings(payload.count, payload.warning) + .Warnings(payload.count, payload.warning, payload.reachedMaxCount) /** Convert the runtime profiling info to the context registry protocol * representation. 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 fd53347e9c60..6ee85baf4438 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 @@ -205,8 +205,13 @@ object ContextRegistryProtocol { * * @param count the number of attached warnings * @param value textual representation of the attached warning + * @param reachedMaxCount indicated whether maximal number of warnings has been reached */ - case class Warnings(count: Int, value: Option[String]) + case class Warnings( + count: Int, + value: Option[String], + reachedMaxCount: Boolean + ) } case class Pending(message: Option[String], progress: Option[Double]) diff --git a/engine/polyglot-api/src/main/java/org/enso/polyglot/RuntimeOptions.java b/engine/polyglot-api/src/main/java/org/enso/polyglot/RuntimeOptions.java index 6adcfc7f4e31..8485e820637e 100644 --- a/engine/polyglot-api/src/main/java/org/enso/polyglot/RuntimeOptions.java +++ b/engine/polyglot-api/src/main/java/org/enso/polyglot/RuntimeOptions.java @@ -104,6 +104,16 @@ public class RuntimeOptions { private static final OptionDescriptor ENABLE_EXECUTION_TIMER_DESCRIPTOR = OptionDescriptor.newBuilder(ENABLE_EXECUTION_TIMER_KEY, ENABLE_EXECUTION_TIMER).build(); + public static final String WARNINGS_LIMIT = optionName("warningsLimit"); + + @Option( + help = "Maximal number of warnings that can be attached to a value.", + category = OptionCategory.INTERNAL) + public static final OptionKey WARNINGS_LIMIT_KEY = new OptionKey<>(100); + + private static final OptionDescriptor WARNINGS_LIMIT_DESCRIPTOR = + OptionDescriptor.newBuilder(WARNINGS_LIMIT_KEY, WARNINGS_LIMIT).build(); + public static final OptionDescriptors OPTION_DESCRIPTORS = OptionDescriptors.create( Arrays.asList( @@ -122,7 +132,8 @@ public class RuntimeOptions { PREINITIALIZE_DESCRIPTOR, WAIT_FOR_PENDING_SERIALIZATION_JOBS_DESCRIPTOR, USE_GLOBAL_IR_CACHE_LOCATION_DESCRIPTOR, - ENABLE_EXECUTION_TIMER_DESCRIPTOR)); + ENABLE_EXECUTION_TIMER_DESCRIPTOR, + WARNINGS_LIMIT_DESCRIPTOR)); /** * Canonicalizes the option name by prefixing it with the language name. 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 47a5107f81b7..0bca713ce829 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 @@ -417,8 +417,13 @@ object Runtime { * * @param count the number of attached warnings. * @param warning textual representation of the attached warning. + * @param reachedMaxCount true when reported a maximal number of allowed warnings, false otherwise. */ - case class Warnings(count: Int, warning: Option[String]) + case class Warnings( + count: Int, + warning: Option[String], + reachedMaxCount: Boolean + ) } /** TBD diff --git a/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala b/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala index 765137213dd5..0faff393e031 100644 --- a/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala +++ b/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala @@ -28,6 +28,7 @@ class ContextFactory { * location * @param options additional options for the Context * @param executionEnvironment optional name of the execution environment to use during execution + * @param warningsLimit maximal number of warnings reported to the user * @return configured Context instance */ def create( @@ -42,6 +43,7 @@ class ContextFactory { useGlobalIrCacheLocation: Boolean = true, enableAutoParallelism: Boolean = false, executionEnvironment: Option[String] = None, + warningsLimit: Int = 100, options: java.util.Map[String, String] = java.util.Collections.emptyMap ): PolyglotContext = { executionEnvironment.foreach { name => @@ -67,6 +69,10 @@ class ContextFactory { RuntimeOptions.ENABLE_AUTO_PARALLELISM, enableAutoParallelism.toString ) + .option( + RuntimeOptions.WARNINGS_LIMIT, + warningsLimit.toString + ) .option("js.foreign-object-prototype", "true") .out(out) .in(in) diff --git a/engine/runner/src/main/scala/org/enso/runner/Main.scala b/engine/runner/src/main/scala/org/enso/runner/Main.scala index ab3783cf7b71..1efad57b73f3 100644 --- a/engine/runner/src/main/scala/org/enso/runner/Main.scala +++ b/engine/runner/src/main/scala/org/enso/runner/Main.scala @@ -75,6 +75,7 @@ object Main { private val AUTO_PARALLELISM_OPTION = "with-auto-parallelism" private val SKIP_GRAALVM_UPDATER = "skip-graalvm-updater" private val EXECUTION_ENVIRONMENT_OPTION = "execution-environment" + private val WARNINGS_LIMIT = "warnings-limit" private lazy val logger = Logger[Main.type] @@ -366,6 +367,16 @@ object Main { ) .build() + val warningsLimitOption = CliOption.builder + .longOpt(WARNINGS_LIMIT) + .hasArg(true) + .numberOfArgs(1) + .argName("limit") + .desc( + "Specifies a maximal number of reported warnings. Defaults to `100`." + ) + .build() + val options = new Options options .addOption(help) @@ -408,6 +419,7 @@ object Main { .addOption(autoParallelism) .addOption(skipGraalVMUpdater) .addOption(executionEnvironmentOption) + .addOption(warningsLimitOption) options } @@ -542,7 +554,7 @@ object Main { * @param enableIrCaches are IR caches enabled * @param inspect shall inspect option be enabled * @param dump shall graphs be sent to the IGV - * @apram executionEnvironment optional name of the execution environment to use during execution + * @param executionEnvironment optional name of the execution environment to use during execution */ private def run( path: String, @@ -554,7 +566,8 @@ object Main { enableAutoParallelism: Boolean, inspect: Boolean, dump: Boolean, - executionEnvironment: Option[String] + executionEnvironment: Option[String], + warningsLimit: Int ): Unit = { val file = new File(path) if (!file.exists) { @@ -595,6 +608,7 @@ object Main { strictErrors = true, enableAutoParallelism = enableAutoParallelism, executionEnvironment = executionEnvironment, + warningsLimit = warningsLimit, options = options ) if (projectMode) { @@ -1111,7 +1125,10 @@ object Main { line.hasOption(INSPECT_OPTION), line.hasOption(DUMP_GRAPHS_OPTION), Option(line.getOptionValue(EXECUTION_ENVIRONMENT_OPTION)) - .orElse(Some("live")) + .orElse(Some("live")), + Option(line.getOptionValue(WARNINGS_LIMIT)) + .map(Integer.parseInt(_)) + .getOrElse(100) ) } if (line.hasOption(REPL_OPTION)) { diff --git a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index 85812546b1a3..55cd1500183b 100644 --- a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -4387,7 +4387,9 @@ class RuntimeServerTest methodPointer = Some(Api.MethodPointer(moduleName, moduleName, "attach")), payload = Api.ExpressionUpdate.Payload.Value( - Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"))) + Some( + Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"), false) + ) ) ), TestMessages @@ -4400,7 +4402,7 @@ class RuntimeServerTest payload = Api.ExpressionUpdate.Payload.Value( Some( Api.ExpressionUpdate.Payload.Value - .Warnings(1, Some("(My_Warning.Value 42)")) + .Warnings(1, Some("(My_Warning.Value 42)"), false) ) ) ), @@ -4412,7 +4414,9 @@ class RuntimeServerTest methodPointer = Some(Api.MethodPointer(moduleName, moduleName, "attach")), payload = Api.ExpressionUpdate.Payload - .Value(Some(Api.ExpressionUpdate.Payload.Value.Warnings(2, None))) + .Value( + Some(Api.ExpressionUpdate.Payload.Value.Warnings(2, None, false)) + ) ), context.executionComplete(contextId) ) @@ -4473,7 +4477,9 @@ class RuntimeServerTest idX, ConstantsGen.INTEGER, payload = Api.ExpressionUpdate.Payload.Value( - Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"))) + Some( + Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"), false) + ) ) ), TestMessages @@ -4482,7 +4488,9 @@ class RuntimeServerTest idY, ConstantsGen.INTEGER, payload = Api.ExpressionUpdate.Payload.Value( - Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"))) + Some( + Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"), false) + ) ) ), TestMessages @@ -4491,7 +4499,9 @@ class RuntimeServerTest idRes, ConstantsGen.NOTHING, payload = Api.ExpressionUpdate.Payload.Value( - Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"))) + Some( + Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"), false) + ) ) ), context.executionComplete(contextId) diff --git a/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala b/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala index bc574d488026..a4b554577b98 100644 --- a/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala +++ b/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala @@ -2874,7 +2874,9 @@ class RuntimeVisualizationsTest idMain, ConstantsGen.INTEGER, payload = Api.ExpressionUpdate.Payload.Value( - Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"))) + Some( + Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"), false) + ) ) ), context.executionComplete(contextId) @@ -3074,7 +3076,9 @@ class RuntimeVisualizationsTest ) ), payload = Api.ExpressionUpdate.Payload.Value( - Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'x'"))) + Some( + Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'x'"), false) + ) ) ), TestMessages.update( @@ -3082,7 +3086,9 @@ class RuntimeVisualizationsTest idRes, s"$moduleName.Newtype", payload = Api.ExpressionUpdate.Payload.Value( - Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'x'"))) + Some( + Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'x'"), false) + ) ), methodPointer = Some( Api.MethodPointer(moduleName, s"$moduleName.Newtype", "Mk_Newtype") diff --git a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/WarningBenchmarks.java b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/WarningBenchmarks.java index 58e42fb2d6d1..b234d7f370b7 100644 --- a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/WarningBenchmarks.java +++ b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/WarningBenchmarks.java @@ -18,7 +18,10 @@ import org.openjdk.jmh.infra.BenchmarkParams; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; +import java.util.Random; import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.AverageTime) @@ -28,27 +31,49 @@ @OutputTimeUnit(TimeUnit.MILLISECONDS) @State(Scope.Benchmark) public class WarningBenchmarks extends TestBase { - private static final int INPUT_VEC_SIZE = 10000; + private static final int INPUT_VEC_SIZE = 10_000; + private static final int INPUT_DIFF_VEC_SIZE = 10_000; private Context ctx; private Value vecSumBench; private Value createVec; + private Value mapVecWithWarnings; private Value noWarningsVec; private Value sameWarningVec; + private Value randomVec; + private Value randomElemsWithWarningsVec; + private Value constElem; + private Value constElemWithWarning; - private Value elem; + private String benchmarkName; - private Value elemWithWarning; + private int randomVectorSum = 0; - private String benchmarkName; + private record GeneratedVector(StringBuilder repr, int sum) {} + + private GeneratedVector generateRandomVector(Random random, String vectorName, long vectorSize, int maxRange) { + List primitiveValues = new ArrayList<>(); + random.ints(vectorSize, 0, maxRange).forEach(primitiveValues::add); + var sb = new StringBuilder(); + sb.append(vectorName).append(" = ["); + var sum = 0; + for (Integer intValue : primitiveValues) { + sb.append(intValue).append(","); + sum += Math.abs(intValue); + } + sb.setCharAt(sb.length() - 1, ']'); + sb.append('\n'); + return new GeneratedVector(sb, sum); + } @Setup public void initializeBench(BenchmarkParams params) throws IOException { ctx = createDefaultContext(); + var random = new Random(42); benchmarkName = SrcUtil.findName(params); - var code = """ + var code = new StringBuilder(""" from Standard.Base import all vec_sum_bench : Vector Integer -> Integer @@ -61,18 +86,34 @@ public void initializeBench(BenchmarkParams params) throws IOException { elem = 42 - elem_with_warning = + elem_const_with_warning = x = 42 Warning.attach "Foo!" x - """; - var src = SrcUtil.source(benchmarkName, code); + + elem_with_warning v = + Warning.attach "Foo!" v + + map_vector_with_warnings vec = + vec.map (e-> elem_with_warning e) + """); + + // generate random vector + var randomIntVectorName = "vector_with_random_values"; + var vectorWithRandomValues = generateRandomVector(random, randomIntVectorName, INPUT_DIFF_VEC_SIZE, 3_000); + code.append(vectorWithRandomValues.repr()); + randomVectorSum = vectorWithRandomValues.sum(); + + var src = SrcUtil.source(benchmarkName, code.toString()); Value module = ctx.eval(src); vecSumBench = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "vec_sum_bench")); createVec = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create_vec")); - elem = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "elem")); - elemWithWarning = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "elem_with_warning")); - noWarningsVec = createVec.execute(INPUT_VEC_SIZE, elem); - sameWarningVec = createVec.execute(INPUT_VEC_SIZE, elemWithWarning); + mapVecWithWarnings = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "map_vector_with_warnings")); + constElem = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "elem")); + constElemWithWarning = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "elem_const_with_warning")); + noWarningsVec = createVec.execute(INPUT_VEC_SIZE, constElem); + sameWarningVec = createVec.execute(INPUT_VEC_SIZE, constElemWithWarning); + randomVec = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, randomIntVectorName)); + randomElemsWithWarningsVec = mapVecWithWarnings.execute(randomVec); } @TearDown @@ -83,17 +124,30 @@ public void cleanup() { @Benchmark public void noWarningsVecSum() { Value res = vecSumBench.execute(noWarningsVec); - checkResult(res); + checkResult(res, INPUT_VEC_SIZE*42); } @Benchmark public void sameWarningVecSum() { Value res = vecSumBench.execute(sameWarningVec); - checkResult(res); + checkResult(res, INPUT_VEC_SIZE*42); + } + + @Benchmark + public void randomElementsVecSum() { + Value res = vecSumBench.execute(randomVec); + checkResult(res, randomVectorSum); } - private static void checkResult(Value res) { - if (res.asInt() != INPUT_VEC_SIZE*42) { + @Benchmark + public void diffWarningRandomElementsVecSum() { + Value res = vecSumBench.execute(randomElemsWithWarningsVec); + checkResult(res, randomVectorSum); + } + + + private static void checkResult(Value res, int expected) { + if (res.asInt() != expected) { throw new AssertionError("Expected result: " + INPUT_VEC_SIZE*42 + ", got: " + res.asInt()); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java index 969d41d4db74..798dc5afd453 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java @@ -87,7 +87,7 @@ Object invokeWithWarnings( isTail); Warning[] extracted = warnings.getWarnings(warning, null); - return WithWarnings.wrap(result, extracted); + return WithWarnings.wrap(EnsoContext.get(this), result, extracted); } catch (UnsupportedMessageException e) { throw CompilerDirectives.shouldNotReachHere(e); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeConversionNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeConversionNode.java index d497adeee9d8..7029749f7829 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeConversionNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeConversionNode.java @@ -156,7 +156,7 @@ Object doWarning( argumentsExecutionMode, isTail, thatArgumentPosition); - return WithWarnings.appendTo(result, warnings); + return WithWarnings.appendTo(EnsoContext.get(this), result, warnings); } @Specialization(guards = "interop.isString(that)") diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java index 5083da87b578..a4fabaf4b64d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java @@ -134,7 +134,7 @@ Object doWarning( argumentsExecutionMode, isTail, thisArgumentPosition); - return WithWarnings.appendTo(result, warnings); + return WithWarnings.appendTo(EnsoContext.get(this), result, warnings); } @Specialization diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java index aa6c951380d5..4b7d95504b0b 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java @@ -297,9 +297,9 @@ public Object invokeWarnings( if (result instanceof DataflowError) { return result; } else if (result instanceof WithWarnings withWarnings) { - return withWarnings.append(extracted); + return withWarnings.append(EnsoContext.get(this), extracted); } else { - return WithWarnings.wrap(result, extracted); + return WithWarnings.wrap(EnsoContext.get(this), result, extracted); } } catch (UnsupportedMessageException e) { throw CompilerDirectives.shouldNotReachHere(e); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeConversionNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeConversionNode.java index 3df215b17fb4..e0ddccf44f09 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeConversionNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeConversionNode.java @@ -175,7 +175,7 @@ Object doWarning( ArrayRope warnings = that.getReassignedWarningsAsRope(this); Object result = childDispatch.execute(frame, state, conversion, self, that.getValue(), arguments); - return WithWarnings.appendTo(result, warnings); + return WithWarnings.appendTo(EnsoContext.get(this), result, warnings); } @Specialization(guards = "interop.isString(that)") diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java index 79b74ab577d1..7949001e7852 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java @@ -340,7 +340,7 @@ Object doWarning( arguments[thisArgumentPosition] = selfWithoutWarnings; Object result = childDispatch.execute(frame, state, symbol, selfWithoutWarnings, arguments); - return WithWarnings.appendTo(result, arrOfWarnings); + return WithWarnings.appendTo(EnsoContext.get(this), result, arrOfWarnings); } @ExplodeLoop @@ -392,7 +392,7 @@ Object doPolyglot( Object res = hostMethodCallNode.execute(polyglotCallType, symbol.getName(), self, args); if (anyWarnings) { anyWarningsProfile.enter(); - res = WithWarnings.appendTo(res, accumulatedWarnings); + res = WithWarnings.appendTo(EnsoContext.get(this), res, accumulatedWarnings); } return res; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java index bd308bba84e4..6e1cd3042709 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java @@ -81,9 +81,10 @@ public Object doPanicSentinel(VirtualFrame frame, PanicSentinel sentinel) { Object doWarning( VirtualFrame frame, Object object, @CachedLibrary(limit = "3") WarningsLibrary warnings) { try { + EnsoContext ctx = EnsoContext.get(this); Warning[] ws = warnings.getWarnings(object, this); Object result = doMatch(frame, warnings.removeWarnings(object), warnings); - return WithWarnings.wrap(result, ws); + return WithWarnings.wrap(ctx, result, ws); } catch (UnsupportedMessageException e) { throw new IllegalStateException(e); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java index 19b87eb1eeb7..c3562d4878cd 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java @@ -10,6 +10,7 @@ import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.api.profiles.ConditionProfile; import org.enso.interpreter.node.ExpressionNode; +import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.atom.unboxing.Layout; import org.enso.interpreter.runtime.data.ArrayRope; @@ -94,7 +95,8 @@ Object doExecute( } } if (anyWarningsProfile.profile(anyWarnings)) { - return WithWarnings.appendTo(createInstanceNode.execute(argumentValues), accumulatedWarnings); + return WithWarnings.appendTo( + EnsoContext.get(this), createInstanceNode.execute(argumentValues), accumulatedWarnings); } else { return createInstanceNode.execute(argumentValues); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/SortVectorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/SortVectorNode.java index 49dfbe35c76b..cfbca3b41c6b 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/SortVectorNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/SortVectorNode.java @@ -52,6 +52,8 @@ @GenerateUncached public abstract class SortVectorNode extends Node { + private static final int MAX_SORT_WARNINGS = 10; + public static SortVectorNode build() { return SortVectorNodeGen.create(); } @@ -336,12 +338,14 @@ private List splitByComparators( } private Object attachWarnings(Object vector, Set warnings) { + var ctx = EnsoContext.get(this); var warnArray = warnings.stream() .map(Text::create) - .map(text -> Warning.create(EnsoContext.get(this), text, this)) + .map(text -> Warning.create(ctx, text, this)) + .limit(MAX_SORT_WARNINGS) .toArray(Warning[]::new); - return WithWarnings.appendTo(vector, new ArrayRope<>(warnArray)); + return WithWarnings.appendTo(ctx, vector, warnArray.length < warnings.size(), warnArray); } private Object attachDifferentComparatorsWarning(Object vector, List groups) { @@ -351,8 +355,9 @@ private Object attachDifferentComparatorsWarning(Object vector, List grou .map(comparator -> comparator.getQualifiedName().toString()) .collect(Collectors.joining(", ")); var text = Text.create("Different comparators: [" + diffCompsMsg + "]"); - var warn = Warning.create(EnsoContext.get(this), text, this); - return WithWarnings.appendTo(vector, new ArrayRope<>(warn)); + var ctx = EnsoContext.get(this); + var warn = Warning.create(ctx, text, this); + return WithWarnings.appendTo(ctx, vector, false, warn); } private String getDefaultComparatorQualifiedName() { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/CoerceNothing.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/CoerceNothing.java index e0809f4bc1c0..d41147e24dd2 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/CoerceNothing.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/CoerceNothing.java @@ -35,11 +35,12 @@ public Object doNothing( @CachedLibrary(limit = "1") InteropLibrary interop, @CachedLibrary(limit = "3") WarningsLibrary warningsLibrary, @Cached("createCountingProfile()") ConditionProfile nullWarningProfile) { - var nothing = EnsoContext.get(this).getBuiltins().nothing(); + var ctx = EnsoContext.get(this); + var nothing = ctx.getBuiltins().nothing(); if (nullWarningProfile.profile(warningsLibrary.hasWarnings(value))) { try { Warning[] attachedWarnings = warningsLibrary.getWarnings(value, null); - return WithWarnings.wrap(nothing, attachedWarnings); + return WithWarnings.wrap(ctx, nothing, attachedWarnings); } catch (UnsupportedMessageException e) { return nothing; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index 1cff5992521e..98063be66bec 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -82,6 +82,8 @@ public class EnsoContext { private final Shape rootStateShape = Shape.newBuilder().layout(State.Container.class).build(); private ExecutionEnvironment executionEnvironment; + private final int warningsLimit; + /** * Creates a new Enso context. * @@ -127,6 +129,7 @@ public EnsoContext( this.notificationHandler = notificationHandler; this.lockManager = lockManager; this.distributionManager = distributionManager; + this.warningsLimit = getOption(RuntimeOptions.WARNINGS_LIMIT_KEY); } /** Perform expensive initialization logic for the context. */ @@ -517,6 +520,11 @@ public void setExecutionEnvironment(ExecutionEnvironment executionEnvironment) { this.executionEnvironment = executionEnvironment; } + /** Returns a maximal number of warnings that can be attached to a value */ + public int getWarningsLimit() { + return this.warningsLimit; + } + public Shape getRootStateShape() { return rootStateShape; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java index 6417441ddc3d..019c1ff6a0de 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java @@ -19,6 +19,7 @@ import java.util.Arrays; import org.enso.interpreter.runtime.error.WithWarnings; +import org.enso.polyglot.RuntimeOptions; import org.graalvm.collections.EconomicSet; /** A primitive boxed array type for use in the runtime. */ @@ -114,7 +115,7 @@ public Object readArrayElement( if (warnings.hasWarnings(v)) { v = warnings.removeWarnings(v); } - return WithWarnings.wrap(v, extracted); + return WithWarnings.wrap(EnsoContext.get(warnings), v, extracted); } return v; } @@ -237,6 +238,16 @@ Array removeWarnings(@CachedLibrary(limit = "3") WarningsLibrary warnings) return new Array(items); } + @ExportMessage + boolean isLimitReached(@CachedLibrary(limit = "3") WarningsLibrary warnings) { + try { + int limit = EnsoContext.get(warnings).getWarningsLimit(); + return getWarnings(null, warnings).length >= limit; + } catch (UnsupportedMessageException e) { + return false; + } + } + @ExportMessage Type getType(@CachedLibrary("this") TypesLibrary thisLib) { return EnsoContext.get(thisLib).getBuiltins().array(); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ArraySlice.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ArraySlice.java index cca1a46d2933..eedb6132dd1e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ArraySlice.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ArraySlice.java @@ -11,6 +11,7 @@ import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; +import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.error.Warning; import org.enso.interpreter.runtime.error.WarningsLibrary; import org.enso.interpreter.runtime.error.WithWarnings; @@ -95,7 +96,7 @@ public Object readArrayElement( if (warnings.hasWarnings(v)) { v = warnings.removeWarnings(v); } - return WithWarnings.wrap(toEnso.execute(v), extracted); + return WithWarnings.wrap(EnsoContext.get(warnings), toEnso.execute(v), extracted); } return toEnso.execute(v); } @@ -157,4 +158,9 @@ Object removeWarnings(@CachedLibrary(limit = "3") WarningsLibrary warnings) thro return new ArraySlice(newStorage, start, end); } + @ExportMessage + boolean isLimitReached(@CachedLibrary(limit = "3") WarningsLibrary warnings) { + return warnings.isLimitReached(this.storage); + } + } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Vector.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Vector.java index 494651cf8ebe..31c282702bc1 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Vector.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Vector.java @@ -123,7 +123,7 @@ public Object readArrayElement( if (warnings.hasWarnings(v)) { v = warnings.removeWarnings(v); } - return WithWarnings.wrap(toEnso.execute(v), extracted); + return WithWarnings.wrap(EnsoContext.get(interop), toEnso.execute(v), extracted); } return toEnso.execute(v); } @@ -222,6 +222,11 @@ Vector removeWarnings(@CachedLibrary(limit = "3") WarningsLibrary warnings) return new Vector(warnings.removeWarnings(this.storage)); } + @ExportMessage + boolean isLimitReached(@CachedLibrary(limit = "3") WarningsLibrary warnings) { + return warnings.isLimitReached(this.storage); + } + // // helper methods // diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/Warning.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/Warning.java index 41716e1cbb33..f1dfaf15de7f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/Warning.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/Warning.java @@ -74,7 +74,7 @@ public Array getReassignments() { @Builtin.Specialize public static WithWarnings attach( EnsoContext ctx, WithWarnings value, Object warning, Object origin) { - return value.append(new Warning(warning, origin, ctx.nextSequenceId())); + return value.append(ctx, new Warning(warning, origin, ctx.nextSequenceId())); } @Builtin.Method( @@ -83,7 +83,7 @@ public static WithWarnings attach( autoRegister = false) @Builtin.Specialize(fallback = true) public static WithWarnings attach(EnsoContext ctx, Object value, Object warning, Object origin) { - return WithWarnings.wrap(value, new Warning(warning, origin, ctx.nextSequenceId())); + return WithWarnings.wrap(ctx, value, new Warning(warning, origin, ctx.nextSequenceId())); } @Builtin.Method( @@ -117,6 +117,24 @@ public static Array getAll(Object value, WarningsLibrary warnings) { } } + @Builtin.Method( + description = + "Returns `true` if the maximal number of warnings has been reached, `false` otherwise.", + autoRegister = false) + @Builtin.Specialize + public static boolean limitReached(WithWarnings value, WarningsLibrary warnings) { + return value.isLimitReached(); + } + + @Builtin.Method( + description = + "Returns `true` if the maximal number of warnings has been reached, `false` otherwise.", + autoRegister = false) + @Builtin.Specialize(fallback = true) + public static boolean limitReached(Object value, WarningsLibrary warnings) { + return warnings.hasWarnings(value) ? warnings.isLimitReached(value) : false; + } + @CompilerDirectives.TruffleBoundary private static void sortArray(Warning[] arr) { Arrays.sort(arr, Comparator.comparing(Warning::getSequenceId).reversed()); @@ -133,8 +151,9 @@ public static Warning[] fromSetToArray(EconomicSet set) { description = "Sets all the warnings associated with the value.", autoRegister = false) @Builtin.Specialize - public static Object set(WithWarnings value, Object warnings, InteropLibrary interop) { - return setGeneric(value.getValue(), interop, warnings); + public static Object set( + EnsoContext ctx, WithWarnings value, Object warnings, InteropLibrary interop) { + return setGeneric(ctx, value.getValue(), interop, warnings); } @Builtin.Method( @@ -142,11 +161,12 @@ public static Object set(WithWarnings value, Object warnings, InteropLibrary int description = "Sets all the warnings associated with the value.", autoRegister = false) @Builtin.Specialize(fallback = true) - public static Object set(Object value, Object warnings, InteropLibrary interop) { - return setGeneric(value, interop, warnings); + public static Object set(EnsoContext ctx, Object value, Object warnings, InteropLibrary interop) { + return setGeneric(ctx, value, interop, warnings); } - private static Object setGeneric(Object value, InteropLibrary interop, Object warnings) { + private static Object setGeneric( + EnsoContext ctx, Object value, InteropLibrary interop, Object warnings) { try { var size = interop.getArraySize(warnings); if (size == 0) { @@ -156,7 +176,7 @@ private static Object setGeneric(Object value, InteropLibrary interop, Object wa for (int i = 0; i < warningsCast.length; i++) { warningsCast[i] = (Warning) interop.readArrayElement(warnings, i); } - return WithWarnings.wrap(value, warningsCast); + return WithWarnings.wrap(ctx, value, warningsCast); } catch (UnsupportedMessageException | InvalidArrayIndexException ex) { CompilerDirectives.transferToInterpreter(); throw new IllegalStateException(ex); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WarningsLibrary.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WarningsLibrary.java index 31fc820d917d..13e03e187c2f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WarningsLibrary.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WarningsLibrary.java @@ -64,4 +64,15 @@ public Warning[] getWarnings(Object receiver, Node location) throws UnsupportedM public Object removeWarnings(Object receiver) throws UnsupportedMessageException { throw UnsupportedMessageException.create(); } + + /** + * Checks if the receiver reached a maximal number of warnings that could be reported. + * + * @param receiver the receiver to analyze + * @return whether the receiver reached a maximal number of warnings + */ + @GenerateLibrary.Abstract(ifExported = {"hasWarnings"}) + public boolean isLimitReached(Object receiver) { + return false; + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WithWarnings.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WithWarnings.java index d80981d4058a..7504c188bb53 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WithWarnings.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WithWarnings.java @@ -1,6 +1,7 @@ package org.enso.interpreter.runtime.error; import com.oracle.truffle.api.CompilerDirectives; +import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.data.ArrayRope; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; @@ -12,35 +13,69 @@ import com.oracle.truffle.api.library.Message; import com.oracle.truffle.api.library.ReflectionLibrary; import com.oracle.truffle.api.nodes.Node; +import org.enso.polyglot.RuntimeOptions; import org.graalvm.collections.EconomicSet; import org.graalvm.collections.Equivalence; -import java.util.Arrays; - @ExportLibrary(TypesLibrary.class) @ExportLibrary(WarningsLibrary.class) @ExportLibrary(ReflectionLibrary.class) public final class WithWarnings implements TruffleObject { + private final EconomicSet warnings; private final Object value; - private WithWarnings(Object value, Warning... warnings) { + private final boolean limitReached; + private final int maxWarnings; + + /** + * Creates a new instance of value wrapped in warnings. `limitReached` parameter allows for indicating if some + * custom warnings filtering on `warnings` have already been performed. + * + * @param value value to be wrapped in warnings + * @param maxWarnings maximal number of warnings allowed to be attached to the value + * @param limitReached if `true`, indicates that `warnings` have already been limited for a custom-method, `false` otherwise + * @param warnings warnings to be attached to a value + */ + private WithWarnings(Object value, int maxWarnings, boolean limitReached, Warning... warnings) { assert !(value instanceof WithWarnings); - this.warnings = createSetFromArray(warnings); + this.warnings = createSetFromArray(maxWarnings, warnings); this.value = value; + this.limitReached = limitReached || this.warnings.size() >= maxWarnings; + this.maxWarnings = maxWarnings; + } + private WithWarnings(Object value, int maxWarnings, Warning... warnings) { + this(value, maxWarnings, false, warnings); } - private WithWarnings(Object value, EconomicSet warnings, Warning... additionalWarnings) { + + /** + * Creates a new instance of value wrapped in warnings. `limitReached` parameter allows for indicating if some + * custom warnings filtering on `additionalWarnings` have already been performed. + * + * @param value value to be wrapped in warnings + * @param maxWarnings maximal number of warnings allowed to be attached to the value + * @param warnings warnings originally attached to a value + * @param limitReached if `true`, indicates that `warnings` have already been limited for a custom-method, `false` otherwise + * @param additionalWarnings additional warnings to be appended to the list of `warnings` + */ + private WithWarnings(Object value, int maxWarnings, EconomicSet warnings, boolean limitReached, Warning... additionalWarnings) { assert !(value instanceof WithWarnings); - this.warnings = cloneSetAndAppend(warnings, additionalWarnings); + this.warnings = cloneSetAndAppend(maxWarnings, warnings, additionalWarnings); this.value = value; + this.limitReached = limitReached || this.warnings.size() >= maxWarnings; + this.maxWarnings = maxWarnings; + } + + private WithWarnings(Object value, int maxWarnings, EconomicSet warnings, Warning... additionalWarnings) { + this(value, maxWarnings, warnings, false, additionalWarnings); } - public static WithWarnings wrap(Object value, Warning... warnings) { + public static WithWarnings wrap(EnsoContext ctx, Object value, Warning... warnings) { if (value instanceof WithWarnings with) { - return with.append(warnings); + return with.append(ctx, warnings); } else { - return new WithWarnings(value, warnings); + return new WithWarnings(value, ctx.getWarningsLimit(), warnings); } } @@ -48,12 +83,16 @@ public Object getValue() { return value; } - public WithWarnings append(Warning... newWarnings) { - return new WithWarnings(value, warnings, newWarnings); + public WithWarnings append(EnsoContext ctx, boolean limitReached, Warning... newWarnings) { + return new WithWarnings(value, ctx.getWarningsLimit(), warnings, limitReached, newWarnings); } - public WithWarnings append(ArrayRope newWarnings) { - return new WithWarnings(value, warnings, newWarnings.toArray(Warning[]::new)); + public WithWarnings append(EnsoContext ctx, Warning... newWarnings) { + return new WithWarnings(value, ctx.getWarningsLimit(), warnings, newWarnings); + } + + public WithWarnings append(EnsoContext ctx, ArrayRope newWarnings) { + return new WithWarnings(value, ctx.getWarningsLimit(), warnings, newWarnings.toArray(Warning[]::new)); } public Warning[] getWarningsArray(WarningsLibrary warningsLibrary) { @@ -61,7 +100,7 @@ public Warning[] getWarningsArray(WarningsLibrary warningsLibrary) { if (warningsLibrary != null && warningsLibrary.hasWarnings(value)) { try { Warning[] valueWarnings = warningsLibrary.getWarnings(value, null); - EconomicSet tmp = cloneSetAndAppend(warnings, valueWarnings); + EconomicSet tmp = cloneSetAndAppend(maxWarnings, warnings, valueWarnings); allWarnings = Warning.fromSetToArray(tmp); } catch (UnsupportedMessageException e) { throw new IllegalStateException(e); @@ -89,19 +128,23 @@ public Warning[] getReassignedWarnings(Node location, WarningsLibrary warningsLi return warnings; } - public static WithWarnings appendTo(Object target, ArrayRope warnings) { + public static WithWarnings appendTo(EnsoContext ctx, Object target, ArrayRope warnings) { if (target instanceof WithWarnings) { - return ((WithWarnings) target).append(warnings.toArray(Warning[]::new)); + return ((WithWarnings) target).append(ctx, warnings.toArray(Warning[]::new)); } else { - return new WithWarnings(target, warnings.toArray(Warning[]::new)); + return new WithWarnings(target, ctx.getWarningsLimit(), warnings.toArray(Warning[]::new)); } } - public static WithWarnings appendTo(Object target, Warning[] warnings) { + public static WithWarnings appendTo(EnsoContext ctx, Object target, Warning... warnings) { + return appendTo(ctx, target, false, warnings); + } + + public static WithWarnings appendTo(EnsoContext ctx, Object target, boolean reachedMaxCount, Warning... warnings) { if (target instanceof WithWarnings) { - return ((WithWarnings) target).append(warnings); + return ((WithWarnings) target).append(ctx, reachedMaxCount, warnings); } else { - return new WithWarnings(target, warnings); + return new WithWarnings(target, ctx.getWarningsLimit(), reachedMaxCount, warnings); } } @@ -136,6 +179,11 @@ Object removeWarnings(@CachedLibrary(limit = "3") WarningsLibrary warnings) } } + @ExportMessage + public boolean isLimitReached() { + return limitReached; + } + @ExportMessage boolean hasSpecialDispatch() { return true; @@ -158,22 +206,42 @@ public int hashCode(Object o) { } @CompilerDirectives.TruffleBoundary - private EconomicSet createSetFromArray(Warning[] entries) { + private EconomicSet createSetFromArray(int maxWarnings, Warning[] entries) { EconomicSet set = EconomicSet.create(new WarningEquivalence()); - set.addAll(Arrays.stream(entries).iterator()); + for (int i=0; i cloneSetAndAppend(int maxWarnings, EconomicSet initial, Warning[] entries) { + return initial.size() == maxWarnings ? initial : cloneSetAndAppendSlow(maxWarnings, initial, entries); + } + @CompilerDirectives.TruffleBoundary - private EconomicSet cloneSetAndAppend(EconomicSet initial, Warning[] entries) { + private EconomicSet cloneSetAndAppendSlow(int maxWarnings, EconomicSet initial, Warning[] entries) { EconomicSet set = EconomicSet.create(new WarningEquivalence()); - set.addAll(initial.iterator()); - set.addAll(Arrays.stream(entries).iterator()); + for (Warning warning: initial) { + if (set.size() == maxWarnings) { + return set; + } + set.add(warning); + } + for (int i=0; i diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/WarningsTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/WarningsTest.java index 32a267a77b3e..ac88db1ad685 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/WarningsTest.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/WarningsTest.java @@ -39,8 +39,8 @@ public void doubleWithWarningsWrap() { var warn2 = Warning.create(ensoContext, "w2", this); var value = 42; - var with1 = WithWarnings.wrap(42, warn1); - var with2 = WithWarnings.wrap(with1, warn2); + var with1 = WithWarnings.wrap(ensoContext, 42, warn1); + var with2 = WithWarnings.wrap(ensoContext, with1, warn2); assertEquals(value, with1.getValue()); assertEquals(value, with2.getValue()); diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java index 1a298dbcf0a6..60b25b6e9d57 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java @@ -295,7 +295,8 @@ private void generateCode(MethodDefinition methodDefinition) throws IOException out.println(" if (anyWarnings) {"); out.println(" internals.anyWarningsProfile.enter();"); out.println(" Object result = " + executeCall + ";"); - out.println(" return WithWarnings.appendTo(result, gatheredWarnings);"); + out.println(" EnsoContext ctx = EnsoContext.get(bodyNode);"); + out.println(" return WithWarnings.appendTo(ctx, result, gatheredWarnings);"); out.println(" } else {"); out.println(" return " + executeCall + ";"); out.println(" }"); diff --git a/test/Tests/src/Data/Vector_Spec.enso b/test/Tests/src/Data/Vector_Spec.enso index 8ba859377564..586ec76064ab 100644 --- a/test/Tests/src/Data/Vector_Spec.enso +++ b/test/Tests/src/Data/Vector_Spec.enso @@ -696,6 +696,22 @@ type_spec name alter = Test.group name <| expected = "abet".utf_8 input.sort . should_equal expected + Test.specify "should report only a limited number of warnings for incomparable values" <| + gen x = case (x % 10) of + 0 -> Nothing + 1 -> "foo"+x.to_text + 2 -> x + 3 -> Number.nan + 4 -> Date.new + 5 -> [] + 6 -> -x + 7 -> Number.nan + 8 -> Time_Of_Day.new + _ -> x + input = 0.up_to 500 . map gen + sorted = input.sort on_incomparable=Problem_Behavior.Report_Warning + Warning.get_all sorted . length . should_equal 10 + Warning.limit_reached sorted . should_equal True spec = Test.group "Vector/Array equality" <| diff --git a/test/Tests/src/Semantic/Warnings_Spec.enso b/test/Tests/src/Semantic/Warnings_Spec.enso index 2d399139c5b6..252bc9b3b94e 100644 --- a/test/Tests/src/Semantic/Warnings_Spec.enso +++ b/test/Tests/src/Semantic/Warnings_Spec.enso @@ -408,4 +408,15 @@ spec = Test.group "Dataflow Warnings" <| result_4 = f a 1 + f a 2 + f a 3 Warning.get_all result_4 . map (x-> x.value.to_text) . should_equal ["Baz!", "Baz!", "Baz!"] + Test.specify "should only report the first 100 unique warnings" <| + vec = (0.up_to 500).map(e -> Warning.attach "Foo!" e) + vec_plus_1 = vec.map(e -> e+1) + Warning.get_all vec_plus_1 . length . should_equal 100 + Warning.limit_reached vec . should_equal True + + warn = Warning.attach "Boo!" 42 + vec_2 = (0.up_to 500).map(e -> if (e < 30) then Warning.attach "Foo!" e else (warn + e)) + Warning.get_all vec_2 . length . should_equal 31 + Warning.limit_reached vec_2 . should_equal False + main = Test_Suite.run_main spec