fix(parsing): resolve an alias to an anchor on a null value
An anchor on an empty mapping value (`a: &anchor`) failed to register, so a
later `*anchor` errored as an undefined alias. Two bugs compounded:
- The parser dispatched the anchor-only value position to a nested block
mapping, absorbing the sibling key as the anchored node's child instead of
emitting the empty anchored node. It now emits an empty anchored node when
only a sibling mapping key follows, since the indented and inline-nested
cases are already handled by earlier branches.
- The stream composer registered an anchor by its raw scalar text, so an
anchor on an empty scalar came back as "" and a typed scalar (`&a 42`) as
text. It now registers the composed value, fixing a latent typed-anchor bug.
Fixes yaml-test-suite 6KGN; raises parse-level conformance to 306/333 (91.9%)
and value-level to 199/199 (100.0%), with the should-error rate unchanged.
This commit is contained in:
parent
7879818ce5
commit
e3caa5be0d
11
CHANGELOG.md
11
CHANGELOG.md
@ -84,6 +84,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- An anchor on an empty mapping value (`a: &anchor`) now registers that value's implicit null node,
|
||||
so a later alias resolves to null instead of failing as an undefined alias. Two bugs compounded:
|
||||
the parser dispatched the anchor-only value position to a nested block mapping, absorbing the
|
||||
sibling key (`b:`) as the anchored node's child instead of emitting the empty anchored node; and
|
||||
the stream composer registered an anchor by its raw scalar text rather than the resolved node, so
|
||||
even a registered anchor on an empty scalar came back as `""` and a typed scalar (`&a 42`) came
|
||||
back as text. The value position now emits an empty anchored node when only a sibling mapping key
|
||||
follows, and an anchor registers the composed value. Fixes yaml-test-suite 6KGN and a latent
|
||||
typed-scalar anchor bug; raises parse-level conformance to 306/333 (91.9%) and value-level to
|
||||
199/199 (100.0%), with the should-error rate unchanged.
|
||||
|
||||
- A `%YAML` directive with a higher minor version than 1.2 is now accepted rather than rejected. The
|
||||
spec calls for processing a document with a higher minor version, with a warning, so `%YAML 1.3` is
|
||||
read as a YAML 1.x document instead of erroring. The version check now accepts any well-formed
|
||||
|
||||
@ -92,7 +92,9 @@ private extension PureYAML.Parsing.EventStreamComposer {
|
||||
) throws {
|
||||
let model = try scalarParser.composeScalarValue(value, tag: tag, style: style, mark: mark)
|
||||
if let anchor {
|
||||
anchors[anchor] = .string(value)
|
||||
// Register the resolved node, not the raw text: an alias resolves to the anchored node's
|
||||
// value, so an empty scalar anchors null and a typed scalar (`&a 42`) anchors that value.
|
||||
anchors[anchor] = model
|
||||
}
|
||||
try complete(.init(
|
||||
value: model,
|
||||
|
||||
@ -33,6 +33,13 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
minimumColumn: cursor.current?.mark.column,
|
||||
)
|
||||
}
|
||||
// A value position holding only properties (`a: &anchor`) followed by a sibling mapping key
|
||||
// has an empty (null) node carrying those properties, not a nested mapping: the indented
|
||||
// nested case is consumed above by the indent branch, and an inline nested mapping is caught
|
||||
// by the mapping-key-with-same-line-node branch, so a bare mapping key here is a sibling.
|
||||
if properties != .none, cursor.current?.kind.isMappingKey == true {
|
||||
return try emitEmptyNodeOrThrow(properties: properties)
|
||||
}
|
||||
if properties != .none || hasNodeOnSameLine(after: valueIndicator) {
|
||||
return try parseNode(properties: properties)
|
||||
}
|
||||
|
||||
26
Tests/AnchorOnNullValueTests.swift
Normal file
26
Tests/AnchorOnNullValueTests.swift
Normal file
@ -0,0 +1,26 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("Anchor on a null or typed value resolves through its alias")
|
||||
struct AnchorOnNullValueTests {
|
||||
/// An anchor on a `key:` with no value anchors that key's implicit null node, so a later alias
|
||||
/// resolves to null rather than failing as undefined (yaml-test-suite 6KGN). The empty value is
|
||||
/// followed by a sibling mapping key, which must not be absorbed as a nested mapping.
|
||||
@Test("An anchor on an empty mapping value registers a null node for its alias")
|
||||
func test_anchorOnEmptyValueResolvesToNull() throws {
|
||||
let value = try PureYAML.parse("a: &anchor\nb: *anchor\n")
|
||||
let mapping = try requireMapping(value)
|
||||
#expect(mapping?["a"] == .null)
|
||||
#expect(mapping?["b"] == .null)
|
||||
}
|
||||
|
||||
/// The anchored node is resolved before registration, so a typed scalar anchors its typed value
|
||||
/// (here an integer), not its raw text: the alias must come back as the same `.int(42)`.
|
||||
@Test("An anchor on a typed scalar registers the resolved value, not its raw text")
|
||||
func test_anchorOnTypedScalarResolvesToTypedValue() throws {
|
||||
let value = try PureYAML.parse("a: &a 42\nb: *a\n")
|
||||
let mapping = try requireMapping(value)
|
||||
#expect(mapping?["a"] == .int(42))
|
||||
#expect(mapping?["b"] == .int(42))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user