Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
6f9bd23
WIP: t-substrate-valuebody-map-worker
briansrls Apr 25, 2026
dff8098
WIP: t-substrate-valuebody-map-worker
briansrls Apr 25, 2026
7d9fe37
WIP: t-substrate-valuebody-map-worker
briansrls Apr 25, 2026
3020d76
WIP: t-substrate-valuebody-map-worker
briansrls Apr 25, 2026
32c27f7
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
970c938
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
ab80290
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
7b40bef
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
cf7c6bc
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
a576076
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
211e273
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
c3d82b6
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
3a7ae1b
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
c036dad
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
f78bdc7
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
5f70381
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 2026
805070c
Merge remote-tracking branch 'origin/main' into session/wise-boar-480
briansrls Apr 25, 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
3 changes: 3 additions & 0 deletions scripts/regen_runtime_mirrors.py
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,8 @@ def parse_surface_field_type(record_name: str, label: str, ty: str) -> str:
return "SurfaceExpr"
if record_name == "SurfaceMatchArm" and label == "body":
return "SurfaceExpr"
if record_name == "SurfaceMapEntry" and label == "value":
return "SurfaceExpr"
return rust_type(ty)


Expand Down Expand Up @@ -759,6 +761,7 @@ def render_parse_surface_module(records: dict[str, RecordDef], sums: dict[str, l
render_parse_surface_record(records["SurfaceField"]),
render_parse_surface_record(records["SurfaceVariant"]),
render_parse_surface_record(records["SurfaceRecordField"]),
render_parse_surface_record(records["SurfaceMapEntry"]),
render_parse_surface_record(records["SurfaceMatchArm"]),
render_parse_surface_record(records["SurfacePatternField"]),
render_parse_surface_sum("VariantPayload", sums["VariantPayload"]),
Expand Down
93 changes: 92 additions & 1 deletion src/v3/compiler/parse_parser_body.txt
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,26 @@ impl<'a> Parser<'a> {
span: SourceSpan::new(self.file, kw.span.byte_start, body_end),
});
}
// Sibling lookahead for map literals (`{ String : ...`). Routes
// string-keyed `{...}` data bodies into `parse_map_literal`,
// emitting `SurfaceExpr::Map`. Disambiguation is unambiguous:
// record-literal lookahead requires `{ Ident :`; map-literal
// requires `{ StringLit :`. Bare `{}` (empty) and any other
// shape continue to fall through to `skip_brace_balanced`.
if self.looks_like_map_literal() {
let body_expr = self.parse_map_literal()?;
let body_end = match &body_expr {
SurfaceExpr::Map { span, .. } => span.byte_end,
_ => unreachable!("parse_map_literal always returns Map"),
};
return Ok(SurfaceItem::Data {
name,
ty,
body: Some(body_expr),
body_span: SourceSpan::new(self.file, open_span.byte_start, body_end),
span: SourceSpan::new(self.file, kw.span.byte_start, body_end),
});
}
let end = self.skip_brace_balanced()?;
let body_span = SourceSpan::new(self.file, open_span.byte_start, end);
Ok(SurfaceItem::Data {
Expand Down Expand Up @@ -278,6 +298,76 @@ impl<'a> Parser<'a> {
})
}

/// 3-token lookahead for the map-literal disambiguation in
/// `parse_data_item`. Returns `true` when the next three tokens are
/// `{`, then `StringLit(_)`, then `:` — the unambiguous start of a
/// map-literal entry. Sibling to `looks_like_record_literal`
/// (`{ Ident :`); the two are mutually exclusive at the entry token
/// because a string-literal token is structurally distinct from an
/// identifier token. Empty `{}` falls through to neither (and routes
/// into `skip_brace_balanced` per the existing scaffolded-body
/// path) — string-key non-empty maps are the only shape this lane
/// admits; empty-map syntax is deferred until a real consumer needs
/// it (no consumer in the 22-declaration survey produces an empty
/// map literal at the top level).
fn looks_like_map_literal(&self) -> bool {
let t0 = self.tokens.get(self.pos);
let t1 = self.tokens.get(self.pos + 1);
let t2 = self.tokens.get(self.pos + 2);
match (t0, t1, t2) {
(Some(a), Some(b), Some(c)) => {
matches!(a.kind, TokenKind::LBrace)
&& matches!(b.kind, TokenKind::StringLit(_))
&& matches!(c.kind, TokenKind::Colon)
}
_ => false,
}
}

