Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0f5a4b7

Browse files
authored
cty: Value.HasWhollyKnownType
This tests whether a value contains any unknown values of unknown type. This is different than just testing if any of the nested types are DynamicPseudoType, because a null value of DynamicPseudoType has a different meaning than an unknown value of DynamicPseudoType: the null value's type can't become any more "known".
1 parent f9ae910 commit 0f5a4b7

File tree

5 files changed

+253
-6
lines changed

5 files changed

+253
-6
lines changed

cty/type.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func (t Type) HasDynamicTypes() bool {
8787
case t.IsPrimitiveType():
8888
return false
8989
case t.IsCollectionType():
90-
return false
90+
return t.ElementType().HasDynamicTypes()
9191
case t.IsObjectType():
9292
attrTypes := t.AttributeTypes()
9393
for _, at := range attrTypes {

cty/type_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package cty
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestHasDynamicTypes(t *testing.T) {
9+
tests := []struct {
10+
ty Type
11+
expected bool
12+
}{
13+
{
14+
DynamicPseudoType,
15+
true,
16+
},
17+
{
18+
List(DynamicPseudoType),
19+
true,
20+
},
21+
{
22+
Tuple([]Type{String, DynamicPseudoType}),
23+
true,
24+
},
25+
{
26+
Object(map[string]Type{
27+
"a": String,
28+
"unknown": DynamicPseudoType,
29+
}),
30+
true,
31+
},
32+
{
33+
List(Object(map[string]Type{
34+
"a": String,
35+
"unknown": DynamicPseudoType,
36+
})),
37+
true,
38+
},
39+
{
40+
Tuple([]Type{Object(map[string]Type{
41+
"a": String,
42+
"unknown": DynamicPseudoType,
43+
})}),
44+
true,
45+
},
46+
}
47+
48+
for _, test := range tests {
49+
t.Run(fmt.Sprintf("%#v.HasDynamicTypes()", test.ty), func(t *testing.T) {
50+
got := test.ty.HasDynamicTypes()
51+
if got != test.expected {
52+
t.Errorf("Equals returned %#v; want %#v", got, test.expected)
53+
}
54+
})
55+
}
56+
}

cty/value.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,37 @@ func (val Value) IsWhollyKnown() bool {
106106
return true
107107
}
108108
}
109+
110+
// HasWhollyKnownType checks if the value is dynamic, or contains any nested
111+
// DynamicVal. This implies that both the value is not known, and the final
112+
// type may change.
113+
func (val Value) HasWhollyKnownType() bool {
114+
// a null dynamic type is known
115+
if val.IsNull() {
116+
return true
117+
}
118+
119+
// an unknown DynamicPseudoType is a DynamicVal, but we don't want to
120+
// check that value for equality here, since this method is used within the
121+
// equality check.
122+
if !val.IsKnown() && val.ty == DynamicPseudoType {
123+
return false
124+
}
125+
126+
if val.CanIterateElements() {
127+
// if the value is not known, then we can look directly at the internal
128+
// types
129+
if !val.IsKnown() {
130+
return !val.ty.HasDynamicTypes()
131+
}
132+
133+
for it := val.ElementIterator(); it.Next(); {
134+
_, ev := it.Element()
135+
if !ev.HasWhollyKnownType() {
136+
return false
137+
}
138+
}
139+
}
140+
141+
return true
142+
}

cty/value_ops.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ func (val Value) Equals(other Value) Value {
133133
case val.IsKnown() && !other.IsKnown():
134134
switch {
135135
case val.IsNull(), other.ty.HasDynamicTypes():
136-
// If known is Null, we need to wait for the unkown value since
136+
// If known is Null, we need to wait for the unknown value since
137137
// nulls of any type are equal.
138-
// An unkown with a dynamic type compares as unknown, which we need
138+
// An unknown with a dynamic type compares as unknown, which we need
139139
// to check before the type comparison below.
140140
return UnknownVal(Bool)
141141
case !val.ty.Equals(other.ty):
@@ -148,9 +148,9 @@ func (val Value) Equals(other Value) Value {
148148
case other.IsKnown() && !val.IsKnown():
149149
switch {
150150
case other.IsNull(), val.ty.HasDynamicTypes():
151-
// If known is Null, we need to wait for the unkown value since
151+
// If known is Null, we need to wait for the unknown value since
152152
// nulls of any type are equal.
153-
// An unkown with a dynamic type compares as unknown, which we need
153+
// An unknown with a dynamic type compares as unknown, which we need
154154
// to check before the type comparison below.
155155
return UnknownVal(Bool)
156156
case !other.ty.Equals(val.ty):
@@ -171,7 +171,15 @@ func (val Value) Equals(other Value) Value {
171171
return BoolVal(false)
172172
}
173173

174-
if val.ty.HasDynamicTypes() || other.ty.HasDynamicTypes() {
174+
// Check if there are any nested dynamic values making this comparison
175+
// unknown.
176+
if !val.HasWhollyKnownType() || !other.HasWhollyKnownType() {
177+
// Even if we have dynamic values, we can still determine inequality if
178+
// there is no way the types could later conform.
179+
if val.ty.TestConformance(other.ty) != nil && other.ty.TestConformance(val.ty) != nil {
180+
return BoolVal(false)
181+
}
182+
175183
return UnknownVal(Bool)
176184
}
177185

cty/value_ops_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,80 @@ func TestValueEquals(t *testing.T) {
633633
NumberIntVal(1),
634634
False, // because no string value -- even null -- can be equal to a non-null number
635635
},
636+
{
637+
ObjectVal(map[string]Value{
638+
"a": StringVal("a"),
639+
}),
640+
// A null value is always known
641+
ObjectVal(map[string]Value{
642+
"a": NullVal(DynamicPseudoType),
643+
}),
644+
BoolVal(false),
645+
},
646+
{
647+
ObjectVal(map[string]Value{
648+
"a": NullVal(DynamicPseudoType),
649+
}),
650+
ObjectVal(map[string]Value{
651+
"a": NullVal(DynamicPseudoType),
652+
}),
653+
BoolVal(true),
654+
},
655+
{
656+
ObjectVal(map[string]Value{
657+
"a": StringVal("a"),
658+
"b": UnknownVal(Number),
659+
}),
660+
// While we have a dynamic type, the different object types should
661+
// still compare false
662+
ObjectVal(map[string]Value{
663+
"a": NullVal(DynamicPseudoType),
664+
"c": UnknownVal(Number),
665+
}),
666+
BoolVal(false),
667+
},
668+
{
669+
ObjectVal(map[string]Value{
670+
"a": StringVal("a"),
671+
"b": UnknownVal(Number),
672+
}),
673+
// While we have a dynamic type, the different object types should
674+
// still compare false
675+
ObjectVal(map[string]Value{
676+
"a": DynamicVal,
677+
"c": UnknownVal(Number),
678+
}),
679+
BoolVal(false),
680+
},
681+
{
682+
ObjectVal(map[string]Value{
683+
"a": NullVal(DynamicPseudoType),
684+
}),
685+
ObjectVal(map[string]Value{
686+
"a": DynamicVal,
687+
}),
688+
UnknownVal(Bool),
689+
},
690+
{
691+
ObjectVal(map[string]Value{
692+
"a": NullVal(List(String)),
693+
}),
694+
// While the unknown val does contain dynamic types, the overall
695+
// container types can't conform.
696+
ObjectVal(map[string]Value{
697+
"a": UnknownVal(List(List(DynamicPseudoType))),
698+
}),
699+
BoolVal(false),
700+
},
701+
{
702+
ObjectVal(map[string]Value{
703+
"a": NullVal(List(List(String))),
704+
}),
705+
ObjectVal(map[string]Value{
706+
"a": UnknownVal(List(List(DynamicPseudoType))),
707+
}),
708+
UnknownVal(Bool),
709+
},
636710

637711
// Marks
638712
{
@@ -2435,3 +2509,78 @@ func TestValueGoString(t *testing.T) {
24352509
})
24362510
}
24372511
}
2512+
2513+
func TestHasWhollyKnownType(t *testing.T) {
2514+
tests := []struct {
2515+
Value Value
2516+
Want bool
2517+
}{
2518+
{
2519+
Value: DynamicVal,
2520+
Want: false,
2521+
},
2522+
{
2523+
Value: ObjectVal(map[string]Value{
2524+
"dyn": DynamicVal,
2525+
}),
2526+
Want: false,
2527+
},
2528+
{
2529+
Value: NullVal(Object(map[string]Type{
2530+
"dyn": DynamicPseudoType,
2531+
})),
2532+
Want: true,
2533+
},
2534+
{
2535+
Value: TupleVal([]Value{
2536+
StringVal("a"),
2537+
NullVal(DynamicPseudoType),
2538+
}),
2539+
Want: true,
2540+
},
2541+
{
2542+
Value: ListVal([]Value{
2543+
ObjectVal(map[string]Value{
2544+
"null": NullVal(DynamicPseudoType),
2545+
}),
2546+
}),
2547+
Want: true,
2548+
},
2549+
{
2550+
Value: ListVal([]Value{
2551+
NullVal(Object(map[string]Type{
2552+
"dyn": DynamicPseudoType,
2553+
})),
2554+
}),
2555+
Want: true,
2556+
},
2557+
{
2558+
Value: ObjectVal(map[string]Value{
2559+
"tuple": TupleVal([]Value{
2560+
StringVal("a"),
2561+
NullVal(DynamicPseudoType),
2562+
}),
2563+
}),
2564+
Want: true,
2565+
},
2566+
{
2567+
Value: ObjectVal(map[string]Value{
2568+
"tuple": TupleVal([]Value{
2569+
ObjectVal(map[string]Value{
2570+
"dyn": DynamicVal,
2571+
}),
2572+
}),
2573+
}),
2574+
Want: false,
2575+
},
2576+
}
2577+
for _, test := range tests {
2578+
t.Run(test.Value.GoString(), func(t *testing.T) {
2579+
got := test.Value.HasWhollyKnownType()
2580+
want := test.Want
2581+
if got != want {
2582+
t.Errorf("wrong result\ngot: %v\nwant: %v", got, want)
2583+
}
2584+
})
2585+
}
2586+
}

0 commit comments

Comments
 (0)