Code Monkey home page Code Monkey logo

nabl's Introduction

Build status

NaBL: A Meta-Language for Declarative Name Binding and Scope Rules

In Spoofax, name bindings are specified in NaBL. NaBL stands for Name Binding Language and the acronym is pronounced 'enable'. Name binding is specified in terms of namespaces, binding instances (name declarations), bound instances (name references), scopes and imports.

Documentation

We maintain the NaBL documentation in our documentation repository.

nabl's People

Contributors

apanatshka avatar azwn avatar blaisorblade avatar casperbp avatar dcharkes avatar dependabot[bot] avatar eelcovisser avatar gohla avatar guwac avatar hendrikvanantwerpen avatar jasperdenkers avatar jochembroekhoff avatar lennartcl avatar meamanusername avatar msteindorfer avatar oskar-van-rest avatar passalaqua avatar pvdstel avatar taeir avatar udesou avatar virtlink avatar vvergu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nabl's Issues

Permission Analysis crashes on Unresolved Predicate Reference

Bug description
Analysis of Statix Spec fails after renaming some functions

Versions
Eclipse: org.eclipse.platform.ide 4.16.0.I20200604-0540
Spoofax: org.metaborg.spoofax.eclipse 2.6.0.20210914-132345-master
System: Mac OS X x86_64 10.14.6
Statix setup: multi-file

Steps to reproduce the behavior
https://github.com/MeAmAnUsername/pie/tree/bug-statix-spec-nabl2-analysis-fails/lang/lang (note the branch and directory)
Either:
Build standalone with Gradle
Go to the project root directory (i.e. directory with lang, api, root etc.) in the terminal
Run ./gradlew :pie.lang.root:pie.lang:build
Note: make sure you use Java 8 when running Gradle, e.g. with the flag -Dorg.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home if you use sdkman

Import into eclipse and build there
Requires eclipse, maybe requires Gradle installed / some Gradle plugin for Eclipse
Import the language project (i.e. lang/lang) in eclipse using Gradle 6.8 or 6.9.1 and Java 8
Build the project

Build with Gradle in devenv
This requires checking out some other metaborg projects as well.
Go to the project root directory (i.e. directory with lang, api, root etc.) in the terminal
Run ./gradlew cleanAll buildAll --info --stacktrace
Note: make sure you use Java 8 when running Gradle, e.g. with the flag -Dorg.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home if you use sdkman

Observed behavior
Build fails with an error. This is consistent, it happened after cleaning the project, restarting Eclipse, running in Gradle in devenv, after checking out another commit, rebuilding, and then going back to this commit, cleaning, restarting and rebuilding, and when building the language project standalone with Gradle.
Error log (from Eclipse, but all of them fail with the same Stratego error)

13:10 | INFO  | s.e.m.b.GenerateSourcesBuilder - Generating sources for language project eclipse:///lang
13:10 | INFO  | o.m.c.build.Builder            - Building eclipse:///lang
13:10 | INFO  | o.m.c.build.Builder            - Building 12 sources, 4 includes of language impl. org.metaborg:org.metaborg.meta.lang.template:2.6.0-SNAPSHOT
13:11 | INFO  | o.m.c.build.Builder            - Building 32 sources, 0 includes of language impl. org.metaborg:statix.lang:2.6.0-SNAPSHOT
13:12 | ERROR | o.m.c.build.Builder            - Analysis failed unexpectedly
org.metaborg.core.analysis.AnalysisException: org.metaborg.core.MetaborgException: Invoking Stratego strategy editor-analyze failed at term:
	( ExtParam(ExtDecl(CVar("statics/type.stx", "d-1028")), 3)
, ExtLit([Label("type_arg"{TermIndex("statics/base.stx", 2789)}){TermIndex("statics/base.stx", 2790)}])
)
Stratego trace:
	editor_analyze_0_0
	editor_analyze_0_0
	nabl2_analyze_1_0
	constraint_analysis_compat_1_0
	with_1_1
	nabl2__analyze_compat_1_0
	with_1_1
	nabl2_custom_analysis_final_hook_0_1
	with_1_1
	nabl2_custom_analysis_final_hook_p__0_1
	with_1_1
	solve_ext_constraints_0_0
	with_1_1
	filter_1_0
	ext_spec_entries_0_0
	with_1_1 <==
	nabl2_get_occurrence_name_0_0
	nabl2__occurrence_name_0_0
Internal error: 'with' clause failed unexpectedly in 'ext-spec-entries'
	at org.metaborg.spoofax.core.analysis.constraint.AbstractConstraintAnalyzer.callAnalysis(AbstractConstraintAnalyzer.java:320)
	at org.metaborg.spoofax.core.analysis.constraint.AbstractConstraintAnalyzer.doAnalysis(AbstractConstraintAnalyzer.java:186)
	at org.metaborg.spoofax.core.analysis.constraint.AbstractConstraintAnalyzer.analyzeAll(AbstractConstraintAnalyzer.java:153)
	at org.metaborg.spoofax.core.analysis.constraint.AbstractConstraintAnalyzer.analyzeAll(AbstractConstraintAnalyzer.java:1)
	at org.metaborg.core.analysis.AnalysisService.analyzeAll(AnalysisService.java:43)
	at org.metaborg.spoofax.core.analysis.SpoofaxAnalysisService.analyzeAll(SpoofaxAnalysisService.java:25)
	at org.metaborg.spoofax.core.analysis.SpoofaxAnalysisService.analyzeAll(SpoofaxAnalysisService.java:1)
	at org.metaborg.core.build.Builder.analyze(Builder.java:402)
	at org.metaborg.core.build.Builder.updateLanguageResources(Builder.java:272)
	at org.metaborg.core.build.Builder.build(Builder.java:167)
	at org.metaborg.spoofax.core.build.SpoofaxBuilder.build(SpoofaxBuilder.java:48)
	at org.metaborg.spoofax.core.build.SpoofaxBuilder.build(SpoofaxBuilder.java:1)
	at org.metaborg.spoofax.eclipse.build.BuildRunnable.run(BuildRunnable.java:60)
	at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:2292)
	at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:2317)
	at org.metaborg.spoofax.eclipse.processing.RunnableTask.schedule(RunnableTask.java:45)
	at org.metaborg.spoofax.eclipse.build.ProjectBuilder.build(ProjectBuilder.java:121)
	at org.eclipse.core.internal.events.BuildManager$2.run(BuildManager.java:832)
	at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)
	at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:220)
	at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:263)
	at org.eclipse.core.internal.events.BuildManager$1.run(BuildManager.java:316)
	at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)
	at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:319)
	at org.eclipse.core.internal.events.BuildManager.basicBuildLoop(BuildManager.java:371)
	at org.eclipse.core.internal.events.BuildManager.build(BuildManager.java:392)
	at org.eclipse.core.internal.resources.Workspace.buildInternal(Workspace.java:515)
	at org.eclipse.core.internal.resources.Workspace.build(Workspace.java:412)
	at org.eclipse.ui.actions.BuildAction$1.runInWorkspace(BuildAction.java:291)
	at org.eclipse.core.internal.resources.InternalWorkspaceJob.run(InternalWorkspaceJob.java:42)
	at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
Caused by: org.metaborg.core.MetaborgException: Invoking Stratego strategy editor-analyze failed at term:
	( ExtParam(ExtDecl(CVar("statics/type.stx", "d-1028")), 3)
, ExtLit([Label("type_arg"{TermIndex("statics/base.stx", 2789)}){TermIndex("statics/base.stx", 2790)}])
)
Stratego trace:
	editor_analyze_0_0
	editor_analyze_0_0
	nabl2_analyze_1_0
	constraint_analysis_compat_1_0
	with_1_1
	nabl2__analyze_compat_1_0
	with_1_1
	nabl2_custom_analysis_final_hook_0_1
	with_1_1
	nabl2_custom_analysis_final_hook_p__0_1
	with_1_1
	solve_ext_constraints_0_0
	with_1_1
	filter_1_0
	ext_spec_entries_0_0
	with_1_1 <==
	nabl2_get_occurrence_name_0_0
	nabl2__occurrence_name_0_0
Internal error: 'with' clause failed unexpectedly in 'ext-spec-entries'
	at org.metaborg.spoofax.core.stratego.StrategoCommon.handleException(StrategoCommon.java:206)
	at org.metaborg.spoofax.core.stratego.StrategoCommon.handleException(StrategoCommon.java:218)
	at org.metaborg.spoofax.core.stratego.StrategoCommon.invoke(StrategoCommon.java:154)
	at org.metaborg.spoofax.core.analysis.constraint.AbstractConstraintAnalyzer.callAnalysis(AbstractConstraintAnalyzer.java:318)
	... 30 common frames omitted
Caused by: org.spoofax.interpreter.core.InterpreterErrorExit: Internal error: 'with' clause failed unexpectedly in 'ext-spec-entries'
	at org.strategoxt.lang.InteropSDefT.evaluate(InteropSDefT.java:194)
	at org.strategoxt.lang.InteropSDefT.evaluate(InteropSDefT.java:183)
	at org.strategoxt.lang.InteropSDefT$StrategyBody.evaluate(InteropSDefT.java:245)
	at org.spoofax.interpreter.core.Interpreter.evaluate(Interpreter.java:105)
	at org.spoofax.interpreter.core.Interpreter.invoke(Interpreter.java:80)
	at org.strategoxt.HybridInterpreter.invoke(HybridInterpreter.java:458)
	at org.metaborg.spoofax.core.stratego.StrategoCommon.invoke(StrategoCommon.java:148)
	... 31 common frames omitted
13:12 | INFO  | o.m.c.build.Builder            - Building 27 sources, 8 includes of language impl. org.metaborg:org.metaborg.meta.lang.esv:2.6.0-SNAPSHOT
13:12 | INFO  | o.m.s.e.m.b.CompileBuilder     - Building language project eclipse:///lang
13:12 | INFO  | Build log                      - > Generate sources
13:12 | INFO  | Build log                      - > Compile normalized grammar to parse table using the Java implementation
13:12 | INFO  | Build log                      - > Extract parenthesis structure from grammar using the Java implementation
13:12 | INFO  | Build log                      - > Compile normalized grammar to parse table using the Java implementation (completions)
13:12 | INFO  | Build log                      - > Compile Stratego code
13:12 | INFO  | Build log                      - Execute strj -i /Users/ivo/programming/devenv/pie/lang/lang/trans/pie.str -o /Users/ivo/programming/devenv/pie/lang/lang/target/metaborg/stratego.ctree -p pie.lang.trans --library --clean -I /Users/ivo/programming/devenv/pie/lang/lang/trans -I /Users/ivo/programming/devenv/pie/lang/lang/src-gen -I /Users/ivo/programming/devenv/pie/lang/lang -I /Applications/spoofax_2.6.0.app/Contents/Eclipse/plugins/statix.runtime.eclipse_2.6.0.20210914-132345-master/target/unpacked/latest/trans -I /Applications/spoofax_2.6.0.app/Contents/Eclipse/plugins/statix.runtime.eclipse_2.6.0.20210914-132345-master/target/unpacked/latest/src-gen -I /Applications/spoofax_2.6.0.app/Contents/Eclipse/plugins/meta.lib.spoofax.eclipse_2.6.0.20210914-132345-master/target/unpacked/latest/trans -I /Users/ivo/programming/devenv/java-front/lang.java/src-gen -I /Users/ivo/programming/devenv/pie/lang/lang -I /Applications/spoofax_2.6.0.app/Contents/Eclipse/plugins/statix.runtime.eclipse_2.6.0.20210914-132345-master/target/unpacked/latest/trans -I /Applications/spoofax_2.6.0.app/Contents/Eclipse/plugins/statix.runtime.eclipse_2.6.0.20210914-132345-master/target/unpacked/latest/src-gen --cache-dir /Users/ivo/programming/devenv/pie/lang/lang/target/stratego-cache -la stratego-lib -la stratego-sglr -la stratego-gpp -la stratego-xtc -la stratego-aterm -la stratego-sdf -la strc -F
13:13 | INFO  | .m.s.m.c.b.LanguageSpecBuilder - Compiling Main ESV file eclipse:///lang/editor/Main.esv
13:13 | INFO  | o.m.c.build.Builder            - Building eclipse:///lang
13:13 | INFO  | o.m.c.build.Builder            - Building 1 sources, 8 includes of language impl. org.metaborg:org.metaborg.meta.lang.esv:2.6.0-SNAPSHOT
13:13 | INFO  | o.m.s.e.m.b.PackageBuilder     - Packaging language project eclipse:///lang
13:13 | INFO  | Build log                      - > Package language implementation
13:13 | INFO  | Build log                      - > Creating JAR file
13:13 | INFO  | o.m.s.e.m.b.PackageBuilder     - Archiving language project eclipse:///lang
13:13 | INFO  | Build log                      - > Archive language implementation
13:13 | INFO  | o.m.s.e.m.b.PackageBuilder     - Reloading language project eclipse:///lang
13:13 | ERROR | .m.s.c.s.p.AResourcesPrimitive - Could not find src-gen/statix/statics/project.spec.aterm

Expected behavior
Build succeeds

Additional context
Caused by this commit (i.e. previous commit works). This commit renames typeArgsOk to typeOfTypeArgs (along with some related functions). The error seems to indicate that it fails on the type_arg relation, which is related in the sense that the typeOfTypeArg function makes a declaration in that relation. I have no clue why this rename leads to this failure.

Tests cannot be cancelled

Bug description
When executing stxtests, cancellation is not possible

Steps to reproduce the behavior
Execute the following test:

resolve loop()

rules
  
  loop:
  loop() :- loop().

After a while, click the red cancel button in the Eclipse Progress view.

Observed behavior
The test continues indefinately.

Expected behavior
The test will stop executing within a few seconds, possibly with an error message.

Additional context
NullCancels are passed to tests, breaking the connection with the button.

Cannot retrieve scope data from scopes outside of the current analysis context with concurrent solver enabled

Bug description
The stx-get-scopegraph-data API method returns the empty list if the given scope is not part of the current analysis context.

