Expect correct type from a conditional with nulls (#116)

The type unification done when evaluating a conditional normally needs
to return a DynamicPseudoType when either condition is dynamic. However,
a null dynamic value represents a known value of the desired type rather
than an unknown type, and we can be certain that it is convertible to
the desired type during the final evaluation.  Rather than unifying
types against nulls, directly assign the needed conversion when
evaluating the conditional.
This commit is contained in:
James Bardin 2019-07-02 14:56:34 -04:00 committed by GitHub
parent 0b64543c96
commit 5b39d9ff3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 2 deletions

View File

@ -473,8 +473,26 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic
falseResult, falseDiags := e.FalseResult.Value(ctx)
var diags hcl.Diagnostics
// Try to find a type that both results can be converted to.
resultType, convs := convert.UnifyUnsafe([]cty.Type{trueResult.Type(), falseResult.Type()})
var resultType cty.Type
convs := make([]convert.Conversion, 2)
switch {
// If either case is a dynamic null value (which would result from a
// literal null in the config), we know that it can convert to the expected
// type of the opposite case, and we don't need to speculatively reduce the
// final result type to DynamicPseudoType.
case trueResult.RawEquals(cty.NullVal(cty.DynamicPseudoType)):
resultType = falseResult.Type()
convs[0] = convert.GetConversionUnsafe(cty.DynamicPseudoType, resultType)
case falseResult.RawEquals(cty.NullVal(cty.DynamicPseudoType)):
resultType = trueResult.Type()
convs[1] = convert.GetConversionUnsafe(cty.DynamicPseudoType, resultType)
default:
// Try to find a type that both results can be converted to.
resultType, convs = convert.UnifyUnsafe([]cty.Type{trueResult.Type(), falseResult.Type()})
}
if resultType == cty.NilType {
return cty.DynamicVal, hcl.Diagnostics{
{

View File

@ -1351,6 +1351,56 @@ EOT
cty.False,
0,
},
{
`true ? var : null`,
&hcl.EvalContext{
Variables: map[string]cty.Value{
"var": cty.ObjectVal(map[string]cty.Value{"a": cty.StringVal("A")}),
},
},
cty.ObjectVal(map[string]cty.Value{"a": cty.StringVal("A")}),
0,
},
{
`true ? var : null`,
&hcl.EvalContext{
Variables: map[string]cty.Value{
"var": cty.UnknownVal(cty.DynamicPseudoType),
},
},
cty.UnknownVal(cty.DynamicPseudoType),
0,
},
{
`true ? ["a", "b"] : null`,
nil,
cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}),
0,
},
{
`true ? null: ["a", "b"]`,
nil,
cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
0,
},
{
`false ? ["a", "b"] : null`,
nil,
cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
0,
},
{
`false ? null: ["a", "b"]`,
nil,
cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}),
0,
},
{
`false ? null: null`,
nil,
cty.NullVal(cty.DynamicPseudoType),
0,
},
}
for _, test := range tests {