Project item conflict diagnostics through enclosing scopes

This commit is contained in:
2026-10-17 04:54:01 +00:00
parent 023e428b9c
commit 8e3a0892d4
4 changed files with 127 additions and 10 deletions

View File

@@ -214,6 +214,9 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
closeErr := access.close()
if len(diagnostics) != 0 {
if mainIsItem {
diagnostics = prependCompositionDiagnosticPaths(diagnostics, destination.Index(0))
}
return nil, cloneSourceInfos(state.sources), diagnostics
}
if closeErr != nil {
@@ -227,7 +230,7 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
}
completed, failures := composeDocuments(documents...)
if len(failures) != 0 {
return nil, cloneSourceInfos(state.sources), prependDiagnosticPaths(failures, destination.Index(0))
return nil, cloneSourceInfos(state.sources), prependCompositionDiagnosticPaths(failures, destination.Index(0))
}
if versionState != nil {
protectionRule := versionState.localRule
@@ -525,7 +528,7 @@ func (s *importTraversal) visitItemSource(canonical string, declaration importDe
itemScope := s.scope
s.scope, s.itemScope, s.itemPrefix, s.itemFallback = parentScope, parentItemScope, parentPrefix, parentFallback
if len(diagnostics) != 0 {
return diagnostics
return projectItemDiagnosticPaths(diagnostics, parentScope, destination)
}
documents := make([]*document.Node, 0, len(itemScope.contributions))
for _, contribution := range itemScope.contributions {
@@ -533,11 +536,7 @@ func (s *importTraversal) visitItemSource(canonical string, declaration importDe
}
completed, diagnostics := composeDocuments(documents...)
if len(diagnostics) != 0 {
prefix := destination.Index(0)
if parentItemScope {
prefix = appendDestination(parentPrefix, prefix)
}
return prependDiagnosticPaths(diagnostics, prefix)
return projectItemDiagnosticPaths(diagnostics, parentScope, destination)
}
itemScope.destination = Path{}
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(itemScope)}
@@ -580,9 +579,12 @@ func importedVersionClaim(documents []*document.Node, composed *composedDocument
return nil
}
func prependDiagnosticPaths(diagnostics []Diagnostic, prefix Path) []Diagnostic {
func prependCompositionDiagnosticPaths(diagnostics []Diagnostic, prefix Path) []Diagnostic {
cloned := append([]Diagnostic(nil), diagnostics...)
for index := range cloned {
if !isCompositionDiagnostic(cloned[index]) {
continue
}
cloned[index].Path = appendDestination(prefix, cloned[index].Path)
cloned[index].Primary = cloneLocation(cloned[index].Primary)
cloned[index].Related = cloneLocations(cloned[index].Related)
@@ -590,6 +592,56 @@ func prependDiagnosticPaths(diagnostics []Diagnostic, prefix Path) []Diagnostic
return cloned
}
func isCompositionDiagnostic(diagnostic Diagnostic) bool {
return diagnostic.Code == CodeComposeConflict || diagnostic.Code == CodeComposeInvalid
}
// On a composition failure only the earlier contributions are needed to locate
// this item. Project one enclosing scope at a time as traversal unwinds; syntax
// and access diagnostics keep their original source-local coordinates.
func projectItemDiagnosticPaths(diagnostics []Diagnostic, parent acquisitionScope, destination Path) []Diagnostic {
for _, diagnostic := range diagnostics {
if isCompositionDiagnostic(diagnostic) {
index := priorSequenceLength([]acquisitionScope{parent}, destination)
return prependCompositionDiagnosticPaths(diagnostics, destination.Index(index))
}
}
return diagnostics
}
// This scan is confined to failed loads, not the successful construction path.
func priorSequenceLength(scopes []acquisitionScope, path Path) int {
length := 0
for _, scope := range scopes {
for _, contribution := range scope.contributions {
if node, present := documentAtPath(contribution.document, path); present && node.Kind == document.Sequence {
length += len(node.Sequence)
}
}
}
return length
}
func projectPriorInputDiagnosticPaths(diagnostics []Diagnostic, scopes []acquisitionScope) []Diagnostic {
cloned := cloneDiagnostics(diagnostics)
for index := range cloned {
if !isCompositionDiagnostic(cloned[index]) {
continue
}
for position, segment := range cloned[index].Path.segments {
if segment.kind != pathSegmentIndex {
continue
}
container := Path{segments: cloned[index].Path.segments[:position]}
cloned[index].Path.segments[position].index += priorSequenceLength(scopes, container)
// Registration does not address paths through item indexes. A prior
// input shifts the outer collection, not its item-local sequences.
break
}
}
return cloned
}
func projectNestedEvidence(composed *composedDocument, scope acquisitionScope) {
if composed == nil {
return

View File

@@ -175,7 +175,7 @@ func acquireUnderlayGroupWithPolicyVersion(group loadGroup, index int, rule *Ver
}
mergeResourceContexts(&contexts, acquired.contexts)
if len(diagnostics) != 0 {
return nil, sources, contexts, diagnostics
return nil, sources, contexts, projectPriorInputDiagnosticPaths(diagnostics, scopes)
}
if acquired.document == nil {
continue