Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2f98027
Encapsulate semantic graph assembly and work accounting
edigonzales Nov 6, 2026
02aeadd
Separate name, reference, and translation binding
edigonzales Nov 6, 2026
0761a80
Give type binding and type-family rules dedicated owners
edigonzales Nov 6, 2026
6988e56
Separate expression and object path binding
edigonzales Nov 6, 2026
25a4bfb
Move declaration, inheritance, and constraint rules into binders
edigonzales Nov 6, 2026
473062e
Wire semantic binders through explicit graph capabilities
edigonzales Nov 6, 2026
4421dd8
Separate graph definitions from type and expression completion
edigonzales Nov 6, 2026
272181a
Preserve ambiguous reference resolutions and ordered candidates
edigonzales Nov 6, 2026
d127fba
Report actual compiler phase completion status
edigonzales Nov 6, 2026
18f25cb
Derive analysis coverage from syntax and semantic results
edigonzales Nov 6, 2026
027b68c
Own LSP document transitions in one registry
edigonzales Nov 6, 2026
c744ce6
Centralize physical LSP file identities
edigonzales Nov 6, 2026
2361f3f
Own LSP input readiness and cancellation
edigonzales Nov 6, 2026
cac1abe
Publish LSP diagnostics from one accepted source view
edigonzales Nov 6, 2026
ca0d7ed
Fix builtin type completion and portable coverage
edigonzales Nov 6, 2026
f14adb2
Merge commit 'ca0d7ed0ef850a3fffd0a67f60e2d13777e9f874' into codex/in…
edigonzales Nov 7, 2026
ee6ba2a
Refer to the JVM module registry in contributor instructions
edigonzales Nov 7, 2026
b72e879
Reconcile compiler contract bindings with the integrated sources
edigonzales Nov 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
[Verträge](docs/README.md) und [Modulgrenzen](docs/architecture.md).
Veröffentlichungen und Arbeiten an Produktionssystemen brauchen einen
konkreten Auftrag.
- Nutze [Jenesis 0.18.1 mit Java 25](docs/development/build.md) für alle elf
JVM-Module. POMs definieren Koordinaten und Abhängigkeiten. Der Gradle-Wrapper
- Nutze [Jenesis 0.18.1 mit Java 25](docs/development/build.md) für alle
[registrierten JVM-Module](build/interlis/JvmBuildInputs.java). POMs definieren
Koordinaten und Abhängigkeiten. Der Gradle-Wrapper
baut die beiden Wasm-Backends mit explizitem geprüftem ConsumerSnapshot.
`build/jenesis/`, `build/interlis/`, `build/InterlisJvmBuild.java` und der
Upstreampin sind Quellen. Verwende die gezielten Clean-Sichten.
Expand Down
38 changes: 38 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,37 @@ Namen dienen der Sprachsemantik und ersetzen keine dauerhafte Identität.
Fachliche Reihenfolgen liegen in geordneten Werten. Maps dienen als Indizes.
Ein veröffentlichter Snapshot bleibt nach Schliessen des Workspace lesbar.

## Aufbau des semantischen Graphen

`GraphBuilder` komponiert die lauflokalen Fachbinder und ruft sie in einer
festen Reihenfolge auf. Der Deklarationsindex enthält die Eingaben für die
Namensauflösung und den bedarfsweisen Aufbau. Die Deklarationsbindung
liefert bereits vorhandene Symbole und baut weitere Deklarationen bei
Bedarf auf. Die Typbindung kann einen Domänen- oder Attributtyp
reservieren und auflösen, bevor die zugehörigen Constraints gebunden
werden. Rekursionsmengen und fachliche Memos gehören den jeweiligen
Bindern.

`SemanticBuildState` speichert den entstehenden Graphen. Seine lesende
Sicht ist intern und lauflokal. Typisierte Schreiboperationen unterscheiden
die Definition und Vervollständigung von Symbolen, Typ- und
Ausdrucksdaten sowie Basiskanten und effektive Member. Die Fachbinder
erhalten die zu ihrer Aufgabe passenden Schreiboperationen. Referenzen
verwenden eine gemeinsame Registrierung und Sequenz. Geordnete
Zwischenstände erhalten die ursprüngliche Reihenfolge und Herkunft.

