Skip to content

Commit 3c0f5c5

Browse files
committed
Consider typed, value-less variables to have null value
A variable with a type but no default value or override resulted in an empty string. This matches the legacy behavior of untyped variables, but does not make sense when using types (an empty string is itself a type violation for everything except `string`). All variables defined with a type but with no value are now a typed `null`. A variable explicitly typed `any` was previously treated as if the typing was omitted; with no defined value or override, that resulted in an empty string. The `any` type is now distinguished from an omitted type; these variables, with no default or override, are also `null`. In other respects, the behavior of `any` is unchanged and largely behaves as if the type was omitted. It's not clear whether it should be supported, let alone how it should behave, so these tests were removed. It's being treated as undefined behavior. Signed-off-by: Roberto Villarreal <[email protected]>
1 parent ea2b702 commit 3c0f5c5

File tree

2 files changed

+64
-35
lines changed

2 files changed

+64
-35
lines changed

bake/hcl_test.go

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,63 @@ func TestHCLNullVariables(t *testing.T) {
423423
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["foo"])
424424
}
425425

426+
func TestHCLTypedNullVariables(t *testing.T) {
427+
types := []string{
428+
"any",
429+
"string", "number", "bool",
430+
"list(string)", "set(string)", "map(string)",
431+
"tuple([string])", "object({val: string})",
432+
}
433+
for _, varType := range types {
434+
tName := fmt.Sprintf("variable typed %q with null default remains null", varType)
435+
t.Run(tName, func(t *testing.T) {
436+
dt := fmt.Sprintf(`
437+
variable "FOO" {
438+
type = %s
439+
default = null
440+
}
441+
442+
target "default" {
443+
args = {
444+
foo = equal(FOO, null)
445+
}
446+
}`, varType)
447+
c, err := ParseFile([]byte(dt), "docker-bake.hcl")
448+
require.NoError(t, err)
449+
require.Equal(t, 1, len(c.Targets))
450+
require.Equal(t, "true", *c.Targets[0].Args["foo"])
451+
})
452+
}
453+
}
454+
455+
func TestHCLTypedValuelessVariables(t *testing.T) {
456+
types := []string{
457+
"any",
458+
"string", "number", "bool",
459+
"list(string)", "set(string)", "map(string)",
460+
"tuple([string])", "object({val: string})",
461+
}
462+
for _, varType := range types {
463+
tName := fmt.Sprintf("variable typed %q with no default is null", varType)
464+
t.Run(tName, func(t *testing.T) {
465+
dt := fmt.Sprintf(`
466+
variable "FOO" {
467+
type = %s
468+
}
469+
470+
target "default" {
471+
args = {
472+
foo = equal(FOO, null)
473+
}
474+
}`, varType)
475+
c, err := ParseFile([]byte(dt), "docker-bake.hcl")
476+
require.NoError(t, err)
477+
require.Equal(t, 1, len(c.Targets))
478+
require.Equal(t, "true", *c.Targets[0].Args["foo"])
479+
})
480+
}
481+
}
482+
426483
func TestJSONNullVariables(t *testing.T) {
427484
dt := []byte(`{
428485
"variable": {
@@ -1877,19 +1934,6 @@ func TestTypedVarOverrides(t *testing.T) {
18771934
override: `"hello"`,
18781935
wantValue: `"hello"`,
18791936
},
1880-
{
1881-
name: "any",
1882-
varType: "any",
1883-
override: "[1,2]",
1884-
wantValue: "[1,2]",
1885-
},
1886-
{
1887-
name: "any never convert to complex types",
1888-
varType: "any",
1889-
override: "[1,2]",
1890-
argValue: "length(FOO)",
1891-
wantErrorMsg: "collection must be a list",
1892-
},
18931937
{
18941938
name: "proper CSV list of strings",
18951939
varType: "list(string)",
@@ -2090,19 +2134,6 @@ func TestTypedVarOverrides_JSON(t *testing.T) {
20902134
override: `"hello"`,
20912135
wantValue: "hello",
20922136
},
2093-
{
2094-
name: "any",
2095-
varType: "any",
2096-
override: "[1,2]",
2097-
wantValue: "[1,2]",
2098-
},
2099-
{
2100-
name: "any never convert to complex types",
2101-
varType: "any",
2102-
override: "[1,2]",
2103-
argValue: "length(FOO)",
2104-
wantErrorMsg: "collection must be a list",
2105-
},
21062137
{
21072138
name: "list of strings",
21082139
varType: "list(string)",
@@ -2313,6 +2344,7 @@ func TestJSONOverridePriority(t *testing.T) {
23132344
dt := []byte(`
23142345
variable "foo" {
23152346
type = number
2347+
default = 101
23162348
}
23172349
23182350
target "default" {
@@ -2325,8 +2357,7 @@ func TestJSONOverridePriority(t *testing.T) {
23252357
c, err := ParseFile(dt, "docker-bake.hcl")
23262358
require.NoError(t, err)
23272359
require.Equal(t, 1, len(c.Targets))
2328-
// a variable with no value has always resulted in an empty string
2329-
require.Equal(t, "", *c.Targets[0].Args["bar"])
2360+
require.Equal(t, "101", *c.Targets[0].Args["bar"])
23302361

23312362
t.Setenv("foo_JSON", "42")
23322363
c, err = ParseFile(dt, "docker-bake.hcl")

bake/hclparser/hclparser.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
282282
}
283283

284284
var diags hcl.Diagnostics
285-
varType := cty.DynamicPseudoType
285+
varType, typeSpecified := cty.DynamicPseudoType, false
286286
def, ok := p.attrs[name]
287287
if !ok {
288288
vr, ok := p.vars[name]
@@ -295,12 +295,13 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
295295
if diags.HasErrors() {
296296
return diags
297297
}
298+
typeSpecified = !varType.Equals(cty.DynamicPseudoType) || hcl.ExprAsKeyword(vr.Type) == "any"
298299
}
299300

300301
if def == nil {
301-
// lack of specified value is considered to have an empty string value,
302-
// but any overrides get type checked
303-
if _, ok, _ := p.valueHasOverride(name, false); !ok {
302+
// Lack of specified value, when untyped is considered to have an empty string value.
303+
// A typed variable with no value will result in (typed) nil.
304+
if _, ok, _ := p.valueHasOverride(name, false); !ok && !typeSpecified {
304305
vv := cty.StringVal("")
305306
v = &vv
306307
return
@@ -322,9 +323,6 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
322323
}
323324
}
324325

325-
// Not entirely true... this doesn't differentiate between a user that specified 'any'
326-
// and a user that specified nothing. But the result is the same; both are treated as strings.
327-
typeSpecified := !varType.Equals(cty.DynamicPseudoType)
328326
envv, hasEnv, jsonEnv := p.valueHasOverride(name, typeSpecified)
329327
_, isVar := p.vars[name]
330328

0 commit comments

Comments
 (0)