Skip to content

Commit 52280ba

Browse files
authored
Clean up unused source info after checker rewrites the AST. (#1258)
* Clean up unused source info after checker rewrites the AST. * Move ClearUnusedIDs() to AST. Also add a check for unused IDs in parser_test.go * Add comment for ClearUnusedIDs.
1 parent 3cb5705 commit 52280ba

5 files changed

Lines changed: 74 additions & 0 deletions

File tree

checker/checker.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ func Check(parsed *ast.AST, source common.Source, env *Env) (*ast.AST, *common.E
6767
for id, t := range c.TypeMap() {
6868
c.SetType(id, substitute(c.mappings, t, true))
6969
}
70+
// Remove source info for IDs without a corresponding AST node. This can happen because
71+
// check() deletes some nodes while rewriting the AST. For example the Select operand is
72+
// deleted when a variable reference is replaced with a Ident expression.
73+
c.AST.ClearUnusedIDs()
7074
return c.AST, errs
7175
}
7276

checker/checker_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/google/cel-go/common"
2424
"github.com/google/cel-go/common/ast"
2525
"github.com/google/cel-go/common/containers"
26+
"github.com/google/cel-go/common/debug"
2627
"github.com/google/cel-go/common/decls"
2728
"github.com/google/cel-go/common/stdlib"
2829
"github.com/google/cel-go/common/types"
@@ -2553,6 +2554,18 @@ func TestCheck(t *testing.T) {
25532554
t.Errorf("Expected error not thrown: %s", tc.err)
25542555
}
25552556

2557+
astIDs := cAst.IDs()
2558+
unusedIDs := []int64{}
2559+
for id := range cAst.SourceInfo().OffsetRanges() {
2560+
if !astIDs[id] {
2561+
unusedIDs = append(unusedIDs, id)
2562+
}
2563+
}
2564+
if len(unusedIDs) > 0 {
2565+
t.Errorf("SourceInfo has offset range for IDs %v, but no such nodes exists in AST: %s",
2566+
unusedIDs, debug.ToDebugStringWithIDs(cAst.Expr()))
2567+
}
2568+
25562569
actual := cAst.GetType(pAst.Expr().ID())
25572570
if tc.err == "" {
25582571
if actual == nil || !actual.IsEquivalentType(tc.outType) {

common/ast/ast.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,26 @@ func MaxID(a *AST) int64 {
160160
return visitor.maxID + 1
161161
}
162162

163+
// IDs returns the set of AST node IDs, including macro calls.
164+
func (a *AST) IDs() map[int64]bool {
165+
visitor := make(idVisitor)
166+
PostOrderVisit(a.Expr(), visitor)
167+
for _, call := range a.SourceInfo().MacroCalls() {
168+
PostOrderVisit(call, visitor)
169+
}
170+
return visitor
171+
}
172+
173+
// ClearUnusedIDs removes IDs not used in the AST or macro calls from SourceInfo.
174+
func (a *AST) ClearUnusedIDs() {
175+
ids := a.IDs()
176+
for id := range a.SourceInfo().OffsetRanges() {
177+
if !ids[id] {
178+
a.SourceInfo().ClearOffsetRange(id)
179+
}
180+
}
181+
}
182+
163183
// Heights computes the heights of all AST expressions and returns a map from expression id to height.
164184
func Heights(a *AST) map[int64]int {
165185
visitor := make(heightVisitor)
@@ -533,3 +553,13 @@ func (hv heightVisitor) maxEntryHeight(entries ...EntryExpr) int {
533553
}
534554
return max
535555
}
556+
557+
type idVisitor map[int64]bool
558+
559+
func (v idVisitor) VisitExpr(e Expr) {
560+
v[e.ID()] = true
561+
}
562+
563+
func (v idVisitor) VisitEntryExpr(e EntryExpr) {
564+
v[e.ID()] = true
565+
}

common/debug/debug.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,18 @@ func (w *debugWriter) removeIndent() {
312312
func (w *debugWriter) String() string {
313313
return w.buffer.String()
314314
}
315+
316+
type idAdorner struct{}
317+
318+
func (a *idAdorner) GetMetadata(elem any) string {
319+
e, isExpr := elem.(ast.Expr)
320+
if !isExpr {
321+
return ""
322+
}
323+
return fmt.Sprintf("@id:%d ", e.ID())
324+
}
325+
326+
// ToDebugStringWithIDs returns a string representation with AST node IDs.
327+
func ToDebugStringWithIDs(e ast.Expr) string {
328+
return ToAdornedDebugString(e, &idAdorner{})
329+
}

parser/parser_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,6 +2262,18 @@ func TestParse(t *testing.T) {
22622262
t.Fatal(test.DiffMessage(fmt.Sprintf("Macro Calls - %s", failureDisplayMethod), actualAdornedMacroCalls, tc.M))
22632263
}
22642264
}
2265+
2266+
astIDs := parsed.IDs()
2267+
unusedIDs := []int64{}
2268+
for id := range parsed.SourceInfo().OffsetRanges() {
2269+
if !astIDs[id] {
2270+
unusedIDs = append(unusedIDs, id)
2271+
}
2272+
}
2273+
if len(unusedIDs) > 0 {
2274+
t.Errorf("SourceInfo has offset range for IDs %v, but no such nodes exists in AST: %s",
2275+
unusedIDs, debug.ToDebugStringWithIDs(parsed.Expr()))
2276+
}
22652277
})
22662278
}
22672279
}

0 commit comments

Comments
 (0)