Iter 4a: ail check --json mit strukturierten Diagnostics
Neue Top-Level-API check_module(&Module) -> Vec<Diagnostic> in ailang-check, plus stabile Codes (unbound-var, type-mismatch, arity-mismatch, non-exhaustive-match, unknown-ctor-in-pattern, duplicate-def, …). CLI bekommt --json-Flag für maschinenlesbares Output, Exit 1 bei Errors. Text-Modus unverändert. E2E-Test check_json_unbound_var sichert das Format ab.
This commit is contained in:
Generated
+2
@@ -20,6 +20,8 @@ version = "0.0.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"ailang-core",
|
"ailang-core",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+42
-5
@@ -42,7 +42,13 @@ enum Cmd {
|
|||||||
json: bool,
|
json: bool,
|
||||||
},
|
},
|
||||||
/// Typprüft ein Modul.
|
/// Typprüft ein Modul.
|
||||||
Check { path: PathBuf },
|
Check {
|
||||||
|
path: PathBuf,
|
||||||
|
/// Strukturierte Diagnostics als JSON-Array auf stdout.
|
||||||
|
/// Exit-Code 1, wenn mindestens ein Error gemeldet wird.
|
||||||
|
#[arg(long)]
|
||||||
|
json: bool,
|
||||||
|
},
|
||||||
/// Schreibt LLVM IR (.ll) für das Modul.
|
/// Schreibt LLVM IR (.ll) für das Modul.
|
||||||
EmitIr {
|
EmitIr {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
@@ -163,10 +169,41 @@ fn main() -> Result<()> {
|
|||||||
print!("{}", ailang_core::pretty::module(&one));
|
print!("{}", ailang_core::pretty::module(&one));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Cmd::Check { path } => {
|
Cmd::Check { path, json } => {
|
||||||
let m = ailang_core::load_module(&path)?;
|
if json {
|
||||||
let r = ailang_check::check(&m)?;
|
// JSON-Modus: stdout enthält ausschließlich das Diagnostics-
|
||||||
println!("ok ({} symbols)", r.symbols.len());
|
// Array. Schema-Mismatch wird als strukturiertes Diagnostic
|
||||||
|
// emittiert (mit Code `schema-mismatch`); echte I/O-Fehler
|
||||||
|
// propagieren als fatal, weil sie keinen Modul-bezogenen
|
||||||
|
// Diagnostic-Kontext haben.
|
||||||
|
let diags = match ailang_core::load_module(&path) {
|
||||||
|
Ok(m) => ailang_check::check_module(&m),
|
||||||
|
Err(ailang_core::Error::SchemaMismatch { expected, got }) => {
|
||||||
|
vec![ailang_check::Diagnostic::error(
|
||||||
|
"schema-mismatch",
|
||||||
|
format!(
|
||||||
|
"schema mismatch: expected {expected:?}, got {got:?}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.with_ctx(serde_json::json!({
|
||||||
|
"expected": expected,
|
||||||
|
"actual": got,
|
||||||
|
}))]
|
||||||
|
}
|
||||||
|
Err(e) => return Err(anyhow::anyhow!(e)),
|
||||||
|
};
|
||||||
|
println!("{}", serde_json::to_string(&diags)?);
|
||||||
|
if diags
|
||||||
|
.iter()
|
||||||
|
.any(|d| matches!(d.severity, ailang_check::Severity::Error))
|
||||||
|
{
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let m = ailang_core::load_module(&path)?;
|
||||||
|
let r = ailang_check::check(&m)?;
|
||||||
|
println!("ok ({} symbols)", r.symbols.len());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Cmd::EmitIr { path, out } => {
|
Cmd::EmitIr { path, out } => {
|
||||||
let m = ailang_core::load_module(&path)?;
|
let m = ailang_core::load_module(&path)?;
|
||||||
|
|||||||
@@ -63,3 +63,34 @@ fn list_sum_via_match() {
|
|||||||
let stdout = build_and_run("list.ail.json");
|
let stdout = build_and_run("list.ail.json");
|
||||||
assert_eq!(stdout.trim(), "42");
|
assert_eq!(stdout.trim(), "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Schützt das `--json`-Diagnostic-Format für Tooling-Konsumenten.
|
||||||
|
/// `broken_unbound.ail.json` referenziert eine nicht-existente Variable;
|
||||||
|
/// erwartet wird Exit-Code 1 und mindestens ein Diagnostic mit
|
||||||
|
/// `severity == "error"` und `code == "unbound-var"`.
|
||||||
|
#[test]
|
||||||
|
fn check_json_unbound_var() {
|
||||||
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
|
let src = workspace.join("examples").join("broken_unbound.ail.json");
|
||||||
|
|
||||||
|
let output = Command::new(ail_bin())
|
||||||
|
.args(["check", src.to_str().unwrap(), "--json"])
|
||||||
|
.output()
|
||||||
|
.expect("ail check --json failed to run");
|
||||||
|
|
||||||
|
let code = output.status.code().expect("process terminated by signal");
|
||||||
|
assert_eq!(code, 1, "expected exit code 1, stderr: {}", String::from_utf8_lossy(&output.stderr));
|
||||||
|
|
||||||
|
let stdout = String::from_utf8(output.stdout).expect("stdout utf8");
|
||||||
|
let diags: serde_json::Value =
|
||||||
|
serde_json::from_str(stdout.trim()).expect("stdout must be valid JSON");
|
||||||
|
let arr = diags.as_array().expect("diagnostics must be a JSON array");
|
||||||
|
assert!(
|
||||||
|
arr.iter().any(|d| {
|
||||||
|
d.get("severity").and_then(|v| v.as_str()) == Some("error")
|
||||||
|
&& d.get("code").and_then(|v| v.as_str()) == Some("unbound-var")
|
||||||
|
}),
|
||||||
|
"expected at least one error diagnostic with code unbound-var; got: {stdout}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ license.workspace = true
|
|||||||
ailang-core.workspace = true
|
ailang-core.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
//! Strukturierte Diagnostics für `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.
|
||||||
|
//!
|
||||||
|
//! 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.
|
||||||
|
//!
|
||||||
|
//! Stabile Codes (Stand Iteration 4):
|
||||||
|
//! - `schema-mismatch`
|
||||||
|
//! - `unknown-type`
|
||||||
|
//! - `unbound-var`
|
||||||
|
//! - `type-mismatch` — `ctx`: `{"expected": "...", "actual": "..."}`
|
||||||
|
//! - `arity-mismatch` — `ctx`: `{"expected": N, "actual": M}`
|
||||||
|
//! - `unknown-ctor`
|
||||||
|
//! - `unknown-effect-op`
|
||||||
|
//! - `non-exhaustive-match` — `ctx`: `{"missing": ["..."]}`
|
||||||
|
//! - `unknown-ctor-in-pattern`
|
||||||
|
//! - `nested-ctor-pattern-not-allowed`
|
||||||
|
//! - `duplicate-def`
|
||||||
|
//! - `not-a-function`
|
||||||
|
//! - `undeclared-effect`
|
||||||
|
//! - `fn-type-required`
|
||||||
|
//! - `param-count-mismatch`
|
||||||
|
//! - `polymorphic-not-supported`
|
||||||
|
//! - `const-has-effects`
|
||||||
|
//! - `pattern-type-mismatch`
|
||||||
|
//! - `primitive-needs-wildcard`
|
||||||
|
//! - `duplicate-type`
|
||||||
|
//! - `duplicate-ctor`
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum Severity {
|
||||||
|
Error,
|
||||||
|
Warning,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Clone)]
|
||||||
|
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.
|
||||||
|
pub def: Option<String>,
|
||||||
|
/// Freier strukturierter Kontext. Leer = `{}`.
|
||||||
|
pub ctx: serde_json::Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic {
|
||||||
|
pub fn error(code: impl Into<String>, message: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
severity: Severity::Error,
|
||||||
|
code: code.into(),
|
||||||
|
message: message.into(),
|
||||||
|
def: None,
|
||||||
|
ctx: serde_json::Value::Object(serde_json::Map::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_def(mut self, def: impl Into<String>) -> Self {
|
||||||
|
self.def = Some(def.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_ctx(mut self, ctx: serde_json::Value) -> Self {
|
||||||
|
self.ctx = ctx;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serializes_with_stable_shape() {
|
||||||
|
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).
|
||||||
|
assert!(s.contains("\"severity\":\"error\""), "{s}");
|
||||||
|
assert!(s.contains("\"code\":\"unbound-var\""), "{s}");
|
||||||
|
assert!(s.contains("\"def\":\"main\""), "{s}");
|
||||||
|
assert!(s.contains("\"ctx\":{}"), "{s}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ctx_can_carry_structured_data() {
|
||||||
|
let d = Diagnostic::error("type-mismatch", "type mismatch")
|
||||||
|
.with_ctx(serde_json::json!({"expected": "Int", "actual": "Bool"}));
|
||||||
|
let s = serde_json::to_string(&d).unwrap();
|
||||||
|
assert!(s.contains("\"expected\":\"Int\""), "{s}");
|
||||||
|
assert!(s.contains("\"actual\":\"Bool\""), "{s}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,9 @@ use indexmap::IndexMap;
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
pub mod builtins;
|
pub mod builtins;
|
||||||
|
pub mod diagnostic;
|
||||||
|
|
||||||
|
pub use diagnostic::{Diagnostic, Severity};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum CheckError {
|
pub enum CheckError {
|
||||||
@@ -61,6 +64,9 @@ pub enum CheckError {
|
|||||||
#[error("type `{ty}` has no constructor `{ctor}`")]
|
#[error("type `{ty}` has no constructor `{ctor}`")]
|
||||||
UnknownCtor { ty: String, ctor: String },
|
UnknownCtor { ty: String, ctor: String },
|
||||||
|
|
||||||
|
#[error("unknown constructor `{0}` in pattern")]
|
||||||
|
UnknownCtorInPattern(String),
|
||||||
|
|
||||||
#[error("constructor `{ty}/{ctor}` arity: expected {expected} fields, got {got}")]
|
#[error("constructor `{ty}/{ctor}` arity: expected {expected} fields, got {got}")]
|
||||||
CtorArity {
|
CtorArity {
|
||||||
ty: String,
|
ty: String,
|
||||||
@@ -83,10 +89,116 @@ pub enum CheckError {
|
|||||||
|
|
||||||
#[error("duplicate constructor: `{ctor}` (in types `{a}` and `{b}`)")]
|
#[error("duplicate constructor: `{ctor}` (in types `{a}` and `{b}`)")]
|
||||||
DuplicateCtor { ctor: String, a: String, b: String },
|
DuplicateCtor { ctor: String, a: String, b: String },
|
||||||
|
|
||||||
|
#[error("duplicate definition: `{0}`")]
|
||||||
|
DuplicateDef(String),
|
||||||
|
|
||||||
|
#[error("nested constructor pattern not allowed in MVP: `{0}`")]
|
||||||
|
NestedCtorPatternNotAllowed(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, 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.
|
||||||
|
pub fn code(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
CheckError::Def(_, inner) => inner.code(),
|
||||||
|
CheckError::TypeMismatch { .. } => "type-mismatch",
|
||||||
|
CheckError::UnknownIdent(_) => "unbound-var",
|
||||||
|
CheckError::UnknownEffectOp(_) => "unknown-effect-op",
|
||||||
|
CheckError::NotAFunction(..) => "not-a-function",
|
||||||
|
CheckError::ArityMismatch { .. } => "arity-mismatch",
|
||||||
|
CheckError::UndeclaredEffect(_) => "undeclared-effect",
|
||||||
|
CheckError::FnTypeRequired(..) => "fn-type-required",
|
||||||
|
CheckError::ParamCountMismatch { .. } => "param-count-mismatch",
|
||||||
|
CheckError::PolymorphicNotSupported(_) => "polymorphic-not-supported",
|
||||||
|
CheckError::ConstHasEffects(..) => "const-has-effects",
|
||||||
|
CheckError::UnknownType(_) => "unknown-type",
|
||||||
|
CheckError::UnknownCtor { .. } => "unknown-ctor",
|
||||||
|
CheckError::UnknownCtorInPattern(_) => "unknown-ctor-in-pattern",
|
||||||
|
CheckError::CtorArity { .. } => "arity-mismatch",
|
||||||
|
CheckError::NonExhaustive { .. } => "non-exhaustive-match",
|
||||||
|
CheckError::PrimitiveNeedsWildcard(_) => "primitive-needs-wildcard",
|
||||||
|
CheckError::PatternTypeMismatch { .. } => "pattern-type-mismatch",
|
||||||
|
CheckError::DuplicateType(_) => "duplicate-type",
|
||||||
|
CheckError::DuplicateCtor { .. } => "duplicate-ctor",
|
||||||
|
CheckError::DuplicateDef(_) => "duplicate-def",
|
||||||
|
CheckError::NestedCtorPatternNotAllowed(_) => "nested-ctor-pattern-not-allowed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Strukturierter Kontext für ein Diagnostic. Kommt direkt im JSON unter
|
||||||
|
/// dem Schlüssel `ctx` an. Leeres Objekt, wenn kein Kontext vorhanden.
|
||||||
|
pub fn ctx(&self) -> serde_json::Value {
|
||||||
|
match self {
|
||||||
|
CheckError::Def(_, inner) => inner.ctx(),
|
||||||
|
CheckError::TypeMismatch { expected, got } => {
|
||||||
|
serde_json::json!({"expected": expected, "actual": got})
|
||||||
|
}
|
||||||
|
CheckError::ArityMismatch { expected, got, .. } => {
|
||||||
|
serde_json::json!({"expected": expected, "actual": got})
|
||||||
|
}
|
||||||
|
CheckError::CtorArity {
|
||||||
|
expected, got, ..
|
||||||
|
} => serde_json::json!({"expected": expected, "actual": got}),
|
||||||
|
CheckError::ParamCountMismatch {
|
||||||
|
ty_count,
|
||||||
|
param_count,
|
||||||
|
..
|
||||||
|
} => serde_json::json!({"expected": ty_count, "actual": param_count}),
|
||||||
|
CheckError::NonExhaustive { missing, .. } => {
|
||||||
|
serde_json::json!({"missing": missing})
|
||||||
|
}
|
||||||
|
_ => serde_json::Value::Object(serde_json::Map::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Falls dieser Fehler durch [`CheckError::Def`] gewrappt ist, liefert
|
||||||
|
/// diese Methode den Namen der betroffenen Def. Sonst `None`.
|
||||||
|
pub fn def(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
CheckError::Def(n, _) => Some(n.as_str()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Auspacken des potenziell durch [`CheckError::Def`] gewrappten Fehlers.
|
||||||
|
pub fn inner(&self) -> &CheckError {
|
||||||
|
match self {
|
||||||
|
CheckError::Def(_, inner) => inner.inner(),
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Nicht-`Def`-gewrapptes Message. Ohne `def: ...`-Präfix.
|
||||||
|
pub fn message(&self) -> String {
|
||||||
|
format!("{}", self.inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_diagnostic(&self) -> Diagnostic {
|
||||||
|
let mut d = Diagnostic::error(self.code(), self.message()).with_ctx(self.ctx());
|
||||||
|
if let Some(name) = self.def() {
|
||||||
|
d = d.with_def(name);
|
||||||
|
}
|
||||||
|
d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Top-Level-API für strukturierte 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.
|
||||||
|
pub fn check_module(m: &Module) -> Vec<Diagnostic> {
|
||||||
|
match check(m) {
|
||||||
|
Ok(_) => Vec::new(),
|
||||||
|
Err(e) => vec![e.to_diagnostic()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Ergebnis der Typprüfung eines Moduls: Mapping vom Symbolnamen zum
|
/// Ergebnis der Typprüfung eines Moduls: Mapping vom Symbolnamen zum
|
||||||
/// (Typ, Hash) — bereit für `manifest`-Ausgabe.
|
/// (Typ, Hash) — bereit für `manifest`-Ausgabe.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -123,13 +235,21 @@ pub fn check(m: &Module) -> Result<CheckedModule> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 1b: alle Top-Level-Werte-Symbole registrieren.
|
// Pass 1b: alle Top-Level-Werte-Symbole registrieren. Doppelte Namen
|
||||||
|
// (zwischen fn/const) sind unzulässig — das schützt davor, dass sich
|
||||||
|
// zwei Defs einen Namensraum teilen.
|
||||||
for def in &m.defs {
|
for def in &m.defs {
|
||||||
match def {
|
match def {
|
||||||
Def::Fn(f) => {
|
Def::Fn(f) => {
|
||||||
|
if env.globals.contains_key(&f.name) {
|
||||||
|
return Err(CheckError::DuplicateDef(f.name.clone()));
|
||||||
|
}
|
||||||
env.globals.insert(f.name.clone(), f.ty.clone());
|
env.globals.insert(f.name.clone(), f.ty.clone());
|
||||||
}
|
}
|
||||||
Def::Const(c) => {
|
Def::Const(c) => {
|
||||||
|
if env.globals.contains_key(&c.name) {
|
||||||
|
return Err(CheckError::DuplicateDef(c.name.clone()));
|
||||||
|
}
|
||||||
env.globals.insert(c.name.clone(), c.ty.clone());
|
env.globals.insert(c.name.clone(), c.ty.clone());
|
||||||
}
|
}
|
||||||
Def::Type(_) => {}
|
Def::Type(_) => {}
|
||||||
@@ -485,12 +605,18 @@ fn type_check_pattern(
|
|||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
Pattern::Ctor { ctor, fields } => {
|
Pattern::Ctor { ctor, fields } => {
|
||||||
let cref = env.ctor_index.get(ctor).ok_or_else(|| {
|
// MVP: Sub-Patterns von Ctor-Patterns dürfen nur `Var` oder `Wild`
|
||||||
CheckError::UnknownCtor {
|
// sein. Nested Ctor- oder Lit-Patterns brauchen ein
|
||||||
ty: "<unknown>".into(),
|
// Decision-Tree-Lowering, das wir im Codegen noch nicht haben.
|
||||||
ctor: ctor.clone(),
|
for sub in fields {
|
||||||
|
if !matches!(sub, Pattern::Var { .. } | Pattern::Wild) {
|
||||||
|
return Err(CheckError::NestedCtorPatternNotAllowed(ctor.clone()));
|
||||||
}
|
}
|
||||||
})?;
|
}
|
||||||
|
let cref = env
|
||||||
|
.ctor_index
|
||||||
|
.get(ctor)
|
||||||
|
.ok_or_else(|| CheckError::UnknownCtorInPattern(ctor.clone()))?;
|
||||||
// expected muss diese ADT sein.
|
// expected muss diese ADT sein.
|
||||||
match expected {
|
match expected {
|
||||||
Type::Con { name } if name == &cref.type_name => {}
|
Type::Con { name } if name == &cref.type_name => {}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"schema": "ailang/v0",
|
||||||
|
"name": "broken_unbound",
|
||||||
|
"imports": [],
|
||||||
|
"defs": [
|
||||||
|
{
|
||||||
|
"kind": "fn",
|
||||||
|
"name": "main",
|
||||||
|
"type": {
|
||||||
|
"k": "fn",
|
||||||
|
"params": [],
|
||||||
|
"ret": { "k": "con", "name": "Int" },
|
||||||
|
"effects": []
|
||||||
|
},
|
||||||
|
"params": [],
|
||||||
|
"doc": "Referenziert eine nicht-existente Variable -> unbound-var.",
|
||||||
|
"body": { "t": "var", "name": "does_not_exist" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user