Preserve caller scope during requirement inspection
This commit is contained in:
@@ -22,6 +22,7 @@ type dotIdentity uint8
|
||||
const (
|
||||
dotRoot dotIdentity = iota
|
||||
dotNonRoot
|
||||
dotUnknown
|
||||
)
|
||||
|
||||
type inputEvidence struct {
|
||||
@@ -175,6 +176,14 @@ func (walker *requirementWalker) evaluateCommand(command *parse.CommandNode, rea
|
||||
|
||||
func (walker *requirementWalker) evaluateValue(node parse.Node, reach requirementReachability, dot dotIdentity) conditionEvaluation {
|
||||
switch node := node.(type) {
|
||||
case *parse.DotNode:
|
||||
if dot == dotRoot {
|
||||
return conditionEvaluation{value: walker.vars, known: true}
|
||||
}
|
||||
if dot == dotUnknown {
|
||||
walker.unresolvedVariableScope(reach)
|
||||
}
|
||||
return conditionEvaluation{unsupported: true}
|
||||
case *parse.StringNode:
|
||||
return conditionEvaluation{value: node.Text, known: true}
|
||||
case *parse.BoolNode:
|
||||
@@ -183,6 +192,9 @@ func (walker *requirementWalker) evaluateValue(node parse.Node, reach requiremen
|
||||
return conditionEvaluation{value: nil, known: true}
|
||||
case *parse.FieldNode:
|
||||
if dot != dotRoot || len(node.Ident) == 0 {
|
||||
if dot == dotUnknown {
|
||||
walker.unresolvedVariableScope(reach)
|
||||
}
|
||||
return conditionEvaluation{unsupported: true}
|
||||
}
|
||||
name := node.Ident[0]
|
||||
@@ -197,7 +209,11 @@ func (walker *requirementWalker) evaluateValue(node parse.Node, reach requiremen
|
||||
}
|
||||
return conditionEvaluation{value: value, known: true}
|
||||
case *parse.VariableNode:
|
||||
if len(node.Ident) == 1 && node.Ident[0] == "$" && walker.rootKnown {
|
||||
return conditionEvaluation{value: walker.vars, known: true}
|
||||
}
|
||||
if len(node.Ident) < 2 || node.Ident[0] != "$" || !walker.rootKnown {
|
||||
walker.visitVariable(node, reach)
|
||||
return conditionEvaluation{unsupported: true}
|
||||
}
|
||||
name := node.Ident[1]
|
||||
@@ -483,7 +499,8 @@ func (walker *requirementWalker) visit(node parse.Node, reach requirementReachab
|
||||
if err := walker.visit(node.Pipe, requirementInactive, dot); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := walker.visit(node.List, requirementInactive, dotNonRoot); err != nil {
|
||||
bodyDot := walker.withDotIdentity(node.Pipe, dot, conditionEvaluation{})
|
||||
if err := walker.visit(node.List, requirementInactive, bodyDot); err != nil {
|
||||
return err
|
||||
}
|
||||
return walker.visit(node.ElseList, requirementInactive, dot)
|
||||
@@ -492,7 +509,8 @@ func (walker *requirementWalker) visit(node parse.Node, reach requirementReachab
|
||||
before := walker.dependencies
|
||||
thenReach := branchReach(reach, condition.known, condition.truth(), true)
|
||||
elseReach := branchReach(reach, condition.known, condition.truth(), false)
|
||||
if err := walker.visit(node.List, thenReach, dotNonRoot); err != nil {
|
||||
bodyDot := walker.withDotIdentity(node.Pipe, dot, condition)
|
||||
if err := walker.visit(node.List, thenReach, bodyDot); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := walker.visit(node.ElseList, elseReach, dot); err != nil {
|
||||
@@ -561,6 +579,13 @@ func (walker *requirementWalker) visit(node parse.Node, reach requirementReachab
|
||||
walker.addIssue("", node.Ident[0], "A chained field access is outside the direct-key scope.")
|
||||
}
|
||||
}
|
||||
if dot == dotUnknown {
|
||||
walker.unresolvedVariableScope(reach)
|
||||
}
|
||||
case *parse.DotNode:
|
||||
if dot == dotUnknown {
|
||||
walker.unresolvedVariableScope(reach)
|
||||
}
|
||||
case *parse.VariableNode:
|
||||
walker.visitVariable(node, reach)
|
||||
case *parse.ChainNode:
|
||||
@@ -668,6 +693,11 @@ func (walker *requirementWalker) visitCommand(command *parse.CommandNode, reach
|
||||
walker.recordOptionalVariable(key, reach)
|
||||
return nil
|
||||
}
|
||||
// A local map may alias the caller root even though its value is not
|
||||
// tracked. A lookup on it must not vanish from complete discovery.
|
||||
if variable, ok := unwrapSingleValue(command.Args[1]).(*parse.VariableNode); ok && len(variable.Ident) == 1 {
|
||||
walker.unresolvedVariableScope(reach)
|
||||
}
|
||||
}
|
||||
if !isIdentifier && len(command.Args) == 1 {
|
||||
return walker.visit(command.Args[0], reach, dot)
|
||||
@@ -692,7 +722,11 @@ func (walker *requirementWalker) visitCommand(command *parse.CommandNode, reach
|
||||
}
|
||||
|
||||
func (walker *requirementWalker) visitVariable(variable *parse.VariableNode, reach requirementReachability) {
|
||||
if len(variable.Ident) < 2 || variable.Ident[0] != "$" {
|
||||
if len(variable.Ident) < 2 {
|
||||
return
|
||||
}
|
||||
if variable.Ident[0] != "$" {
|
||||
walker.unresolvedVariableScope(reach)
|
||||
return
|
||||
}
|
||||
if !walker.rootKnown {
|
||||
@@ -708,8 +742,35 @@ func (walker *requirementWalker) visitVariable(variable *parse.VariableNode, rea
|
||||
}
|
||||
}
|
||||
|
||||
func (walker *requirementWalker) unresolvedVariableScope(reach requirementReachability) {
|
||||
if reach != requirementInactive {
|
||||
walker.unknownVariableEffect = true
|
||||
walker.addIssue("", "", "A caller-variable scope is not statically known.")
|
||||
}
|
||||
}
|
||||
|
||||
func (walker *requirementWalker) withDotIdentity(pipe *parse.PipeNode, dot dotIdentity, condition conditionEvaluation) dotIdentity {
|
||||
if pipe != nil && len(pipe.Cmds) == 1 && len(pipe.Decl) == 0 && !pipe.IsAssign && len(pipe.Cmds[0].Args) == 1 &&
|
||||
isRootMap(pipe.Cmds[0].Args[0], dot, walker.rootKnown) {
|
||||
return dotRoot
|
||||
}
|
||||
if condition.known {
|
||||
if _, root := condition.value.(map[string]string); root {
|
||||
return dotRoot
|
||||
}
|
||||
return dotNonRoot
|
||||
}
|
||||
if condition.inputNameUnknown ||
|
||||
!condition.unsupported && (len(condition.missingVariables) > 0 || len(condition.contentInputs) > 0) {
|
||||
// Supported caller values and input bodies are strings; supported
|
||||
// comparisons and negation return booleans, never the root map.
|
||||
return dotNonRoot
|
||||
}
|
||||
return dotUnknown
|
||||
}
|
||||
|
||||
func isRootMap(node parse.Node, dot dotIdentity, rootKnown bool) bool {
|
||||
switch node := node.(type) {
|
||||
switch node := unwrapSingleValue(node).(type) {
|
||||
case *parse.DotNode:
|
||||
return dot == dotRoot
|
||||
case *parse.VariableNode:
|
||||
@@ -719,6 +780,16 @@ func isRootMap(node parse.Node, dot dotIdentity, rootKnown bool) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func unwrapSingleValue(node parse.Node) parse.Node {
|
||||
for {
|
||||
pipe, ok := node.(*parse.PipeNode)
|
||||
if !ok || len(pipe.Cmds) != 1 || len(pipe.Decl) != 0 || pipe.IsAssign || len(pipe.Cmds[0].Args) != 1 {
|
||||
return node
|
||||
}
|
||||
node = pipe.Cmds[0].Args[0]
|
||||
}
|
||||
}
|
||||
|
||||
func (walker *requirementWalker) recordInput(name string, reach requirementReachability) {
|
||||
evidence := walker.inputs[name]
|
||||
if evidence == nil {
|
||||
|
||||
Reference in New Issue
Block a user