Skip to content

Commit 9813ed0

Browse files
mpvlcueckoo
authored andcommitted
internal/core/adt: add builtin for user-defined errors
Fixes #291 Issue #943 Signed-off-by: Marcel van Lohuizen <[email protected]> Change-Id: Ic7c5e8264a4ab7125f2fa34e319f15297cb3e826 Dispatch-Trailer: {"type":"trybot","CL":1217235,"patchset":6,"ref":"refs/changes/35/1217235/6","targetBranch":"master"}
1 parent 64ffd0d commit 9813ed0

File tree

9 files changed

+375
-9
lines changed

9 files changed

+375
-9
lines changed

cue/testdata/builtins/error.txtar

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
-- in.cue --
2+
selection: {
3+
dropError: 1 | error("drop me")
4+
5+
useUserError: 1&2 | error("use me")
6+
7+
twoUserErrors: 1&2 | error("error one") | error("error two")
8+
9+
x: int
10+
dropIncomplete: x + 1 | error("user msg: x + 1 failed")
11+
}
12+
13+
interpolation: {
14+
a: {b: int}
15+
world: "world"
16+
17+
substituteAll: a.notExist | error("hello \(world)")
18+
19+
substituteFail: a.b + 1 | a.notExist | error("reference failed: \(a.b) | \(a.notExist)")
20+
}
21+
indirect: {
22+
x: error
23+
y: x("msg")
24+
}
25+
-- out/eval/stats --
26+
Leaks: 0
27+
Freed: 30
28+
Reused: 25
29+
Allocs: 5
30+
Retain: 0
31+
32+
Unifications: 16
33+
Conjuncts: 38
34+
Disjuncts: 30
35+
-- out/evalalpha --
36+
Errors:
37+
selection.twoUserErrors: 2 errors in empty disjunction:
38+
selection.useUserError: use me:
39+
./in.cue:4:22
40+
selection.twoUserErrors: error one:
41+
./in.cue:6:23
42+
selection.twoUserErrors: error two:
43+
./in.cue:6:44
44+
indirect.y: msg:
45+
./in.cue:22:5
46+
47+
Result:
48+
(_|_){
49+
// [user]
50+
selection: (_|_){
51+
// [user]
52+
dropError: (int){ 1 }
53+
useUserError: (_|_){
54+
// [user] selection.useUserError: use me:
55+
// ./in.cue:4:22
56+
}
57+
twoUserErrors: (_|_){
58+
// [user] selection.twoUserErrors: 2 errors in empty disjunction:
59+
// selection.twoUserErrors: error one:
60+
// ./in.cue:6:23
61+
// selection.twoUserErrors: error two:
62+
// ./in.cue:6:44
63+
}
64+
x: (int){ int }
65+
dropIncomplete: (_|_){
66+
// [incomplete] selection.dropIncomplete: user msg: x + 1 failed:
67+
// ./in.cue:9:26
68+
}
69+
}
70+
interpolation: (struct){
71+
a: (struct){
72+
b: (int){ int }
73+
}
74+
world: (string){ "world" }
75+
substituteAll: (_|_){
76+
// [incomplete] interpolation.substituteAll: hello world:
77+
// ./in.cue:16:30
78+
}
79+
substituteFail: (_|_){
80+
// [incomplete] interpolation.substituteFail: reference failed: int | a.notExist:
81+
// ./in.cue:18:41
82+
// ./in.cue:13:9
83+
// ./in.cue:18:77
84+
}
85+
}
86+
indirect: (_|_){
87+
// [user]
88+
x: (string){ error() }
89+
y: (_|_){
90+
// [user] indirect.y: msg:
91+
// ./in.cue:22:5
92+
}
93+
}
94+
}
95+
-- diff/-out/evalalpha<==>+out/eval --
96+
diff old new
97+
--- old
98+
+++ new
99+
@@ -1,12 +1,5 @@
100+
Errors:
101+
-selection.twoUserErrors: 3 errors in empty disjunction:
102+
-selection.twoUserErrors: conflicting values 2 and 1:
103+
- ./in.cue:6:17
104+
- ./in.cue:6:19
105+
-selection.useUserError: 2 errors in empty disjunction:
106+
-selection.useUserError: conflicting values 2 and 1:
107+
- ./in.cue:4:16
108+
- ./in.cue:4:18
109+
+selection.twoUserErrors: 2 errors in empty disjunction:
110+
selection.useUserError: use me:
111+
./in.cue:4:22
112+
selection.twoUserErrors: error one:
113+
@@ -23,18 +16,11 @@
114+
// [user]
115+
dropError: (int){ 1 }
116+
useUserError: (_|_){
117+
- // [user] selection.useUserError: 2 errors in empty disjunction:
118+
- // selection.useUserError: conflicting values 2 and 1:
119+
- // ./in.cue:4:16
120+
- // ./in.cue:4:18
121+
- // selection.useUserError: use me:
122+
+ // [user] selection.useUserError: use me:
123+
// ./in.cue:4:22
124+
}
125+
twoUserErrors: (_|_){
126+
- // [user] selection.twoUserErrors: 3 errors in empty disjunction:
127+
- // selection.twoUserErrors: conflicting values 2 and 1:
128+
- // ./in.cue:6:17
129+
- // ./in.cue:6:19
130+
+ // [user] selection.twoUserErrors: 2 errors in empty disjunction:
131+
// selection.twoUserErrors: error one:
132+
// ./in.cue:6:23
133+
// selection.twoUserErrors: error two:
134+
@@ -42,11 +28,7 @@
135+
}
136+
x: (int){ int }
137+
dropIncomplete: (_|_){
138+
- // [incomplete] selection.dropIncomplete: 2 errors in empty disjunction:
139+
- // selection.dropIncomplete: non-concrete value int in operand to +:
140+
- // ./in.cue:9:18
141+
- // ./in.cue:8:5
142+
- // selection.dropIncomplete: user msg: x + 1 failed:
143+
+ // [incomplete] selection.dropIncomplete: user msg: x + 1 failed:
144+
// ./in.cue:9:26
145+
}
146+
}
147+
@@ -56,20 +38,11 @@
148+
}
149+
world: (string){ "world" }
150+
substituteAll: (_|_){
151+
- // [incomplete] interpolation.substituteAll: 2 errors in empty disjunction:
152+
- // interpolation.substituteAll: undefined field: notExist:
153+
- // ./in.cue:16:19
154+
- // interpolation.substituteAll: hello world:
155+
+ // [incomplete] interpolation.substituteAll: hello world:
156+
// ./in.cue:16:30
157+
}
158+
substituteFail: (_|_){
159+
- // [incomplete] interpolation.substituteFail: 3 errors in empty disjunction:
160+
- // interpolation.substituteFail: non-concrete value int in operand to +:
161+
- // ./in.cue:18:18
162+
- // ./in.cue:13:9
163+
- // interpolation.substituteFail: undefined field: notExist:
164+
- // ./in.cue:18:30
165+
- // interpolation.substituteFail: reference failed: int | a.notExist:
166+
+ // [incomplete] interpolation.substituteFail: reference failed: int | a.notExist:
167+
// ./in.cue:18:41
168+
// ./in.cue:13:9
169+
// ./in.cue:18:77
170+
-- out/eval --
171+
Errors:
172+
selection.twoUserErrors: 3 errors in empty disjunction:
173+
selection.twoUserErrors: conflicting values 2 and 1:
174+
./in.cue:6:17
175+
./in.cue:6:19
176+
selection.useUserError: 2 errors in empty disjunction:
177+
selection.useUserError: conflicting values 2 and 1:
178+
./in.cue:4:16
179+
./in.cue:4:18
180+
selection.useUserError: use me:
181+
./in.cue:4:22
182+
selection.twoUserErrors: error one:
183+
./in.cue:6:23
184+
selection.twoUserErrors: error two:
185+
./in.cue:6:44
186+
indirect.y: msg:
187+
./in.cue:22:5
188+
189+
Result:
190+
(_|_){
191+
// [user]
192+
selection: (_|_){
193+
// [user]
194+
dropError: (int){ 1 }
195+
useUserError: (_|_){
196+
// [user] selection.useUserError: 2 errors in empty disjunction:
197+
// selection.useUserError: conflicting values 2 and 1:
198+
// ./in.cue:4:16
199+
// ./in.cue:4:18
200+
// selection.useUserError: use me:
201+
// ./in.cue:4:22
202+
}
203+
twoUserErrors: (_|_){
204+
// [user] selection.twoUserErrors: 3 errors in empty disjunction:
205+
// selection.twoUserErrors: conflicting values 2 and 1:
206+
// ./in.cue:6:17
207+
// ./in.cue:6:19
208+
// selection.twoUserErrors: error one:
209+
// ./in.cue:6:23
210+
// selection.twoUserErrors: error two:
211+
// ./in.cue:6:44
212+
}
213+
x: (int){ int }
214+
dropIncomplete: (_|_){
215+
// [incomplete] selection.dropIncomplete: 2 errors in empty disjunction:
216+
// selection.dropIncomplete: non-concrete value int in operand to +:
217+
// ./in.cue:9:18
218+
// ./in.cue:8:5
219+
// selection.dropIncomplete: user msg: x + 1 failed:
220+
// ./in.cue:9:26
221+
}
222+
}
223+
interpolation: (struct){
224+
a: (struct){
225+
b: (int){ int }
226+
}
227+
world: (string){ "world" }
228+
substituteAll: (_|_){
229+
// [incomplete] interpolation.substituteAll: 2 errors in empty disjunction:
230+
// interpolation.substituteAll: undefined field: notExist:
231+
// ./in.cue:16:19
232+
// interpolation.substituteAll: hello world:
233+
// ./in.cue:16:30
234+
}
235+
substituteFail: (_|_){
236+
// [incomplete] interpolation.substituteFail: 3 errors in empty disjunction:
237+
// interpolation.substituteFail: non-concrete value int in operand to +:
238+
// ./in.cue:18:18
239+
// ./in.cue:13:9
240+
// interpolation.substituteFail: undefined field: notExist:
241+
// ./in.cue:18:30
242+
// interpolation.substituteFail: reference failed: int | a.notExist:
243+
// ./in.cue:18:41
244+
// ./in.cue:13:9
245+
// ./in.cue:18:77
246+
}
247+
}
248+
indirect: (_|_){
249+
// [user]
250+
x: (string){ error() }
251+
y: (_|_){
252+
// [user] indirect.y: msg:
253+
// ./in.cue:22:5
254+
}
255+
}
256+
}
257+
-- out/compile --
258+
--- in.cue
259+
{
260+
selection: {
261+
dropError: (1|error("drop me"))
262+
useUserError: ((1 & 2)|error("use me"))
263+
twoUserErrors: ((1 & 2)|error("error one")|error("error two"))
264+
x: int
265+
dropIncomplete: ((〈0;x〉 + 1)|error("user msg: x + 1 failed"))
266+
}
267+
interpolation: {
268+
a: {
269+
b: int
270+
}
271+
world: "world"
272+
substituteAll: (〈0;a〉.notExist|error("hello \(〈0;world〉)"))
273+
substituteFail: ((〈0;a〉.b + 1)|〈0;a〉.notExist|error("reference failed: \(〈0;a〉.b) | \(〈0;a〉.notExist)"))
274+
}
275+
indirect: {
276+
x: error
277+
y: 〈0;x〉("msg")
278+
}
279+
}

internal/core/adt/call.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,22 @@ func (c *CallContext) Arg(i int) Value {
9191
return c.ctx.EvaluateKeepState(x)
9292
}
9393

94+
// Expr returns the nth argument expression without evaluating it.
95+
func (c *CallContext) Expr(i int) Expr {
96+
// If the call context represents a validator call, the argument will be
97+
// offset by 1.
98+
if c.isValidator {
99+
if i == 0 {
100+
c.Errf("Expr may not be called for 0th argument of validator")
101+
return nil
102+
}
103+
i--
104+
}
105+
x := c.call.Args[i]
106+
107+
return x
108+
}
109+
94110
func (c *CallContext) Errf(format string, args ...interface{}) *Bottom {
95111
return c.ctx.NewErrf(format, args...)
96112
}

internal/core/adt/disjunct2.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,12 +467,23 @@ func combineDefault2(a, b defaultMode, dropsDefaultA, dropsDefaultB bool) defaul
467467
// collectErrors collects errors from a failed disjunctions.
468468
func (n *nodeContext) collectErrors(dn *envDisjunct) (errs *Bottom) {
469469
code := EvalError
470+
hasUserError := false
470471
for _, d := range dn.disjuncts {
471472
if b := d.err; b != nil {
472-
n.disjunctErrs = append(n.disjunctErrs, b)
473473
if b.Code > code {
474474
code = b.Code
475475
}
476+
switch {
477+
case b.Code == UserError:
478+
if !hasUserError {
479+
n.disjunctErrs = n.disjunctErrs[:0]
480+
}
481+
hasUserError = true
482+
483+
case hasUserError:
484+
continue
485+
}
486+
n.disjunctErrs = append(n.disjunctErrs, b)
476487
}
477488
}
478489

internal/core/adt/errorcode_string.go

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/core/adt/errors.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,14 @@ const (
4848
// An EvalError is a fatal evaluation error.
4949
EvalError ErrorCode = iota // eval
5050

51-
// A UserError is a fatal error originating from the user.
51+
// A UserError is a fatal error originating from the user using the error
52+
// builtin.
5253
UserError // user
5354

55+
// A LegacyUserError is a fatal error originating from the user using the
56+
// _|_ token, which we intend to phase out.
57+
LegacyUserError // user
58+
5459
// StructuralCycleError means a structural cycle was found. Structural
5560
// cycles are permanent errors, but they are not passed up recursively,
5661
// as a unification of a value with a structural cycle with one that
@@ -108,7 +113,7 @@ func (b *Bottom) IsIncomplete() bool {
108113
// isLiteralBottom reports whether x is an error originating from a user.
109114
func isLiteralBottom(x Expr) bool {
110115
b, ok := x.(*Bottom)
111-
return ok && b.Code == UserError
116+
return ok && b.Code == LegacyUserError
112117
}
113118

114119
// isError reports whether v is an error or nil.

internal/core/adt/expr.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,6 +1806,9 @@ func (x *Builtin) call(call *CallContext) Expr {
18061806
c.IsValidator = saved
18071807
}()
18081808

1809+
if x.RawFunc != nil {
1810+
return x.RawFunc(call)
1811+
}
18091812
return x.Func(call)
18101813
}
18111814

0 commit comments

Comments
 (0)