Namensbindung, Typbindung, Ausdrücke, Objektpfade, Constraints,
Vererbung und Übersetzung haben eigene Zuständigkeiten. Benannte
Kollaboratoren und Rückaufrufe verbinden die bedarfsweisen Operationen
in einem Lauf. Ein Arbeitsbudget zählt auch bereits verbrauchte
Overlayarbeit. Die Diagnostik schreibt in den laufgebundenen Collector.

Die Übersetzungsbindung prüft und ergänzt die gebundenen Paare bis zum
Abschluss. Nach Transfernamespace und Predefined-Abschluss friert der
Speicher den Graphen ein. Die Coverage wird für den Lauf ermittelt.
`CompilationGate` verwendet Analyse, Diagnostik und Closure für die
einzige Entscheidung über die gültige Schemasicht.

## Lauf und Hostgrenzen

1. Der Host erfasst Quellen und Konfiguration und beschafft fehlende
Expand All @@ -71,6 +102,13 @@ Ein veröffentlichter Snapshot bleibt nach Schliessen des Workspace lesbar.
4. Der Host verwirft veraltete Ergebnisse vor Übernahme oder Veröffentlichung.
Darstellung und Layout bleiben vom Compiler getrennt.

Im LSP-Host verwaltet `LspDocumentRegistry` die akzeptierten Dokumentstände
und `LspFileIdentityRegistry` die vom Dateisystem bestätigten Identitäten.
`LspInputReadiness` ordnet Dokumentoperationen und Anfragen während laufender
Dateiaufnahmen. `AnalysisScheduler` serialisiert Zustandsänderungen auf der
Owner-Queue und führt Compilerarbeit auf unveränderlichen Captures ausserhalb
der Queue aus. Diagnosen werden aus einer akzeptierten Quellsicht veröffentlicht.

Laufbeobachtung und Abbruch gelten pro Operation. Ein interner Fehler erzeugt
einen eigenen Ergebniszustand. Projektoren weisen ihre Fähigkeiten explizit
aus und melden ununterstützte Elemente, bevor Daten verloren gehen. Reine
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package guru.interlis.compiler.internal;

