Skip to content

Commit

Permalink
Preserve marks when traversing unknown values
Browse files Browse the repository at this point in the history
When traversing an unknown value or a DynamicVal, the marks from that
initial value must be preserved for HCL Index and GetAttr operations.
This mirrors the behavior of GetAttr and Index when used directly the
underlying cty values.
  • Loading branch information
jbardin committed Sep 24, 2024
1 parent bf54697 commit e2f43f4
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 9 deletions.
16 changes: 8 additions & 8 deletions ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
ty := collection.Type()
kty := key.Type()
if kty == cty.DynamicPseudoType || ty == cty.DynamicPseudoType {
return cty.DynamicVal, nil
return cty.DynamicVal.WithSameMarks(collection), nil
}

switch {
Expand Down Expand Up @@ -87,9 +87,9 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
has, _ := collection.HasIndex(key).Unmark()
if !has.IsKnown() {
if ty.IsTupleType() {
return cty.DynamicVal, nil
return cty.DynamicVal.WithSameMarks(collection), nil
} else {
return cty.UnknownVal(ty.ElementType()), nil
return cty.UnknownVal(ty.ElementType()).WithSameMarks(collection), nil
}
}
if has.False() {
Expand Down Expand Up @@ -196,10 +196,10 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
}
}
if !collection.IsKnown() {
return cty.DynamicVal, nil
return cty.DynamicVal.WithSameMarks(collection), nil
}
if !key.IsKnown() {
return cty.DynamicVal, nil
return cty.DynamicVal.WithSameMarks(collection), nil
}

key, _ = key.Unmark()
Expand Down Expand Up @@ -291,13 +291,13 @@ func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagno
}

if !obj.IsKnown() {
return cty.UnknownVal(ty.AttributeType(attrName)), nil
return cty.UnknownVal(ty.AttributeType(attrName)).WithSameMarks(obj), nil
}

return obj.GetAttr(attrName), nil
case ty.IsMapType():
if !obj.IsKnown() {
return cty.UnknownVal(ty.ElementType()), nil
return cty.UnknownVal(ty.ElementType()).WithSameMarks(obj), nil
}

idx := cty.StringVal(attrName)
Expand All @@ -319,7 +319,7 @@ func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagno

return obj.Index(idx), nil
case ty == cty.DynamicPseudoType:
return cty.DynamicVal, nil
return cty.DynamicVal.WithSameMarks(obj), nil
case ty.IsListType() && ty.ElementType().IsObjectType():
// It seems a common mistake to try to access attributes on a whole
// list of objects rather than on a specific individual element, so
Expand Down
109 changes: 108 additions & 1 deletion ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,113 @@ func TestApplyPath(t *testing.T) {
cty.NilVal,
`Attempt to get attribute from null value: This value is null, so it does not have any attributes.`,
},

// Marks should be retained during index and getattr ops, even when
// types and values are unknown. This reflects the same behavior when
// using cty to directly call GetAttr and Index methods.
{
cty.DynamicVal.Mark("marked"),
(cty.Path)(nil).GetAttr("foo"),
cty.DynamicVal.Mark("marked"),
``,
},
{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("should be marked"),
}).Mark("marked"),
(cty.Path)(nil).GetAttr("foo"),
cty.StringVal("should be marked").Mark("marked"),
``,
},
{
cty.UnknownVal(cty.Object(map[string]cty.Type{
"foo": cty.DynamicPseudoType,
})).Mark("marked"),
(cty.Path)(nil).GetAttr("foo"),
cty.DynamicVal.Mark("marked"),
``,
},
{
cty.DynamicVal.Mark("marked"),
(cty.Path)(nil).Index(cty.StringVal("foo")),
cty.DynamicVal.Mark("marked"),
``,
},
{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("should be marked"),
}).Mark("marked"),
(cty.Path)(nil).Index(cty.StringVal("foo")),
cty.StringVal("should be marked").Mark("marked"),
``,
},
{
cty.UnknownVal(cty.Object(map[string]cty.Type{
"foo": cty.DynamicPseudoType,
})).Mark("marked"),
(cty.Path)(nil).Index(cty.StringVal("foo")),
cty.DynamicVal.Mark("marked"),
``,
},
{
cty.DynamicVal.Mark("marked"),
(cty.Path)(nil).Index(cty.NumberIntVal(0)),
cty.DynamicVal.Mark("marked"),
``,
},
{
cty.ListVal([]cty.Value{cty.StringVal("should be marked")}).Mark("marked"),
(cty.Path)(nil).Index(cty.NumberIntVal(0)),
cty.StringVal("should be marked").Mark("marked"),
``,
},
{
cty.UnknownVal(cty.List(cty.String)).Mark("marked"),
(cty.Path)(nil).Index(cty.NumberIntVal(0)),
cty.UnknownVal(cty.String).Mark("marked"),
``,
},

{
cty.DynamicVal.Mark("marked"),
(cty.Path)(nil).Index(cty.UnknownVal(cty.String)),
cty.DynamicVal.Mark("marked"),
``,
},
{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("should be marked"),
}).Mark("marked"),
(cty.Path)(nil).Index(cty.UnknownVal(cty.String)),
cty.DynamicVal.Mark("marked"),
``,
},
{
cty.UnknownVal(cty.Object(map[string]cty.Type{
"foo": cty.DynamicPseudoType,
})).Mark("marked"),
(cty.Path)(nil).Index(cty.UnknownVal(cty.String)),
cty.DynamicVal.Mark("marked"),
``,
},
{
cty.DynamicVal.Mark("marked"),
(cty.Path)(nil).Index(cty.UnknownVal(cty.Number)),
cty.DynamicVal.Mark("marked"),
``,
},
{
cty.ListVal([]cty.Value{cty.StringVal("should be marked")}).Mark("marked"),
(cty.Path)(nil).Index(cty.UnknownVal(cty.Number)),
cty.UnknownVal(cty.String).Mark("marked"),
``,
},
{
cty.UnknownVal(cty.List(cty.String)).Mark("marked"),
(cty.Path)(nil).Index(cty.UnknownVal(cty.Number)),
cty.UnknownVal(cty.String).Mark("marked"),
``,
},
}

for _, test := range tests {
Expand All @@ -257,7 +364,7 @@ func TestApplyPath(t *testing.T) {
t.Logf("testing ApplyPath\nstart: %#v\npath: %#v", test.Start, test.Path)

for _, diag := range diags {
t.Logf(diag.Error())
t.Log(diag.Error())
}

if test.WantErr != "" {
Expand Down

0 comments on commit e2f43f4

Please sign in to comment.