diff --git a/internal/addrs/count_attr.go b/internal/addrs/count_attr.go index 90a5faf0e..0be5c0264 100644 --- a/internal/addrs/count_attr.go +++ b/internal/addrs/count_attr.go @@ -10,3 +10,9 @@ type CountAttr struct { func (ca CountAttr) String() string { return "count." + ca.Name } + +func (ca CountAttr) UniqueKey() UniqueKey { + return ca // A CountAttr is its own UniqueKey +} + +func (ca CountAttr) uniqueKeySigil() {} diff --git a/internal/addrs/for_each_attr.go b/internal/addrs/for_each_attr.go index 7a6385035..6b0c06096 100644 --- a/internal/addrs/for_each_attr.go +++ b/internal/addrs/for_each_attr.go @@ -10,3 +10,9 @@ type ForEachAttr struct { func (f ForEachAttr) String() string { return "each." + f.Name } + +func (f ForEachAttr) UniqueKey() UniqueKey { + return f // A ForEachAttr is its own UniqueKey +} + +func (f ForEachAttr) uniqueKeySigil() {} diff --git a/internal/addrs/input_variable.go b/internal/addrs/input_variable.go index 975c72f1e..e85743bcd 100644 --- a/internal/addrs/input_variable.go +++ b/internal/addrs/input_variable.go @@ -14,6 +14,12 @@ func (v InputVariable) String() string { return "var." + v.Name } +func (v InputVariable) UniqueKey() UniqueKey { + return v // A InputVariable is its own UniqueKey +} + +func (v InputVariable) uniqueKeySigil() {} + // Absolute converts the receiver into an absolute address within the given // module instance. func (v InputVariable) Absolute(m ModuleInstance) AbsInputVariableInstance { diff --git a/internal/addrs/local_value.go b/internal/addrs/local_value.go index 61a07b9c7..601765006 100644 --- a/internal/addrs/local_value.go +++ b/internal/addrs/local_value.go @@ -14,6 +14,12 @@ func (v LocalValue) String() string { return "local." + v.Name } +func (v LocalValue) UniqueKey() UniqueKey { + return v // A LocalValue is its own UniqueKey +} + +func (v LocalValue) uniqueKeySigil() {} + // Absolute converts the receiver into an absolute address within the given // module instance. func (v LocalValue) Absolute(m ModuleInstance) AbsLocalValue { diff --git a/internal/addrs/module_call.go b/internal/addrs/module_call.go index c55433ac6..c6af85f08 100644 --- a/internal/addrs/module_call.go +++ b/internal/addrs/module_call.go @@ -15,6 +15,12 @@ func (c ModuleCall) String() string { return "module." + c.Name } +func (c ModuleCall) UniqueKey() UniqueKey { + return c // A ModuleCall is its own UniqueKey +} + +func (c ModuleCall) uniqueKeySigil() {} + // Instance returns the address of an instance of the receiver identified by // the given key. func (c ModuleCall) Instance(key InstanceKey) ModuleCallInstance { @@ -79,6 +85,12 @@ func (c ModuleCallInstance) String() string { return fmt.Sprintf("module.%s%s", c.Call.Name, c.Key) } +func (c ModuleCallInstance) UniqueKey() UniqueKey { + return c // A ModuleCallInstance is its own UniqueKey +} + +func (c ModuleCallInstance) uniqueKeySigil() {} + func (c ModuleCallInstance) Absolute(moduleAddr ModuleInstance) ModuleInstance { ret := make(ModuleInstance, len(moduleAddr), len(moduleAddr)+1) copy(ret, moduleAddr) @@ -118,6 +130,12 @@ func (m ModuleCallOutput) String() string { return fmt.Sprintf("%s.%s", m.Call.String(), m.Name) } +func (m ModuleCallOutput) UniqueKey() UniqueKey { + return m // A ModuleCallOutput is its own UniqueKey +} + +func (m ModuleCallOutput) uniqueKeySigil() {} + // ModuleCallInstanceOutput is the address of a particular named output produced by // an instance of a module call. type ModuleCallInstanceOutput struct { @@ -139,6 +157,12 @@ func (co ModuleCallInstanceOutput) String() string { return fmt.Sprintf("%s.%s", co.Call.String(), co.Name) } +func (co ModuleCallInstanceOutput) UniqueKey() UniqueKey { + return co // A ModuleCallInstanceOutput is its own UniqueKey +} + +func (co ModuleCallInstanceOutput) uniqueKeySigil() {} + // AbsOutputValue returns the absolute output value address that corresponds // to the receving module call output address, once resolved in the given // calling module. diff --git a/internal/addrs/path_attr.go b/internal/addrs/path_attr.go index cfc13f4bc..9de5e134d 100644 --- a/internal/addrs/path_attr.go +++ b/internal/addrs/path_attr.go @@ -10,3 +10,9 @@ type PathAttr struct { func (pa PathAttr) String() string { return "path." + pa.Name } + +func (pa PathAttr) UniqueKey() UniqueKey { + return pa // A PathAttr is its own UniqueKey +} + +func (pa PathAttr) uniqueKeySigil() {} diff --git a/internal/addrs/referenceable.go b/internal/addrs/referenceable.go index 211083a5f..fbbc753d4 100644 --- a/internal/addrs/referenceable.go +++ b/internal/addrs/referenceable.go @@ -7,6 +7,9 @@ type Referenceable interface { // in lang.Scope.buildEvalContext. referenceableSigil() + // All Referenceable address types must have unique keys. + UniqueKeyer + // String produces a string representation of the address that could be // parsed as a HCL traversal and passed to ParseRef to produce an identical // result. diff --git a/internal/addrs/resource.go b/internal/addrs/resource.go index 97b7f5dd9..f22db05cb 100644 --- a/internal/addrs/resource.go +++ b/internal/addrs/resource.go @@ -32,6 +32,12 @@ func (r Resource) Equal(o Resource) bool { return r.Mode == o.Mode && r.Name == o.Name && r.Type == o.Type } +func (r Resource) UniqueKey() UniqueKey { + return r // A Resource is its own UniqueKey +} + +func (r Resource) uniqueKeySigil() {} + // Instance produces the address for a specific instance of the receiver // that is idenfied by the given key. func (r Resource) Instance(key InstanceKey) ResourceInstance { @@ -94,6 +100,12 @@ func (r ResourceInstance) Equal(o ResourceInstance) bool { return r.Key == o.Key && r.Resource.Equal(o.Resource) } +func (r ResourceInstance) UniqueKey() UniqueKey { + return r // A ResourceInstance is its own UniqueKey +} + +func (r ResourceInstance) uniqueKeySigil() {} + // Absolute returns an AbsResourceInstance from the receiver and the given module // instance address. func (r ResourceInstance) Absolute(module ModuleInstance) AbsResourceInstance { diff --git a/internal/addrs/resource_phase.go b/internal/addrs/resource_phase.go index 9bdbdc421..c62a7fc83 100644 --- a/internal/addrs/resource_phase.go +++ b/internal/addrs/resource_phase.go @@ -44,6 +44,12 @@ func (rp ResourceInstancePhase) String() string { return fmt.Sprintf("%s#%s", rp.ResourceInstance, rp.Phase) } +func (rp ResourceInstancePhase) UniqueKey() UniqueKey { + return rp // A ResourceInstancePhase is its own UniqueKey +} + +func (rp ResourceInstancePhase) uniqueKeySigil() {} + // ResourceInstancePhaseType is an enumeration used with ResourceInstancePhase. type ResourceInstancePhaseType string @@ -103,3 +109,9 @@ func (rp ResourcePhase) String() string { // because this special address type should never be exposed in the UI. return fmt.Sprintf("%s#%s", rp.Resource, rp.Phase) } + +func (rp ResourcePhase) UniqueKey() UniqueKey { + return rp // A ResourcePhase is its own UniqueKey +} + +func (rp ResourcePhase) uniqueKeySigil() {} diff --git a/internal/addrs/self.go b/internal/addrs/self.go index 7f24eaf08..64c8f6ecf 100644 --- a/internal/addrs/self.go +++ b/internal/addrs/self.go @@ -12,3 +12,9 @@ func (s selfT) referenceableSigil() { func (s selfT) String() string { return "self" } + +func (s selfT) UniqueKey() UniqueKey { + return Self // Self is its own UniqueKey +} + +func (s selfT) uniqueKeySigil() {} diff --git a/internal/addrs/set.go b/internal/addrs/set.go new file mode 100644 index 000000000..ef82c5915 --- /dev/null +++ b/internal/addrs/set.go @@ -0,0 +1,43 @@ +package addrs + +// Set represents a set of addresses of types that implement UniqueKeyer. +type Set map[UniqueKey]UniqueKeyer + +func (s Set) Has(addr UniqueKeyer) bool { + _, exists := s[addr.UniqueKey()] + return exists +} + +func (s Set) Add(addr UniqueKeyer) { + s[addr.UniqueKey()] = addr +} + +func (s Set) Remove(addr UniqueKeyer) { + delete(s, addr.UniqueKey()) +} + +func (s Set) Union(other Set) Set { + ret := make(Set) + for k, addr := range s { + ret[k] = addr + } + for k, addr := range other { + ret[k] = addr + } + return ret +} + +func (s Set) Intersection(other Set) Set { + ret := make(Set) + for k, addr := range s { + if _, exists := other[k]; exists { + ret[k] = addr + } + } + for k, addr := range other { + if _, exists := s[k]; exists { + ret[k] = addr + } + } + return ret +} diff --git a/internal/addrs/terraform_attr.go b/internal/addrs/terraform_attr.go index a880182ae..d3d11677c 100644 --- a/internal/addrs/terraform_attr.go +++ b/internal/addrs/terraform_attr.go @@ -10,3 +10,9 @@ type TerraformAttr struct { func (ta TerraformAttr) String() string { return "terraform." + ta.Name } + +func (ta TerraformAttr) UniqueKey() UniqueKey { + return ta // A TerraformAttr is its own UniqueKey +} + +func (ta TerraformAttr) uniqueKeySigil() {} diff --git a/internal/addrs/unique_key.go b/internal/addrs/unique_key.go new file mode 100644 index 000000000..c3321a298 --- /dev/null +++ b/internal/addrs/unique_key.go @@ -0,0 +1,23 @@ +package addrs + +// UniqueKey is an interface implemented by values that serve as unique map +// keys for particular addresses. +// +// All implementations of UniqueKey are comparable and can thus be used as +// map keys. Unique keys generated from different address types are always +// distinct. All functionally-equivalent keys for the same address type +// always compare equal, and likewise functionally-different values do not. +type UniqueKey interface { + uniqueKeySigil() +} + +// UniqueKeyer is an interface implemented by types that can be represented +// by a unique key. +// +// Some address types naturally comply with the expectations of a UniqueKey +// and may thus be their own unique key type. However, address types that +// are not naturally comparable can implement this interface by returning +// proxy values. +type UniqueKeyer interface { + UniqueKey() UniqueKey +} diff --git a/internal/addrs/unique_key_test.go b/internal/addrs/unique_key_test.go new file mode 100644 index 000000000..416899ca4 --- /dev/null +++ b/internal/addrs/unique_key_test.go @@ -0,0 +1,64 @@ +package addrs + +import ( + "fmt" + "testing" +) + +// TestUniqueKeyer aims to ensure that all of the types that have unique keys +// will continue to meet the UniqueKeyer contract under future changes. +// +// If you add a new implementation of UniqueKey, consider adding a test case +// for it here. +func TestUniqueKeyer(t *testing.T) { + tests := []UniqueKeyer{ + CountAttr{Name: "index"}, + ForEachAttr{Name: "key"}, + TerraformAttr{Name: "workspace"}, + PathAttr{Name: "module"}, + InputVariable{Name: "foo"}, + ModuleCall{Name: "foo"}, + ModuleCallInstance{ + Call: ModuleCall{Name: "foo"}, + Key: StringKey("a"), + }, + ModuleCallOutput{ + Call: ModuleCall{Name: "foo"}, + Name: "bar", + }, + ModuleCallInstanceOutput{ + Call: ModuleCallInstance{ + Call: ModuleCall{Name: "foo"}, + Key: StringKey("a"), + }, + Name: "bar", + }, + Resource{ + Mode: ManagedResourceMode, + Type: "foo", + Name: "bar", + }, + ResourceInstance{ + Resource: Resource{ + Mode: ManagedResourceMode, + Type: "foo", + Name: "bar", + }, + Key: IntKey(1), + }, + Self, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%s", test), func(t *testing.T) { + a := test.UniqueKey() + b := test.UniqueKey() + + // The following comparison will panic if the unique key is not + // of a comparable type. + if a != b { + t.Fatalf("the two unique keys are not equal\na: %#v\b: %#v", a, b) + } + }) + } +}