import guru.interlis.diagnostics.Diagnostic;
import guru.interlis.diagnostics.DiagnosticId;
import guru.interlis.diagnostics.Phase;
import guru.interlis.diagnostics.Severity;
import guru.interlis.execution.CancellationToken;
import guru.interlis.model.AnalysisCoverage;
import guru.interlis.model.CoverageStatus;
import guru.interlis.model.ErrorType;
import guru.interlis.source.SourceId;
import guru.interlis.source.SourceRange;
import guru.interlis.syntax.SyntaxCoverage;
import guru.interlis.syntax.SyntaxSnapshot;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/** Reports coverage of the requested analysis, using the same diagnostic selection as the snapshot. */
final class AnalysisCoverageBuilder {
private static final List<Phase> SEMANTIC_PHASES = List.of(
Phase.DECLARATION_INDEXING, Phase.BINDING, Phase.TYPE_CHECKING, Phase.SEMANTIC_CHECKING);

private AnalysisCoverageBuilder() { }

private record Location(Phase phase, Optional<SourceId> source) { }

static AnalysisCoverage build(Closure closure, Set<SourceId> syntaxSources,
Map<SourceId, SyntaxSnapshot> syntax, List<Diagnostic> diagnostics,
Set<DiagnosticId> resolutionDiagnostics, GraphBuilder.GraphResult graph,
CancellationToken token) {
var modeledSources = new HashSet<SourceId>();
closure.models().forEach(model -> modeledSources.add(model.sourceId()));
var typeFailures = new HashSet<>(graph.fragmentTypeFailures());
for (var type : graph.graph().types()) {
token.throwIfCancellationRequested();
if (type instanceof ErrorType error) typeFailures.addAll(error.diagnosticIds());
}

var blockers = new HashMap<Location, DiagnosticId>();
var unsupportedSources = new HashSet<SourceId>();
var unsupportedEntries = new ArrayList<AnalysisCoverage.CoverageEntry>();
for (var diagnostic : diagnostics) {
token.throwIfCancellationRequested();
if (diagnostic.severity() != Severity.ERROR || resolutionDiagnostics.contains(diagnostic.id())) continue;
if (diagnostic.primaryRange().isPresent() && !closure.contains(diagnostic.primaryRange().orElseThrow())) continue;
var feature = graph.unsupportedFeatures().get(diagnostic.id());
if (feature != null) {
unsupportedSources.add(feature.source());
unsupportedEntries.add(new AnalysisCoverage.CoverageEntry(Phase.SEMANTIC_CHECKING,
Optional.of(feature.source()), CoverageStatus.NOT_IMPLEMENTED,
Optional.of(feature.feature()), Optional.of(diagnostic.id())));
continue;
}
var source = diagnostic.primaryRange().map(SourceRange::source);
if (SEMANTIC_PHASES.contains(diagnostic.phase()))
blockers.putIfAbsent(new Location(diagnostic.phase(), source), diagnostic.id());
// A generic semantic-rule or binding diagnostic may also have prevented an actual type result.
if (typeFailures.contains(diagnostic.id()))
blockers.putIfAbsent(new Location(Phase.TYPE_CHECKING, source), diagnostic.id());
}

var entries = new ArrayList<AnalysisCoverage.CoverageEntry>();
for (var source : syntaxSources) {
token.throwIfCancellationRequested();
var snapshot = syntax.get(source);
if (snapshot == null) continue;
var coverage = snapshot.coverage();
var location = Optional.of(source);
entries.add(entry(Phase.LEXING, location, syntaxStatus(snapshot, Phase.LEXING, coverage.lexicallyComplete())));
boolean parsed = coverage.parseComplete() && coverage.consumedEntireInput();
entries.add(entry(Phase.PARSING, location, syntaxStatus(snapshot, Phase.PARSING, parsed)));
if (!modeledSources.contains(source)) continue;
for (var phase : SEMANTIC_PHASES) {
var status = incompleteStatus(coverage);
if (status == CoverageStatus.COMPLETE) {
if (!parsed || !coverage.lexicallyComplete() || blockers.containsKey(new Location(phase, location)))
status = CoverageStatus.BLOCKED_BY_ERROR;
else if (phase == Phase.SEMANTIC_CHECKING && unsupportedSources.contains(source))
status = CoverageStatus.NOT_IMPLEMENTED;
}
entries.add(entry(phase, location, status));
}
}
for (var phase : SEMANTIC_PHASES) {
var diagnostic = blockers.get(new Location(phase, Optional.empty()));
if (diagnostic != null)
entries.add(new AnalysisCoverage.CoverageEntry(phase, Optional.empty(), CoverageStatus.BLOCKED_BY_ERROR,
Optional.empty(), Optional.of(diagnostic)));
}
entries.addAll(unsupportedEntries);
return new AnalysisCoverage(entries);
}

private static CoverageStatus syntaxStatus(SyntaxSnapshot snapshot, Phase phase, boolean structurallyComplete) {
var status = incompleteStatus(snapshot.coverage());
if (status != CoverageStatus.COMPLETE) return status;
boolean error = snapshot.diagnostics().stream()
.anyMatch(diagnostic -> diagnostic.severity() == Severity.ERROR && diagnostic.phase() == phase);
return structurallyComplete && !error ? CoverageStatus.COMPLETE : CoverageStatus.BLOCKED_BY_ERROR;
}

private static CoverageStatus incompleteStatus(SyntaxCoverage coverage) {
if (coverage.cancelled()) return CoverageStatus.CANCELLED;
if (coverage.budgetExceeded()) return CoverageStatus.BUDGET_EXCEEDED;
return CoverageStatus.COMPLETE;
}

private static AnalysisCoverage.CoverageEntry entry(Phase phase, Optional<SourceId> source, CoverageStatus status) {
return new AnalysisCoverage.CoverageEntry(phase, source, status, Optional.empty(), Optional.empty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
import guru.interlis.compiler.CompilerServices;
import guru.interlis.compiler.SourceSet;
import guru.interlis.diagnostics.Diagnostic;
import guru.interlis.diagnostics.DiagnosticId;
import guru.interlis.diagnostics.Phase;
import guru.interlis.execution.CancellationReason;
import guru.interlis.execution.CancellationToken;
import guru.interlis.execution.OperationCancelledException;
import guru.interlis.execution.PhaseStatus;
import guru.interlis.execution.RunLifecycle;
import guru.interlis.model.SnapshotId;
import guru.interlis.source.ContentFingerprint;
Expand All @@ -26,6 +30,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/** One run-local compiler execution; mutable state is never published. */
final class CompilationRun {
Expand All @@ -47,7 +52,7 @@ final class CompilationRun {

RunOutput execute(ParseMode mode, RunLifecycle lifecycle) {
SourceSet sourceSet = request.sources();
verifySourceSet(sourceSet, lifecycle);
var resolutionDiagnostics = verifySourceSet(sourceSet, lifecycle);
parseSources(sourceSet, mode, lifecycle);
token.throwIfCancellationRequested();
IndexedDeclarations declarations = new DeclarationIndexer().index(syntax.values().stream().toList(), reuse);
Expand All @@ -57,7 +62,9 @@ RunOutput execute(ParseMode mode, RunLifecycle lifecycle) {
Closure closure;
var indexPhase = lifecycle.beginPhase(Phase.DECLARATION_INDEXING,
new guru.interlis.execution.RunEvent.PhaseMetadata(modelIndex.all().size()));
PhaseStatus indexStatus = PhaseStatus.FAILED;
try {
token.throwIfCancellationRequested();
RootSelector rootSelector = new RootSelector();
roots = rootSelector.select(request.roots(), modelIndex, collector);
ImportBinder importBinder = new ImportBinder();
Expand All @@ -66,8 +73,12 @@ RunOutput execute(ParseMode mode, RunLifecycle lifecycle) {
closure = new Closure(List.of(), List.of(), java.util.Set.of(), List.of(),
closure.diagnostics(), new java.util.LinkedHashSet<>(closure.reasons()));
}
indexStatus = PhaseStatus.SUCCEEDED;
} catch (OperationCancelledException | AnalysisBudgetExceededException cancelled) {
indexStatus = PhaseStatus.CANCELLED;
throw cancelled;
} finally {
indexPhase.finish(guru.interlis.execution.PhaseStatus.SUCCEEDED);
indexPhase.finish(indexStatus);
}

CompilationFingerprinter fingerprinter = new CompilationFingerprinter();
Expand Down Expand Up @@ -99,28 +110,39 @@ RunOutput execute(ParseMode mode, RunLifecycle lifecycle) {
var bindingPhase = lifecycle.beginPhase(Phase.BINDING,
new guru.interlis.execution.RunEvent.PhaseMetadata(declarations.all().size()));
GraphBuilder.GraphResult graphResult;
PhaseStatus bindingStatus = PhaseStatus.FAILED;
try {
token.throwIfCancellationRequested();
graphResult = builder.build();
bindingStatus = PhaseStatus.SUCCEEDED;
} catch (OperationCancelledException | AnalysisBudgetExceededException cancelled) {
bindingStatus = PhaseStatus.CANCELLED;
throw cancelled;
} finally {
bindingPhase.finish(guru.interlis.execution.PhaseStatus.SUCCEEDED);
bindingPhase.finish(bindingStatus);
}
token.throwIfCancellationRequested();
List<Diagnostic> diagnostics = collector.freeze();
var syntaxSources = RequestDiagnosticSelector.syntaxSources(closure, request, token);
var requestDiagnostics = RequestDiagnosticSelector.select(diagnostics, closure, roots, syntaxSources, token);
var coverage = AnalysisCoverageBuilder.build(closure, syntaxSources, syntax, requestDiagnostics,
resolutionDiagnostics, graphResult, token);
AnalysisProvenance provenance = new AnalysisProvenance(Fingerprints.COMPILER_VERSION,
Fingerprints.GRAMMAR_VERSION, Fingerprints.SEMANTIC_PROFILE,
sourceSet.fingerprint().value(), request.options().fingerprint(),
sourceFingerprints(sourceSet));
AnalysisSnapshot analysis = new AnalysisSnapshot(snapshotId, graphResult.graph(),
revisions(sourceSet), syntax, diagnostics,
RequestDiagnosticSelector.select(diagnostics, closure, roots, request, token), graphResult.coverage(),
revisions(sourceSet), syntax, diagnostics, requestDiagnostics, coverage,
fingerprint, provenance);
return new RunOutput(analysis, closure, roots, graphResult);
}

private void verifySourceSet(SourceSet sourceSet, RunLifecycle lifecycle) {
private Set<DiagnosticId> verifySourceSet(SourceSet sourceSet, RunLifecycle lifecycle) {
var phase = lifecycle.beginPhase(Phase.SOURCE_RESOLUTION,
new guru.interlis.execution.RunEvent.PhaseMetadata(sourceSet.sources().size()));
PhaseStatus status = PhaseStatus.FAILED;
try {
token.throwIfCancellationRequested();
long total = 0;
for (SourceText text : sourceSet.sources().values()) {
total = Math.addExact(total, text.length());
Expand All @@ -130,16 +152,23 @@ private void verifySourceSet(SourceSet sourceSet, RunLifecycle lifecycle) {
request.options().budget().maxTotalSourceUtf16Length());
}
new SourceSetVerifier().verify(sourceSet, collector);
collector.importAll(sourceSet.resolutionDiagnostics());
var importedDiagnostics = Set.copyOf(collector.importAll(sourceSet.resolutionDiagnostics()).values());
status = PhaseStatus.SUCCEEDED;
return importedDiagnostics;
} catch (OperationCancelledException | AnalysisBudgetExceededException cancelled) {
status = PhaseStatus.CANCELLED;
throw cancelled;
} finally {
phase.finish(guru.interlis.execution.PhaseStatus.SUCCEEDED);
phase.finish(status);
}
}

private void parseSources(SourceSet sourceSet, ParseMode mode, RunLifecycle lifecycle) {
var phase = lifecycle.beginPhase(Phase.LEXING,
new guru.interlis.execution.RunEvent.PhaseMetadata(sourceSet.sources().size()));
PhaseStatus status = PhaseStatus.FAILED;
try {
token.throwIfCancellationRequested();
List<SourceId> ids = new ArrayList<>(sourceSet.sources().keySet());
ids.sort(Comparator.comparing(SourceId::value));
for (SourceId id : ids) {
Expand All @@ -158,8 +187,8 @@ private void parseSources(SourceSet sourceSet, ParseMode mode, RunLifecycle life
new ParseOptions(mode, java.util.Optional.empty(),
request.options().budget()), token);
}
if(snapshot.coverage().budgetExceeded())throw new guru.interlis.execution.OperationCancelledException(
guru.interlis.execution.CancellationReason.RESOURCE_LIMIT);
if (snapshot.coverage().budgetExceeded())
throw new OperationCancelledException(CancellationReason.RESOURCE_LIMIT);
syntax.put(id, snapshot);
collector.importAll(snapshot.diagnostics());
}
Expand All @@ -168,8 +197,12 @@ private void parseSources(SourceSet sourceSet, ParseMode mode, RunLifecycle life
throw new AnalysisBudgetExceededException("maxTokens",
request.options().budget().maxTokens());
}
status = PhaseStatus.SUCCEEDED;
} catch (OperationCancelledException | AnalysisBudgetExceededException cancelled) {
status = PhaseStatus.CANCELLED;
throw cancelled;
} finally {
phase.finish(guru.interlis.execution.PhaseStatus.SUCCEEDED);
phase.finish(status);
}
}

Expand Down
Loading
Loading