Versions
Spoofax version: 2.5.16.
Statix setup: multi-file, concurrent solver enabled.

Steps to reproduce the behavior

  • Create two files where the second file somehow references a scope of the first file.
  • The scope must be declared in the first file and have some data on it declared.
  • When transforming the second file, retrieve this particular Scope/2 instance.
  • Retrieve the analysis context using stx-get-ast-analysis, for a term of the second file.
  • Call the API method, e.g. <stx-get-scopegraph-data(|a, "identifier/of!relation")> s.
  • Observe that the result is always [].

Observed behavior
The result is the empty list.

Expected behavior
The result is not necessarily the empty list, but the actual relevant members.

Additional context
Original Slack thread: https://slde.slack.com/archives/C7254SF60/p1649660409194699.

Workaround

stx-get-scopegraph-data-ext(|a, rel) = ?s
; stx-get-scopegraph-data(|a, rel)
; try(
    ?[]; !s
    // extract the resource identifier from the scope and drop the "/./" prefix
  ; where(Scope(string-as-chars(drop(|3)) => resource, id))
    // attempt to retrieve the analysis state from the external scope
  ; where(a' := <stx-get-ast-analysis> (){TermIndex(resource, -1)})
    // re-execute in the external context
  ; stx-get-scopegraph-data(|a', rel)
  )

NPE in ApplyRelaxed.apply

Bug description

Sometimes, there is a NullPointerException at mb.statix.spec.ApplyRelaxed.apply(ApplyRelaxed.java:35) when running the Statix solver.

Versions

Spoofax version: Spoofax 3 development versions
Statix setup: multi-file

Steps to reproduce the behavior

Unfortunately, this only happens sometimes, I have not been able to reproduce it deterministically. I've seen it happen on the buildfarm on pie.lang and on my laptop on sdf3 in Spoofax 3.

Observed behavior

NullPointerException is thrown.

Expected behavior

No exceptions are thrown.

Additional context

Stack trace from the buildfarm:

> Task :pie.lang.root:pie.lang.test:spoofaxBuild FAILED
Analysis failed unexpectedly
org.metaborg.core.analysis.AnalysisException: org.metaborg.core.MetaborgException: Invoking Stratego strategy editor-analyze failed unexpectedly
Stratego trace:
	editor_analyze_0_0
	stx_editor_analyze_2_3
	stx__editor_analyze_2_3
	with_1_1
	stx__solve_multi_file_0_5
Exception during evaluation: null
	at org.metaborg.spoofax.core.analysis.constraint.AbstractConstraintAnalyzer.callAnalysis(AbstractConstraintAnalyzer.java:320)
	at org.metaborg.spoofax.core.analysis.constraint.AbstractConstraintAnalyzer.doAnalysis(AbstractConstraintAnalyzer.java:186)
	at org.metaborg.spoofax.core.analysis.constraint.AbstractConstraintAnalyzer.analyzeAll(AbstractConstraintAnalyzer.java:153)
	at org.metaborg.spoofax.core.analysis.constraint.AbstractConstraintAnalyzer.analyzeAll(AbstractConstraintAnalyzer.java:1)
	at org.metaborg.core.analysis.AnalysisService.analyzeAll(AnalysisService.java:43)
	at org.metaborg.spoofax.core.analysis.SpoofaxAnalysisService.analyzeAll(SpoofaxAnalysisService.java:25)
	at org.metaborg.spoofax.core.analysis.SpoofaxAnalysisService.analyzeAll(SpoofaxAnalysisService.java:1)
	at org.metaborg.core.build.Builder.analyze(Builder.java:402)
	at org.metaborg.core.build.Builder.updateLanguageResources(Builder.java:272)
	at org.metaborg.core.build.Builder.build(Builder.java:167)
	at org.metaborg.spoofax.core.build.SpoofaxBuilder.build(SpoofaxBuilder.java:48)
	at org.metaborg.spoofax.core.build.SpoofaxBuilder.build(SpoofaxBuilder.java:1)
	at org.metaborg.core.build.IBuilder.build(IBuilder.java:55)
	at org.metaborg.spoofax.core.build.SpoofaxBuilder.build(SpoofaxBuilder.java:52)
	at mb.spoofax.gradle.task.SpoofaxBuildTask.execute(SpoofaxBuildTask.kt:86)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)
	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51)
	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$2.run(ExecuteActionsTaskExecuter.java:494)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:479)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:462)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.access$400(ExecuteActionsTaskExecuter.java:105)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.executeWithPreviousOutputFiles(ExecuteActionsTaskExecuter.java:273)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.execute(ExecuteActionsTaskExecuter.java:251)
	at org.gradle.internal.execution.steps.ExecuteStep.lambda$executeOperation$1(ExecuteStep.java:66)
	at java.base/java.util.Optional.orElseGet(Optional.java:369)
	at org.gradle.internal.execution.steps.ExecuteStep.executeOperation(ExecuteStep.java:66)
	at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:34)
	at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:47)
	at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:44)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:44)
	at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:34)
	at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:72)
	at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:42)
	at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:53)
	at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:39)
	at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:44)
	at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:77)
	at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:58)
	at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:54)
	at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:32)
	at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:57)
	at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:38)
	at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:63)
	at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:30)
	at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:176)
	at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:76)
	at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:47)
	at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:43)
	at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:32)
	at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:39)
	at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:25)
	at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:102)
	at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$0(SkipUpToDateStep.java:95)
	at java.base/java.util.Optional.map(Optional.java:265)
	at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:55)
	at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:39)
	at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:83)
	at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:44)
	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
	at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:96)
	at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:52)
	at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:83)
	at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:54)
	at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:74)
	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.lambda$execute$2(SkipEmptyWorkStep.java:88)
	at java.base/java.util.Optional.orElseGet(Optional.java:369)
	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:88)
	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:34)
	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
	at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:46)
	at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:34)
	at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:43)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution$3.withWorkspace(ExecuteActionsTaskExecuter.java:286)
	at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:43)
	at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:33)
	at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:40)
	at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:30)
	at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:54)
	at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:40)
	at org.gradle.internal.execution.impl.DefaultExecutionEngine.execute(DefaultExecutionEngine.java:41)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:183)
	at java.base/java.util.Optional.orElseGet(Optional.java:369)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:183)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:173)
	at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:109)
	at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
	at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:62)
	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
	at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
	at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:41)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:411)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:398)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:391)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:377)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
	at org.gradle.execution.plan.DefaultPlanExecutor.process(DefaultPlanExecutor.java:72)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.executeWithServices(DefaultTaskExecutionGraph.java:196)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.execute(DefaultTaskExecutionGraph.java:189)
	at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:39)
	at org.gradle.execution.DefaultBuildWorkExecutor.execute(DefaultBuildWorkExecutor.java:40)
	at org.gradle.execution.DefaultBuildWorkExecutor.access$000(DefaultBuildWorkExecutor.java:24)
	at org.gradle.execution.DefaultBuildWorkExecutor$1.proceed(DefaultBuildWorkExecutor.java:48)
	at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:49)
	at org.gradle.execution.DefaultBuildWorkExecutor.execute(DefaultBuildWorkExecutor.java:40)
	at org.gradle.execution.DefaultBuildWorkExecutor.execute(DefaultBuildWorkExecutor.java:33)
	at org.gradle.execution.IncludedBuildLifecycleBuildWorkExecutor.execute(IncludedBuildLifecycleBuildWorkExecutor.java:36)
	at org.gradle.execution.DeprecateUndefinedBuildWorkExecutor.execute(DeprecateUndefinedBuildWorkExecutor.java:44)
	at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor$ExecuteTasks.run(BuildOperationFiringBuildWorkerExecutor.java:57)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
	at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor.execute(BuildOperationFiringBuildWorkerExecutor.java:42)
	at org.gradle.initialization.DefaultGradleLauncher.runWork(DefaultGradleLauncher.java:260)
	at org.gradle.initialization.DefaultGradleLauncher.doClassicBuildStages(DefaultGradleLauncher.java:172)
	at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:148)
	at org.gradle.initialization.DefaultGradleLauncher.executeTasks(DefaultGradleLauncher.java:124)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withSharedLease(DefaultWorkerLeaseService.java:109)
	at org.gradle.composite.internal.DefaultIncludedBuild.execute(DefaultIncludedBuild.java:196)
	at org.gradle.composite.internal.DefaultIncludedBuildController.doBuild(DefaultIncludedBuildController.java:220)
	at org.gradle.composite.internal.DefaultIncludedBuildController.run(DefaultIncludedBuildController.java:113)
	at org.gradle.composite.internal.DefaultIncludedBuildControllers$BuildOpRunnable.run(DefaultIncludedBuildControllers.java:129)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: org.metaborg.core.MetaborgException: Invoking Stratego strategy editor-analyze failed unexpectedly
Stratego trace:
	editor_analyze_0_0
	stx_editor_analyze_2_3
	stx__editor_analyze_2_3
	with_1_1
	stx__solve_multi_file_0_5
Exception during evaluation: null
	at org.metaborg.spoofax.core.stratego.StrategoCommon.handleException(StrategoCommon.java:223)
	at org.metaborg.spoofax.core.stratego.StrategoCommon.invoke(StrategoCommon.java:154)
	at org.metaborg.spoofax.core.analysis.constraint.AbstractConstraintAnalyzer.callAnalysis(AbstractConstraintAnalyzer.java:318)
	... 180 more
Caused by: org.spoofax.interpreter.core.InterpreterException: Exception during evaluation: null
	at org.spoofax.interpreter.core.Interpreter.evaluate(Interpreter.java:113)
	at org.spoofax.interpreter.core.Interpreter.invoke(Interpreter.java:80)
	at org.strategoxt.HybridInterpreter.invoke(HybridInterpreter.java:457)
	at org.metaborg.spoofax.core.stratego.StrategoCommon.invoke(StrategoCommon.java:148)
	... 181 more
Caused by: java.lang.NullPointerException
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
	at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:603)
	at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:678)
	at java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:737)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:919)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
	at mb.statix.spoofax.STX_solve_multi_file.call(STX_solve_multi_file.java:62)
	at mb.statix.spoofax.StatixPrimitive.call(StatixPrimitive.java:84)
	at mb.statix.spoofax.StatixPrimitive.call(StatixPrimitive.java:69)
	at org.spoofax.interpreter.stratego.PrimT.eval(PrimT.java:61)
	at org.spoofax.interpreter.stratego.Strategy.evaluate(Strategy.java:86)
	at org.spoofax.interpreter.stratego.SDefT.evaluate(SDefT.java:213)
	at org.strategoxt.lang.InteropStrategy.invokeDynamic(InteropStrategy.java:57)
	at org.strategoxt.lang.DynamicStrategy.invoke(DynamicStrategy.java:22)
	at org.strategoxt.stratego_lib.with_1_1.invoke(with_1_1.java:31)
	at org.strategoxt.lang.Strategy.invokeDynamic(Strategy.java:44)
	at org.strategoxt.lang.InteropSDefT.evaluate(InteropSDefT.java:192)
	at org.strategoxt.lang.InteropSDefT.evaluate(InteropSDefT.java:183)
	at org.strategoxt.lang.InteropSDefT$StrategyBody.evaluate(InteropSDefT.java:245)
	at org.strategoxt.lang.InteropSDefT$StrategyBody.eval(InteropSDefT.java:238)
	at org.spoofax.interpreter.stratego.Strategy.evaluate(Strategy.java:86)
	at org.spoofax.interpreter.core.Interpreter.evaluate(Interpreter.java:105)
	... 184 more
Caused by: java.lang.NullPointerException
	at mb.statix.spec.ApplyRelaxed.apply(ApplyRelaxed.java:35)
	at mb.statix.spec.RuleUtil.apply(RuleUtil.java:154)
	at mb.statix.spec.RuleUtil.applyOrdered(RuleUtil.java:121)
	at mb.statix.spec.RuleUtil.applyOrderedOne(RuleUtil.java:78)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:655)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:1)
	at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:408)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:1)
	at mb.statix.constraints.CExists.matchOrThrow(CExists.java:90)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:672)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:1)
	at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:408)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:1)
	at mb.statix.constraints.CExists.matchOrThrow(CExists.java:90)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:672)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:1)
	at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:408)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:1)
	at mb.statix.constraints.CExists.matchOrThrow(CExists.java:90)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:408)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:1)
	at mb.statix.constraints.CExists.matchOrThrow(CExists.java:90)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:672)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:1)
	at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:408)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:1)
	at mb.statix.constraints.CExists.matchOrThrow(CExists.java:90)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:672)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:1)
	at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:408)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:1)
	at mb.statix.constraints.CExists.matchOrThrow(CExists.java:90)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:408)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:1)
	at mb.statix.constraints.CExists.matchOrThrow(CExists.java:90)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:672)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:1)
	at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:408)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:1)
	at mb.statix.constraints.CExists.matchOrThrow(CExists.java:90)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseConj(GreedySolver.java:354)
	at mb.statix.solver.persistent.GreedySolver$1.caseConj(GreedySolver.java:1)
	at mb.statix.constraints.CConj.matchOrThrow(CConj.java:59)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:672)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:1)
	at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:408)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:1)
	at mb.statix.constraints.CExists.matchOrThrow(CExists.java:90)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:672)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:1)
	at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:672)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:1)
	at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseConj(GreedySolver.java:354)
	at mb.statix.solver.persistent.GreedySolver$1.caseConj(GreedySolver.java:1)
	at mb.statix.constraints.CConj.matchOrThrow(CConj.java:59)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:672)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:1)
	at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:408)
	at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:1)
	at mb.statix.constraints.CExists.matchOrThrow(CExists.java:90)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:257)
	at mb.statix.solver.persistent.GreedySolver.access$5(GreedySolver.java:213)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:672)
	at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:1)
	at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
	at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:321)
	at mb.statix.solver.persistent.GreedySolver.solve(GreedySolver.java:180)
	at mb.statix.solver.persistent.Solver.solve(Solver.java:63)
	at mb.statix.spoofax.STX_solve_multi_file.solveConstraint(STX_solve_multi_file.java:75)
	at mb.statix.spoofax.STX_solve_multi_file.lambda$3(STX_solve_multi_file.java:56)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:952)
	at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:926)
	at java.base/java.util.stream.AbstractTask.compute(AbstractTask.java:327)
	at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)

