Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 10 additions & 3 deletions src/v2/00_core.dag
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ type DeclaredFuncEnv {

type Node {
name: String
ident: Int

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invariant violation: Making Node.ident a total Int adds a second authority for authored identity and leaves no structural way to represent “no identity,” violating INVARIANTS.md “No duplicate representations” and “No fallbacks that fabricate.”

span: SourceSpan
ident_span: SourceSpan?
// Fractal composition -- operations, capabilities, items are all children
Expand Down Expand Up @@ -1262,7 +1263,7 @@ fn transport_env(t: Node, source_indices: Map<String, NewlineIndex>) -> List<Nod

fn map_children(node: Node, transform: fn(Node) -> Node) -> Node {
Node {
name: node.name, span: node.span, ident_span: node.ident_span,
name: node.name, ident: node.ident, span: node.span, ident_span: node.ident_span,
children: node.children |> map(child => transform(child)),
connective: node.connective,
params: node.params, inferred: node.inferred,
Expand Down Expand Up @@ -1653,10 +1654,16 @@ fn merge_intern_tables(tables: List<InternTable>) -> InternTable {
)
}

fn pre_intern_tokens(tokens: List<Token>, table: InternTable) -> InternTable {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invariant violation: pre_intern_tokens derives identity from every token lexeme, so ids shift with unrelated earlier syntax and literals instead of declaration structure, violating INVARIANTS.md “Root-Cause Depth Invariant” and “No duplicate representations.”

fold(tokens, init: table, f: (t, tok) =>
if tok.text != "" { intern(table: t, s: tok.text).table } else { t }
)
}

fn with_optional_cardinality(n: Node) -> Node {
Node { name: n.name, span: n.span, ident_span: n.ident_span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: CardOptional, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, match_pattern: n.match_pattern, expr_data: n.expr_data }
Node { name: n.name, ident: n.ident, span: n.span, ident_span: n.ident_span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: CardOptional, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, match_pattern: n.match_pattern, expr_data: n.expr_data }
}

fn with_required_cardinality(n: Node) -> Node {
Node { name: n.name, span: n.span, ident_span: n.ident_span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: Required, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, match_pattern: n.match_pattern, expr_data: n.expr_data }
Node { name: n.name, ident: n.ident, span: n.span, ident_span: n.ident_span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: Required, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, match_pattern: n.match_pattern, expr_data: n.expr_data }
}
12 changes: 10 additions & 2 deletions src/v2/02_parse.dag
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import v2.std.core {
error_type, is_compiler_error,
node_name_span, no_span,
NewlineIndex,
InternTable, InternResult, empty_intern_table, intern
InternTable, InternResult, empty_intern_table, intern, intern_find_or_empty, pre_intern_tokens
}

import v2.compiler.languages {
Expand Down Expand Up @@ -880,7 +880,8 @@ fn parse_dotted_ident_rest(tokens: List<Token>, state: ParserState, acc: String,
// =========================================================================

fn parse(tokens: List<Token>, source_indices: Map<String, NewlineIndex>) -> ParseResult {
let state = ParserState { pos: 0, source_indices: source_indices, intern_table: empty_intern_table() }
let pre_interned = pre_intern_tokens(tokens: tokens, table: empty_intern_table())
let state = ParserState { pos: 0, source_indices: source_indices, intern_table: pre_interned }
let r = parse_module(tokens: tokens, state: state)
if has_err(err: r.err) {
ParseResult { module: none, error: r.err, intern_table: r.state.intern_table }
Expand Down Expand Up @@ -923,6 +924,7 @@ fn parse_module(tokens: List<Token>, state: ParserState) -> ModuleResult {
let mod_ir = intern(table: s.intern_table, s: mod_name)
let s = with(s, { intern_table: mod_ir.table })
let mod = module_node(name: mod_name, imports: imports, items: items, span: start_span)
let mod = with(mod, { ident: mod_ir.id })
ModuleResult { module: mod, state: s, err: none }
}

Expand Down Expand Up @@ -986,11 +988,17 @@ fn parse_import(tokens: List<Token>, state: ParserState) -> ImportResult {
if has_err(err: r.err) { return ImportResult { import: err_import, state: r.state, err: r.err } }
let s = skip_newlines(tokens: tokens, state: r.state)
let imp = import_node(module_path: mod_path, is_all: false, specific_names: names, span: start_span)
let imp_ir = intern(table: s.intern_table, s: mod_path)
let s = with(s, { intern_table: imp_ir.table })
let imp = with(imp, { ident: imp_ir.id })
ImportResult { import: imp, state: s, err: none }
} else {
// Bare import (no braces): import all from module.
let s = skip_newlines(tokens: tokens, state: s)
let imp = import_node(module_path: mod_path, is_all: true, specific_names: [], span: start_span)
let imp_ir = intern(table: s.intern_table, s: mod_path)
let s = with(s, { intern_table: imp_ir.table })
let imp = with(imp, { ident: imp_ir.id })
ImportResult { import: imp, state: s, err: none }
}
}
Expand Down
46 changes: 45 additions & 1 deletion src/v2/05_emit_rust.dag
Original file line number Diff line number Diff line change
Expand Up @@ -3554,7 +3554,43 @@ fn emit_typed_record_lit(type_name: String?, fields: List<Node>, parent_enum: St
} else { val_str }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invariant violation: emit_typed_record_lit now completes missing struct fields using EmitTypeSummary plus rust_zero_value string matches on type names, conflicting with INVARIANTS.md “No case enumeration for open sets,” “Heuristics indicate lost structure,” and “No fallbacks that fabricate” (silent completion of literals without IR witnesses).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invariant violation: emit_typed_record_lit now completes missing struct fields using EmitTypeSummary plus rust_zero_value branches on string type names, conflicting with INVARIANTS.md “No case enumeration for open sets,” “Heuristics indicate lost structure,” and “No fallbacks that fabricate” when literals lack explicit field witnesses.

concat(" ", emit_ident(name: f_name, target: Rust), ": ", field_val, ",")
)
let all_field_strs = field_strs
// Fill default values for struct fields not explicitly set in .dag source.
// Only emits defaults if ALL missing fields have known zero values — this avoids
// false positives from cross-module type name collisions in the summary registry.
let provided_names = fields |> map(f => field_init_node_name_at(n: f, source_indices: scope.type_env.source_indices))
let provided_set = fold(provided_names, init: empty_map(), f: (acc, n) => map_insert(acc, n, true))
let summary = lookup_emit_type_summary(emit_info: emit_info, type_name: tn)
let default_strs = match summary {
Some { value: s } =>
let fs = s.field_summaries
let ftm = s.field_type_map
let missing = map_keys(fs) |> filter(k => !emit_map_has(m: provided_set, key: k))
let all_defaultable = missing |> all(fname =>
let has_zv = match map_get(ftm, fname) {
Some { value: ft } => match rust_zero_value(type_name: ft) { Some { value: _ } => true, None => false }
None => false
}
has_zv || is_optional_struct_field(emit_info: emit_info, struct_name: tn, field_name: fname)
)
if all_defaultable {
missing |> flat_map(fname =>
if is_optional_struct_field(emit_info: emit_info, struct_name: tn, field_name: fname) {
[concat(" ", emit_ident(name: fname, target: Rust), ": None,")]
} else {
match map_get(ftm, fname) {
Some { value: ft } =>
match rust_zero_value(type_name: ft) {
Some { value: zv } => [concat(" ", emit_ident(name: fname, target: Rust), ": ", zv, ",")]
None => []
}
None => []
}
}
)
} else { [] }
None => []
}
let all_field_strs = concat(field_strs, default_strs)
let fields_str = all_field_strs |> join(separator: "\n")
let raw = concat(display_tn, " {\n", fields_str, "\n}")
// Rc wrapping is the caller's responsibility (data_decl_body, emit_typed_expr, etc.).
Expand All @@ -3564,6 +3600,14 @@ fn emit_typed_record_lit(type_name: String?, fields: List<Node>, parent_enum: St
}
}

// Zero-value for a Rust type, used to fill struct fields not set in .dag source.
// Returns none if the type has no obvious zero-value (caller must set it explicitly).
fn rust_zero_value(type_name: String) -> String? {
if type_name == "Int" { Some { value: "0" } }
else if type_name == "Bool" { Some { value: "false" } }
else if type_name == "String" { Some { value: concat("\"\"", ".to_string()") } }
else { none }
}

fn emit_typed_bin_op(op: BinOp, algebra_field: AlgebraFieldKind?, left: Node, right: Node, registry: Map<String, ItemInfo>, scope: InferScope, depth: Int, shared_types: Map<String, Bool>, emit_info: EmitGraphInfo) -> String {
let l_str = emit_typed_expr(texpr: left, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info, fuel: 1024)
Expand Down
2 changes: 1 addition & 1 deletion src/v2/stage0/src/v2_compiler_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ pub fn front_end_sources(sources: Rc<Vec<Rc<SourceFile>>>) -> Rc<FrontendResult>
}), |acc: Rc<FrontendAccum>, s: Rc<SourceFile>| { let acc = Rc::try_unwrap(acc).unwrap_or_else(|rc| (*rc).clone()); {
let tokens = tokenize(&s.content.clone(), s.path.clone());
let si = build_newline_index(s.path.clone(), &s.content.clone());
let pr = parse(tokens.clone(), v2_rt::rc_map_insert(v2_rt::rc_empty_map::<Rc<NewlineIndex>>(), si.file.clone(), si.clone()));
let pr = parse(&tokens, v2_rt::rc_map_insert(v2_rt::rc_empty_map::<Rc<NewlineIndex>>(), si.file.clone(), si.clone()));
Rc::new(FrontendAccum {
parse_results: v2_rt::rc_list_push(acc.parse_results, pr.clone()),
newline_indices: v2_rt::rc_list_push(acc.newline_indices, si.clone()),
Expand Down
55 changes: 54 additions & 1 deletion src/v2/stage0/src/v2_compiler_emit_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ pub fn type_variable_node(id: String) -> Rc<Node> {
has_non_tail_self_call: false,
match_pattern: None,
expr_data: Rc::new(ExprData::NoExprData),
ident: 0,
})
}

Expand Down Expand Up @@ -3634,7 +3635,43 @@ let field_val = if needs_wrap.clone() {
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invariant violation: The same missing-field synthesis and rust_zero_value policy are hand-maintained in stage0 alongside 05_emit_rust.dag, violating INVARIANTS.md “No duplicate representations” / “No parallel implementations” for bootstrap-generated Rust versus the .dag source of truth.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invariant violation: The same default-filling block and rust_zero_value live in hand-edited stage0 in parallel with 05_emit_rust.dag, violating INVARIANTS.md “No duplicate representations” / “No parallel implementations” until regeneration is the only write path.

v2_rt::concat(v2_rt::concat(v2_rt::concat(v2_rt::concat(" ".to_string(), emit_ident(f_name.clone(), RenderTarget::Rust)), ": ".to_string()), field_val.clone()), ",".to_string())
}); } __result });
let all_field_strs = field_strs;
let provided_names = Rc::new({ let mut __result = Vec::new(); for f in fields.clone().iter().cloned() { __result.push(field_init_node_name_at(f.clone(), scope.type_env.clone().source_indices.clone())); } __result });
let provided_set = provided_names.iter().cloned().fold(v2_rt::rc_empty_map::<bool>(), |acc: Rc<HashMap<String, bool>>, n: String| v2_rt::rc_map_insert(acc.clone(), n.clone(), true));
let summary = lookup_emit_type_summary(emit_info.clone(), tn.clone());
let default_strs = match summary {
Some(s) => {
let fs = s.field_summaries.clone();
let ftm = s.field_type_map.clone();
let missing = Rc::new({ let mut __result = Vec::new(); for k in Rc::new(v2_rt::map_keys(&fs)).iter().cloned() { if !emit_map_has(provided_set.clone(), k.clone()) { __result.push(k); } } __result });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard default injection against summary name collisions

This default-filling path derives missing from whatever lookup_emit_type_summary(tn) returns, then emits those fields unconditionally when they are defaultable. type_summaries is keyed by bare type name (add_emit_item_summary inserts by summary.name), so two modules can legally collide on names like Config; in that case, emitting module A can pick module B’s summary and inject foreign fields (e.g., Int/Bool/String defaults), producing invalid Rust even when A’s literal already provides all of its real fields.

Useful? React with 👍 / 👎.

let all_defaultable = { let mut __all = true; for fname in missing.clone().iter().cloned() { if !({
let has_zv = match v2_rt::map_get(&ftm, fname.clone()) {
Some(ft) => match rust_zero_value(&ft) {
Some(_) => true,
None => false,
},
None => false,
};
(has_zv.clone() || is_optional_struct_field(emit_info.clone(), tn.clone(), fname.clone()))
}) { __all = false; break; } } __all };
if all_defaultable {
Rc::new({ let mut __result = Vec::new(); for fname in missing.clone().iter().cloned() { __result.extend((*if is_optional_struct_field(emit_info.clone(), tn.clone(), fname.clone()) {
Rc::new(vec![v2_rt::concat(v2_rt::concat(" ".to_string(), emit_ident(fname.clone(), RenderTarget::Rust)), ": None,".to_string())])
} else {
match v2_rt::map_get(&ftm, fname.clone()) {
Some(ft) => match rust_zero_value(&ft) {
Some(zv) => Rc::new(vec![v2_rt::concat(v2_rt::concat(v2_rt::concat(v2_rt::concat(" ".to_string(), emit_ident(fname.clone(), RenderTarget::Rust)), ": ".to_string()), zv.clone()), ",".to_string())]),
None => Rc::new(vec![]),
},
None => Rc::new(vec![]),
}
}).iter().cloned()); } __result })
} else {
Rc::new(vec![])
}
},
None => Rc::new(vec![]),
};
let all_field_strs = v2_rt::concat(field_strs, default_strs);
let fields_str = all_field_strs.join(&"\n".to_string());
let raw = v2_rt::concat(v2_rt::concat(v2_rt::concat(display_tn, " {\n".to_string()), fields_str), "\n}".to_string());
raw
Expand All @@ -3646,6 +3683,22 @@ raw
}
}

pub fn rust_zero_value(type_name: &String) -> Option<String> {
if (type_name.clone().as_str() == "Int".to_string().as_str()) {
Some("0".to_string())
} else {
if (type_name.clone().as_str() == "Bool".to_string().as_str()) {
Some("false".to_string())
} else {
if (type_name.clone().as_str() == "String".to_string().as_str()) {
Some(v2_rt::concat("\"\"".to_string(), ".to_string()".to_string()))
} else {
None
}
}
}
}

pub fn emit_typed_bin_op(op: &BinOp, algebra_field: Option<AlgebraFieldKind>, left: &Rc<Node>, right: &Rc<Node>, registry: &Rc<HashMap<String, Rc<ItemInfo>>>, scope: &Rc<InferScope>, depth: i64, shared_types: &Rc<HashMap<String, bool>>, emit_info: &Rc<EmitGraphInfo>) -> String {
{
let l_str = emit_typed_expr(left.clone(), registry.clone(), &scope, depth.clone(), shared_types.clone(), emit_info.clone(), 1024);
Expand Down
Loading
Loading