/// Parse a map literal starting at `{`. Called from
/// `parse_data_item` after `looks_like_map_literal` confirms the
/// lookahead. Reads `"key": expr` pairs separated by `,` (or
/// whitespace), terminated by `}`. Each entry's key is the parsed
/// string-literal contents (the parser only enters this path after
/// `looks_like_map_literal` matched a `StringLit` token; any
/// non-string key here is an internal invariant violation).
fn parse_map_literal(&mut self) -> Result<SurfaceExpr, Diagnostic> {
let open = self.expect_kind(TokenKind::LBrace)?;
let mut entries: Vec<SurfaceMapEntry> = Vec::new();
while !matches!(self.peek().kind, TokenKind::RBrace) {
let key_token = self.bump().clone();
let (key, key_span) = match key_token.kind {
TokenKind::StringLit(s) => (s, key_token.span.clone()),
other => {
return Err(Diagnostic::ParseError {
message: format!(
"expected string-literal key in map literal, got {other:?}"
),
span: key_token.span,
fixes: Vec::new(),
});
}
};
self.expect_kind(TokenKind::Colon)?;
let value = self.parse_expr()?;
let value_end = expr_span(&value).byte_end;
entries.push(SurfaceMapEntry {
key,
key_span: key_span.clone(),
value,
span: SourceSpan::new(self.file, key_span.byte_start, value_end),
});
if matches!(self.peek().kind, TokenKind::Comma) {
self.bump();
}
}
let close = self.expect_kind(TokenKind::RBrace)?;
Ok(SurfaceExpr::Map {
entries,
span: SourceSpan::new(self.file, open.span.byte_start, close.span.byte_end),
})
}

fn parse_named_expr_fields(
&mut self,
) -> Result<(Vec<SurfaceRecordField>, SourceSpan), Diagnostic> {
Expand Down Expand Up @@ -1457,6 +1547,7 @@ pub(crate) fn expr_span(expr: &SurfaceExpr) -> &SourceSpan {
| SurfaceExpr::If { span, .. }
| SurfaceExpr::Match { span, .. }
| SurfaceExpr::Record { span, .. }
| SurfaceExpr::List { span, .. } => span,
| SurfaceExpr::List { span, .. }
| SurfaceExpr::Map { span, .. } => span,
}
}
2,478 changes: 1,242 additions & 1,236 deletions src/v3/compiler/src/bootstrap_generated.rs

Large diffs are not rendered by default.

2,263 changes: 1,133 additions & 1,130 deletions src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions src/v3/compiler/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,7 @@ fn refinement_predicate_out_of_fragment(expr: &SurfaceExpr) -> Option<(&'static
SurfaceExpr::List { elements, .. } => elements
.iter()
.find_map(refinement_predicate_out_of_fragment),
SurfaceExpr::Map { span, .. } => Some(("map literal", span.clone())),
}
}

Expand Down Expand Up @@ -1396,6 +1397,14 @@ fn collect_scope_bound_free_vars(
collect_scope_bound_free_vars(el, scope, out);
}
}
SurfaceExpr::Map { entries, .. } => {
// Map keys are string literals (no scope-bound names).
// Values can reference scope-bound names like any other
// expression position.
for entry in entries {
collect_scope_bound_free_vars(&entry.value, scope, out);
}
}
}
}

