Memoize flag alias resolution by context
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Consolidated audit catalog
|
# Consolidated audit catalog
|
||||||
|
|
||||||
Status: **12 distinct open findings: 12 P3**, consolidated from 30
|
Status: **11 distinct open findings: 11 P3**, consolidated from 30
|
||||||
numbered entries in seven audit documents. Reviewed audit state:
|
numbered entries in seven audit documents. Reviewed audit state:
|
||||||
`db732e3a1de55c844f77870e1c6db683c2685d5d`.
|
`db732e3a1de55c844f77870e1c6db683c2685d5d`.
|
||||||
|
|
||||||
@@ -274,6 +274,10 @@ ownership; it is separate from the closed-reporting-handle retention in F07.
|
|||||||
|
|
||||||
## F13 — P3: Flag-alias compilation repeatedly walks shared chain suffixes
|
## F13 — P3: Flag-alias compilation repeatedly walks shared chain suffixes
|
||||||
|
|
||||||
|
**Complete.** Each effective context memoizes its target resolutions, including
|
||||||
|
inactive endpoints, while retaining canonical precedence and cycle diagnostics.
|
||||||
|
Focused alias and discovery tests pass, including shared suffixes and registration.
|
||||||
|
|
||||||
For A chained effective flag mappings across C contexts, independent target
|
For A chained effective flag mappings across C contexts, independent target
|
||||||
walks can perform Θ(CA²) target examinations, including inactive chains. Each
|
walks can perform Θ(CA²) target examinations, including inactive chains. Each
|
||||||
source repeats work already performed for another source's suffix.
|
source repeats work already performed for another source's suffix.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Audit remediation implementation plan
|
# Audit remediation implementation plan
|
||||||
|
|
||||||
Status: in progress; Stages 1–14 complete.
|
Status: in progress; Stages 1–15 complete.
|
||||||
|
|
||||||
Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint,
|
Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint,
|
||||||
intervening changes recorded decisions and retired the completed older
|
intervening changes recorded decisions and retired the completed older
|
||||||
@@ -500,6 +500,12 @@ clear, with selected syntax and provenance preserved.
|
|||||||
|
|
||||||
## Stage 15 — Memoize each context's flag-alias graph
|
## Stage 15 — Memoize each context's flag-alias graph
|
||||||
|
|
||||||
|
**Complete.** Context-local traversal memoizes canonical and unavailable
|
||||||
|
endpoints, with an active stack for cycle diagnostics. Source review confirms
|
||||||
|
resolved suffixes are reused within each context. Focused alias, registration,
|
||||||
|
cycle and discovery tests passed offline with Go 1.27.0 and `-mod=readonly`,
|
||||||
|
including shared suffixes and unavailable destinations.
|
||||||
|
|
||||||
Findings: F13. Source: `flag_aliases.go`.
|
Findings: F13. Source: `flag_aliases.go`.
|
||||||
|
|
||||||
1. Replace independent suffix walks with context-local visitation and memoized
|
1. Replace independent suffix walks with context-local visitation and memoized
|
||||||
|
|||||||
@@ -230,14 +230,54 @@ func resolveFlagAliases(context flagAliasContext, mappings map[string]flagAliasM
|
|||||||
sources = append(sources, source)
|
sources = append(sources, source)
|
||||||
}
|
}
|
||||||
sort.Strings(sources)
|
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 {
|
for _, sourceKey := range sources {
|
||||||
mapping := mappings[sourceKey]
|
mapping := mappings[sourceKey]
|
||||||
source := spellingFromKey(sourceKey)
|
source := spellingFromKey(sourceKey)
|
||||||
target, active, err := resolveFlagTarget(source, mapping.target, mappings, context.canonical)
|
terminal, err := resolve(mapping.target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resolvedFlagAliases{}, fmt.Errorf("%s: %w", flagContextDescription(context), err)
|
return resolvedFlagAliases{}, fmt.Errorf("%s: %w", flagContextDescription(context), err)
|
||||||
}
|
}
|
||||||
if !active {
|
if !terminal.active {
|
||||||
if mapping.scoped {
|
if mapping.scoped {
|
||||||
return resolvedFlagAliases{}, fmt.Errorf("scoped flag alias %s has no canonical destination in %s", source.token(), flagContextDescription(context))
|
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 {
|
if _, collision := context.canonical[sourceKey]; collision {
|
||||||
return resolvedFlagAliases{}, fmt.Errorf("flag alias %s conflicts with a canonical spelling in %s", source.token(), flagContextDescription(context))
|
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
|
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 {
|
func spellingFromKey(key string) capturedFlagSpelling {
|
||||||
if strings.HasPrefix(key, "short:") {
|
if strings.HasPrefix(key, "short:") {
|
||||||
return capturedFlagSpelling{name: strings.TrimPrefix(key, "short:"), short: true}
|
return capturedFlagSpelling{name: strings.TrimPrefix(key, "short:"), short: true}
|
||||||
|
|||||||
@@ -295,3 +295,21 @@ func capturedSpelling(token string) capturedFlagSpelling {
|
|||||||
issues := []error(nil)
|
issues := []error(nil)
|
||||||
return captureFlagSpelling("test", token, &issues)
|
return captureFlagSpelling("test", token, &issues)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFlagAliasSharedSuffixAndUnavailableDestination(t *testing.T) {
|
||||||
|
app, err := New(Identity{Name: "Example"}, flagAliasCommand("active", stringFlag("color", "")), flagAliasCommand("inactive"),
|
||||||
|
FlagAlias("--first", "--common"), FlagAlias("--second", "--common"), FlagAlias("--common", "--color"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, name := range []string{"first", "second", "common"} {
|
||||||
|
key := flagSpellingKey(capturedFlagSpelling{name: name})
|
||||||
|
active := app.flagAliases[commandpath.Path{"active"}.Key()].bySpelling
|
||||||
|
if alias, ok := active[key]; !ok || alias.target.token() != "--color" {
|
||||||
|
t.Fatalf("active %s = %#v", name, alias)
|
||||||
|
}
|
||||||
|
if _, ok := app.flagAliases[commandpath.Path{"inactive"}.Key()].bySpelling[key]; ok {
|
||||||
|
t.Fatalf("inactive %s was installed", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user