Stack trace from my laptop:

> Task :spoofax3.lwb.root:sdf3:test

AnalyzerTest > analyzeMultipleSuccess() FAILED
    mb.constraint.common.ConstraintAnalyzerExceptions$StrategoInvokeFail: Constraint analyzer failed unexpectedly due to failure in Stratego execution

        Caused by:
        mb.stratego.common.StrategoExceptions$ExceptionalFail: Invoking Stratego strategy 'statix-editor-analyze' failed exceptionally
        Stratego stack trace:
        	stx__solve_multi_file_0_5
        	with_1_1
        	stx__editor_analyze_2_3
        	stx_editor_analyze_2_3
        	statix_editor_analyze_0_0
        Stratego input term:
        AnalyzeMulti(
          ("java##jimfs://24c6350c-2fbe-4694-9b2e-7fad133810f5/", Added((){TermIndex("java##jimfs://24c6350c-2fbe-4694-9b2e-7fad133810f5/", 0)}))
        , [ ( "java##jimfs://24c6350c-2fbe-4694-9b2e-7fad133810f5/b.sdf3"
            , Added(
                Module(
                  Unparameterized("b")
                , [Imports([Module(Unparameterized("a"))])]
                , [ SDFSection(CfSorts(["B"]))
                  , SDFSection(
                      ContextFreeSyntax(
                        [SdfProduction(SortDef("B"), Rhs([Sort("A")]), NoAttrs())]
                      )
                    )
                  ]
                )
              )
            )
          , ( "java##jimfs://24c6350c-2fbe-4694-9b2e-7fad133810f5/a.sdf3"
            , Added(
                Module(
                  Unparameterized("a")
                , []
                , [ SDFSection(CfSorts(["A"]))
                  , SDFSection(
                      ContextFreeSyntax(
                        [ SdfProductionWithCons(
                            SortCons(SortDef("A"), Constructor("A"))
                          , Rhs([Lit("\"A\"")])
                          , NoAttrs()
                          )
                        ]
                      )
                    )
                  ]
                )
              )
            )
          , ( "java##jimfs://24c6350c-2fbe-4694-9b2e-7fad133810f5/c.sdf3"
            , Added(
                Module(
                  Unparameterized("c")
                , [Imports([Module(Unparameterized("a")), Module(Unparameterized("b"))])]
                , [ SDFSection(CfSorts(["C"]))
                  , SDFSection(
                      ContextFreeSyntax(
                        [SdfProduction(SortDef("C"), Rhs([Sort("A")]), NoAttrs())]
                      )
                    )
                  , SDFSection(
                      ContextFreeSyntax(
                        [SdfProduction(SortDef("C"), Rhs([Sort("B")]), NoAttrs())]
                      )
                    )
                  ]
                )
              )
            )
          ]
        , ()
        , BLOB_ThreadCancel()()
        )

            Caused by:
            org.spoofax.interpreter.core.InterpreterException: Exception during evaluation: null
                at org.spoofax.interpreter.core.Interpreter.evaluate(Interpreter.java:113)
                at org.spoofax.interpreter.core.Interpreter.invoke(Interpreter.java:80)
                at org.strategoxt.HybridInterpreter.invoke(HybridInterpreter.java:458)
                at mb.stratego.common.StrategoRuntime.invokeOrNull(StrategoRuntime.java:169)
                at mb.stratego.common.StrategoRuntime.invokeOrNull(StrategoRuntime.java:147)
                at mb.stratego.common.StrategoRuntime.invoke(StrategoRuntime.java:56)
                at mb.constraint.common.ConstraintAnalyzer.doAnalyze(ConstraintAnalyzer.java:383)
                at mb.constraint.common.ConstraintAnalyzer.analyze(ConstraintAnalyzer.java:285)
                at mb.spoofax.test.SingleBaseLanguageTestBase.analyze(SingleBaseLanguageTestBase.java:161)
                at mb.sdf3.language.AnalyzerTest.analyzeMultipleSuccess(AnalyzerTest.java:86)

                Caused by:
                java.lang.NullPointerException
                    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
                    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
                    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
                    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
                    at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:603)
                    at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:678)
                    at java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:737)
                    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:919)
                    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
                    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
                    at mb.statix.spoofax.STX_solve_multi_file.call(STX_solve_multi_file.java:62)
                    at mb.statix.spoofax.StatixPrimitive.call(StatixPrimitive.java:91)
                    at mb.statix.spoofax.StatixPrimitive.call(StatixPrimitive.java:76)
                    at org.spoofax.interpreter.stratego.PrimT.eval(PrimT.java:61)
                    at org.spoofax.interpreter.stratego.Strategy.evaluate(Strategy.java:86)
                    at org.spoofax.interpreter.stratego.SDefT.evaluate(SDefT.java:213)
                    at org.strategoxt.lang.InteropStrategy.invokeDynamic(InteropStrategy.java:57)
                    at org.strategoxt.lang.DynamicStrategy.invoke(DynamicStrategy.java:22)
                    at org.strategoxt.stratego_lib.with_1_1.invoke(with_1_1.java:31)
                    at org.strategoxt.lang.Strategy.invokeDynamic(Strategy.java:44)
                    at org.strategoxt.lang.InteropSDefT.evaluate(InteropSDefT.java:192)
                    at org.strategoxt.lang.InteropSDefT.evaluate(InteropSDefT.java:183)
                    at org.strategoxt.lang.InteropSDefT$StrategyBody.evaluate(InteropSDefT.java:245)
                    at org.strategoxt.lang.InteropSDefT$StrategyBody.eval(InteropSDefT.java:238)
                    at org.spoofax.interpreter.stratego.Strategy.evaluate(Strategy.java:86)
                    at org.spoofax.interpreter.core.Interpreter.evaluate(Interpreter.java:105)
                    ... 9 more

                    Caused by:
                    java.lang.NullPointerException
                        at mb.statix.spec.ApplyRelaxed.apply(ApplyRelaxed.java:35)
                        at mb.statix.spec.RuleUtil.apply(RuleUtil.java:157)
                        at mb.statix.spec.RuleUtil.applyOrdered(RuleUtil.java:124)
                        at mb.statix.spec.RuleUtil.applyOrderedOne(RuleUtil.java:81)
                        at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:664)
                        at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:323)
                        at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
                        at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:323)
                        at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:258)
                        at mb.statix.solver.persistent.GreedySolver.access$400(GreedySolver.java:94)
                        at mb.statix.solver.persistent.GreedySolver$1.caseConj(GreedySolver.java:356)
                        at mb.statix.solver.persistent.GreedySolver$1.caseConj(GreedySolver.java:323)
                        at mb.statix.constraints.CConj.matchOrThrow(CConj.java:59)
                        at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:323)
                        at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:258)
                        at mb.statix.solver.persistent.GreedySolver.access$400(GreedySolver.java:94)
                        at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:681)
                        at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:323)
                        at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
                        at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:323)
                        at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:258)
                        at mb.statix.solver.persistent.GreedySolver.access$400(GreedySolver.java:94)
                        at mb.statix.solver.persistent.GreedySolver$1.caseConj(GreedySolver.java:356)
                        at mb.statix.solver.persistent.GreedySolver$1.caseConj(GreedySolver.java:323)
                        at mb.statix.constraints.CConj.matchOrThrow(CConj.java:59)
                        at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:323)
                        at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:258)
                        at mb.statix.solver.persistent.GreedySolver.access$400(GreedySolver.java:94)
                        at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:681)
                        at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:323)
                        at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
                        at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:323)
                        at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:258)
                        at mb.statix.solver.persistent.GreedySolver.access$400(GreedySolver.java:94)
                        at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:410)
                        at mb.statix.solver.persistent.GreedySolver$1.caseExists(GreedySolver.java:323)
                        at mb.statix.constraints.CExists.matchOrThrow(CExists.java:90)
                        at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:323)
                        at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:258)
                        at mb.statix.solver.persistent.GreedySolver.access$400(GreedySolver.java:94)
                        at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:681)
                        at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:323)
                        at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
                        at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:323)
                        at mb.statix.solver.persistent.GreedySolver.success(GreedySolver.java:258)
                        at mb.statix.solver.persistent.GreedySolver.access$400(GreedySolver.java:94)
                        at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:681)
                        at mb.statix.solver.persistent.GreedySolver$1.caseUser(GreedySolver.java:323)
                        at mb.statix.constraints.CUser.matchOrThrow(CUser.java:89)
                        at mb.statix.solver.persistent.GreedySolver.k(GreedySolver.java:323)
                        at mb.statix.solver.persistent.GreedySolver.solve(GreedySolver.java:181)
                        at mb.statix.solver.persistent.Solver.solve(Solver.java:63)
                        at mb.statix.spoofax.STX_solve_multi_file.solveConstraint(STX_solve_multi_file.java:75)
                        at mb.statix.spoofax.STX_solve_multi_file.lambda$call$3(STX_solve_multi_file.java:56)
                        at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
                        at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
                        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
                        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
                        at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:952)
                        at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:926)
                        at java.base/java.util.stream.AbstractTask.compute(AbstractTask.java:327)
                        at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
                        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
                        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
                        at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
                        at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
                        at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)

Document `editor-analyze` inputs/outputs.

Short description
Document the editor-analyze API's better, to answer the following questions:

  • How is the input represented (terms of AnalysisAction sort).
  • How is the output represented (terms of the AnalysisResult sort, including Full and Update).

Problem description.
The input and output of the solver wrapper strategies is not documented very well.

Describe the solution you'd like
A good overview on https://www.spoofax.dev/references/statix/stratego-api/.

Renaming: bag properties

Bug description
When a language uses a bag property for references, renaming does not work anymore.

Versions
Spoofax version: 2.5.16
Statix setup: Both single-file and multi-file

Steps to reproduce the behavior
Consider a language with the following resolveVar constraint:

