Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport of Improve marks support for length and lookup into v0.15 #28645

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 33 additions & 15 deletions lang/funcs/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var LengthFunc = function.New(&function.Spec{
Type: cty.DynamicPseudoType,
AllowDynamicType: true,
AllowUnknown: true,
AllowMarked: true,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
Expand All @@ -34,15 +35,16 @@ var LengthFunc = function.New(&function.Spec{
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
coll := args[0]
collTy := args[0].Type()
marks := coll.Marks()
switch {
case collTy == cty.DynamicPseudoType:
return cty.UnknownVal(cty.Number), nil
return cty.UnknownVal(cty.Number).WithMarks(marks), nil
case collTy.IsTupleType():
l := len(collTy.TupleElementTypes())
return cty.NumberIntVal(int64(l)), nil
return cty.NumberIntVal(int64(l)).WithMarks(marks), nil
case collTy.IsObjectType():
l := len(collTy.AttributeTypes())
return cty.NumberIntVal(int64(l)), nil
return cty.NumberIntVal(int64(l)).WithMarks(marks), nil
case collTy == cty.String:
// We'll delegate to the cty stdlib strlen function here, because
// it deals with all of the complexities of tokenizing unicode
Expand Down Expand Up @@ -212,12 +214,14 @@ var IndexFunc = function.New(&function.Spec{
var LookupFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "inputMap",
Type: cty.DynamicPseudoType,
Name: "inputMap",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
{
Name: "key",
Type: cty.String,
Name: "key",
Type: cty.String,
AllowMarked: true,
},
},
VarParam: &function.Parameter{
Expand All @@ -226,6 +230,7 @@ var LookupFunc = function.New(&function.Spec{
AllowUnknown: true,
AllowDynamicType: true,
AllowNull: true,
AllowMarked: true,
},
Type: func(args []cty.Value) (ret cty.Type, err error) {
if len(args) < 1 || len(args) > 3 {
Expand All @@ -240,7 +245,8 @@ var LookupFunc = function.New(&function.Spec{
return cty.DynamicPseudoType, nil
}

key := args[1].AsString()
keyVal, _ := args[1].Unmark()
key := keyVal.AsString()
if ty.HasAttribute(key) {
return args[0].GetAttr(key).Type(), nil
} else if len(args) == 3 {
Expand All @@ -266,34 +272,46 @@ var LookupFunc = function.New(&function.Spec{
defaultValueSet := false

if len(args) == 3 {
// intentionally leave default value marked
defaultVal = args[2]
defaultValueSet = true
}

mapVar := args[0]
lookupKey := args[1].AsString()
// keep track of marks from the collection and key
var markses []cty.ValueMarks

// unmark collection, retain marks to reapply later
mapVar, mapMarks := args[0].Unmark()
markses = append(markses, mapMarks)

// include marks on the key in the result
keyVal, keyMarks := args[1].Unmark()
if len(keyMarks) > 0 {
markses = append(markses, keyMarks)
}
lookupKey := keyVal.AsString()

if !mapVar.IsKnown() {
return cty.UnknownVal(retType), nil
return cty.UnknownVal(retType).WithMarks(markses...), nil
}

if mapVar.Type().IsObjectType() {
if mapVar.Type().HasAttribute(lookupKey) {
return mapVar.GetAttr(lookupKey), nil
return mapVar.GetAttr(lookupKey).WithMarks(markses...), nil
}
} else if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True {
return mapVar.Index(cty.StringVal(lookupKey)), nil
return mapVar.Index(cty.StringVal(lookupKey)).WithMarks(markses...), nil
}

if defaultValueSet {
defaultVal, err = convert.Convert(defaultVal, retType)
if err != nil {
return cty.NilVal, err
}
return defaultVal, nil
return defaultVal.WithMarks(markses...), nil
}

return cty.UnknownVal(cty.DynamicPseudoType), fmt.Errorf(
return cty.UnknownVal(cty.DynamicPseudoType).WithMarks(markses...), fmt.Errorf(
"lookup failed to find '%s'", lookupKey)
},
})
Expand Down
130 changes: 130 additions & 0 deletions lang/funcs/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,54 @@ func TestLength(t *testing.T) {
cty.DynamicVal,
cty.UnknownVal(cty.Number),
},
{ // Marked collections return a marked length
cty.ListVal([]cty.Value{
cty.StringVal("hello"),
cty.StringVal("world"),
}).Mark("secret"),
cty.NumberIntVal(2).Mark("secret"),
},
{ // Marks on values in unmarked collections do not propagate
cty.ListVal([]cty.Value{
cty.StringVal("hello").Mark("a"),
cty.StringVal("world").Mark("b"),
}),
cty.NumberIntVal(2),
},
{ // Marked strings return a marked length
cty.StringVal("hello world").Mark("secret"),
cty.NumberIntVal(11).Mark("secret"),
},
{ // Marked tuples return a marked length
cty.TupleVal([]cty.Value{
cty.StringVal("hello"),
cty.StringVal("world"),
}).Mark("secret"),
cty.NumberIntVal(2).Mark("secret"),
},
{ // Marks on values in unmarked tuples do not propagate
cty.TupleVal([]cty.Value{
cty.StringVal("hello").Mark("a"),
cty.StringVal("world").Mark("b"),
}),
cty.NumberIntVal(2),
},
{ // Marked objects return a marked length
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("hello"),
"b": cty.StringVal("world"),
"c": cty.StringVal("nice to meet you"),
}).Mark("secret"),
cty.NumberIntVal(3).Mark("secret"),
},
{ // Marks on object attribute values do not propagate
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("hello").Mark("a"),
"b": cty.StringVal("world").Mark("b"),
"c": cty.StringVal("nice to meet you").Mark("c"),
}),
cty.NumberIntVal(3),
},
}

for _, test := range tests {
Expand Down Expand Up @@ -747,6 +795,88 @@ func TestLookup(t *testing.T) {
cty.DynamicVal, // if the key is unknown then we don't know which object attribute and thus can't know the type
false,
},
{ // successful marked collection lookup returns marked value
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep"),
}).Mark("a"),
cty.StringVal("boop"),
cty.StringVal("nope"),
},
cty.StringVal("beep").Mark("a"),
false,
},
{ // apply collection marks to unknown return vaue
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep"),
"frob": cty.UnknownVal(cty.String),
}).Mark("a"),
cty.StringVal("frob"),
cty.StringVal("nope"),
},
cty.UnknownVal(cty.String).Mark("a"),
false,
},
{ // propagate collection marks to default when returning
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep"),
}).Mark("a"),
cty.StringVal("frob"),
cty.StringVal("nope").Mark("b"),
},
cty.StringVal("nope").WithMarks(cty.NewValueMarks("a", "b")),
false,
},
{ // on unmarked collection, return only marks from found value
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep").Mark("a"),
"frob": cty.StringVal("honk").Mark("b"),
}),
cty.StringVal("frob"),
cty.StringVal("nope").Mark("c"),
},
cty.StringVal("honk").Mark("b"),
false,
},
{ // on unmarked collection, return default exactly on missing
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep").Mark("a"),
"frob": cty.StringVal("honk").Mark("b"),
}),
cty.StringVal("squish"),
cty.StringVal("nope").Mark("c"),
},
cty.StringVal("nope").Mark("c"),
false,
},
{ // retain marks on default if converted
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep").Mark("a"),
"frob": cty.StringVal("honk").Mark("b"),
}),
cty.StringVal("squish"),
cty.NumberIntVal(5).Mark("c"),
},
cty.StringVal("5").Mark("c"),
false,
},
{ // propagate marks from key
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep"),
"frob": cty.StringVal("honk"),
}),
cty.StringVal("boop").Mark("a"),
cty.StringVal("nope"),
},
cty.StringVal("beep").Mark("a"),
false,
},
}

for _, test := range tests {
Expand Down