Memoize flag alias resolution by context

This commit is contained in:
2026-11-07 15:54:36 +00:00
parent 02cd49d6b6
commit 0d3f7299cc
4 changed files with 73 additions and 32 deletions

View File

@@ -230,14 +230,54 @@ func resolveFlagAliases(context flagAliasContext, mappings map[string]flagAliasM
sources = append(sources, source)
}
sort.Strings(sources)
type terminal struct {
spelling capturedFlagSpelling
active bool
}
resolved := make(map[string]terminal)
visiting := make(map[string]int)
var stack []capturedFlagSpelling
var resolve func(capturedFlagSpelling) (terminal, error)
resolve = func(current capturedFlagSpelling) (terminal, error) {
key := flagSpellingKey(current)
if endpoint, exists := context.canonical[key]; exists {
return terminal{spelling: endpoint, active: true}, nil
}
if result, exists := resolved[key]; exists {
return result, nil
}
if start, exists := visiting[key]; exists {
cycle := append(append([]capturedFlagSpelling(nil), stack[start:]...), current)
spellings := make([]string, 0, len(cycle))
for _, spelling := range cycle {
spellings = append(spellings, spelling.token())
}
return terminal{}, fmt.Errorf("flag alias cycle: %s", strings.Join(spellings, " → "))
}
mapping, exists := mappings[key]
if !exists {
resolved[key] = terminal{}
return terminal{}, nil
}
visiting[key] = len(stack)
stack = append(stack, current)
result, err := resolve(mapping.target)
stack = stack[:len(stack)-1]
delete(visiting, key)
if err != nil {
return terminal{}, err
}
resolved[key] = result
return result, nil
}
for _, sourceKey := range sources {
mapping := mappings[sourceKey]
source := spellingFromKey(sourceKey)
target, active, err := resolveFlagTarget(source, mapping.target, mappings, context.canonical)
terminal, err := resolve(mapping.target)
if err != nil {
return resolvedFlagAliases{}, fmt.Errorf("%s: %w", flagContextDescription(context), err)
}
if !active {
if !terminal.active {
if mapping.scoped {
return resolvedFlagAliases{}, fmt.Errorf("scoped flag alias %s has no canonical destination in %s", source.token(), flagContextDescription(context))
}
@@ -246,38 +286,11 @@ func resolveFlagAliases(context flagAliasContext, mappings map[string]flagAliasM
if _, collision := context.canonical[sourceKey]; collision {
return resolvedFlagAliases{}, fmt.Errorf("flag alias %s conflicts with a canonical spelling in %s", source.token(), flagContextDescription(context))
}
result.bySpelling[sourceKey] = resolvedFlagAlias{source: source, target: target}
result.bySpelling[sourceKey] = resolvedFlagAlias{source: source, target: terminal.spelling}
}
return result, nil
}
func resolveFlagTarget(source, target capturedFlagSpelling, mappings map[string]flagAliasMapping, canonical map[string]capturedFlagSpelling) (capturedFlagSpelling, bool, error) {
visited := map[string]int{flagSpellingKey(source): 0}
chain := []capturedFlagSpelling{source}
current := target
for {
key := flagSpellingKey(current)
if endpoint, exists := canonical[key]; exists {
return endpoint, true, nil
}
if start, exists := visited[key]; exists {
cycle := append(append([]capturedFlagSpelling(nil), chain[start:]...), current)
spellings := make([]string, 0, len(cycle))
for _, spelling := range cycle {
spellings = append(spellings, spelling.token())
}
return capturedFlagSpelling{}, false, fmt.Errorf("flag alias cycle: %s", strings.Join(spellings, " → "))
}
visited[key] = len(chain)
chain = append(chain, current)
next, exists := mappings[key]
if !exists {
return capturedFlagSpelling{}, false, nil
}
current = next.target
}
}
func spellingFromKey(key string) capturedFlagSpelling {
if strings.HasPrefix(key, "short:") {
return capturedFlagSpelling{name: strings.TrimPrefix(key, "short:"), short: true}