resolveVar(s, x) = T :- {x'}
  query var 
    filter P* I* and eq(x)
    in s |-> [(_, (x', T))],
  @x.ref += x'.

In an example program:

  1. Select a variable name
  2. Open the menu Spoofax > Refactoring > Rename
  3. Enter a new name in the dialog that appears, and press OK

Observed behavior
Renaming fails with an exception

Expected behavior
All occurrences of the selected variable in the program should be renamed.

Cases construct

Short description
Add a cases construct for inline matching/branching.

Problem description.
Currently, the only branching construct in Statix is rule selection. When a particular constraint should handle certain terms different than other terms (often empty/non-empty query outputs), this must always be expressed using a new rule. This is verbose, as it adds a lot of user-defined constraints that are only used at one place in the specification.

For an example, see this snippet of the Java specification.

Describe the solution you'd like
It should be possible to express these rules as an inline constraint. For example:

query var /* ... */ in s |-> R,
cases R of {
  []            => /* constraint 1 */,
  [(_, (_, T))] => /* constraint 2 */,
  _             => /* constraint 3 */
}

The syntax of this construct should look approximately like:

Constraint.CCases = <cases <Term> {
  <{Case ",\n"}+>
} <Message>

Case.Case = [[case:Term] => [Constraint]]

The term that is matched on, and the terms in the cases should have the same sort. Variables introduced in the case term should be visible in the corresponding constraint only.

This constraint should be normalized to a separate user-defined constraint, with a rule for each case. When the match is not exhaustive, (i.e. the last pattern is not a wildcard), a wildcard rule with body false should be added, to prevent refinement of the matched term.

Because rule selection is done based on specificity ordering, and case statements are interpreted as evaluated from top to bottom, we should statically prevent more general patterns to occur before more specific ones.

To be considered: How can we properly deal with outer variables references inside a case? Should they all become arguments to the generated constraint?

Describe alternatives you've considered
An interesting alternative/complementary construct is having a 'functional' cases term as follows:

Term.FCases = <cases <Term> {
  <{FCase ",\n"}+>
} <Message>

FCase.FCase = [[case:Term] => [Term]]

This construct actually 'returns' a term, which can then be used at another position. Here, it holds that the resulting terms should all have the same sort as well.

Additional context
This construct is different from unification in the sense that it is one-sided: it should not allow refinement of the matched term.

Warning on improper indentation of user constraints in tail.

Short description
Give an editor warning when a rule accidentally ends with a , instead of a ..

Problem description.
Consider having a specification snippet like:

rule(C()) :-
  constraint, /* <- ',' ipv '.' */
rule(_).

Syntactically, this is interpreted as:

rule(C()) :-
  constraint,
  rule(_).

However, the intended meaning in most cases is:

rule(C()) :-
  constraint.
rule(_).

This behavior can exhibit strange semantics, but these mistakes are often hard to spot.

Describe the solution you'd like
Specifications that are formatted as the first snippet should show a warning that indicates that the formatting suggests that the latter constraint should be a rule.

Additional context
Similar warnings already exist for the reverse case:

rule(C()) :-
  constraint.
  rule(_).

Show incoming edges in scope graph

Short description
Add a section 'incoming edges' in the scope graph. Rename the section 'edges' to 'outgoing edges'

Problem description.
Right now, it is easy to see the outgoing edges of a scope in the a .scopegraph file, but the incoming edges have to be derived by searching for that edge and scope, which is made particularly difficult due to the way Statix lays out multiple targets with the same edge.

Describe the solution you'd like
Instead of just edges, show incoming edges and outgoing edges in .scopegraph:

  #.-s_func_instantiated_125-26 {
    relations {
      statics/base!generic_arg : (GenericParameter(#src/main/test.pie-s_func_60-17, "F"), ?.-arg_ty-230)
    }
    outgoing edges {
      statics/base!P : #src/main/test.pie-s_func_inner_16-5
                       #src/main/test.pie-s_func_60-17
    }
    incoming edges {
      statics/base!INHERIT : #src/main/test.pie-s_func_inner_14-23
    }
  }

Describe alternatives you've considered

  1. Some kind of menu action that can be used when on the scope. Sounds awful but would work.
  2. Show references by CTRL+clicking the scope declaration (requires #85). Has two problems: first, it shows every reference, not just incoming edges. More importantly, Eclipse uses menu actions to show references, not just CTRL+click on a declaration like IntelliJ.

Additional context
This feature would work very well in combination with #85. The combination of these two would allow more or less effortless following edges back and forth between scopes.

Additional ideas for sections

  • References: relations in other scopes that refer to this scope.
  • Queries that were started from this scope
  • Queries that resolved to this scope
  • Queries that pathed via this scope

Some of these might become big very quickly, so it is probably a good idea to make sections collapsible, with menu options like collapse all > {scopes, sections, relations > {all, own, references}, edges > {all, outgoing edges, incoming edges}, queries > {all, started, resolved, pathed}}

Example:

  #.-s_data_def_125-26 {
    relations {
      statics/base!generic_param : ("F", GenericParameter(#src/main/test.pie-s_data_def_125-26, "F"))
                                   ("T", GenericParameter(#src/main/test.pie-s_data_def_125-26, "T"))
    }
    references {
      statics/base!generic_arg : #src/main/test.pie-s_data_instance_16-5 : (GenericParameter(#src/main/test.pie-s_data_def_125-26, "F"), IntType())
                                                                           (GenericParameter(#src/main/test.pie-s_data_def_125-26, "T"), StrType())
                                 #src/main/test.pie-s_data_instance_24-9 : (GenericParameter(#src/main/test.pie-s_data_def_125-26, "F"), BoolType())
                                                                           (GenericParameter(#src/main/test.pie-s_data_def_125-26, "T"), DataType(#src/main/test.pie-s_data_instance_16-5))
    }
    outgoing edges {
      statics/base!P : #src/main/test.pie-s_func_inner_16-5
                       #src/main/test.pie-s_func_60-17
    }
    incoming edges {
      statics/base!INHERIT : #src/main/test.pie-s_data_instance_14-23
    }
    queries originating {
      query var { name' :- name' == "foo" } filter P min P in #.-s_data_def_125-26 |-> []
    }
    queries resolving {
      query data { name' :- name' == "Box" } filter P min P in #.-s_func_124-27 |-> [((<...too lazy to write out path...>), #.-s_data_def_125-26)]
    }
    queries traversing {
    }
  }

Consider what should happen to annotations on explicated terms

Short description

I recently got bit because I was accidentally setting properties on explicated (A2B) nodes, which disappear during implication. I feel like this may be a pitfall that can come to bite others in the future, so I'm writing a feature request/issue to get some discussion going on possible ways to make this clearer.

Problem description.

In my case, my grammar looked something like this:

Literal.Int = <<INT>>
Exp.Var = <<ID>>
Exp = Literal

I then had a statix rule that looked like this:

  boxIfNeeded : Exp * TYPE * TYPE
  boxIfNeeded(x, INT(), OBJECT()) :- @x.box += "int".
  boxIfNeeded(x, BOOL(), OBJECT()) :- @x.box += "bool".
  boxIfNeeded(x, STRING(), OBJECT()) :- @x.box += "str".
  boxIfNeeded(x, _, _).

Which was used somewhat like this:

  stmtOk(s, Return(e)) :-
    typeOfExp(s, e) == T,
    lookupReturnType(s) == RT,
    assignableTo(T, RT) | error $[Cannot return values of type [T] in a function declared with [RT] as return type.],
    boxIfNeeded(e, T, RT).

The intention here is to attach the box annotation on any expressions that must first be boxed when executed in a runtime that has separate representations for primitives and objects (e.g. Java).

However, when testing this with return 3, the actual AST that Statix processes is Return(Literal2Exp(Int("3")), which in return results in boxIfNeeded attaching annotations to Exp2Literal, which disappear after implication.

Possible approaches

There are some approaches we could consider. The ones that come to mind are:

  • Error if annotations exist on explicated constructors during implication.
    • This is the cleanest method, but will likely break quite a few specs because it is quite common to assign type(/ref to a lesser extent) annotations to explicated nodes (although likely done unintentionally).
  • Error if custom (non-ref/type) annotations exist on explicated constructors during implication.
    • Less extreme version of the previous. This would be a good approach, since we can assume the user has assigned them with a reason and is intending to query them through Java/Stratego.
  • Merge/move annotations on explicated constructors to their direct children during implication.
    • Unsure if this is possible, but would require the least amount of effort for the author as they will not have to worry about implications.

Error in PIE file with incremental/concurrent solver but not with traditional solver

Bug description
Error in PIE file with incremental/concurrent solver but not with traditional solver

Versions
Eclipse: org.eclipse.platform.ide 4.16.0.I20200604-0540
Spoofax: org.metaborg.spoofax.eclipse 2.6.0.20220405-144549-master
System: Mac OS X x86_64 10.14.6
Statix setup: multi-file

Steps to reproduce the behavior
Not the smallest, but it is self contained: switchToIncremental.txt.
Github only allows specific file types, so you'll have to change the extension back to stxtest

Observed behavior
Evaluating the test as normally results in zero errors, evaluating the file with the concurrent solver results in 2 errors.

Expected behavior
No errors regardless of the solver. At the very least I expect that the results match.

Additional context
Originally found with incremental solver, but there is no option to evaluate a stxtest with the incremental solver yet.

Statix Stratego API root scope retrieval

Short description
I would like an additional strategy in the Statix Stratego API that retrieves the root scope from the scope graph of a given analysis result.

Problem description.
Current analysis related strategies in the Stratego API operate on a given Scope term, but there is no simple way yet to retrieve the root scope of an analysis to bootstrap the utilization of these strategies.

Describe the solution you'd like
A stx-get-root-scope strategy that maps a given analysis result to the root Scope term would solve this problem.

Describe alternatives you've considered
Retrieving the root Scope term as a property of an AST node is already possible, but this seems more of a workaround as I believe it should still be possible to query the scope graph while disregarding the AST.

Additional context
N/A

Unification on AST terms looses indices

Bug description
When an AST term is bound to a unification variable, it looses its term index.

Versions
Spoofax version: 2.5.16, nightly april 2022.

Steps to reproduce the behavior
Use a constraint as follows

typeOfExp(s, arg) = T :- {ref id}
  arg == Var2Exp(ref),
  astId(ref, id),
  try { false } | note $[Term index: [id]].

Observed behavior
id is a free variable.

Expected behavior
id is bound to a particular term index.

Additional context
Originally found by @toinehartman

Warning on unused variables

Short description
When a (non-wildcard) variable is not used, issue an inline warning.

Problem description.
Specifications with too many variable names are more prone to naming errors, and are less easy to read.

Describe the solution you'd like
Consider the following rule:

rule(v1) = v2 :- {v3} true.

In this rule, all three variables are not used, and could be replaced with wildcards, or in the case of v3, removed.

Fail to show message on try if inner constraint fails

Bug description
Calling a failing predicate in a try block will not show a message on the try if the predicate has a warning or note after the constraint that fails.

Versions
Eclipse: org.eclipse.platform.ide 4.16.0.I20200604-0540
Spoofax: org.metaborg.spoofax.eclipse 2.6.0.20210816-152350-master
System: Mac OS X x86_64 10.14.6
Statix setup: (none, it's not a language project, just a stxtest)

Steps to reproduce the behavior

resolve
  try { pred(1) } | note "pred 1 failed",
  try { pred(2) } | note "pred 2 failed",
  try { pred(3) } | note "pred 3 failed",
  try { pred(4) } | note "pred 4 failed",
  try { pred(5) } | note "pred 5 failed",
  try { pred(6) } | note "pred 6 failed",
  try { pred(7) } | note "pred 7 failed"

rules

  pred : int

// Bug only happens if it is a note or warning that is checked _after_ the failing constraint false.
// correctly show messages
  pred(1) :- false.
  pred(2) :- false.
  pred(3) :-
    try {false} | note $[in pred 3],
    false.
  pred(4) :-
    try {false} | warning $[in pred 4],
    false.
  pred(5) :-
    false,
    try {false} | error $[in pred 5].

// do not show messages
  pred(6) :-
    false,
    try {false} | warning $[in pred 6].
  pred(7) :-
    false,
    try {false} | note $[in pred 7].

Observed behavior
it prints messages for pred X failed for 1-5, but not for 6 and 7.

Expected behavior
It prints messages for all seven predicates.

Get list of references to an AST node

Short description
Some way to get the list of nodes referencing an AST node.

Problem description.
I want to find unused variables, imports, etc.

Describe the solution you'd like
A built-in function (or a library function) that takes an AST node and returns a list of AST nodes: getRefs : astId -> list(astId).
Note: I use astId as general AST node, but if there is another way that would work too.
Problems with this solution: the returned list is just general AST nodes. Those cannot be used in function that expect a specific sort without casting, which is currently not implemented.

Describe alternatives you've considered
1
To solve the problem, a function to just return whether a node has references would be enough:isUsed : astId -> BOOLEAN. BOOLEAN is not a built-in type though. Additionally, returning a list of references may be useful for purposes besides my current use-case.

2
Detecting unused variables can (probably) also be done with a helper relation where a declaration is made when a variable is used, which can then be checked by the original node. I think this would work without deadlocking, but it seems a little boilerplate-heavy.

relations
  used : string // the name of the variable. Uses string so that it works for every possible name.

rules
  declareVar : scope * VAR_ID * TYPE
  declareVar(s, name, T) :-
    !var[name, T] in s,
    // duplicate checks, etc. omitted for simplicity
    query used in s |-> [_|_] | warning $[variable [name] is unused] @name.

  resolveVar : scope * VAR_ID -> TYPE
  resolveVar(s, name) = T :-
    query var filter {name'' :- name == name'' } in s |-> [(path, (name', T))],
    // could not resolve errors etc. omitted for simplicity
    @name.ref := name',
    !used[name'] in getResolvedScope(path).

  getResolvedScope : path -> scope // get the end scope from a path.

3
Use DynSem to detect and give warnings for unused variables. DynSem can give warnings on useless assignments, so that will almost certainly also work for unused variables. I don't think that this is part of the DynSem domain though.

Additional context
Giving warnings on unused variables allows the user to remove those variables, which improves the code quality.

Incremental analysis gives errors when language id is interpolated

Bug description
The incremental analysis gives an error when the Statix specification is for a language that uses yaml interpolation for its id

Versions
Eclipse: org.eclipse.platform.ide 4.16.0.I20200604-0540
Spoofax: org.metaborg.spoofax.eclipse 2.6.0.20220405-144549-master
System: Mac OS X x86_64 10.14.6
Statix setup: multi-file

Steps to reproduce the behavior

  1. Check out the PIE DSL language project in eclipse (metaborg/pie/lang/lang)
  2. Create a normal project and add metaborg.yaml with a compile dependency on PIE.
  3. Create an example test.pie file in the normal project
  4. Add
  statix:
    mode: incremental

in metaborg/pie>metaborg.yaml (before language.stratego)
5. Restart eclipse and rebuild the PIE project

Observed behavior
Error:

18:00 | ERROR | .m.s.c.s.p.AResourcesPrimitive - Could not find lib/stxlibs
18:00 | ERROR | o.m.c.b.p.LanguagePathService  - Getting source paths from provider org.metaborg.core.build.paths.DependencyPathProvider@22277482 failed unexpectedly, skipping this provider
org.metaborg.core.build.dependency.MissingDependencyException: Language for dependency org.metaborg:pie.lang:${pie.version} does not exist

See buildLog.txt for full log.
This always happens, immediately after building and also after every edit to the pie file (if the edit triggers an analysis).
The file is correctly analyzed though, the error does not seem to break anything.
This error does not occur in PIE files in the PIE project itself, only in the project that imports the PIE language.

Expected behavior
No error

Additional context
I have tried to create a minimal project, but I could not reproduce it.
This issue is made in the hope that you go "ah, should have used a different method to resolve those interpolations".

Cast in `ScopeGraphUtils`

Short description
Remove cast in ScopeGraphUtils.

Problem description.
In order to keep Java 11 type inference happy, an cast was inserted in

t -> (Map.Entry<L, Iterable<S>>)new AbstractMap.SimpleImmutableEntry<L, Iterable<S>>(t.getKey().getValue(), t.getValue()), Collectors.toSet())));

Describe the solution you'd like
It would be better to fin the correct type bounds, and add those explicitly

Describe alternatives you've considered

Additional context
Not having the cast works in Java 8

Cascading errors in the WebDSL specification

Short description
A rare case of a cascading error during typechecking of a WebDSL program.

Problem description.
In the following WebDSL snippet, a predicate should contain a boolean result, but a more specific error occurs in the addition (adding int + bool). Statix shows both while in the ideal case it would only show the error of adding an int to a bool.

  predicate foo(i:Int){
    i+false
  }

The error should be caught with the following constraint:

equalType(typeOfExp(s_predicate, exp), bool) | error $[Predicate should contain a Bool expression] @exp.

normalized:
      (typeOfExp(s_predicate, exp, typeOfExp332) | error $[Predicate should contain a Bool expression] @exp,
       equalType(typeOfExp332, bool) | error $[Predicate should contain a Bool expression] @exp),

Describe the solution you'd like
Ideally the error given by the typeOfExp predicate should be the only error shown, namely that adding an int to a bool is untyped.

Describe alternatives you've considered
None

Additional context
Failing test on the WebDSL buildfarm: https://buildfarm.metaborg.org/job/webdsl/job/webdsl-statix/job/master/117/artifact/analysis-results/analysis__1-original-compiler-tests__analysis-fails__ac__predicateboolexp-2__FAILED.

Statix snippet: https://github.com/webdsl/webdsl-statix/blob/584fdf1b29ce2c2a6b094bae0e24b64a1f1efb7d/webdslstatix/trans/static-semantics/webdsl-ac.stx#L188-L199

Permissions lost when scope is used in non-linear pattern

Bug description
Scope permissions are lost when a scope is used in a non-linear pattern.

Steps to reproduce the behavior

Open the following test:

resolve {s1 s2}
  new s1,
  prgmOk(s1) == s2

signature
  relations
    rel:

rules

  prgmOk: scope -> scope
  prgmOk(s) = s :-
    !rel[] in s.

Observed behavior
There is an permission error on prgmOk(s1) == s2.

Expected behavior
No error, because s1 is owned.

Additional context

Perhaps introduced by e67871d

Add match constraint to Statix

Short description
match constraint to match on a value and apply different constraints based on the value.
Example:

{some_type}
match someFunction(input_values)
| case SomeConstructor() { false | error $[That's not right], some_type == UnkownType() }
| case AnotherConstructor(returned_values) { finagleThings(returned_values) == some_type }
| case YetAnotherCase(some_other_values, some_type) { doSomethingElse(some_other_values) },
use_type(some_type).

Problem description.
This avoids helper functions for two common issues. Helper functions are annoying because they require a few extra lines, but more importantly they are annoying to change. If I need an extra parameter from the main function in the helper function, I need to add it to the call, signature and every case of the helper function. With match all variables defined in the main function are available directly.

The first issue is simply matching on things. For example, functions in the PIE DSL have a list of type arguments. This list can either be explicit (supplier<int>(7)) or omitted (supplier(8)).
Supplier is built-in because reasons, so there is explicit code that handles all the cases for the number of type arguments and normal arguments (as opposed to getting handled by code for regular functions):

  typeOfExpImpl(s1, CreateSupplier(_, NoTypeArgs(), [exp])) = (s2, SupplierType(ty)) :-
    typeOfExp(s1, exp) == (s2, ty).
  typeOfExpImpl(s1, CreateSupplier(_, TypeArgs([arg_ty_lex]), [exp])) = (s2, SupplierType(arg_ty)) :-
    {exp_ty}
    typeOf(s1, arg_ty_lex) == arg_ty,
    typeOfExp(s1, exp) == (s2, exp_ty),
    assignableTo(exp_ty, arg_ty) | error $[Type mismatch: argument with type [exp_ty] is not assignable to type argument [arg_ty]] @exp.
  typeOfExpImpl(s, CreateSupplier(name, type_args, args)) = (s, DataType(emptyScope(s))) :-
    typeOfCreateSupplier_1(type_args, args),
    try { args != [] } | error $[Not enough arguments. Expected one argument, but got zero] @name,
    try { args != [_,_|_] } | error $[Too many arguments. Expected one argument, but got multiple] @name.

    typeOfCreateSupplier_1 : TypeArgs * list(Exp)
    typeOfCreateSupplier_1(NoTypeArgs(), _).
    typeOfCreateSupplier_1(type_args@TypeArgs([]), args) :-
      false | error $[Not enough type arguments. Expected one type argument, got zero. To infer the type argument, omit the type argument list: `supplier([args])`] @type_args.
    typeOfCreateSupplier_1(type_args@TypeArgs([_]), _).
    typeOfCreateSupplier_1(type_args@TypeArgs([_,_|_]), _) :-
      false | error $[Too many type arguments. Expected one argument, got multiple] @type_args.

This could be simplified with a match:

  typeOfExpImpl(s1, CreateSupplier(name, type_args, args)) = (s2, SupplierType(ty)) :-
    match args
    | [] { false | error $[Not enough arguments. Expected one argument, but got zero] @name, s2 == s1, arg_ty == UNKOWN() }
    | [arg] { typeOfExp(s1, arg) == (s2, arg_ty) }
    | [_,_|_] { false | error $[Too many arguments. Expected one argument, but got multiple] @name, s2 == s1, arg_ty == UNKOWN() },
    match type_args
    | (NoTypeArgs(), _) { ty == arg_ty}
    | TypeArgs([]) { false | error $[Not enough type arguments. Expected one type argument, got zero. To infer the type argument, omit the type argument list: `supplier([args])`] @type_args, ty == arg_ty }
    | TypeArgs([type_arg]) { partial match args | case [arg] { assignableTo(arg, type_arg) }, ty == type_arg }
    | TypeArgs([_,_|_]) { false | error $[Too many type arguments. Expected one argument, got multiple] @type_args, ty == arg_ty }.

The second problem is to only run a function if an earlier function succeeded (can be seen from the return type). The current solution for that is to create a helper function which does the matching. In the following example, we only want to check that the argument types match the parameter types if the function resolved. (this example is a simplified version of what happens for the PIE DSL).

typeOfCall(s, Call(name, args)) = typeOfCall_1(s, resolveUniqueFunc(s, name), typeOfExps(s, args), name).

  typeOfCall_1 : scope * ResolutionResult * list(TYPE) -> TYPE
  typeOfCall_1(_, NoDefinitions(), _, name) = UnkownType() :-
    false | error $[Undefined function [name]] @name.
  typeOfCall_1(s, Definition(param_tys, return_ty), arg_tys, _) = return_ty,
    subtypes(arg_tys, param_tys).
  typeOfCall_1(_, MultipleDefinitions(_), _, name) = UnknownType() :-
    false | error $[Duplicate definitions for function [name]] @name.

With matching, it could be shortened to this

typeOfCall(s, Call(name, args)) = return_ty :-
  typeOfExps(s, args),
  match resolveUniqueFunc(s, name)
  | case NoDefinitions() { false | error $[Undefined function [name]] @name, return_ty == UnkownType() }
  | case Definition(param_tys, return_ty) { subtypes(arg_tys, param_tys) }
  | case MultipleDefinitions(_) { false | error $[Duplicate definitions for function [name]] @name, return_ty == UnkownType() }.

Describe the solution you'd like

// match
$PartialOpt match $Term $MessageOpt
$Cases
// match on a term and give cases that the term may deconstruct to.
// Cases is a list of `Case`s, separated by newlines
// MessageOpt is the optional message that will be used if the match itself fails, similar to normal constraints, e.g.
// match some_term | error $[Could not match on [some_term]]  <cases on following lines>

// Case
| case $Term $CaseConstraints // The matched term and the constraints for this case.

// CaseConstraints
  // leaving it empty means no case constraints.
{ $Constraints } // constraints enclosed in curly braces, e.g.
                 // { typeOfParams(params) == param_tys, subtypes(arg_tys, param_tys) }

| $Severity $MessageText // syntactic sugar for `{ false | $Severity $Message }` e.g.
                         // | error "this case is an error"

Checking which case is hit can use the same logic that is used to determine which rule of a function should be called.
Any variables within case terms will be unified with the regular unification magic that Statix employs.
Only variables in the actual case that is hit get unified. This could be important with cases like
| case SomeConstructor(some_var@some_other_var), which unifies some_var with some_other_var. This should only be done if this case is actually hit.

If there is no case for the actual value of the matched term (or the term is unbound), the match constraint fails (in the same way that a function fails if there is no matching rule). If a message is provided, it will be used with the given severity and message text, similar to normal constraints. The $Message for the match constraint itself is on the first line, and not after all the cases, because that would be ambiguous:

match some_var
| case SomeConstructor()
| error "Is this error for the match or for the case?"

Layout suggests that the error is for the match but I think using layout for disambiguation is bad practice.

To ignore missing cases, add the keyword partial, e.g. partial match some_var. This will not give errors when there is no case for the value of the term. It should probably still give an error if the term is unbound.

Something that may or may not be possible is to statically verify that there are cases for all possible values, and give a static error in Statix if that is not the case, for example:

match some_list
| case [] | error $[Expected one argument, got zero]
| case [_,_|_] | error $[Expected one argument, got multiple].

Which gives an error on the match keyword (or the entire match constraint, although I feel that is excessive): "Unmatched cases: [_]. Use partial match to allow missing cases"

If this is implemented, I suggest adding some way to explicitly allow missing cases but still give a runtime error if they are hit. An option is exhaustive match. This seems suboptimal because it seems like this would be the one that requires everything exhaustive (instead of match), but I feel like it would be beneficial to have match be the one that gives errors, with some modifier to disable these errors, rather than match not giving errors and requiring a modifier to get the errors.

Finally, we could consider to add syntax for multiple cases at once, e.g. case SomeConstructor() | SomeOtherConstructor() { constraintsForBoth() }. This particular syntax is probably too much (| is overloaded enough as it is, I can't imagine that adding this won't introduce an ambiguity), but some other syntax might work.
If there is only a single case where there are multiple constructors one can use an unbound variable, but that does not work if there are multiple such cases:

match some_term
| case C1() | C2() | note "C1 or C2"
| case C3() | C4() | note "C3 or C4"

It may also improve (for certain definitions of improve) the example code above

Describe alternatives you've considered
For the second problem (only add constraint if earlier constraint passed), I have considered a sequencing constraint: C1; C2
Constraint C2 will not be evaluated until C1 passes.
The problem here is that it only works when C1 can either pass or fail. If C1 can pass or fail in multiple ways, it still requires that C2 be put in a helper function.
It's also a bit less clear than match, especially if the difference between sequential and parallel constrains is wether you separate them with ; or ,. The separator could be changed, but still.

Additional context
This would probably remove the need for 80% of helper functions in my code. A simple search for _1 returns 201 results. That includes both the definition and cases, but even if we are generous and say that all of them have 4 cases (1 definition + 4 rules = 5 search results per helper function) , that is still 40 helper functions that can be removed if this is implemented.

Stratego is getting match expressions because it improves performance (I think), maybe it could improve performance for Statix as well? (instead of calling a helper function, the helper function is more or less inlined by the user)

I have considered if there should be a default case, e.g. | default | note $[Did not match any other case], but it seems to me that that is equivalent to just using an unbound variable: | case _ | note $[Did not match any other case]. Here is an alternative to the example code above that uses such a default case:

typeOfCall(s, Call(name, args)) = return_ty :-
  typeOfExps(s, args),
  match resolveUniqueFunc(s, name)
  | case Definition(param_tys, return_ty) { subtypes(arg_tys, param_tys) }
  | case other {
    return_ty == UnkownType(),
    match other
    | case NoDefinitions() | error $[Undefined function [name]] @name
    | case MultipleDefinitions(_) | error $[Duplicate definitions for function [name]] @name
  }

Preserve `note`s from entailment contexts

Short description
Messages from entailment contexts are now always suppressed, which is not always desirable.

Problem description.
Often, Statix specifications are debugged using try {false } | note $[<message>] constraints. However, when these constraints are anywhere in an entailment context, their messages are not included in the final solver result. Therefore, entailed constraints are much harder to debug.

Describe the solution you'd like
Messages with note level should be transferred from an entailment context to the outer context.

Describe alternatives you've considered
There are several alternative solutions:

  • Introducing a special debug level which is transferred, instead of note. It would be possible to suppress these messages in 'production' usages.
  • Transfer all messages with equal or lower severity on failure of the inner constraint, and with lower severity when the constraint succeeds, always including the note messages.

Type ascription in Statix breaks checking

Bug description
Using type ascription in Statix breaks checking.

Versions
Spoofax version: Spoofax3 0.15.3
Statix setup: single-file

Steps to reproduce the behavior

Using type ascription as follows produces an error:

func_defOk(s1, fd@FuncDef(name, typed_vars, defcls: list(GNFV_defcl), stmts: list(Stmt))) = (s2, T) :- {argTs s3 s4}
...

func_defOk(s1, fd@FuncDefWithReturnType(name: ID, typed_vars: list(Typed_var), ret_type: Type, defcls: list(GNFV_defcl), stmts: list(Stmt))) = (s2, T) :- {argTs s3 s4 rT} 
...

removing these type ascriptions as follows fixes the error:

func_defOk(s1, fd@FuncDef(name, typed_vars, defcls, stmts)) = (s2, T) :- {argTs s3 s4}
...

func_defOk(s1, fd@FuncDefWithReturnType(name, typed_vars, ret_type, defcls, stmts)) = (s2, T) :- {argTs s3 s4 rT}
...

Observed behavior

The following error occurs when checking the Statix spec:

11:59:36.439 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |   Caused by: org.spoofax.interpreter.core.InterpreterException: Exception during evaluation: Exception in execution of primitive 'STX_compare_patterns'
11:59:36.439 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.spoofax.interpreter.core.Interpreter.evaluate(Interpreter.java:113)
11:59:36.439 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.spoofax.interpreter.core.Interpreter.invoke(Interpreter.java:80)
11:59:36.439 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.HybridInterpreter.invoke(HybridInterpreter.java:458)
11:59:36.439 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.stratego.common.StrategoRuntime.invokeOrNull(StrategoRuntime.java:169)
11:59:36.439 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.stratego.common.StrategoRuntime.invokeOrNull(StrategoRuntime.java:147)
11:59:36.439 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.stratego.common.StrategoRuntime.invoke(StrategoRuntime.java:56)
11:59:36.439 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.constraint.common.ConstraintAnalyzer.doAnalyze(ConstraintAnalyzer.java:383)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.constraint.common.ConstraintAnalyzer.analyze(ConstraintAnalyzer.java:285)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.statix.task.spoofax.StatixAnalyzeMultiWrapper.lambda$analyze$0(StatixAnalyzeMultiWrapper.java:55)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.common.option.Option.mapThrowingOrElse(Option.java:133)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.statix.task.spoofax.StatixAnalyzeMultiWrapper.lambda$analyze$1(StatixAnalyzeMultiWrapper.java:54)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.common.option.Option.mapThrowingOrElse(Option.java:133)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.common.result.Result.mapThrowingOrElse(Result.java:364)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.statix.task.spoofax.StatixAnalyzeMultiWrapper.analyze(StatixAnalyzeMultiWrapper.java:53)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.constraint.pie.ConstraintAnalyzeMultiTaskDef.exec(ConstraintAnalyzeMultiTaskDef.java:141)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.constraint.pie.ConstraintAnalyzeMultiTaskDef.exec(ConstraintAnalyzeMultiTaskDef.java:27)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.api.Task.exec(Task.java:56)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TaskExecutor.execInternal(TaskExecutor.java:130)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TaskExecutor.exec(TaskExecutor.java:87)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.exec(TopDownRunner.java:192)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.executeOrGetExisting(TopDownRunner.java:137)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.require(TopDownRunner.java:80)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.ExecContextImpl.require(ExecContextImpl.java:118)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.ExecContextImpl.require(ExecContextImpl.java:98)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.statix.task.StatixCheckMulti.exec(StatixCheckMulti.java:51)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.statix.task.StatixCheckMulti.exec(StatixCheckMulti.java:18)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.api.Task.exec(Task.java:56)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TaskExecutor.execInternal(TaskExecutor.java:130)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TaskExecutor.exec(TaskExecutor.java:87)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.exec(TopDownRunner.java:192)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.executeOrGetExisting(TopDownRunner.java:137)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.require(TopDownRunner.java:80)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.ExecContextImpl.require(ExecContextImpl.java:118)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.ExecContextImpl.require(ExecContextImpl.java:98)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.spoofax.lwb.compiler.statix.CheckStatix.lambda$exec$0(CheckStatix.java:33)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.common.option.Option.mapOrElse(Option.java:129)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.spoofax.lwb.compiler.statix.CheckStatix.lambda$exec$1(CheckStatix.java:32)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.common.option.Option.mapOrElse(Option.java:129)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.common.result.Result.mapOrElse(Result.java:356)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.spoofax.lwb.compiler.statix.CheckStatix.exec(CheckStatix.java:31)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.spoofax.lwb.compiler.statix.CheckStatix.exec(CheckStatix.java:13)
11:59:36.444 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.api.Task.exec(Task.java:56)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TaskExecutor.execInternal(TaskExecutor.java:130)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TaskExecutor.exec(TaskExecutor.java:87)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.exec(TopDownRunner.java:192)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.executeOrGetExisting(TopDownRunner.java:137)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.require(TopDownRunner.java:80)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.ExecContextImpl.require(ExecContextImpl.java:118)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.ExecContextImpl.require(ExecContextImpl.java:98)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.spoofax.lwb.compiler.CheckLanguageSpecification.exec(CheckLanguageSpecification.java:56)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.spoofax.lwb.compiler.CheckLanguageSpecification.exec(CheckLanguageSpecification.java:24)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.api.Task.exec(Task.java:56)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TaskExecutor.execInternal(TaskExecutor.java:130)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TaskExecutor.exec(TaskExecutor.java:87)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.exec(TopDownRunner.java:192)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.executeOrGetExisting(TopDownRunner.java:137)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.require(TopDownRunner.java:80)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.exec.TopDownRunner.requireInitial(TopDownRunner.java:59)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.MixedSessionImpl.lambda$require$1(MixedSessionImpl.java:99)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.SessionImpl.handleException(SessionImpl.java:215)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.pie.runtime.MixedSessionImpl.require(MixedSessionImpl.java:99)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder.topDownBuild(SpoofaxLwbBuilder.java:162)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder.fullBuild(SpoofaxLwbBuilder.java:131)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder.access$000(SpoofaxLwbBuilder.java:61)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder$1.run(SpoofaxLwbBuilder.java:86)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:2292)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder.build(SpoofaxLwbBuilder.java:101)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.internal.events.BuildManager$2.run(BuildManager.java:846)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:229)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:277)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.internal.events.BuildManager$1.run(BuildManager.java:330)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:333)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.internal.events.BuildManager.basicBuildLoop(BuildManager.java:385)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.internal.events.BuildManager.build(BuildManager.java:406)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.internal.resources.Workspace.buildInternal(Workspace.java:515)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.internal.resources.Workspace.build(Workspace.java:405)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.ui.actions.GlobalBuildAction$1.run(GlobalBuildAction.java:180)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
11:59:36.445 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |   Caused by: org.strategoxt.lang.StrategoException: Exception in execution of primitive 'STX_compare_patterns'
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.Context.invokePrimitive(Context.java:225)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.Context.invokePrimitive(Context.java:201)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.compare_patterns_0_0.invoke(compare_patterns_0_0.java:10)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.patterns_eq_0_0.invoke(patterns_eq_0_0.java:14)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.rule_head_cmp_1_0.invoke(rule_head_cmp_1_0.java:61)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.rule_pattern_cmp_0_1.invoke(rule_pattern_cmp_0_1.java:18)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.distinct_patterns_0_1_lifted2.invoke(distinct_patterns_0_1_lifted2.java:38)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_EXT_filter_1_0.filterMaintainAnnos(SRTS_EXT_filter_1_0.java:65)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_EXT_filter_1_0.filterMaintainAnnos(SRTS_EXT_filter_1_0.java:68)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_EXT_filter_1_0.invoke(SRTS_EXT_filter_1_0.java:37)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.compat.override.performance_tweaks.filter_1_0_override.invoke(filter_1_0_override.java:21)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.distinct_patterns_0_1_lifted1.invoke(distinct_patterns_0_1_lifted1.java:23)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_all.mapMaintainAnnos(SRTS_all.java:90)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_all.invoke(SRTS_all.java:23)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.compat.override.performance_tweaks.map_1_0_override.invoke(map_1_0_override.java:29)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.distinct_patterns_0_1_lifted0.invoke(distinct_patterns_0_1_lifted0.java:18)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.stratego_lib.with_1_1.invoke(with_1_1.java:31)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.distinct_patterns_0_1.invoke(distinct_patterns_0_1.java:27)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.solve_pattern_constraint_0_1_lifted4.invoke(solve_pattern_constraint_0_1_lifted4.java:12)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_all.mapMaintainAnnos(SRTS_all.java:90)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_all.mapMaintainAnnos(SRTS_all.java:101)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_all.mapMaintainAnnos(SRTS_all.java:101)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_all.mapMaintainAnnos(SRTS_all.java:101)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_all.mapMaintainAnnos(SRTS_all.java:101)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_all.invoke(SRTS_all.java:23)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.compat.override.performance_tweaks.map_1_0_override.invoke(map_1_0_override.java:29)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.solve_pattern_constraint_0_1.invoke(solve_pattern_constraint_0_1.java:27)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.detect_overlapping_rules_0_1_lifted0.invoke(detect_overlapping_rules_0_1_lifted0.java:10)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_EXT_filter_1_0.filterIgnoreAnnos(SRTS_EXT_filter_1_0.java:83)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.SRTS_EXT_filter_1_0.invoke(SRTS_EXT_filter_1_0.java:34)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.compat.override.performance_tweaks.filter_1_0_override.invoke(filter_1_0_override.java:21)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.detect_overlapping_rules_0_1.invoke(detect_overlapping_rules_0_1.java:19)
11:59:36.446 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.nabl2_custom_analysis_final_hook_p__0_1_lifted0.invoke(nabl2_custom_analysis_final_hook_p__0_1_lifted0.java:27)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.stratego_lib.with_1_1.invoke(with_1_1.java:31)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.nabl2_custom_analysis_final_hook_p__0_1.invoke(nabl2_custom_analysis_final_hook_p__0_1.java:38)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.nabl2_custom_analysis_final_hook_0_1_lifted0.invoke(nabl2_custom_analysis_final_hook_0_1_lifted0.java:39)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.stratego_lib.with_1_1.invoke(with_1_1.java:31)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.nabl2_custom_analysis_final_hook_0_1.invoke(nabl2_custom_analysis_final_hook_0_1.java:34)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.nabl2__analyze_compat_1_0_lifted6.invoke(nabl2__analyze_compat_1_0_lifted6.java:62)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.stratego_lib.with_1_1.invoke(with_1_1.java:31)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.nabl2__analyze_compat_1_0_fragment_0.invoke(nabl2__analyze_compat_1_0_fragment_0.java:266)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.nabl2__analyze_compat_1_0.invoke(nabl2__analyze_compat_1_0.java:13)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.nabl2_analyze_1_0_lifted0.invoke(nabl2_analyze_1_0_lifted0.java:10)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.constraint_analysis_compat_1_0_lifted5.invoke(constraint_analysis_compat_1_0_lifted5.java:159)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.stratego_lib.with_1_1.invoke(with_1_1.java:31)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.constraint_analysis_compat_1_0.invoke(constraint_analysis_compat_1_0.java:116)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.nabl2_analyze_1_0.invoke(nabl2_analyze_1_0.java:13)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at statix.lang.trans.editor_analyze_0_0.invoke(editor_analyze_0_0.java:11)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.Strategy.invokeDynamic(Strategy.java:33)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.InteropSDefT.evaluate(InteropSDefT.java:192)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.InteropSDefT.evaluate(InteropSDefT.java:183)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.InteropSDefT$StrategyBody.evaluate(InteropSDefT.java:245)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.spoofax.interpreter.core.Interpreter.evaluate(Interpreter.java:105)
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     ... 79 more in common with parent stacktrace
11:59:36.447 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |   Caused by: java.lang.IllegalArgumentException: Cannot match: As(Var("fd"),Op("FuncDef",[Ascribe(…,…),Ascribe(…,…)|…]))
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.matching.TermMatch$M.lambda$req$81(TermMatch.java:466)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at java.base/java.util.Optional.orElseThrow(Optional.java:408)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.matching.TermMatch$M.lambda$req$82(TermMatch.java:466)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.matching.TermMatch$M.lambda$listElems$41(TermMatch.java:313)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.Terms$1.caseList(Terms.java:38)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.build.AConsTerm.match(AConsTerm.java:53)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.matching.TermMatch$M.lambda$listElems$42(TermMatch.java:310)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.matching.TermMatch$M.lambda$appl1$14(TermMatch.java:106)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.Terms$1.caseAppl(Terms.java:34)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.build.AAppl1Term.match(AAppl1Term.java:38)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.matching.TermMatch$M.lambda$appl1$15(TermMatch.java:102)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.matching.TermMatch$M.lambda$casesFix$80(TermMatch.java:447)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.matching.TermMatch$M.lambda$appl2$18(TermMatch.java:122)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.Terms$1.caseAppl(Terms.java:34)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.build.AAppl2Term.match(AAppl2Term.java:36)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.matching.TermMatch$M.lambda$appl2$19(TermMatch.java:118)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.nabl2.terms.matching.TermMatch$IMatcher.match(TermMatch.java:515)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.statix.spoofax.STX_compare_patterns.call(STX_compare_patterns.java:28)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.statix.spoofax.StatixPrimitive.call(StatixPrimitive.java:91)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at mb.statix.spoofax.StatixPrimitive.call(StatixPrimitive.java:76)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     at org.strategoxt.lang.Context.invokePrimitive(Context.java:212)
11:59:36.448 | DEBUG | Worker-5: Building Workspace                       | mb.spoofax.lwb.eclipse.SpoofaxLwbBuilder           |     ... 131 more in common with parent stacktrace

Expected behavior
No error to occur

Additional context
Can be reproduced in this Spoofax 3 project: https://gitlab.ewi.tudelft.nl/CS4200/2021-2022/student-tvandersteenho/-/tree/5e61158fdbd18240598eb284fcaee0ad848469d1 in the statics/Func_def.stx file.

Test logging

Short description
A debug log of an stxtest should be available, in order to have more debugging information available.

Problem description.
When executing an stxtest, the final result is provided, but no information on how the solver achieved that result. This gives little insight in the underlying reasons of unexpected behavior. Even worse, when a test does not terminate, no information is available.

Describe the solution you'd like
Alongside the stxresult file, an stxlog file should be generated, which contains the debug log of the solver.
This feature should be configurable in the runtime.statix.testlog: <log level> option in the metaborg.yaml.
When the log level is off, no log file should be generated.

Describe alternatives you've considered

  • Providing warnings on certain types of suspicious behavior (such as many levels of recursion), but these are both more complicated to implement, and cover less cases.

Additional context

Outline for Statix spec

Short description
Show strategies in the outline.

Problem description.
I want to go to a function in a file somewhere. Pretty much all my files are hundreds of lines long, so scrolling takes a while and requires looking where I am approximately.

Describe the solution you'd like
Show an outline of the Statix file in the outline view. Ideally it has nodes for every section, with only the rules section expanded. The rules section shows predicates / functions, and merges consecutive rules of the same function/predicate, for example:

imports
  signatures/Common
  statics/whatever

signature
  sorts Foo constructors
    Foo1 : Foo
    Foo2 : Foo

  sorts BOOLEAN FuncKind
  constructors
    FALSE : BOOLEAN
    ForeignJavaKind : FuncKind
    TRUE : BOOLEAN
    PieFuncKind : FuncKind


rules
  someFunc : Foo -> string
  someFunc(..) = .. :- ..
  someFunc(..) = .. :- ..

  anotherFunc : int
  anotherFunc(0).
  anotherFunc(1) :- ...

  someFunc(..) = .. :- ..

Would produce the outline

|- imports // no subnodes, I see no reason why you want to see them 
|             in the outline, just click on the imports section.
|             Maybe add subnodes if there are multiple imports sections in the file?
|- signature // this section is collapsed by default
|  |- sort Foo with constructors // use text like this if only one sort declared and following
|  |  |                             constructor section declares only constructors of this sort
|  |  |- Foo1
|  |  \- Foo2
|  |- sorts
|  |  |- BOOLEAN
|  |  \- FuncKind
|  \- constructors
|     |- FALSE
|     |- ForeignJavaKind
|     |- TRUE
|     \- PieFuncKind
\- rules
   |- someFunc // merges definition and the rules. Refers to the first node, i.e. the definition
   |- anotherFunc
   \- someFunc // last rule was not grouped with other `someFunc` rules, gets its own node.

Describe alternatives you've considered
I could use CTRL-F, use global search, or keep scrolling.
Searching gets false positives in the form of references to that function, and scrolling costs time and requires checking where in the file you are.

Additional context
Explanation of how to create an outline: https://www.spoofax.dev/references/editor-services/outline/
The PIE DSL source has the following definition, which seems like it would work decently:

  editor-outline:
    (_, _, ast, path, project-path) -> outline
    where
      outline := <simple-label-outline(to-outline-label)> ast

  to-outline-label = fail

Which has the following documentation:

  /**
   * Creates an outline given a strategy s1 that rewrites AST nodes to outline labels.
   * Only AST nodes for which s1 succeed end up in the outline.
   *
   * Example:
   *   outline = simple-label-outline(to-outline-label)
   *   to-outline-label: Entity(name, _) -> name
   *   to-outline-label: Property(name, _) -> name
   */

This is not a massive improvement, but it is a nice QOL improvement and probably not much work.

Improved pretty-printing in Statix error Messages

Short description
Allow custom pretty-print strategies for message templates.

Problem description.
Statix error message templates can contain interpolated terms, which are now printed literally. This makes messages ugly and obscure when the terms contain scopes.

Describe the solution you'd like
Language designers should be able to provide their own pretty-printing strategies to pretty-print these terms. This strategy should be called on each interpolated subterm in the message. The strategy should be provided access to the analysis result.

Additional context
Something similar does already exist for the editor hover strategy. Additionally, integration with/fallback to default statix term pretty-printing should be trivial.

Inline functions

Short description
User can select a functional constraint or predicate constraint and use Spoofax > refactoring > inline or Spoofax > refactoring > recursively inline.
The first one inlines the body of the function, the second one inlines recursively, i.e. inline body, inline any function calls in that body, inline any function calls in those bodies, etc.

Problem description.
I'm currently debugging my Statix spec and it basically is just importing my full project and then doing all this inlining by hand, interleaved with removing elements that are not relevant, until I find the bug. If this is implemented I don't have to do the inlining by hand anymore, only removing the irrelevant details before inlining again.

Describe the solution you'd like
Example:

  someFunc(s) :-
    declareVar(s, "this", getThis(s)),
    someOtherFunc(s).

  declareVar(s, name, T) :-
    !var[name, T] in s,
    resolveVar(s, name) == [_] | error $[duplicate variable [name]],
    @name.type := T.

  resolveVar(s, name) = occs :-
    query var
      filter P and { name' :- name == name' }
         min $ < P and true
          in s |-> occs.

User can select declareVar(s, "this", getThis(s))) and use Spoofax > refactoring > inline, which results in

  someFunc(s) :-
    {tmp_1}
    tmp_1 == getThis(s),
    !var["this", tmp_1] in s,
    resolveVar(s, "this") == [_] | error $[duplicate variable "this"],
    @"this".type := tmp_1,
    someOtherFunc(s).

  declareVar(s, name, T) :-
    !var[name, T] in s,
    resolveVar(s, name) == [_] | error $[duplicate variable [name]],
    @name.type := T.

  resolveVar(s, name) = occs :-
    query var
      filter P and { name' :- name == name' }
         min $ < P and true
          in s |-> occs.

User can also select declareVar(s, "this", getThis(s))) and use Spoofax > refactoring > inline recursively, which results in

  someFunc(s) :-
    {tmp_1}
    tmp_1 == getThis(s),
    !var["this", tmp_1] in s,
    query var filter P and { name' :- "this" == name' } min $ < P and true in s |-> [_]  | error $[duplicate variable "this"],
    @"this".type := tmp_1,
    someOtherFunc(s).

  declareVar(s, name, T) :-
    !var[name, T] in s,
    resolveVar(s, name) == [_] | error $[duplicate variable [name]],
    @name.type := T.

  resolveVar(s, name) = occs :-
    query var
      filter P and { name' :- name == name' }
         min $ < P and true
          in s |-> occs.

Open questions
If it cannot be determined which rule for a function should be used, there are two options:

  1. Don't inline the function
  2. Inline the common parts, create a helper function which provides rules for the distinct parts.
    Option 1 is probably better in most cases. It's also possible to ask the user what to do.

The example shows @"this".type := tmp_1, but this is a no-op for strings without origin information (I think). Should this be inlined as well or just discarded?
Note that this also did not do anything in the original code (assuming that it is indeed a no-op for strings without origin info).

Describe alternatives you've considered
An earlier iteration of this idea had Statix automatically inline everything in a stxtest.

Additional context
In the examples, the functions resolveVar and declareVar are now unused. It would be nice if there was an action to find and remove unused functions (just finding would be enough).

Renaming for Projects using concurrent Solver

Short description
The renaming menu should work for object language files that are analyzed with the concurrent solver.

Problem description.
Renaming an entity in a source file of a Spoofax language that uses the concurrent solver fails with an exception.

Describe the solution you'd like
Renaming should work in a way similar to the traditional solver

Additional context
The Analysis result from the concurrent solver is stored in a different format. The get-analysis-object from statix/runtime/renaming should be adapted to project those properly.

Export proper Scope Graph Libraries.

Short description
Correctly configure project scope of a library, and limit library to externally visible scopes.

Problem description.
Currently, Statix provides a menu option to export scope graph libraries, which can subsequently be imported by other projects. However, the exported scope graphs have five important deficiencies:

  • The project root scope is added to the second list of scopes, instead of to the first one.
  • The project root scope has a datum set. This is incorrect, because, at library consumer analysis time, the project type checker (not the library type checker) will set the datum of that scope.
  • Scope graph libraries also contain declarations made by the projectOk constraint. These will be duplicated once the library is used.
  • Scope graph libraries also contain the 'local' parts of the scope graph (i.e. the parts that are not reachable from outside). These segments are useless, but still consume disk space, memory and might increase library query execution time.

Describe the solution you'd like
To solve these problems, we should include the project root scope in the ProjectResult. On export, that will allow adding it to the list of shared scopes of the library, and avoid setting the datum.
Addintionally, we can filter out all scopes with the same resource as the project root scope, which effectively removes all declarations from the projectOk constraint.
The fourth problem can be solved by transitively traversing the edges and data of all scopes reachable from the root scope, and only including these scopes in the final library.

Additional context
The manual solution to the first two steps in described in https://www.spoofax.dev/howtos/statix/migrating-to-concurrent-solver/#using-libraries.

Require that a Statix variable is bound

Short description
A way to require that a variable is bound.

Problem description.
I sometimes run into problems where an unbound variable is put in the scope graph. This then takes me a long time to figure out, because the error only shows up at a totally unrelated place where the value is looked up and used. It would help if I could require that the value is bound before it is saved to the scope graph.
Same happens with variables for return values: in my code, these should always be bound at the end of a function.

Describe the solution you'd like
At the very least, a general way to require that a variable is bound, e.g. a constraint requireBound(theVar) that fails if the variable is not bound. Then it will at least show an error at the location where I expect it to be bound, instead of only at the use sites where it turns out that invariant was broken.

Describe alternatives you've considered
Solution I'd like: a workaround is to define a function which uses the variable as input and has two rules with different input shapes. That function will fail with a delayed on foo error if foo is not bound yet. Unfortunately, such a function cannot be defined generically even if/when generics in Statix exist, because it requires two specific constructors for the sort. requireBound could just be syntactic sugar to generate this workaround function automatically.

Even better would be a good root cause analysis that tells me "this is the position where the variable should have been bound, but it wasn't", but that is hard if you assume that constraints are solved in a random order.
In my specific case return values of functions should always be bound "after" the function runs and arguments should be bound "before" the function runs, so given

  foo(some, arguments) == res,
  bar(res, more, arguments) == other_res

if res is unbound, then that is because foo failed to bind it, not because bar failed to bind it.
Using this in combination with cascading error suppression means that any subsequent errors in bar can be suppressed. That may make it really easy to find the root cause, as the error will always be reported at the exact location where it first failed to be bound.
I don't know if this can be generalized to an analysis that is useful for others as well, because it depends on the assumption that it is always the return value that is the root cause of the error.

Better message on non-existing root spec.

Short description
When the root specification passed to stx-editor-analyze does not exist, create a clear error message in the log.

Describe the solution you'd like
Currently, when the root specification passed to stx-editor-analyze does not exist, an error message like the following is emitted:

10:46 | ERROR | .m.s.c.s.p.AResourcesPrimitive - Could not find src-gen/statix/lang/S02/statics.spec.aterm

This error message is not very informative. Instead something like:

The statics module `lang/S02/statics` does not exist. Did you pass the correct module to `stx-editor-analyze` in `trans/analysis.str`?

astId is ignored when used as location for message

Bug description
A message with an astId as location will not be shown on that astId but will be treated as if it didn't have an explicit location (i.e. goes up the stacktrace and uses the first ast node it finds)

Versions
Eclipse: org.eclipse.platform.ide 4.16.0.I20200604-0540
Spoofax: org.metaborg.spoofax.eclipse 2.6.0.20210903-091634-master
System: Mac OS X x86_64 10.14.6
Statix setup: single file

Steps to reproduce the behavior
stx test files do not show the node the error appears on, so you get a full Spoofax project instead.
The full project is attached as statixbugastidmessagelocation.zip, relevant parts are shown here:
Syntax:

context-free syntax
  
  Start.Elems = [[Elem], [Elem]]
  Elem.Elem = [[INT]]

Statix:

module statics

// see README.md for details on how to switch to multi-file analysis

rules // single-file entry point

  programOk : Start

  programOk(Elems(e1, e2)) :-
    check(e1, astId(e1)),
    check(e1, astId(e2)).

  check : Elem * astId
  check(elem, node) :- false | error $[elem [elem], astId([node])] @node.

signature

  sorts Start constructors
    Elems : Elem * Elem -> Start
  
  sorts Elem constructors
    Elem : Elem

Example file (example/test.sta)

1, 2

Observed behavior
There are two errors on 1. This always happens.

Expected behavior
One error on 1, one error on 2.

Additional context
Slack threads:

Intermittent empty query results

Bug description
Query result is sometimes empty even though there is clearly something in the relation.

Versions
Eclipse: org.eclipse.platform.ide 4.16.0.I20200604-0540
Spoofax: org.metaborg.spoofax.eclipse 2.6.0.20210526-183432-master
System: Mac OS X x86_64 10.14.6
(I got this info from Spoofax-meta > Report Issue, let me know if you really need it from the plugins view)

Statix setup: multi-file

Steps to reproduce the behavior
Clone my fork of pie
Checkout branch bug-statix-inconsistent-query (you should be on the commit named Add debugging info + example file for bug report with revision number cc52cb2ff43a8f9c63afd231af7e6c49aec4a511)
Build the project
Open example > example.pie
Force reanalysis by renaming res until there is no error on Fuz<Fish>.

Renaming with keyboard is fine. I assume Spoofax > Refactor > Rename also works, but it takes far longer.
The error normally disappears for me within 0 - 5 modify-analyze cycles.

Observed behavior
In the problems view (or when hovering over Fuz of Fuz<Fish>) there is the following note: DEBUG - type args in "Fuz": [] -- s_data: #.-s_data_instance_42-7; occs: []
It shows the results of the query query generic_arg filter e in s_data |-> occs on line 119 in statics/type_common.
The query returns empty.
However, when opening the scope graph that scope does have a declaration for generic_arg:

  #.-s_data_instance_42-7 {
    relations {
      statics/base!root : #-s_1-1
      statics/base!generic_arg : ("T", DataType(#.-s_data_instance_232-19))
    }
    edges {
      statics/base!P : #example/example.pie-s_data_def_152-27
    }
  }

Happens inconsistently. Changes between analysis. Rebuilding does not affect it. Clean build does have an effect in the sense that some tests no longer fail, but this problem still happens.
I haven't looked into exactly which tests stop (intermittently) failing after a clean build.

Expected behavior
The query consistently returns the single generic argument declared in that scope.

Additional context
This was introduced on the commit named [WIP] Return whether type arguments are correct, revision number 97100d3e92d50758c482e0beb83177792d5da41d.
This is the second time I tried the change. This happened both times. The first time I made this change I assumed I had made some mistake and retried from scratch.
This is not critical for me, it's for removing cascading errors on function calls with incorrect generic arguments. The next commit removed checking whether the arguments matched the parameters if the type arguments are incorrect.
However, I would like to implement that at some point.

Some playing around brings up an interesting hypothesis: it seems like certain names for res will consistently succeed or fail to produce an error.

Reference resolution in errors

Short description
Allow CTRL + click on a function or variable name in errors to go to the declaration of that name or variable

Problem description.
Current process to debug an error: look at error in the eclipse Problems View, (optionally copy to text file for easier reading), decide which of the functions / variables to look into, manually navigate to the position in the source code (either by knowing where it is or searching for the function name).

Describe the solution you'd like
Reference resolution from function names / variables in errors in stxresult files and the Eclipse error view to their declaration in stxtest or stx files, such that I can CTRL+click (Command+click on Mac) on a name to go to the declaration immediately.
If a variable or function is generated by Statix, it should refer to the constraint or function that generated it.
Note that there are four locations where I would like this reference resolution:

  1. In stxresult files (perhaps possible after #85, although depending on the implementation it will require cross-language reference resolution)
  2. In errors shown in programs when hovering over the error (probably impossible without changes to Eclipse)
  3. In errors shown in the Problems View (probably impossible without changes to Eclipse)
  4. In unnamed files (will be hard, but might be possible. Requires some changes to how Spoofax works, and also cross-language reference resolution)

Either one of 2, 3 or 4 would be enough to go to definition for errors in programs.

Describe alternatives you've considered
As is noted in the solution, getting this to work for programs will likely be a lot of work. An alternative solution for 3 is to implement a custom Problems View where we can parse and analyze the errors. This seems like one of the more feasible options, along with option 4. If we are implementing a custom error view anyway, it may be possible to include the full stacktrace and terms in errors, but to collapse them if they become too big, with the option of clicking to unfold it.

Note
Right now I don't feel like this is that useful, especially given how much work it will be to implement this. However, I felt the same about reference resolution in scope graphs, and that turned out to be extremely convenient.

Extension info in Tooltip

Short description
Show scope extensions in tooltips on user-defined constraints and references to those. For rules, only scope extensions of that rule should be shown.

Problem description.
When a specification has circular dependencies on declaration and query constraints, these end up as "unsolved" errors. These problems are notoriously hard to debug. Additional information on the scope extensions of rules/constraints can make debugging these easier.

Describe the solution you'd like
Consider the following specification:

rules
  /* 1 */ declareVar: scope * ID * TYPE
  /* 2 */ declareVar(s, x, T) :- !var[x, T] in s.

  /* 3 */ declOk: scope * Decl
  /* 4 */ declOk(s, VarDecl(x, t)) :- 
    /* 5 */ declareVar(s, x, typeOfType(s, t)).
  /* 6 */ declOk(s, ModDecl(x, decls)) :- {s_mod}
    new s_mod, s_mod -P-> s,
    !mod[x, MOD(s_mod)] in s,
    /* 7 */ declsOk(s_mod, decls). 

Now, the tooltips should show the following for each predicate (reference) at the numbered positions:

  1. "extends: 1 with 'var'"
  2. "extends: s with 'var'"
  3. "extends: 1 with 'var', 'mod'"
  4. "extends: s with 'var'"
  5. "extends: s with 'var'"
  6. "extends: s with 'mod'"
  7. "extends: s_mod with 'mod', 'var"

Describe alternatives you've considered

A first alternative would be to explain people how to inspect the scope extensions term in a compiled specification, but that is both too complicated for many users, and too repetitive.

Additional context

Scope extension properties are stored in the nabl2 custom analysis value, and can be retrieved from there.
Open question: How to access extension analysis for individual rules.

Don't detect deadlocks in nodes depending on knots.

Short description
Detect deadlock on nodes that are part of the deadlocked SCC in the dependency graph.

Problem description.
The concurrent solver uses Chandy-Misra-Haas (CMH) communication deadlock detection algorithm [1] to detect deadlocks. Communication deadlocks occur when there are knots in the wait-for graph.

However, the current implementation allows deadlocks to be detected on nodes that are not part of the knot, but rather only have dependencies on nodes in the knot. For example, consider the following wait-for graph:

1 -> [1, 2, 3]
2 -> [2, 3]
3 -> [2, 3]

(which should be read as: node 1 waits for node 1, 2 and 3, etc...)
In this situation, the actual deadlock occurs in the knot consisting of nodes {2, 3}. However, when node 1 is the last node to become idle, this node will actually detect a deadlock consisting of {1, 2, 3} (as demonstrated in these tests).

From a pragmatic point of view, this is not too much of a problem, because either node 2 or 3 will detect a deadlock of {2, 3}, earlier than node 1. Hence, in case of release/restart, the correct choice will still be made.
However, this relies on the fact that messages are processed in order of arrival, while we formally allow reordering of messages when they originate from different units.

Describe the solution you'd like
In the above example, only the deadlock of {2, 3} should lead to deadlock resolution being executed. This can be done in three different ways:

  1. Using a deadlock detection algorithm that only detects SCCs by construction.

  2. By post-processing when deadlock is detected. This post-processing (on node n) only has to check if all other nodes can reach the detecting node via the wait-for graph (which is equivalent to checking whether n can reach all other nodes in the reverse WF-graph). The opposite is already guaranteed by the deadlock detection itself.

  3. By using a distributed SCC algorithm, to detect the smallest set of deadlocked units.

References
[1] Chandy, K. Mani, Jayadev Misra, and Laura M. Haas. “Distributed Deadlock Detection.” ACM Transactions on Computer Systems 1, no. 2 (May 1, 1983): 144–156. https://doi.org/10.1145/357360.357365.

Invalid errors on Self-imports.

Bug description
A direct self-import in a Statix module gives error messages on rule definitions.

Versions
2.5.17

Steps to reproduce the behavior
Open the following Statix specification:

module mod

imports mod

rules 
  id:
  id().

Observed behavior
Editor error messages:

Unresolved constraint "id"
Unsolved: ?mod.stx-d-2 : ?mod.stx-ty-2
Unsolved: [] is `in-types` of ?mod.stx-ty-2
Unsolved: Rules can only be defined for declared constraints.
Unsolved: Use `c(...) = ... | ...` for functional constraints.

Expected behavior
Either (1) no error messages, or (2) an error on the self-import.

Additional context
Originally reported by @toinehartman.

Spec information in Errors

Short description
When there is an error (exception) in the Statix solver, there is no information what part of a specification caused it.

Problem description.

Describe the solution you'd like

Describe alternatives you've considered

Additional context

Double name qualification

Bug description
Statix qualifies qualified initial constraint names twice.

Versions
Spoofax version: Spoofax 3, 0.16

Steps to reproduce the behavior

  1. In a new project with single-file analysis, move the main statics file to src/statics/statics.stx
  2. In analysis.str, call statix with editor-analyze = stx-editor-analyze(pre-analyze, post-analyze|"statics/statics", "statics/statics!programOk").
  3. Open an arbitrary example file.

Observed behavior
A error statics/statics!statics/programOk(Program([FuncDef2Definition(…)],[IfElse(…,…,…)])).
The error is caused by incorrect name qualification (statics after !).

Expected behavior
Correct initial constraint (statics/statics!programOk) called by statix runtime.

Additional context

Workaround: use unqualified predicate names, such as editor-analyze = stx-editor-analyze(pre-analyze, post-analyze|"statics/statics", "programOk").

Statix AST property debugging

Short description
A mechanism to debug or inspect AST properties that were set to the AST after the Statix solver has finished.

Problem description
AST properties can be set by means of the constraint @astNode.prop := value (or +=).
Correspondingly, AST properties can be read using the stx-get-ast-property(|2) strategy from the Statix API for Stratego.
No other interaction with AST properties exists.
In comparison, the entire scope graph after solving can be exported in a human-readable way, in order to inspect scopes, edges and relations.

Describe the solution you'd like
A menu item in the Spoofax > Statix menu which prints the pre-analyzed AST, annotated with the solved properties (and perhaps excluding all the TermIndex annotations to reduce clutter).
Alternatively, a plain list of term indices with property name and values would be sufficient too, but less user-friendly.

Describe alternatives you've considered

  • Manually attaching a Java debugger.
  • Common debugging tactics for Statix, such as a try { false } | note $[...]@node constraint. However, AST properties are set-only, and can therefore not be retrieved in a message.

Additional context
Original Slack thread: https://slde.slack.com/archives/C7254SF60/p1632562972073600

Parse error on escape characters in template literals

According the the references (https://www.spoofax.dev/references/statix/basic-constraints/#messages):

Just as regular string literals, tabs, newlines and carriage returns can be encoded with \t, \n and \r, respectively.

However, writing \t, \n, or \r in a template literal causes a parse error. For example, the .stxtest program

resolve {} try { false } | error $[test\n test]

results in the error message at the \ token: Syntax error, expected: ']'.

Version info
Spoofax version: 2.6.0.20211108-110809-master
Statix setup: multi-file

Scope Graph Visualization

Short description
Visualization for Scope Graphs.

Problem description.
Textual representations of scope graphs can be difficult to comprehend. Sometimes, a flexible graphical representation of scope graphs can aid understanding of the underlying model.

Describe the solution you'd like
We would like a graphical representation that adheres to the following criteria:

  • Includes all information from the scope graph in a well-formatted manner.
  • Is layed out properly.
  • Has options for filtering
  • Is not tied to a particular editor (e.g. Eclipse), but ideally does not require leaving the editor for inspection.

Background informations
Previous attempts to do similar things:

Custom error message not triggering.

Bug description
When asserting that a query results in exactly one result, my custom error message is not triggered, but instead an error occurs indicating a mismatch between a wildcard and an empty list.

Versions
Spoofax version: Spoofax Eclipse runtime 2.6.0.20220228-175858-master
Statix setup: single-file

Steps to reproduce the behavior
Reproduction in .stxtest file as follows:

resolve classDeclarationOk("MyClass")

signature 

  relations
    ConstructorDeclaration :

rules
  classDeclarationOk : string
  
  classDeclarationOk(id) :- {static_scope}
    new static_scope,
    assertUniqueConstructor(static_scope).

rules
  
  assertUniqueConstructor : scope
  
  assertUniqueConstructor(s) :-
    query ConstructorDeclaration
      filter e
      in s |-> [_] | error "Custom error message".

Observed behavior
Error message in stxtest evaluation:

substitution

analysis
  scope graph

errors
  : [?wld32-2] == []<br>

warnings
notes

Expected behavior
Expected to output the "Custom error message".

--

PS: issue default text suggests to read 'https://www.spoofax.dev/spoofax-docs/howtos/statix/debugging/', but points to a 404, probably should be 'https://www.spoofax.dev/howtos/statix/debugging/'. Same for references/statix.

Custom Error message Influences test result.

Bug description
Custom error messages influence test results.

Versions
Spoofax 3, version 0.19.2 - develop

Steps to reproduce the behavior

  1. Checkout JonathanBrouwer/master-thesis@416852a
  2. Build and test project
  3. Change type_check.stx:83 to expectBetaEq((empty_scope(), e_type), (s, t')) | error $[Custom Message].
  4. Rebuild and run tests.

Observed behavior
First test run, 89/96 tests pass. The second run 85/96 tests pass.

Expected behavior
Both test runs should have the same passing/failing tests.

Additional context
Originally reported by @JonathanBrouwer.

Functional constraint normalization in Template Messages

Bug description
When an functional predicate application is used in a message template, it is not lifted. This causes an exception when loading a specification.

Versions
Spoofax version: Spoofax 2.5.16
Statix setup: Both single-file and multi-file.

Steps to reproduce the behavior
Execute the following test:

resolve false | error $[[fmt()]]

rules
  fmt: -> string
  fmt() = "msg".

Observed behavior
The transformation fails with an exception:

org.metaborg.core.transform.TransformException: Invoking Stratego strategy editor-evaluate-traditional failed at term:
	Test(
  CFalse(
    Message(
      Error(){TermIndex("bugs.stxtest", 1)}
    , Formatted([Term(COp("fmt"{TermIndex("bugs.stxtest", 2)}, []{TermIndex("bugs.stxtest", 4)}){TermIndex("bugs.stxtest", 5)}){TermIndex("bugs.stxtest", 6)}]{TermIndex("bugs.stxtest", 9)}){TermIndex("bugs.stxtest", 10)}
    , NoOrigin(){TermIndex("bugs.stxtest", 11)}
    ){TermIndex("bugs.stxtest", 12)}
  ){TermIndex("bugs.stxtest", 13)}
, [ Rules(
      [ CDecl(InductiveC(){TermIndex("bugs.stxtest", 14)}, "fmt"{TermIndex("bugs.stxtest", 15)}, FunType([]{TermIndex("bugs.stxtest", 17)}, StringSort(){TermIndex("bugs.stxtest", 18)}){TermIndex("bugs.stxtest", 19)}){TermIndex("bugs.stxtest", 20)}
      , Rule(NoName(){TermIndex("bugs.stxtest", 21)}, F("fmt"{TermIndex("bugs.stxtest", 22)}, []{TermIndex("bugs.stxtest", 24)}, Str("msg"{TermIndex("bugs.stxtest", 25)}){TermIndex("bugs.stxtest", 26)}){TermIndex("bugs.stxtest", 27)}, CTrue(){TermIndex("bugs.stxtest", 28)}){TermIndex("bugs.stxtest", 29), DesugaredAxiomRule()}
      ]{TermIndex("bugs.stxtest", 33)}
    ){TermIndex("bugs.stxtest", 34)}
  ]{TermIndex("bugs.stxtest", 37)}
){TermIndex("bugs.stxtest", 38)}
Stratego trace:
	editor_evaluate_traditional_0_0
	editor_evaluate_traditional_0_0
	with_1_1
	evaluate_traditional_0_0
	evaluate_1_0
	with_1_1 <==
	solve_1_2
	stx__solve_constraint_0_2
	stx__solve_constraint_0_4

Expected behavior
We expect the test to execute, and give a .stxresult file containing an error with text "msg".

Additional context

During normalization, functional constraints are translated to predicative constraints. As part of that process, functional constraint instantiations that occur inside other terms are lifted into a separate constraint, and their original position is substituted with a new variable. This lifting is not applied to messages, and hence functional rule application terms remain in the normalized specification, while they should be normalized away.

Stxtest results do not show variables declared after constraints

Bug description
Stxtest results do not show variables declared after constraints.

Versions
Eclipse: org.eclipse.platform.ide 4.16.0.I20200604-0540
Spoofax: org.metaborg.spoofax.eclipse 2.6.0.20210526-183432-master
System: Mac OS X x86_64 10.14.6
multi-file analysis

Steps to reproduce the behavior
Spoofax > Evaluate > Evaluate test

resolve {s1}
  {s2}
  new s1,
  {s3}
  new s3

Observed behavior
Substitution only shows the variable declared before any constraints (s1 and s2), not any of the variables declared after a constraint (s3).

substitution
  s2 |-> ?-s2-1
  s1 |-> #-s1_2-2

Expected behavior
Substitutions shows all declared variables

Additional context
Was debugging a complicated issue and I grouped declarations by section instead of putting them all at the top when I noticed this issue.

Navigation in Test Results

Short description
Navigation in test results/scope graph files.

Problem description.
Test result files can grow pretty large, especially when real-world programs/specifications are involved. This makes them less easy to understand.

Describe the solution you'd like
A quick way to improve comprehension is being able to navigate scope references to their definitions. By integrating scope graph syntax to the language, and analyzing it, this can be implemented using standard reference resolution.

Describe alternatives you've considered
Filtering a scope-graph, but that is hardly possible in an interactive manner when only a textual editor is available.

Additional context
This has been implemented by @MeAmAnUsername (https://github.com/MeAmAnUsername/statixtest). This could serve as a basis to implement

Precompiling query with negation does not terminate.

Bug description
Pre-compiling a query with a negation in its WFL does not terminate

Steps to reproduce the behavior
Use Spoofax > Syntax > Show Pre-compiled AST on

resolve query ()
  filter (~L1)* in new |-> _

signature
  name-resolution
    labels L1

Observed behavior
Transformation running indefinitely.

Expected behavior
Open a file that contains the compiled version of the test.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.