Repository navigation
Release v0.33.0
This release introduces a high-performance Pratt parser, a unified declarative cost model (common/cost) with aggregate sizing and peak memory tracking, raw pointer traversal for native Go structs, and JSON/YAML parsing extensions, while eliminating quadratic type-checking bottlenecks.
Features
| Pull Request | Description and Impact |
|---|---|
Pratt parser and EnablePrattParser option (#1427, #1433, #1442, #1462, #1472, #1497, #1509, #1511, #1521) |
Introduces a recursive-descent Pratt parser ported from cel-cpp with iterative parenthesis handling, bounded expression nesting, and escaped function name support, delivering 8x–19x faster single-threaded parsing. |
Unified cost model (common/cost), AggregateSizingStrategy, and CostModelVersion (#1430, #1435, #1441, #1474, #1478, #1482, #1512, #1519, #1525, #1537, #1548, #1556, #1558) |
Unifies static cost estimation and runtime tracking under a declarative cost.OverloadModel AST with container Key/Elem size propagation, aggregate sizing, value-backed ElemOf/KeyOf/ElemTotal/KeyTotal/Length combinators, and versioned compatibility via cel.CostModelVersion. |
| Runtime peak memory tracking (#1418, #1429) | Adds runtime memory tracking and scaled string/byte sizing options to observe and bound peak allocation footprints during evaluation. |
| Native raw pointer traversal, generic containers, and type aliasing (#1463, #1498, #1503, #1515) | Accelerates cel.NativeTypes struct field selection and presence checks via unsafe.Pointer offset traversal, adds generic Go slice/map adapters, and supports native type aliasing. |
| JSON/YAML parsing and encoding & URL-safe Base64 (#1451, #1461, #1492) | Adds json.parse, yaml.parse, and yaml.encode extensions (returning optional_type with 10MB payload safety limits), alongside base64.encodeUrl and base64.decodeUrl in ext.Encoders. |
| List presence helpers and versioned optional macros (#1447, #1448, #1487) | Introduces hasOnly, hasAny, hasAll, and hasExactly in ext.Lists, optional.hasValue(val) in OptionalTypesVersion(3), and single-evaluation optMap/optFlatMap macros in OptionalTypesVersion(4). |
| HMAC digest and constant-time equality (#1450) | Extends ext/security/hmac with hmac.digest and linear-time equality comparisons. |
Late-bound functions from Activation (#1483) |
Enables resolving cel.FunctionVars bindings dynamically from the runtime Activation or cel.Globals. |
Type-check error suggestions and cel.Env JSON serialization (#1500, #1554) |
Adds edit-distance spellchecking and library enablement hints for undeclared symbols/types, plus JSON/YAML config serialization for cel.Env. |
AST structural equivalence and @block call tagging (#1467, #1544) |
Provides format-insensitive AST structural comparison in common/ast and tags cel.@block calls in compiled ASTs. |
| SLSA + Sigstore artifact signing and OSS-Fuzz consolidation (#1426, #1577) | Signs release artifacts with SLSA/Sigstore provenance and consolidates OSS-Fuzz harnesses under fuzzing/. |
Fixes
| Pull Request | Impact and Technical Details |
|---|---|
| Undo failed assignability checks in place (#1575) | Replaces full type-mapping clones during overload resolution with an in-place undo log, eliminating O(N^2) type-checking latency and memory growth on long conjunctions (#1553). |
Restore v0.32.0 cost behavior at CostModelVersion(0) and v0.29.2 cost limit error (#1571, #1572, #1573) |
Restores EvalCancelledError ("operation cancelled: actual cost limit exceeded") when runtime cost limits are exceeded and replays v0.32.0 static/runtime cost rules when pinned to cel.CostModelVersion(0). |
| Fix unsafe math and saturation in cost estimation and tracking (#1508, #1541) | Adds SafeSubtract, SafeTrunc, and SafeMultiplyByFactorTrunc in common/cost so uint64 and float-scaled costs saturate cleanly at math.MaxUint64 (including NaN) and clamp negative values to 0. |
Nil-safe Program evaluation and concurrent cost tracker race fix (#1480, #1484, #1486, #1488) |
Makes Eval, ContextEval, and ConcurrentEval nil-safe, eliminates a data race when capturing cost trackers across goroutines, and removes time-based flakiness in interrupt checks. |
| Constant folding and partial evaluation AST consistency (#1446, #1453, #1517, #1518) | Prunes folded nodes from reference and type maps, checks unknowns before traversing attribute qualifiers, and fixes custom Foldable two-variable iteration. |
Native types embedded json:"-" tags and proto.ReflectMessage dereference (#1452, #1551, #1567) |
Respects json:"-" skip tags on embedded Go structs, fixes JSON tag conversion in ConvertToNative, and prevents invalid field dereferences over proto.ReflectMessage. |
| Fix regex counted repetition sizing panic (#1438) | Prevents a panic when estimating compiled regex program sizes for counted repetitions. |
Breaking Changes
There are no module path changes in this release, but callers relying on exact AST expansion shapes, golden cost estimates, or positional SizeEstimate literals should note:
- Cost Model v1 Default (
cel.CostModelVersion):cel.CostModelVersion(1)is now the default and tightens static/runtime cost bounds (propagating optional and container element sizes, charging1on absent optional qualifiers, and normalizing rational scale factors). Callers requiring exactv0.32.0cost compatibility can pincel.CostModelVersion(0). - Optional Types v4 Default (
cel.OptionalTypes): Unpinnedcel.OptionalTypes()now defaults toOptionalTypesVersion(4), which wraps non-identifieroptMap/optFlatMaptargets in a single-evaluation comprehension (ComprehensionKind) rather than duplicating the target in a ternary call (CallKind), and includesoptional.hasValue(val)from v3. Pincel.OptionalTypes(cel.OptionalTypesVersion(2))to preserve legacy AST expansion and cost estimates. cost.SizeEstimateStruct Fields:cost.SizeEstimate(aliased bychecker.SizeEstimate) now includesKey *SizeEstimateandElem *SizeEstimatefields to track container element sizing. Unkeyed positional struct literals (SizeEstimate{0, 10}) must be updated to keyed literals (SizeEstimate{Min: 0, Max: 10}) orcost.FixedSizeEstimate/cost.RangedSizeEstimate.
Notes on Performance
This release delivers major throughput and allocation improvements across all three phases of the CEL pipeline: parsing, type-checking, and evaluation.
Expected Impact
- Pratt Parsing (
cel.EnablePrattParser(true)): Replacing ANTLR's reflection and DFA overhead with a custom recursive-descent Pratt parser and iterative parenthesis scanner (#1433, #1442, #1509) speeds up single-threaded parsing by 7.4x–19.6x (and parallel parsing by 17x–38.7x) while cutting parser memory allocations by 8x–15x. - Linear-Time Type Checking on Large Expressions: Rolling back failed type-parameter assignability checks in place (#1575) instead of cloning the full substitution map eliminates quadratic
O(N^2)CPU and heap growth when checking large conjunctions of macros or parameterized overloads, reducing 400-term conjunction checking from 1.46s / 882 MB to 7.1ms / 5.8 MB (-99.5%). - Zero-Reflection Native Struct & Optional Field Access: Computing field offsets via
unsafe.Pointerforcel.NativeTypes(#1515, #1578) and routing optional proto3 field selections through precomputedfieldQualifierplans (#1568) reduces struct access and presence test latency by 57%–78% and eliminates intermediate heap allocations onhas()checks. - Interpreter Frame, Activation, and Container Hot Paths: Rearchitecting
ExecutionFrameas a directActivation(#1489), flatteningdynamicSlotActivation.slotValsfrom[]*slotValto[]slotVal(#1569), lazily initializing standard/extension declarations (#1501, #1502), and using generic slice lists (#1498) reduce per-evaluation allocations across comprehension and block expressions.
Go-lang Benchmark Citation (benchstat)
The performance improvements can be verified running the repository's native benchmark harness with benchstat:
name old time/op new time/op delta
BenchmarkByCategory/Simple (Pratt vs ANTLR) 325.2µs ± 2% 27.4µs ± 1% -91.57% (#1442)
BenchmarkByCategory/Complex (Pratt vs ANTLR) 1179.7µs ± 2% 107.3µs ± 1% -90.90% (#1442)
BenchmarkByCategory/Macros (Pratt vs ANTLR) 423.0µs ± 1% 50.5µs ± 1% -88.06% (#1442)
BenchmarkByCategory/Errors (Pratt vs ANTLR) 1646.1µs ± 3% 83.8µs ± 1% -94.91% (#1442)
BenchmarkCheckLongConjunction/terms=400 1460ms ± 2% 7.1ms ± 1% -99.51% (#1575)
BenchmarkNativeTypes/PrimitiveFieldAccess 390ns ± 2% 100ns ± 1% -74.26% (#1515)
BenchmarkNativeTypes/FieldPresence 320ns ± 2% 86ns ± 1% -73.25% (#1515)
BenchmarkNativeTypes/NestedStructAccess 580ns ± 2% 125ns ± 1% -78.41% (#1515)
BenchmarkInterpreter/select_pb3_optional_fields 463ns ± 1% 197ns ± 1% -57.42% (#1568)
BenchmarkBlockEval 315ns ± 1% 268ns ± 1% -14.78% (#1569)
name old allocs/op new allocs/op delta
BenchmarkByCategory/Simple (Pratt vs ANTLR) 1,646 ± 0% 404 ± 0% -75.46% (#1442)
BenchmarkCheckLongConjunction/terms=400 882MB ± 0% 5.8MB ± 0% -99.34% (#1575, B/op)
BenchmarkNativeTypes/PrimitiveFieldAccess 5.00 ± 0% 1.00 ± 0% -80.00% (#1515)
BenchmarkNativeTypes/FieldPresence 4.00 ± 0% 0.00 ± 0% -100.00% (#1515)
BenchmarkInterpreter/select_pb3_optional_fields 3.00 ± 0% 1.00 ± 0% -66.67% (#1568)
BenchmarkBlockEval 8.00 ± 0% 5.00 ± 0% -37.50% (#1569)
Full Changelog: v0.32.0...v0.33.0