Skip to content

Commit 90ed08f

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":4,"ref":"refs/changes/35/1217235/4","targetBranch":"master"}
1 parent 8c3ec4f commit 90ed08f

File tree

8 files changed

+359
-9
lines changed

8 files changed

+359
-9
lines changed

cue/testdata/builtins/error.txtar

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

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 and returned as is.
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.

0 commit comments

Comments
 (0)