this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cue: implement IsClosed and IsClosedRecursively

Implement IsClosed() and IsClosedRecursively() methods
for Value type. Both methods use DerefValue() to get the
non-forwarded node, ensuring correct behavior with
structure sharing.

IsClosed() returns true if the value is closed either
non-recursively (via close() builtin) or recursively
(via definition reference).

IsClosedRecursively() returns true only if the value is
recursively closed by being referenced as a definition.

Add comprehensive tests covering:
- Open vs closed structs
- close() builtin and definitions
- Structure sharing scenarios
- Disjunctions with various closedness combinations
- Pattern constraints
- Edge cases with scalars and lists

Signed-off-by: Marcel van Lohuizen <mpvl@gmail.com>
Change-Id: I7e7ab1b152619bf034d0e01e338f2fb05577d610
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1224363
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>

authored by

Marcel van Lohuizen and committed by
Roger Peppe
b28a5f3e 28054b05

+339
+42
cue/types.go
··· 1026 1026 return v.v.Accept(c, f) 1027 1027 } 1028 1028 1029 + // IsClosed reports whether the value has been closed at the top level, either 1030 + // with the close function or by being referenced as a definition. 1031 + func (v Value) IsClosed() bool { 1032 + if v.v == nil { 1033 + return false 1034 + } 1035 + // Use the non-forwarded node to get the actual closed state 1036 + x := v.v 1037 + isClosed := x.ClosedNonRecursive || x.ClosedRecursive 1038 + for !isClosed { 1039 + if v, ok := x.BaseValue.(*adt.Vertex); ok { 1040 + isClosed = isClosed || v.ClosedNonRecursive || v.ClosedRecursive 1041 + x = v 1042 + continue 1043 + } 1044 + break 1045 + } 1046 + return isClosed 1047 + } 1048 + 1049 + // IsClosedRecursively reports whether the value has been closed by virtue of 1050 + // being referenced as a definition. 1051 + func (v Value) IsClosedRecursively() bool { 1052 + if v.v == nil { 1053 + return false 1054 + } 1055 + // Use the non-forwarded node to get the actual closed state 1056 + x := v.v 1057 + isClosed := x.ClosedRecursive 1058 + // This loop doesn't seem necessary for ClosedRecursive, but we will keep 1059 + // it as a safety net. 1060 + for !isClosed { 1061 + if v, ok := x.BaseValue.(*adt.Vertex); ok { 1062 + isClosed = isClosed || v.ClosedRecursive 1063 + x = v 1064 + continue 1065 + } 1066 + break 1067 + } 1068 + return isClosed 1069 + } 1070 + 1029 1071 // IsConcrete reports whether the current value is a concrete scalar value 1030 1072 // (not relying on default values), a terminal error, a list, or a struct. 1031 1073 // It does not verify that values of lists or structs are concrete themselves.
+297
cue/types_test.go
··· 4096 4096 cfg := &debug.Config{Compact: true, Raw: true} 4097 4097 return debug.NodeString(ctx, cue.ValueVertex(v), cfg) 4098 4098 } 4099 + 4100 + func TestIsClosed(t *testing.T) { 4101 + testCases := []struct { 4102 + desc string 4103 + input string 4104 + path string 4105 + wantClosed bool 4106 + wantClosedRecursive bool 4107 + }{ 4108 + { 4109 + desc: "open struct", 4110 + input: `x: {a: 1}`, 4111 + path: "x", 4112 + wantClosed: false, 4113 + wantClosedRecursive: false, 4114 + }, 4115 + { 4116 + desc: "struct with close builtin", 4117 + input: `x: close({a: 1})`, 4118 + path: "x", 4119 + wantClosed: true, 4120 + wantClosedRecursive: false, 4121 + }, 4122 + { 4123 + desc: "definition", 4124 + input: `#X: {a: 1}`, 4125 + path: "#X", 4126 + wantClosed: true, 4127 + wantClosedRecursive: true, 4128 + }, 4129 + { 4130 + desc: "field in definition", 4131 + input: `#X: {a: {b: 1}}`, 4132 + path: "#X.a", 4133 + wantClosed: true, 4134 + wantClosedRecursive: true, 4135 + }, 4136 + { 4137 + desc: "structure sharing: definition reference", 4138 + input: `#X: {a: 1}, y: #X`, 4139 + path: "y", 4140 + wantClosed: true, 4141 + wantClosedRecursive: true, 4142 + }, 4143 + { 4144 + desc: "structure sharing: definition nested field", 4145 + input: `#X: {a: {b: 1}}, y: #X`, 4146 + path: "y.a", 4147 + wantClosed: true, 4148 + wantClosedRecursive: true, 4149 + }, 4150 + { 4151 + desc: "structure sharing: mixed open and closed", 4152 + input: `#X: {a: 1}, y: {b: 2, c: #X}`, 4153 + path: "y.c", 4154 + wantClosed: true, 4155 + wantClosedRecursive: true, 4156 + }, 4157 + { 4158 + desc: "structure sharing: open struct in closed", 4159 + input: `#X: {a: open}, open: {b: 1}, y: #X.a`, 4160 + path: "y", 4161 + wantClosed: true, // Inherits closed status from definition 4162 + wantClosedRecursive: true, // Inherits closed status from definition 4163 + }, 4164 + { 4165 + desc: "disjunction with close", 4166 + input: `x: close({a: 1}) | close({b: 2})`, 4167 + path: "x", 4168 + wantClosed: false, // Disjunction itself is not closed 4169 + wantClosedRecursive: false, 4170 + }, 4171 + { 4172 + desc: "disjunction with close and elimination", 4173 + input: `x: close({a: 1}) | close({b: 2}), x: a: 1`, 4174 + path: "x", 4175 + wantClosed: true, 4176 + wantClosedRecursive: false, 4177 + }, 4178 + { 4179 + desc: "disjunction with definition", 4180 + input: `#A: {a: 1}, #B: {b: 2}, x: #A | #B`, 4181 + path: "x", 4182 + wantClosed: false, // Disjunction itself is not closed 4183 + wantClosedRecursive: false, // Disjunction itself is not closed 4184 + }, 4185 + { 4186 + desc: "disjunction with mixed closedness", 4187 + input: `#A: {a: 1}, x: #A | {b: 2}`, 4188 + path: "x", 4189 + wantClosed: false, 4190 + wantClosedRecursive: false, 4191 + }, 4192 + { 4193 + desc: "disjunction resolved to closed", 4194 + input: `#A: {a: 1}, #B: {b: 2}, x: (#A | #B) & {a: 1}`, 4195 + path: "x", 4196 + wantClosed: true, 4197 + wantClosedRecursive: true, 4198 + }, 4199 + { 4200 + desc: "nested definition in open struct", 4201 + input: `x: {#D: {a: 1}}`, 4202 + path: "x.#D", 4203 + wantClosed: true, 4204 + wantClosedRecursive: true, 4205 + }, 4206 + { 4207 + desc: "empty struct with close", 4208 + input: `x: close({})`, 4209 + path: "x", 4210 + wantClosed: true, 4211 + wantClosedRecursive: false, 4212 + }, 4213 + { 4214 + desc: "empty definition", 4215 + input: `#X: {}`, 4216 + path: "#X", 4217 + wantClosed: true, 4218 + wantClosedRecursive: true, 4219 + }, 4220 + { 4221 + desc: "close with pattern constraint", 4222 + input: `x: close({[string]: int})`, 4223 + path: "x", 4224 + wantClosed: true, 4225 + wantClosedRecursive: false, 4226 + }, 4227 + { 4228 + desc: "definition with pattern constraint", 4229 + input: `#X: {[string]: int}`, 4230 + path: "#X", 4231 + wantClosed: true, 4232 + wantClosedRecursive: true, 4233 + }, 4234 + { 4235 + desc: "multiple levels of structure sharing 1", 4236 + input: `#A: {a: 1}, #B: {b: #A}, y: #B.b`, 4237 + path: "y", 4238 + wantClosed: true, 4239 + wantClosedRecursive: true, 4240 + }, 4241 + { 4242 + desc: "multiple levels of structure sharing 2", 4243 + input: `#A: {a: 1}, x: {b: #A}, y: x.b`, 4244 + path: "y", 4245 + wantClosed: true, 4246 + wantClosedRecursive: true, 4247 + }, 4248 + { 4249 + desc: "multiple levels of structure sharing 3", 4250 + input: `#A: {a: {c: 1}}, x: {b: #A}, y: x.b.a`, 4251 + path: "y", 4252 + wantClosed: true, 4253 + wantClosedRecursive: true, 4254 + }, 4255 + { 4256 + desc: "multiple levels of structure sharing 4", 4257 + input: `a: {a: c: 1}, x: {#b: a}, y: x.#b.a`, 4258 + path: "y", 4259 + wantClosed: true, 4260 + wantClosedRecursive: true, 4261 + }, 4262 + { 4263 + desc: "scalar value (non-struct)", 4264 + input: `x: 42`, 4265 + path: "x", 4266 + wantClosed: false, 4267 + wantClosedRecursive: false, 4268 + }, 4269 + { 4270 + desc: "list value", 4271 + input: `x: [1, 2, 3]`, 4272 + path: "x", 4273 + wantClosed: false, 4274 + wantClosedRecursive: false, 4275 + }, 4276 + { 4277 + desc: "unresolved disjunction with all closed options", 4278 + input: ` 4279 + #A: {a: 1} 4280 + #B: {b: 2} 4281 + x: #A | #B 4282 + `, 4283 + path: "x", 4284 + wantClosed: false, // Disjunction itself is not closed 4285 + wantClosedRecursive: false, 4286 + }, 4287 + { 4288 + desc: "unresolved disjunction with mixed closedness", 4289 + input: ` 4290 + #A: {a: 1} 4291 + x: #A | {b: 2} 4292 + `, 4293 + path: "x", 4294 + wantClosed: false, 4295 + wantClosedRecursive: false, 4296 + }, 4297 + { 4298 + desc: "disjunction with close() builtin", 4299 + input: ` 4300 + x: close({a: 1}) | close({b: 2}) 4301 + `, 4302 + path: "x", 4303 + wantClosed: false, // Disjunction itself is not closed 4304 + wantClosedRecursive: false, 4305 + }, 4306 + { 4307 + desc: "default disjunction with closed values", 4308 + input: ` 4309 + #A: {a: 1} 4310 + #B: {b: 2} 4311 + x: *#A | #B 4312 + `, 4313 + path: "x", 4314 + wantClosed: false, // Disjunction itself is not closed 4315 + wantClosedRecursive: false, 4316 + }, 4317 + { 4318 + desc: "nested disjunction in definition", 4319 + input: ` 4320 + x: #X 4321 + #X: { 4322 + field: close({a: 1}) | close({b: 2}) 4323 + } 4324 + `, 4325 + path: "x", 4326 + wantClosed: true, // Definition is closed 4327 + wantClosedRecursive: true, // Definition is recursively closed 4328 + }, 4329 + { 4330 + desc: "triple disjunction all closed", 4331 + input: ` 4332 + #A: {a: 1} 4333 + #B: {b: 2} 4334 + #C: {c: 3} 4335 + x: #A | #B | #C 4336 + `, 4337 + path: "x", 4338 + wantClosed: false, // Disjunction itself is not closed 4339 + wantClosedRecursive: false, 4340 + }, 4341 + { 4342 + desc: "triple disjunction with one open", 4343 + input: ` 4344 + #A: {a: 1} 4345 + #B: {b: 2} 4346 + x: #A | #B | {c: 3} 4347 + `, 4348 + path: "x", 4349 + wantClosed: false, 4350 + wantClosedRecursive: false, 4351 + }, 4352 + { 4353 + desc: "definition with disjunction of scalar and struct", 4354 + input: `#A: 1 | {a!: int}`, 4355 + path: "#A", 4356 + wantClosed: true, 4357 + wantClosedRecursive: true, 4358 + }, 4359 + { 4360 + desc: "closed list with definition elements", 4361 + input: `#A: {a: 1}, x: [#A, #A]`, 4362 + path: "x", 4363 + wantClosed: false, 4364 + wantClosedRecursive: false, 4365 + }, 4366 + { 4367 + desc: "list element from definition", 4368 + input: `#A: {a: 1}, x: [#A, #A]`, 4369 + path: "x[0]", 4370 + wantClosed: true, 4371 + wantClosedRecursive: true, 4372 + }, 4373 + } 4374 + 4375 + for _, tc := range testCases { 4376 + t.Run(tc.desc, func(t *testing.T) { 4377 + cuetdtest.FullMatrix.Run(t, tc.input, func(t *testing.T, m *cuetdtest.M) { 4378 + v := getValue(m, tc.input) 4379 + 4380 + v = v.LookupPath(cue.ParsePath(tc.path)) 4381 + 4382 + if err := v.Err(); err != nil { 4383 + t.Fatalf("unexpected error: %v", err) 4384 + } 4385 + 4386 + qt.Check(t, qt.Equals(v.IsClosed(), tc.wantClosed)) 4387 + qt.Check(t, qt.Equals(v.IsClosedRecursively(), tc.wantClosedRecursive)) 4388 + 4389 + if v.IsClosedRecursively() { 4390 + qt.Check(t, qt.IsTrue(v.IsClosed())) 4391 + } 4392 + }) 4393 + }) 4394 + } 4395 + }