Skip to content

Commit

Permalink
ext/typeexpr: Refinements when applying defaults with unknown values
Browse files Browse the repository at this point in the history
If either the given value is refined non-null or if the default value is
refined non-null then the final attribute value after defaults processing
is also guaranteed non-null even if we don't yet know exactly what the
value will be.

This rule is pretty marginal on its own, but refining some types of value
as non-null creates opportunities to deduce further information when the
value is used under other operations later, such as collapsing an unknown
but definitely not null list of a known length into a known list of that
length containing unknown values.
  • Loading branch information
apparentlymart committed May 31, 2023
1 parent 628da05 commit ea69807
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 0 deletions.
3 changes: 3 additions & 0 deletions ext/typeexpr/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ func (d *Defaults) apply(v cty.Value) cty.Value {
}
values[key] = defaultValue
}
if defaultRng := defaultValue.Range(); defaultRng.DefinitelyNotNull() {
values[key] = values[key].RefineNotNull()
}
}

if v.Type().IsMapType() {
Expand Down
68 changes: 68 additions & 0 deletions ext/typeexpr/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,74 @@ func TestDefaults_Apply(t *testing.T) {
}),
}),
},
"optional attribute with a default can never be null": {
defaults: &Defaults{
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"foo": cty.String,
}, []string{"foo"}),
DefaultValues: map[string]cty.Value{
"foo": cty.StringVal("bar"), // Important: default is non-null
},
},
value: cty.ObjectVal(map[string]cty.Value{
"foo": cty.UnknownVal(cty.String), // could potentially be null once known
}),
want: cty.ObjectVal(map[string]cty.Value{
// Because the default isn't null we can guarantee that the
// result cannot be null even if the given value turns out to be.
"foo": cty.UnknownVal(cty.String).RefineNotNull(),
}),
},
"optional attribute with a null default could be null": {
defaults: &Defaults{
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"foo": cty.String,
}, []string{"foo"}),
DefaultValues: map[string]cty.Value{
"foo": cty.NullVal(cty.String), // Important: default is null
},
},
value: cty.ObjectVal(map[string]cty.Value{
"foo": cty.UnknownVal(cty.String), // could potentially be null once known
}),
want: cty.ObjectVal(map[string]cty.Value{
// The default value is itself null, so this result is nullable.
"foo": cty.UnknownVal(cty.String),
}),
},
"optional attribute with no default could be null": {
defaults: &Defaults{
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"foo": cty.String,
}, []string{"foo"}),
},
value: cty.ObjectVal(map[string]cty.Value{
"foo": cty.UnknownVal(cty.String), // could potentially be null once known
}),
want: cty.ObjectVal(map[string]cty.Value{
// The default value is itself null, so this result is nullable.
"foo": cty.UnknownVal(cty.String),
}),
},
"optional attribute with non-null unknown value cannot be null": {
defaults: &Defaults{
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"foo": cty.String,
}, []string{"foo"}),
DefaultValues: map[string]cty.Value{
"foo": cty.NullVal(cty.String), // Important: default is null
},
},
value: cty.ObjectVal(map[string]cty.Value{
"foo": cty.UnknownVal(cty.String).RefineNotNull(),
}),
want: cty.ObjectVal(map[string]cty.Value{
// If the input is guaranteed not null then the default
// value can't possibly be selected, and so the result can
// also not be null.
"foo": cty.UnknownVal(cty.String).RefineNotNull(),
}),
},
}

for name, tc := range testCases {
Expand Down

0 comments on commit ea69807

Please sign in to comment.