From cf3624c4637a78aad79f492f9215ac78daf4f798 Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Wed, 10 May 2023 13:48:31 +0200 Subject: [PATCH] Limit the number of reported warnings (#6577) Artifically limiting the number of reported warnings to 100. Also added benchmarks with random Ints to investigate perf issues when dealing with warnings (future task). Ideally we would have a custom set-like collection that allows us internally to specify a maximal number of elements. But `EnsoHashMap` (and potentially `EnsoSet`) are still WIP when it comes to being PE-friendly. The change also allows for checking if the limit for the number of reported warnings has been reached. It will visualize by adding an additional "Warnings limit reached." to the visualization. The limit is configurable via `--warnings-limit` parameter to `run`. Closes #6283. --- CHANGELOG.md | 2 + .../Standard/Base/0.0.0-dev/src/Warning.enso | 5 + .../Visualization/0.0.0-dev/src/Warnings.enso | 7 +- .../runtime/ContextEventsListener.scala | 2 +- .../runtime/ContextRegistryProtocol.scala | 7 +- .../org/enso/polyglot/RuntimeOptions.java | 13 +- .../org/enso/polyglot/runtime/Runtime.scala | 7 +- .../org/enso/runner/ContextFactory.scala | 6 + .../src/main/scala/org/enso/runner/Main.scala | 23 +++- .../test/instrument/RuntimeServerTest.scala | 22 +++- .../RuntimeVisualizationsTest.scala | 12 +- .../semantic/WarningBenchmarks.java | 86 ++++++++++--- .../callable/IndirectInvokeCallableNode.java | 2 +- .../IndirectInvokeConversionNode.java | 2 +- .../callable/IndirectInvokeMethodNode.java | 2 +- .../node/callable/InvokeCallableNode.java | 4 +- .../node/callable/InvokeConversionNode.java | 2 +- .../node/callable/InvokeMethodNode.java | 4 +- .../node/controlflow/caseexpr/CaseNode.java | 3 +- .../node/expression/atom/InstantiateNode.java | 4 +- .../builtin/ordering/SortVectorNode.java | 13 +- .../expression/foreign/CoerceNothing.java | 5 +- .../enso/interpreter/runtime/EnsoContext.java | 8 ++ .../enso/interpreter/runtime/data/Array.java | 13 +- .../interpreter/runtime/data/ArraySlice.java | 8 +- .../enso/interpreter/runtime/data/Vector.java | 7 +- .../interpreter/runtime/error/Warning.java | 36 ++++-- .../runtime/error/WarningsLibrary.java | 11 ++ .../runtime/error/WithWarnings.java | 120 ++++++++++++++---- .../job/ProgramExecutionSupport.scala | 6 +- .../enso/interpreter/test/WarningsTest.java | 4 +- .../enso/interpreter/dsl/MethodProcessor.java | 3 +- test/Tests/src/Data/Vector_Spec.enso | 16 +++ test/Tests/src/Semantic/Warnings_Spec.enso | 11 ++ 34 files changed, 385 insertions(+), 91 deletions(-) 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