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
31 changes: 28 additions & 3 deletions src/v2/std/lens_verdict.dag
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import v2.std.diagnostic {
textual_whole_file_diagnostic,
}
import v2.std.integer { integer_int_to_decimal_string }
import extdeps.languages.json.emit { quote_json_string }
import v2.std.node { Node, Symbol }
import v2.std.grammar { node_atom_identity_optional }
import v2.std.optional { Absent, Present }
import v2.std.qualified_name { qualified_name_to_dotted_string }
import v2.std.text { String }

Expand Down Expand Up @@ -94,13 +97,35 @@ fn lens_verdict_extent_text(e: Extent) -> String {
// source graph that minted it; resolving it to a byte range needs that graph's SpanIndex, which
// the stages past parse do not carry -- so this renders the identity the diagnostic HAS rather
// than fabricating a file it does not.
//
// AN ATOM ANCHOR ALSO NAMES ITS SYMBOL. When the anchor is an atom -- a bare reference, the node a
// resolver refusal is located at -- its identity is in the node the diagnostic already carries,
// and the persisted row keeps it; rendering only the ordinal dropped the one word a reader needs
// ("which symbol was ambiguous?") on the text surface while the JSON row still had it. Composite
// anchors carry no single name and render as before. Whether a node is an atom, and which, is
// `v2.std.grammar` `node_atom_identity_optional`'s question -- asked there, not re-destructured here.
//
// THE SPELLING IS QUOTED BY ONE LOSSLESS AUTHORITY, NOT PASTED (side-chat hold on gunbc#12004). A
// symbol's lexeme is any string: pasted between delimiters, an embedded delimiter breaks the
// quoting and a newline or ESC goes onto the operator's line raw. `extdeps.languages.json.emit`
// `quote_json_string` is the repository's modeled RFC 8259 string quotation -- total, lossless and
// unambiguous (backslash and quote escaped, every control character a visible escape) -- so the
// atom renders as a JSON string literal and no second escaping rule is written here.
fn lens_verdict_node_atom_suffix(node: Node) -> String {
match node_atom_identity_optional(node: node) {
Present { value: identity } => concat(" ", quote_json_string(s: symbol_lexeme(sym: identity)))
Absent => ""
}
}

fn lens_verdict_node_occurrence_text(node: Node) -> String {
let atom = lens_verdict_node_atom_suffix(node: node)
match node.occurrence_id {
OccurrenceSynthetic => "<synthetic node occurrence>"
OccurrenceSynthetic => concat("<synthetic node occurrence", concat(atom, ">"))
OccurrenceMinted { id: id } =>
concat("<node occurrence #", concat(integer_int_to_decimal_string(value: id.value), ">"))
concat("<node occurrence #", concat(integer_int_to_decimal_string(value: id.value), concat(atom, ">")))
OccurrenceProjected { id: id, caused_by: _ } =>
concat("<projected node occurrence #", concat(integer_int_to_decimal_string(value: id.value), ">"))
concat("<projected node occurrence #", concat(integer_int_to_decimal_string(value: id.value), concat(atom, ">")))
}
}

Expand Down
56 changes: 54 additions & 2 deletions src/v2/test/claim/native_route/native_refusal_detail_test.dag
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,16 @@ import v2.std.diagnostic {
SourcePortLocus,
Textual,
diagnostics_fatal,
diagnostics_fatal_reason
diagnostics_fatal_reason,
ExternalContractUnknown,
Unavailable,
node_locus
}
import v2.std.lens_verdict { lens_verdict_diagnostic_locus_text }
import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly }
import v2.std.logic { Bool }
import v2.std.node { Symbol }
import v2.std.node { Symbol, Atom, Conj, TypeNode, node_synthetic }
import v2.std.compilers.lexing { symbol_intern_lexeme }
import v2.std.optional { Absent, Optional, Present, optional_absent, optional_present }
import v2.std.qualified_name { qualified_name_from_dotted_string }
import v2.std.text { String }
Expand Down Expand Up @@ -361,6 +365,18 @@ test fn operator_line_renders_the_source_occurrence_holds() -> Bool {
}
}

// AND THE OCCURRENCE NAMES ITS SYMBOL. The unbound refusal is anchored at the atom
// ndp_absent_callee; the persisted row carried that identity while the text line rendered only
// the ordinal, so a reader of the line could not tell which reference failed. A locus renderer
// that dropped the atom reds here.
test fn operator_line_names_the_refused_symbol_holds() -> Bool {
match ndp_unbound_refusal() {
Absent => false
Present { value: d } =>
string_contains(s: lens_verdict_diagnostic_locus_text(d: diagnostics_fatal(d: d)), pattern: " \"ndp_absent_callee\">")
}
}

// THE DENOMINATOR: a module that resolves. Without it every claim above is satisfied by a route
// that refuses everything for any reason.
test fn clean_module_resolves_holds() -> Bool {
Expand All @@ -374,3 +390,39 @@ test fn clean_module_resolves_holds() -> Bool {
}
}
}

// ── the atom spelling is QUOTED, losslessly, through the real locus renderer (side-chat hold on
// gunbc#12004). Each arm renders a node-anchored diagnostic whose anchor atom's lexeme carries the
// hostile characters, and asserts the EXACT text: a raw paste reds the escaping arms, and a
// renderer that dropped the name reds the ordinary one.

fn atom_locus_text(lexeme: String) -> String {
lens_verdict_diagnostic_locus_text(d: Diagnostic {
reason: ^native_refusal_detail_quoting_probe,
at: node_locus(node: node_synthetic(kind: TypeNode { connective: Atom { identity: symbol_intern_lexeme(lexeme: lexeme) } }, children: [])),
correction: Unavailable { reason: ExternalContractUnknown }
})
}

test fn an_ordinary_atom_renders_readably_holds() -> Bool {
atom_locus_text(lexeme: "SubstrateInputsOnly") == "<synthetic node occurrence \"SubstrateInputsOnly\">"
}

test fn an_embedded_backtick_and_backslash_stay_unambiguous_holds() -> Bool {
atom_locus_text(lexeme: concat("left`right", concat(from_code_point(cp: 92), "x")))
== concat("<synthetic node occurrence \"left`right", concat(from_code_point(cp: 92), concat(from_code_point(cp: 92), "x\">")))
}

test fn newline_cr_and_esc_become_visible_escapes_holds() -> Bool {
let bs = from_code_point(cp: 92)
atom_locus_text(lexeme: concat("a", concat(from_code_point(cp: 10), concat("b", concat(from_code_point(cp: 13), concat("c", concat(from_code_point(cp: 27), "d")))))))
== concat("<synthetic node occurrence \"a", concat(bs, concat("nb", concat(bs, concat("rc", concat(bs, "u001Bd\">"))))))
}

test fn a_composite_anchor_renders_unchanged_holds() -> Bool {
lens_verdict_diagnostic_locus_text(d: Diagnostic {
reason: ^native_refusal_detail_quoting_probe,
at: node_locus(node: node_synthetic(kind: TypeNode { connective: Conj }, children: [])),
correction: Unavailable { reason: ExternalContractUnknown }
}) == "<synthetic node occurrence>"
}