Expand Down Expand Up @@ -2420,6 +2429,9 @@ fn lower_data_item(
// DB-10 (3a.2): `data x: T = v` bodies lower by shape:
// - Record literal → ValueBody::Structural (existing path).
// - Scalar literal → ValueBody::Scalar (new path, DB-10).
// - Map literal → ValueBody::Unparsed (parser sub-lane: the
// parser emits `SurfaceExpr::Map`; the sibling substrate
// sub-lane converts this arm to `ValueBody::Map`).
// - Anything else → ValueBody::Unparsed fallback.
let value_body = match body {
Some(SurfaceExpr::Record { fields, .. }) => {
Expand All @@ -2442,6 +2454,15 @@ fn lower_data_item(
None
})
}
// Parser sub-lane: `SurfaceExpr::Map` parses today but does NOT
// yet lower to `ValueBody::Map` — the substrate sub-lane lands
// that variant + its lowering in a follow-up PR. Until then,
// map-bodied declarations route to `ValueBody::Unparsed`,
// identical to the wildcard fallback below; the explicit arm
// exists so `feedback_missing_checks_review_heuristic` flags
// this as the substrate sub-lane's edit point rather than
// hiding the variant under a wildcard.
Some(SurfaceExpr::Map { .. }) => None,
_ => None,
};
let final_body =
Expand Down Expand Up @@ -4073,6 +4094,11 @@ fn collect_lambda_free_names(
collect_lambda_free_names(element, bound, free);
}
}
SurfaceExpr::Map { entries, .. } => {
for entry in entries {
collect_lambda_free_names(&entry.value, bound, free);
}
}
}
}

Expand Down Expand Up @@ -5408,6 +5434,27 @@ fn lower_expr(
segment_spans,
span,
} => lower_field_path_expr(segments, segment_spans, span, dag, scope, symbols),
SurfaceExpr::Map { span, .. } => {
// Map literals are not lowerable as expressions in M1(2.8) —
// the parser admits them only in top-level data-body
// position, where `lower_data_item` handles the variant
// directly. A `SurfaceExpr::Map` reaching `lower_expr` means
// it appeared in expression position (function body, etc.),
// which is out of scope for the parser sub-lane. Fail-closed
// expression-level diagnostic via `unresolved_port` (the
// expression-position diagnostic-port channel) — not
// `report_declaration_error`, which is for declaration-level
// failures and would route the diagnostic through the wrong
// channel here.
unresolved_port(
dag,
Diagnostic::ResolveError {
name: "map literals are not yet supported in expression position (top-level data declarations only)".to_string(),
span: span.clone(),
fixes: Vec::new(),
},
)
}
}
}

Expand Down Expand Up @@ -5789,6 +5836,9 @@ fn is_recursive(expr: &SurfaceExpr, self_name: &str) -> bool {
SurfaceExpr::Record { fields, .. } => {
fields.iter().any(|f| is_recursive(&f.value, self_name))
}
SurfaceExpr::Map { entries, .. } => {
entries.iter().any(|e| is_recursive(&e.value, self_name))
}
}
}

Expand Down Expand Up @@ -5992,6 +6042,16 @@ fn descent_provable(
bindings,
)
}),
SurfaceExpr::Map { entries, .. } => entries.iter().all(|entry| {
descent_provable(
&entry.value,
dag,
first_param_decl,
self_name,
first_param,
bindings,
)
}),
}
}

Expand Down Expand Up @@ -6514,6 +6574,9 @@ impl ClusterDescentChecker<'_> {
SurfaceExpr::List { elements, .. } => elements
.iter()
.all(|element| self.expr(element, bindings, shadowed)),
SurfaceExpr::Map { entries, .. } => entries
.iter()
.all(|entry| self.expr(&entry.value, bindings, shadowed)),
}
}
}
Expand Down Expand Up @@ -6583,6 +6646,11 @@ fn collect_recursive_callees(
collect_recursive_callees(element, function_symbols, shadowed, out);
}
}
SurfaceExpr::Map { entries, .. } => {
for entry in entries {
collect_recursive_callees(&entry.value, function_symbols, shadowed, out);
}
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/v3/compiler/src/lower_helpers_generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ pub fn expr_span(p0: &parse_surface::SurfaceExpr) -> SourceSpan {
elements: __e_elements,
span: __e_span,
} => (__e_span).clone(),
SurfaceExpr::Map {
entries: __e_entries,
span: __e_span,
} => (__e_span).clone(),
}
}
pub fn item_span(p0: &parse_surface::SurfaceItem) -> SourceSpan {
Expand Down
Loading
Loading