Skip to content

Commit

Permalink
hclsyntax: Refinements to unknown splat expression results
Browse files Browse the repository at this point in the history
We know that a splat expression can never produce a null result, and also
in many cases we can use length refinements from the source collection to
also refine the destination collection because we know that a splat
expression produces exactly one result for each input element.

This also allows us to be a little more precise in the case where the
splat operator is projecting a non-list/set value into a zero or one
element list and we know the source value isn't null. This refinement is
a bit more marginal since it would be weird to apply the splat operator
to a value already known to be non-null anyway, but the refinement might
come from far away from the splat expression and so could still have
useful downstream effects in some cases.
  • Loading branch information
apparentlymart committed May 31, 2023
1 parent ea69807 commit 333389d
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 5 deletions.
23 changes: 20 additions & 3 deletions hclsyntax/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -1684,11 +1684,15 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
// example, it is valid to use a splat on a single object to retrieve a
// list of a single attribute, but we still need to check if that
// attribute actually exists.
upgradedUnknown = !sourceVal.IsKnown()
if !sourceVal.IsKnown() {
sourceRng := sourceVal.Range()
if sourceRng.CouldBeNull() {
upgradedUnknown = true
}
}

sourceVal = cty.TupleVal([]cty.Value{sourceVal})
sourceTy = sourceVal.Type()

}

// We'll compute our result type lazily if we need it. In the normal case
Expand Down Expand Up @@ -1727,7 +1731,20 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
// checking to proceed.
ty, tyDiags := resultTy()
diags = append(diags, tyDiags...)
return cty.UnknownVal(ty), diags
ret := cty.UnknownVal(ty)
if ty != cty.DynamicPseudoType {
ret = ret.RefineNotNull()
}
if ty.IsListType() && sourceVal.Type().IsCollectionType() {
// We can refine the length of an unknown list result based on
// the source collection's own length.
sourceRng := sourceVal.Range()
ret = ret.Refine().
CollectionLengthLowerBound(sourceRng.LengthLowerBound()).
CollectionLengthUpperBound(sourceRng.LengthUpperBound()).
NewValue()
}
return ret.WithSameMarks(sourceVal), diags
}

// Unmark the collection, and save the marks to apply to the returned
Expand Down
49 changes: 47 additions & 2 deletions hclsyntax/expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,20 @@ upper(
cty.DynamicVal,
0,
},
{
`unkstr[*]`,
&hcl.EvalContext{
Variables: map[string]cty.Value{
"unkstr": cty.UnknownVal(cty.String).RefineNotNull(),
},
},
// If the unknown string is definitely not null then we already
// know that the result will be a single-element tuple.
cty.TupleVal([]cty.Value{
cty.UnknownVal(cty.String).RefineNotNull(),
}),
0,
},
{
`unkstr.*.name`,
&hcl.EvalContext{
Expand Down Expand Up @@ -1182,6 +1196,20 @@ upper(
cty.DynamicVal,
0,
},
{
`unkobj.*.name`,
&hcl.EvalContext{
Variables: map[string]cty.Value{
"unkobj": cty.UnknownVal(cty.Object(map[string]cty.Type{
"name": cty.String,
})).RefineNotNull(),
},
},
cty.TupleVal([]cty.Value{
cty.UnknownVal(cty.String),
}),
0,
},
{
`unkobj.*.names`,
&hcl.EvalContext{
Expand All @@ -1203,7 +1231,24 @@ upper(
}))),
},
},
cty.UnknownVal(cty.List(cty.String)),
cty.UnknownVal(cty.List(cty.String)).RefineNotNull(),
0,
},
{
`unklistobj.*.name`,
&hcl.EvalContext{
Variables: map[string]cty.Value{
"unklistobj": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
"name": cty.String,
}))).Refine().
CollectionLengthUpperBound(5).
NewValue(),
},
},
cty.UnknownVal(cty.List(cty.String)).Refine().
NotNull().
CollectionLengthUpperBound(5).
NewValue(),
0,
},
{
Expand All @@ -1222,7 +1267,7 @@ upper(
),
},
},
cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.Bool})),
cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.Bool})).RefineNotNull(),
0,
},
{
Expand Down

0 comments on commit 333389d

Please sign in to comment.