Translate project content to English
Make English the project-wide language: all comments, string literals, CLI help text, design docs, journal, agent prompts, and README. Only CLAUDE.md (the user's own instruction file) stays German, and the live conversation between user and Claude continues in German. Adds a new "Project language: English" section to docs/DESIGN.md as the durable convention. No logic changes — translation only. Four internal error strings in the typechecker were retranslated; no test asserts on their wording. Verified: cargo test --workspace passes (44/44). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
//! Built-in Operationen, die der Typchecker (und Codegen) kennen.
|
||||
//! Built-in operations known to the typechecker (and codegen).
|
||||
|
||||
use ailang_core::ast::Type;
|
||||
|
||||
@@ -61,8 +61,8 @@ pub fn install(env: &mut crate::Env) {
|
||||
);
|
||||
}
|
||||
|
||||
/// Liefert die Liste aller registrierten Built-ins. Praktisch für CLI-Subcommand
|
||||
/// `ail builtins`, wenn der LLM erwartete Signaturen prüfen will.
|
||||
/// Returns the list of all registered built-ins. Useful for the CLI subcommand
|
||||
/// `ail builtins`, when the LLM wants to check expected signatures.
|
||||
pub fn list() -> Vec<(&'static str, &'static str)> {
|
||||
vec![
|
||||
("+", "(Int, Int) -> Int"),
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
//! Strukturierte Diagnostics für `ail check --json`.
|
||||
//! Structured diagnostics for `ail check --json`.
|
||||
//!
|
||||
//! Eine [`Diagnostic`] ist die maschinenlesbare Repräsentation eines vom
|
||||
//! Typchecker (oder vorgelagertem Lade-Schritt) gemeldeten Problems.
|
||||
//! Die [`Severity`] serialisiert sich als kleingeschriebener String
|
||||
//! (`"error"` / `"warning"`), [`code`] ist ein stabiler Kebab-Case-Identifier,
|
||||
//! der von Tooling konsumiert werden kann, ohne den `message`-Text zu parsen.
|
||||
//! A [`Diagnostic`] is the machine-readable representation of a problem
|
||||
//! reported by the typechecker (or an upstream load step).
|
||||
//! [`Severity`] serializes as a lowercase string
|
||||
//! (`"error"` / `"warning"`); [`code`] is a stable kebab-case identifier
|
||||
//! that tooling can consume without parsing the `message` text.
|
||||
//!
|
||||
//! Konvention: pro Aufruf von [`super::check_module`] wird höchstens ein
|
||||
//! Diagnostic gemeldet (single-shot). Mehrere Diagnostics pro Lauf sind ein
|
||||
//! späteres Feature; das aktuelle Format erlaubt sie aber bereits.
|
||||
//! Convention: each call to [`super::check_module`] reports at most one
|
||||
//! diagnostic (single-shot). Multiple diagnostics per run are a future
|
||||
//! feature; the current format already allows them.
|
||||
//!
|
||||
//! Stabile Codes (Stand Iteration 5b):
|
||||
//! Stable codes (as of iteration 5b):
|
||||
//! - `schema-mismatch`
|
||||
//! - `unknown-type`
|
||||
//! - `unbound-var`
|
||||
@@ -35,10 +35,10 @@
|
||||
//! - `unknown-module` — `ctx`: `{"module": "<prefix>"}` (Iter 5b)
|
||||
//! - `unknown-import` — `ctx`: `{"module": "<m>", "name": "<def>"}` (Iter 5b)
|
||||
//! - `invalid-def-name` — `ctx`: `{"name": "<n>", "reason": "contains-dot"}` (Iter 5b)
|
||||
//! - `module-not-found` — Workspace-Loader (Iter 5b, im CLI-Pfad)
|
||||
//! - `module-cycle` — Workspace-Loader (Iter 5b, im CLI-Pfad)
|
||||
//! - `module-name-mismatch` — Workspace-Loader (Iter 5b, im CLI-Pfad)
|
||||
//! - `module-hash-mismatch` — Workspace-Loader (Iter 5b, im CLI-Pfad)
|
||||
//! - `module-not-found` — workspace loader (Iter 5b, in the CLI path)
|
||||
//! - `module-cycle` — workspace loader (Iter 5b, in the CLI path)
|
||||
//! - `module-name-mismatch` — workspace loader (Iter 5b, in the CLI path)
|
||||
//! - `module-hash-mismatch` — workspace loader (Iter 5b, in the CLI path)
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -54,11 +54,11 @@ pub struct Diagnostic {
|
||||
pub severity: Severity,
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
/// Welche Top-Level-Definition betroffen ist (falls bekannt). Wird im
|
||||
/// JSON immer ausgegeben — `null`, wenn unbekannt — damit Konsumenten
|
||||
/// das Feld nicht konditionell behandeln müssen.
|
||||
/// Which top-level definition is affected (if known). Always emitted in
|
||||
/// the JSON — `null` when unknown — so consumers don't have to handle
|
||||
/// the field conditionally.
|
||||
pub def: Option<String>,
|
||||
/// Freier strukturierter Kontext. Leer = `{}`.
|
||||
/// Free structured context. Empty = `{}`.
|
||||
pub ctx: serde_json::Value,
|
||||
}
|
||||
|
||||
@@ -93,8 +93,8 @@ mod tests {
|
||||
let d = Diagnostic::error("unbound-var", "unknown identifier: `x`")
|
||||
.with_def("main");
|
||||
let s = serde_json::to_string(&d).unwrap();
|
||||
// Felder müssen alle vorhanden sein, Severity klein, ctx ist ein
|
||||
// leeres Objekt (nicht null, nicht weggelassen).
|
||||
// All fields must be present, severity lowercase, ctx is an
|
||||
// empty object (not null, not omitted).
|
||||
assert!(s.contains("\"severity\":\"error\""), "{s}");
|
||||
assert!(s.contains("\"code\":\"unbound-var\""), "{s}");
|
||||
assert!(s.contains("\"def\":\"main\""), "{s}");
|
||||
|
||||
+106
-105
@@ -1,10 +1,10 @@
|
||||
//! Typchecker für AILang (MVP).
|
||||
//! Typechecker for AILang (MVP).
|
||||
//!
|
||||
//! Monomorpher HM-Subset: keine Type-Variablen im Body, alle Top-Level-Defs
|
||||
//! müssen vollständig annotiert sein. Effekte werden als Set propagiert
|
||||
//! und mit der Annotation am Funktionstyp abgeglichen.
|
||||
//! Monomorphic HM subset: no type variables in the body; all top-level defs
|
||||
//! must be fully annotated. Effects are propagated as a set and reconciled
|
||||
//! against the annotation on the function type.
|
||||
//!
|
||||
//! Eingebaute Operationen werden über [`Builtins`] aufgelöst.
|
||||
//! Built-in operations are resolved via [`Builtins`].
|
||||
|
||||
use ailang_core::ast::*;
|
||||
use ailang_core::Workspace;
|
||||
@@ -110,9 +110,9 @@ pub enum CheckError {
|
||||
type Result<T> = std::result::Result<T, CheckError>;
|
||||
|
||||
impl CheckError {
|
||||
/// Stabiler Kebab-Case-Code für maschinelle Konsumption (`ail check --json`).
|
||||
/// Wird über das `Def`-Wrapping rekursiv durchgereicht — der innere Fehler
|
||||
/// trägt den eigentlichen Code, das Wrapping nur den Def-Kontext.
|
||||
/// Stable kebab-case code for machine consumption (`ail check --json`).
|
||||
/// Passed through recursively via the `Def` wrapping — the inner error
|
||||
/// carries the actual code, the wrapper only the def context.
|
||||
pub fn code(&self) -> &'static str {
|
||||
match self {
|
||||
CheckError::Def(_, inner) => inner.code(),
|
||||
@@ -143,8 +143,8 @@ impl CheckError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Strukturierter Kontext für ein Diagnostic. Kommt direkt im JSON unter
|
||||
/// dem Schlüssel `ctx` an. Leeres Objekt, wenn kein Kontext vorhanden.
|
||||
/// Structured context for a diagnostic. Lands directly in the JSON
|
||||
/// under the key `ctx`. Empty object when no context is available.
|
||||
pub fn ctx(&self) -> serde_json::Value {
|
||||
match self {
|
||||
CheckError::Def(_, inner) => inner.ctx(),
|
||||
@@ -178,8 +178,8 @@ impl CheckError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Falls dieser Fehler durch [`CheckError::Def`] gewrappt ist, liefert
|
||||
/// diese Methode den Namen der betroffenen Def. Sonst `None`.
|
||||
/// If this error is wrapped by [`CheckError::Def`], returns the name
|
||||
/// of the affected def. Otherwise `None`.
|
||||
pub fn def(&self) -> Option<&str> {
|
||||
match self {
|
||||
CheckError::Def(n, _) => Some(n.as_str()),
|
||||
@@ -187,7 +187,7 @@ impl CheckError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Auspacken des potenziell durch [`CheckError::Def`] gewrappten Fehlers.
|
||||
/// Unwraps the error potentially wrapped by [`CheckError::Def`].
|
||||
pub fn inner(&self) -> &CheckError {
|
||||
match self {
|
||||
CheckError::Def(_, inner) => inner.inner(),
|
||||
@@ -195,7 +195,7 @@ impl CheckError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Nicht-`Def`-gewrapptes Message. Ohne `def: ...`-Präfix.
|
||||
/// Non-`Def`-wrapped message. Without the `def: ...` prefix.
|
||||
pub fn message(&self) -> String {
|
||||
format!("{}", self.inner())
|
||||
}
|
||||
@@ -209,18 +209,18 @@ impl CheckError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Top-Level-API für strukturierte Diagnostics.
|
||||
/// Top-level API for structured diagnostics.
|
||||
///
|
||||
/// Leerer Vec = grün. Im aktuellen Stand wird beim ersten Fehler abgebrochen,
|
||||
/// daher enthält der Vec entweder 0 oder 1 Element. Mehrere Diagnostics pro
|
||||
/// Lauf sind ein späteres Feature; das Format erlaubt sie bereits.
|
||||
/// Empty Vec = green. In the current state, processing aborts on the first
|
||||
/// error, so the Vec contains either 0 or 1 element. Multiple diagnostics
|
||||
/// per run are a future feature; the format already allows them.
|
||||
///
|
||||
/// Rückwärtskompatibilität: ein nackter `&Module` wird intern in einen
|
||||
/// Trivial-Workspace gehoben (`modules = {m.name: m}`, `entry = m.name`),
|
||||
/// damit Tooling, das einzelne Module checkt, ohne `Workspace`-Bau auskommt.
|
||||
/// Module mit Imports auf nicht im Trivial-Workspace vorhandene andere
|
||||
/// Module werden hier zwangsläufig `unknown-module`-Fehler bei qualifizierten
|
||||
/// Referenzen liefern — was korrekt ist.
|
||||
/// Backwards compatibility: a bare `&Module` is internally lifted into a
|
||||
/// trivial workspace (`modules = {m.name: m}`, `entry = m.name`) so that
|
||||
/// tooling checking individual modules avoids building a `Workspace`.
|
||||
/// Modules with imports on other modules not present in the trivial
|
||||
/// workspace will inevitably produce `unknown-module` errors on qualified
|
||||
/// references — which is correct.
|
||||
pub fn check_module(m: &Module) -> Vec<Diagnostic> {
|
||||
let mut modules = BTreeMap::new();
|
||||
modules.insert(m.name.clone(), m.clone());
|
||||
@@ -232,33 +232,33 @@ pub fn check_module(m: &Module) -> Vec<Diagnostic> {
|
||||
check_workspace(&ws)
|
||||
}
|
||||
|
||||
/// Top-Level-API für Cross-Module-Typcheck.
|
||||
/// Top-level API for cross-module typecheck.
|
||||
///
|
||||
/// Iteriert über alle Module des Workspaces und prüft jedes mit Zugriff auf
|
||||
/// die Top-Level-Symboltabellen aller anderen Module. Qualifizierte
|
||||
/// Referenzen werden über die Import-Map des jeweiligen Moduls aufgelöst:
|
||||
/// `Term::Var { name }` mit genau einem Punkt im Namen wird als
|
||||
/// `<prefix>.<def>` interpretiert; `<prefix>` ist ein Import-Alias (oder
|
||||
/// der Modulname, falls ohne Alias importiert).
|
||||
/// Iterates over all modules of the workspace and checks each with access
|
||||
/// to the top-level symbol tables of all other modules. Qualified
|
||||
/// references are resolved via the import map of the respective module:
|
||||
/// `Term::Var { name }` with exactly one dot in the name is interpreted
|
||||
/// as `<prefix>.<def>`; `<prefix>` is an import alias (or the module name,
|
||||
/// if imported without an alias).
|
||||
///
|
||||
/// Wie bei `check_module`: pro Lauf maximal **ein** Diagnostic
|
||||
/// (single-shot). Multi-Diagnose ist späteres Feature.
|
||||
/// As with `check_module`: at most **one** diagnostic per run
|
||||
/// (single-shot). Multi-diagnostic is a future feature.
|
||||
pub fn check_workspace(ws: &Workspace) -> Vec<Diagnostic> {
|
||||
// Pass 1: pro Modul Top-Level-Symboltabelle aufbauen — ohne Bodies zu
|
||||
// checken. Damit kann Modul A auf Defs aus Modul B zugreifen, auch
|
||||
// wenn B in der BTreeMap später kommt. Doppelte Def-Namen und
|
||||
// Punkt-im-Def-Namen werden hier sofort gemeldet, weil ohne saubere
|
||||
// Symboltabellen alle weiteren Diagnostics unzuverlässig wären.
|
||||
// Pass 1: build per-module top-level symbol table — without checking
|
||||
// bodies. This lets module A access defs from module B even when B
|
||||
// comes later in the BTreeMap. Duplicate def names and dot-in-def
|
||||
// names are reported here immediately, because without clean symbol
|
||||
// tables all further diagnostics would be unreliable.
|
||||
let module_globals = match build_module_globals(ws) {
|
||||
Ok(g) => g,
|
||||
Err(e) => return vec![e.to_diagnostic()],
|
||||
};
|
||||
|
||||
// Pass 2: pro Modul body-checken. `check_in_workspace` baut den Env
|
||||
// mit zusätzlichen Cross-Module-Globals und einer Import-Map auf.
|
||||
// Iterationsreihenfolge: erst das Eintrittsmodul, dann der Rest in
|
||||
// BTreeMap-Order. Damit ist die erste gemeldete Diagnostik bei
|
||||
// Workspaces deterministisch und nahe am Entry.
|
||||
// Pass 2: body-check per module. `check_in_workspace` builds the env
|
||||
// with additional cross-module globals and an import map.
|
||||
// Iteration order: entry module first, then the rest in
|
||||
// BTreeMap order. This makes the first reported diagnostic for
|
||||
// workspaces deterministic and close to the entry.
|
||||
let mut order: Vec<&String> = Vec::new();
|
||||
if ws.modules.contains_key(&ws.entry) {
|
||||
order.push(&ws.entry);
|
||||
@@ -277,15 +277,15 @@ pub fn check_workspace(ws: &Workspace) -> Vec<Diagnostic> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Ergebnis der Typprüfung eines Moduls: Mapping vom Symbolnamen zum
|
||||
/// (Typ, Hash) — bereit für `manifest`-Ausgabe.
|
||||
/// Result of typechecking a module: mapping from symbol name to
|
||||
/// (type, hash) — ready for `manifest` output.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CheckedModule {
|
||||
pub symbols: IndexMap<String, (Type, String)>,
|
||||
}
|
||||
|
||||
pub fn check(m: &Module) -> Result<CheckedModule> {
|
||||
// Trivial-Workspace: das Modul allein, ohne Cross-Module-Auflösung.
|
||||
// Trivial workspace: the module alone, without cross-module resolution.
|
||||
let mut modules = BTreeMap::new();
|
||||
modules.insert(m.name.clone(), m.clone());
|
||||
let ws = Workspace {
|
||||
@@ -295,7 +295,7 @@ pub fn check(m: &Module) -> Result<CheckedModule> {
|
||||
};
|
||||
let module_globals = build_module_globals(&ws)?;
|
||||
check_in_workspace(m, &ws, &module_globals)?;
|
||||
// Symbole für die Rückgabe sammeln (bestehende Semantik).
|
||||
// Collect symbols for the return value (existing semantics).
|
||||
let mut symbols = IndexMap::new();
|
||||
for def in &m.defs {
|
||||
let h = ailang_core::hash::def_hash(def);
|
||||
@@ -311,9 +311,9 @@ pub fn check(m: &Module) -> Result<CheckedModule> {
|
||||
Ok(CheckedModule { symbols })
|
||||
}
|
||||
|
||||
/// Baut pro Modul die Top-Level-Symboltabelle (für Cross-Module-Lookup),
|
||||
/// ohne Bodies zu prüfen. Dupes und Punkt-im-Def-Namen werden hier sofort
|
||||
/// als Fehler gemeldet — sie würden alle weiteren Diagnostics verfälschen.
|
||||
/// Builds the top-level symbol table per module (for cross-module lookup),
|
||||
/// without checking bodies. Duplicates and dot-in-def names are reported
|
||||
/// here as errors immediately — they would taint all further diagnostics.
|
||||
fn build_module_globals(
|
||||
ws: &Workspace,
|
||||
) -> Result<BTreeMap<String, IndexMap<String, Type>>> {
|
||||
@@ -350,10 +350,10 @@ fn build_module_globals(
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Prüft die Bodies eines einzelnen Moduls im Kontext des Workspaces.
|
||||
/// Annahme: `module_globals` enthält bereits für **alle** Module des
|
||||
/// Workspaces (inklusive `m`) die Top-Level-Symboltabellen — gebaut von
|
||||
/// `build_module_globals`.
|
||||
/// Checks the bodies of a single module in the context of the workspace.
|
||||
/// Assumption: `module_globals` already contains the top-level symbol
|
||||
/// tables for **all** modules of the workspace (including `m`) — built
|
||||
/// by `build_module_globals`.
|
||||
fn check_in_workspace(
|
||||
m: &Module,
|
||||
ws: &Workspace,
|
||||
@@ -362,8 +362,8 @@ fn check_in_workspace(
|
||||
let mut env = Env::new();
|
||||
builtins::install(&mut env);
|
||||
|
||||
// Type-Defs registrieren (lokal pro Modul; ADT-Cross-Module-Sharing ist
|
||||
// explizit nicht Teil von 5b).
|
||||
// Register type defs (local per module; cross-module ADT sharing is
|
||||
// explicitly not part of 5b).
|
||||
for def in &m.defs {
|
||||
if let Def::Type(td) = def {
|
||||
if env.types.contains_key(&td.name) {
|
||||
@@ -394,18 +394,19 @@ fn check_in_workspace(
|
||||
}
|
||||
}
|
||||
|
||||
// Lokale Globals aus der vorgängig gebauten Tabelle übernehmen.
|
||||
// Take local globals from the previously built table.
|
||||
if let Some(g) = module_globals.get(&m.name) {
|
||||
for (n, t) in g {
|
||||
env.globals.insert(n.clone(), t.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Import-Map aufbauen: Alias (oder Modulname, wenn ohne Alias) →
|
||||
// Modulname. Konflikte sind im MVP unzulässig: dieselbe `as`-Klausel
|
||||
// zweimal wäre auffällig und sollte als doppelter Symbolname auffallen
|
||||
// — aktuell „last wins", weil Iter 5b kein eigenes Diagnostic dafür
|
||||
// einführt; falls künftig benötigt → `ambiguous-import`-Code.
|
||||
// Build import map: alias (or module name, if without alias) →
|
||||
// module name. Conflicts are not allowed in the MVP: the same `as`
|
||||
// clause twice would stand out and should surface as a duplicate
|
||||
// symbol name — currently "last wins", because Iter 5b doesn't
|
||||
// introduce a dedicated diagnostic for it; if needed later →
|
||||
// `ambiguous-import` code.
|
||||
let mut import_map: BTreeMap<String, String> = BTreeMap::new();
|
||||
for imp in &m.imports {
|
||||
let key = imp.alias.clone().unwrap_or_else(|| imp.module.clone());
|
||||
@@ -414,9 +415,9 @@ fn check_in_workspace(
|
||||
env.imports = import_map;
|
||||
env.module_globals = module_globals.clone();
|
||||
env.current_module = m.name.clone();
|
||||
// Workspace ist im Env nicht direkt nötig; Cross-Module-Lookup nutzt
|
||||
// ausschließlich `module_globals`. Aber wir behalten ws-Referenz im
|
||||
// Kommentar als Erinnerung, falls künftig ADT-Cross-Module dazukommt.
|
||||
// Workspace isn't directly needed in the env; cross-module lookup uses
|
||||
// only `module_globals`. But we keep the ws reference in the
|
||||
// comment as a reminder, in case cross-module ADTs are added later.
|
||||
let _ = ws;
|
||||
|
||||
for def in &m.defs {
|
||||
@@ -434,8 +435,8 @@ fn check_def(def: &Def, env: &Env) -> Result<()> {
|
||||
}
|
||||
|
||||
fn check_type_def(td: &TypeDef, env: &Env) -> Result<()> {
|
||||
// Felder müssen alle bekannte Typen referenzieren (oder andere ADTs aus
|
||||
// diesem Modul; rekursiv ist erlaubt).
|
||||
// All fields must reference known types (or other ADTs from this
|
||||
// module; recursion is allowed).
|
||||
for c in &td.ctors {
|
||||
for f in &c.fields {
|
||||
check_type_well_formed(f, env)?;
|
||||
@@ -462,7 +463,7 @@ fn check_type_well_formed(t: &Type, env: &Env) -> Result<()> {
|
||||
check_type_well_formed(ret, env)
|
||||
}
|
||||
Type::Var { .. } | Type::Forall { .. } => {
|
||||
// Im MVP keine Polymorphie auf Typebene innerhalb von ADT-Feldern.
|
||||
// No type-level polymorphism inside ADT fields in the MVP.
|
||||
Err(CheckError::PolymorphicNotSupported(
|
||||
"type def".into(),
|
||||
))
|
||||
@@ -537,38 +538,38 @@ fn synth(
|
||||
Literal::Unit => Type::unit(),
|
||||
}),
|
||||
Term::Var { name } => {
|
||||
// 1) Locals haben höchste Priorität — sie können sogar einen
|
||||
// qualifizierten Punkt-Namen schatten, falls jemand einen
|
||||
// Buchstaben-mit-Punkt-Param baut. Das ist im MVP nicht
|
||||
// wirklich erreichbar, aber harmlos.
|
||||
// 1) Locals have highest priority — they can even shadow a
|
||||
// qualified dotted name, if someone builds a
|
||||
// letter-with-dot param. Not really reachable in the MVP,
|
||||
// but harmless.
|
||||
if let Some(t) = locals.get(name) {
|
||||
return Ok(t.clone());
|
||||
}
|
||||
// 2) Lokale Globals.
|
||||
// 2) Local globals.
|
||||
if let Some(t) = env.globals.get(name) {
|
||||
return Ok(t.clone());
|
||||
}
|
||||
// 3) Genau ein Punkt → qualifizierter Cross-Module-Verweis.
|
||||
// Mehr als ein Punkt ist im aktuellen MVP nicht definiert
|
||||
// und fällt unten als `unbound-var` durch.
|
||||
// 3) Exactly one dot → qualified cross-module reference.
|
||||
// More than one dot is undefined in the current MVP
|
||||
// and falls through below as `unbound-var`.
|
||||
if name.matches('.').count() == 1 {
|
||||
let (prefix, suffix) = name.split_once('.').expect("checked");
|
||||
let target_module = match env.imports.get(prefix) {
|
||||
Some(m) => m.clone(),
|
||||
None => {
|
||||
// Selbst-Referenz `<self>.def` ohne Import-Eintrag:
|
||||
// erlauben wir bewusst nicht — Konvention ist, dass
|
||||
// qualifizierte Verweise nur über Imports gehen.
|
||||
// Damit bleibt die Bedeutung von `name` lokal stabil.
|
||||
// Self-reference `<self>.def` without an import entry:
|
||||
// we deliberately disallow this — by convention,
|
||||
// qualified references only go through imports.
|
||||
// That way the meaning of `name` stays locally stable.
|
||||
return Err(CheckError::UnknownModule {
|
||||
module: prefix.to_string(),
|
||||
});
|
||||
}
|
||||
};
|
||||
let g = env.module_globals.get(&target_module).ok_or_else(|| {
|
||||
// Import-Map zeigt auf ein nicht im Workspace geladenes
|
||||
// Modul. Das wäre eigentlich schon vom Workspace-Loader
|
||||
// abgefangen worden; hier defensiv als unknown-module.
|
||||
// Import map points at a module not loaded in the
|
||||
// workspace. The workspace loader should have caught
|
||||
// this already; defensive unknown-module here.
|
||||
CheckError::UnknownModule {
|
||||
module: target_module.clone(),
|
||||
}
|
||||
@@ -700,9 +701,9 @@ fn synth(
|
||||
let mut result_ty: Option<Type> = None;
|
||||
|
||||
for arm in arms {
|
||||
// Lokale Bindings sammeln und ins env pushen, body checken,
|
||||
// wieder poppen — manuell, weil Patterns mehrere Bindings
|
||||
// erzeugen können.
|
||||
// Collect local bindings and push into the env, check the
|
||||
// body, pop again — done manually because patterns can
|
||||
// produce multiple bindings.
|
||||
let bindings = type_check_pattern(&arm.pat, &s_ty, env)?;
|
||||
let mut pushed = Vec::new();
|
||||
for (n, t) in &bindings {
|
||||
@@ -710,7 +711,7 @@ fn synth(
|
||||
pushed.push((n.clone(), prev));
|
||||
}
|
||||
let body_ty = synth(&arm.body, env, locals, effects, in_def)?;
|
||||
// Bindings rückgängig.
|
||||
// Undo bindings.
|
||||
for (n, prev) in pushed.into_iter().rev() {
|
||||
match prev {
|
||||
Some(p) => {
|
||||
@@ -736,7 +737,7 @@ fn synth(
|
||||
covered_ctors.insert(ctor.clone());
|
||||
}
|
||||
Pattern::Lit { .. } => {
|
||||
// Lit-Patterns decken nichts strukturell ab.
|
||||
// Lit patterns don't structurally cover anything.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,8 +773,8 @@ fn synth(
|
||||
}
|
||||
}
|
||||
|
||||
/// Prüft ein Pattern gegen einen Erwartungstyp und gibt die durch das
|
||||
/// Pattern eingeführten Bindings zurück.
|
||||
/// Checks a pattern against an expected type and returns the bindings
|
||||
/// introduced by the pattern.
|
||||
fn type_check_pattern(
|
||||
p: &Pattern,
|
||||
expected: &Type,
|
||||
@@ -793,9 +794,9 @@ fn type_check_pattern(
|
||||
Ok(vec![])
|
||||
}
|
||||
Pattern::Ctor { ctor, fields } => {
|
||||
// MVP: Sub-Patterns von Ctor-Patterns dürfen nur `Var` oder `Wild`
|
||||
// sein. Nested Ctor- oder Lit-Patterns brauchen ein
|
||||
// Decision-Tree-Lowering, das wir im Codegen noch nicht haben.
|
||||
// MVP: sub-patterns of ctor patterns may only be `Var` or `Wild`.
|
||||
// Nested ctor or lit patterns need decision-tree lowering,
|
||||
// which we don't have in codegen yet.
|
||||
for sub in fields {
|
||||
if !matches!(sub, Pattern::Var { .. } | Pattern::Wild) {
|
||||
return Err(CheckError::NestedCtorPatternNotAllowed(ctor.clone()));
|
||||
@@ -805,7 +806,7 @@ fn type_check_pattern(
|
||||
.ctor_index
|
||||
.get(ctor)
|
||||
.ok_or_else(|| CheckError::UnknownCtorInPattern(ctor.clone()))?;
|
||||
// expected muss diese ADT sein.
|
||||
// expected must be this ADT.
|
||||
match expected {
|
||||
Type::Con { name } if name == &cref.type_name => {}
|
||||
_ => {
|
||||
@@ -861,18 +862,18 @@ pub struct Env {
|
||||
pub globals: IndexMap<String, Type>,
|
||||
pub effect_ops: IndexMap<String, builtins::EffectOpSig>,
|
||||
pub types: IndexMap<String, TypeDef>,
|
||||
/// Inverser Index: ctor-name -> Verweis auf die zugehörige ADT.
|
||||
/// Inverse index: ctor name -> reference to the owning ADT.
|
||||
pub ctor_index: IndexMap<String, CtorRef>,
|
||||
/// Import-Map: alias-oder-modulname → tatsächlicher Modulname.
|
||||
/// Genutzt, wenn `Term::Var { name }` einen Punkt enthält
|
||||
/// (qualifizierter Cross-Module-Verweis).
|
||||
/// Import map: alias-or-module-name → actual module name.
|
||||
/// Used when `Term::Var { name }` contains a dot
|
||||
/// (qualified cross-module reference).
|
||||
pub imports: BTreeMap<String, String>,
|
||||
/// Pro Modul des Workspaces dessen Top-Level-Symboltabelle.
|
||||
/// `check_in_workspace` befüllt das aus `build_module_globals`.
|
||||
/// Top-level symbol table per module of the workspace.
|
||||
/// `check_in_workspace` populates this from `build_module_globals`.
|
||||
pub module_globals: BTreeMap<String, IndexMap<String, Type>>,
|
||||
/// Name des aktuell gecheckten Moduls. Genutzt, um beim Var-Lookup
|
||||
/// Selbst-Verweise (Modulname == eigener Name) als lokale Globals zu
|
||||
/// behandeln, ohne den `imports`-Kanal anzufassen.
|
||||
/// Name of the currently checked module. Used during var lookup to
|
||||
/// treat self-references (module name == own name) as local globals,
|
||||
/// without touching the `imports` channel.
|
||||
pub current_module: String,
|
||||
}
|
||||
|
||||
@@ -963,7 +964,7 @@ mod tests {
|
||||
Type::Fn {
|
||||
params: vec![],
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![], // !IO fehlt
|
||||
effects: vec![], // !IO missing
|
||||
},
|
||||
vec![],
|
||||
Term::Do {
|
||||
@@ -1007,7 +1008,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn match_must_be_exhaustive() {
|
||||
// Type Maybe = None | Some(Int); fn f matches nur None -> Fehler.
|
||||
// Type Maybe = None | Some(Int); fn f only matches None -> error.
|
||||
let m = Module {
|
||||
schema: SCHEMA.into(),
|
||||
name: "t".into(),
|
||||
|
||||
Reference in New Issue
Block a user