blob: d68f80eac8b4fcc78025e978cf1c7d54ae8d429b [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package addrs
import (
"fmt"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// PartialExpandedModule represents a set of module instances which all share
// a common known parent module instance but the remaining call instance keys
// are not yet known.
type PartialExpandedModule struct {
// expandedPrefix is the initial part of the module address whose expansion
// is already complete and so has exact instance keys.
expandedPrefix ModuleInstance
// unexpandedSuffix is the remainder of the module address whose instance
// keys are not known yet. This is a slight abuse of type [Module] because
// it's representing a relative path from expandedPrefix rather than a
// path from the root module as usual, so this value must never be exposed
// in the public API of this package.
//
// This can be zero-length in PartialExpandedModule values used as part
// of the internals of a PartialExpandedResource, but should never be
// zero-length in a publicly-exposed PartialExpandedModule because that
// would make this just a degenerate ModuleInstance.
unexpandedSuffix Module
}
// ParsePartialExpandedModule parses a module address traversal and returns a
// PartialExpandedModule representing the known and unknown parts of the
// address.
//
// It returns the parsed PartialExpandedModule, the remaining traversal steps
// that were not consumed by this function, and any diagnostics that were
// generated during parsing.
func ParsePartialExpandedModule(traversal hcl.Traversal) (PartialExpandedModule, hcl.Traversal, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
remain := traversal
var partial PartialExpandedModule
// We'll step through the traversal steps and build up the known prefix
// of the module address. When we reach a call with an unknown index, we'll
// switch to building up the unexpanded suffix.
expanded := true
LOOP:
for len(remain) > 0 {
var next string
switch tt := remain[0].(type) {
case hcl.TraverseRoot:
next = tt.Name
case hcl.TraverseAttr:
next = tt.Name
default:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid address operator",
Detail: "Module address prefix must be followed by dot and then a name.",
Subject: remain[0].SourceRange().Ptr(),
})
break LOOP
}
if next != "module" {
break
}
kwRange := remain[0].SourceRange()
remain = remain[1:]
if len(remain) == 0 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid address operator",
Detail: "Prefix \"module.\" must be followed by a module name.",
Subject: &kwRange,
})
break
}
var moduleName string
switch tt := remain[0].(type) {
case hcl.TraverseAttr:
moduleName = tt.Name
default:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid address operator",
Detail: "Prefix \"module.\" must be followed by a module name.",
Subject: remain[0].SourceRange().Ptr(),
})
break LOOP
}
remain = remain[1:]
if expanded {
step := ModuleInstanceStep{
Name: moduleName,
}
if len(remain) > 0 {
if idx, ok := remain[0].(hcl.TraverseIndex); ok {
remain = remain[1:]
if !idx.Key.IsKnown() {
// We'll switch to building up the unexpanded suffix
// starting with this step.
expanded = false
partial.unexpandedSuffix = append(partial.unexpandedSuffix, moduleName)
continue
}
switch idx.Key.Type() {
case cty.String:
step.InstanceKey = StringKey(idx.Key.AsString())
case cty.Number:
var idxInt int
err := gocty.FromCtyValue(idx.Key, &idxInt)
if err == nil {
step.InstanceKey = IntKey(idxInt)
} else {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid address operator",
Detail: fmt.Sprintf("Invalid module index: %s.", err),
Subject: idx.SourceRange().Ptr(),
})
}
default:
// Should never happen, because no other types are allowed in traversal indices.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid address operator",
Detail: "Invalid module key: must be either a string or an integer.",
Subject: idx.SourceRange().Ptr(),
})
}
}
}
partial.expandedPrefix = append(partial.expandedPrefix, step)
continue
}
// Otherwise, we'll process this as an unexpanded suffix.
partial.unexpandedSuffix = append(partial.unexpandedSuffix, moduleName)
if len(remain) > 0 {
if _, ok := remain[0].(hcl.TraverseIndex); ok {
// Then we have a module instance key. We're now parsing the
// unexpanded suffix of the module address, so we'll just
// ignore it.
remain = remain[1:]
}
}
}
var retRemain hcl.Traversal
if len(remain) > 0 {
retRemain = make(hcl.Traversal, len(remain))
copy(retRemain, remain)
// The first element here might be either a TraverseRoot or a
// TraverseAttr, depending on whether we had a module address on the
// front. To make life easier for callers, we'll normalize to always
// start with a TraverseRoot.
if tt, ok := retRemain[0].(hcl.TraverseAttr); ok {
retRemain[0] = hcl.TraverseRoot{
Name: tt.Name,
SrcRange: tt.SrcRange,
}
}
}
return partial, retRemain, diags
}
func (m ModuleInstance) UnexpandedChild(call ModuleCall) PartialExpandedModule {
return PartialExpandedModule{
expandedPrefix: m,
unexpandedSuffix: Module{call.Name},
}
}
// PartialModule reverses the process of UnknownModuleInstance by converting a
// ModuleInstance back into a PartialExpandedModule.
func (m ModuleInstance) PartialModule() PartialExpandedModule {
pem := PartialExpandedModule{}
for _, step := range m {
if step.InstanceKey == WildcardKey {
pem.unexpandedSuffix = append(pem.unexpandedSuffix, step.Name)
continue
}
pem.expandedPrefix = append(pem.expandedPrefix, step)
}
return pem
}
// UnknownModuleInstance expands the receiver to a full ModuleInstance by
// replacing the unknown instance keys with a wildcard value.
func (pem PartialExpandedModule) UnknownModuleInstance() ModuleInstance {
base := pem.expandedPrefix
for _, call := range pem.unexpandedSuffix {
base = append(base, ModuleInstanceStep{
Name: call,
InstanceKey: WildcardKey,
})
}
return base
}
// LevelsKnown returns the number of module path segments of the address that
// have known instance keys.
//
// This might be useful, for example, for preferring a more-specifically-known
// address over a less-specifically-known one when selecting a placeholder
// value to use to represent an object beneath an unexpanded module address.
func (pem PartialExpandedModule) LevelsKnown() int {
return len(pem.expandedPrefix)
}
// MatchesInstance returns true if and only if the given module instance
// belongs to the recieving partially-expanded module address pattern.
func (pem PartialExpandedModule) MatchesInstance(inst ModuleInstance) bool {
// Total length must always match.
if len(inst) != (len(pem.expandedPrefix) + len(pem.unexpandedSuffix)) {
return false
}
// The known prefix must match exactly.
givenExpandedPrefix := inst[:len(pem.expandedPrefix)]
if !givenExpandedPrefix.Equal(pem.expandedPrefix) {
return false
}
// The known suffix must match the call names, even though we don't yet
// know the specific instance keys.
givenExpandedSuffix := inst[len(pem.expandedPrefix):]
for i := range pem.unexpandedSuffix {
if pem.unexpandedSuffix[i] != givenExpandedSuffix[i].Name {
return false
}
}
// If we passed all the filters above then it's a match.
return true
}
// MatchesPartial returns true if and only if the receiver represents the same
// static module as the other given module and the receiver's known instance
// keys are a prefix of the other module's.
func (pem PartialExpandedModule) MatchesPartial(other PartialExpandedModule) bool {
// The two addresses must represent the same static module, regardless
// of the instance keys of those modules.
if !pem.Module().Equal(other.Module()) {
return false
}
if len(pem.expandedPrefix) > len(other.expandedPrefix) {
return false
}
thisPrefix := pem.expandedPrefix
otherPrefix := other.expandedPrefix[:len(pem.expandedPrefix)]
return thisPrefix.Equal(otherPrefix)
}
// Module returns the unexpanded module address that this pattern originated
// from.
func (pem PartialExpandedModule) Module() Module {
ret := pem.expandedPrefix.Module()
return append(ret, pem.unexpandedSuffix...)
}
// KnownPrefix returns the longest possible ModuleInstance address made of
// known segments of this partially-expanded module instance address.
func (pem PartialExpandedModule) KnownPrefix() ModuleInstance {
if len(pem.expandedPrefix) == 0 {
return nil
}
// Although we can't enforce it with the Go compiler, our convention is
// that we never mutate address values outside of this package and so
// we'll expose our pem.expandedPrefix buffer directly here and trust that
// the caller will play nice with it. However, we do force the unused
// capacity to zero so that the caller can safely construct child addresses,
// which would append new steps to the end.
return pem.expandedPrefix[:len(pem.expandedPrefix):len(pem.expandedPrefix)]
}
// FirstUnexpandedCall returns the address of the first step in the module
// path whose instance keys are not yet known, discarding any subsequent
// calls beneath it.
func (pem PartialExpandedModule) FirstUnexpandedCall() AbsModuleCall {
// NOTE: This assumes that there's always at least one element in
// unexpandedSuffix because it should only be used with the public-facing
// version of PartialExpandedModule where that contract always holds. It's
// not safe to use this for the PartialExpandedModule value hidden in the
// internals of PartialExpandedResource.
return AbsModuleCall{
Module: pem.KnownPrefix(),
Call: ModuleCall{
Name: pem.unexpandedSuffix[0],
},
}
}
// UnexpandedSuffix returns the local addresses of all of the calls whose
// instances are not yet expanded, in the module tree traversal order.
//
// Method KnownPrefix concatenated with UnexpandedSuffix (assuming that were
// actually possible) represents the whole module path that the
// PartialExpandedModule encapsulates.
func (pem PartialExpandedModule) UnexpandedSuffix() []ModuleCall {
if len(pem.unexpandedSuffix) == 0 {
// Should never happen for any publicly-visible value of this type,
// because we should always have at least one unexpanded call,
// but we'll allow it anyway since we have a reasonable return value
// for that case.
return nil
}
// A []ModuleCall is the only representation of a non-rooted chain of
// module calls that we're allowed to export in our public API, and so
// we'll transform our not-quite-allowed unrooted "Module" value in that
// form externally.
ret := make([]ModuleCall, len(pem.unexpandedSuffix))
for i, name := range pem.unexpandedSuffix {
ret[i].Name = name
}
return ret
}
// Child returns the address of a child of the receiver that belongs to the
// given module call.
func (pem PartialExpandedModule) Child(call ModuleCall) PartialExpandedModule {
return PartialExpandedModule{
expandedPrefix: pem.expandedPrefix,
unexpandedSuffix: append(pem.unexpandedSuffix, call.Name),
}
}
// Resource returns the address of a resource within the receiver.
func (pem PartialExpandedModule) Resource(resource Resource) PartialExpandedResource {
return PartialExpandedResource{
module: pem,
resource: resource,
}
}
// String returns a string representation of the pattern where the known
// prefix uses the normal module instance address syntax and the unknown
// suffix steps use a similar syntax but with "[*]" as a placeholder to
// represent instance keys that aren't yet known.
func (pem PartialExpandedModule) String() string {
var buf strings.Builder
if len(pem.expandedPrefix) != 0 {
buf.WriteString(pem.expandedPrefix.String())
}
for i, callName := range pem.unexpandedSuffix {
if i > 0 || len(pem.expandedPrefix) != 0 {
buf.WriteByte('.')
}
buf.WriteString("module.")
buf.WriteString(callName)
buf.WriteString("[*]")
}
return buf.String()
}
func (pem PartialExpandedModule) UniqueKey() UniqueKey {
return partialExpandedModuleKey(pem.String())
}
type partialExpandedModuleKey string
var _ UniqueKey = partialExpandedModuleKey("")
func (partialExpandedModuleKey) uniqueKeySigil() {}
// PartialExpandedResource represents a set of resource instances which all share
// a common known parent module instance but the remaining call instance keys
// are not yet known and the resource's own instance keys are not yet known.
//
// A PartialExpandedResource with a fully-known module instance address is
// semantically interchangable with an [AbsResource], which is useful when we
// need to represent an assortment of variously-unknown resource instance
// addresses, but [AbsResource] is preferable in situations where the module
// instance address is _always_ known and it's only the resource instance
// key that is not represented.
type PartialExpandedResource struct {
// module is the partially-expanded module instance address that this
// resource belongs to.
//
// This value can actually represent a fully-expanded module if its
// unexpandedSuffix field is zero-length, in which case it's only the
// resource itself that's unexpanded, which would make this equivalent
// to an AbsResource.
//
// We mustn't directly expose this value in the public API because
// external callers must never see a PartialExpandedModule that is
// actually fully-expanded; that should be a ModuleInstance instead.
module PartialExpandedModule
resource Resource
}
// ParsePartialExpandedResource parses a resource address traversal and returns
// a PartialExpandedResource representing the known and unknown parts of the
// address.
func ParsePartialExpandedResource(traversal hcl.Traversal) (PartialExpandedResource, hcl.Traversal, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
pem, remain, diags := ParsePartialExpandedModule(traversal)
if len(remain) == 0 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid address",
Detail: "Resource address must be a module address followed by a resource address.",
Subject: traversal.SourceRange().Ptr(),
})
return PartialExpandedResource{}, nil, diags
}
// We know that remain[0] is a hcl.TraverseRoot object as the
// ParsePartialExpandedModule function always returns a hcl.TraverseRoot
// object as the first element in the remain slice.
mode := ManagedResourceMode
if remain.RootName() == "data" {
mode = DataResourceMode
remain = remain[1:]
} else if remain.RootName() == "resource" {
// Starting a resource address with "resource" is optional, so we'll
// just ignore it if it's present.
remain = remain[1:]
}
if len(remain) < 2 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid address",
Detail: "Resource specification must include a resource type and name.",
Subject: remain.SourceRange().Ptr(),
})
return PartialExpandedResource{}, nil, diags
}
var typeName, name string
switch tt := remain[0].(type) {
case hcl.TraverseRoot:
typeName = tt.Name
case hcl.TraverseAttr:
typeName = tt.Name
default:
switch mode {
case ManagedResourceMode:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid address",
Detail: "A resource type name is required.",
Subject: remain[0].SourceRange().Ptr(),
})
case DataResourceMode:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid address",
Detail: "A data source name is required.",
Subject: remain[0].SourceRange().Ptr(),
})
default:
panic("unknown mode")
}
return PartialExpandedResource{}, nil, diags
}
switch tt := remain[1].(type) {
case hcl.TraverseAttr:
name = tt.Name
default:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid address",
Detail: "A resource name is required.",
Subject: remain[1].SourceRange().Ptr(),
})
return PartialExpandedResource{}, nil, diags
}
remain = remain[2:]
if len(remain) > 0 {
if _, ok := remain[0].(hcl.TraverseIndex); ok {
// Then we have a resource instance key. Since, we're building a
// PartialExpandedResource, we'll just ignore it.
remain = remain[1:]
}
}
return PartialExpandedResource{
module: pem,
resource: Resource{
Mode: mode,
Type: typeName,
Name: name,
},
}, remain, diags
}
// UnexpandedResource returns the address of a child resource expressed as a
// [PartialExpandedResource].
//
// The result always has a fully-qualified module instance address and is
// therefore semantically equivalent to an [AbsResource], so this variannt
// should be used only in contexts where we might also be storing resources
// belonging to not-fully-expanded modules and need to use the same static
// address type for all of them.
func (m ModuleInstance) UnexpandedResource(resource Resource) PartialExpandedResource {
return PartialExpandedResource{
module: PartialExpandedModule{
expandedPrefix: m,
},
resource: resource,
}
}
// UnexpandedResource returns the receiver reinterpreted as a
// [PartialExpandedResource], which is an alternative form we use in situations
// where we might also need to mix in resources belonging to not-yet-fully-known
// module instance addresses.
func (r AbsResource) UnexpandedResource() PartialExpandedResource {
return PartialExpandedResource{
module: PartialExpandedModule{
expandedPrefix: r.Module,
},
resource: r.Resource,
}
}
// PartialResource reverses UnknownResourceInstance by converting the
// AbsResourceInstance back into a PartialExpandedResource.
func (r AbsResourceInstance) PartialResource() PartialExpandedResource {
return PartialExpandedResource{
module: r.Module.PartialModule(),
resource: r.Resource.Resource,
}
}
// UnknownResourceInstance returns an [AbsResourceInstance] that represents the
// same resource as the receiver but with all instance keys replaced with a
// wildcard value.
func (per PartialExpandedResource) UnknownResourceInstance() AbsResourceInstance {
return AbsResourceInstance{
Module: per.module.UnknownModuleInstance(),
Resource: per.resource.Instance(WildcardKey),
}
}
// MatchesInstance returns true if and only if the given resource instance
// belongs to the recieving partially-expanded resource address pattern.
func (per PartialExpandedResource) MatchesInstance(inst AbsResourceInstance) bool {
if !per.module.MatchesInstance(inst.Module) {
return false
}
return inst.Resource.Resource.Equal(per.resource)
}
// MatchesResource returns true if and only if the given resource belongs to
// the recieving partially-expanded resource address pattern.
func (per PartialExpandedResource) MatchesResource(inst AbsResource) bool {
if !per.module.MatchesInstance(inst.Module) {
return false
}
return inst.Resource.Equal(per.resource)
}
// MatchesPartial returns true if the underlying partial module address matches
// the given partial module address and the resource type and name match the
// receiver's resource type and name.
func (per PartialExpandedResource) MatchesPartial(other PartialExpandedResource) bool {
if !per.module.MatchesPartial(other.module) {
return false
}
return per.resource.Equal(other.resource)
}
// AbsResource returns the single [AbsResource] that this address represents
// if this pattern is specific enough to match only a single resource, or
// the zero value of AbsResource if not.
//
// The second return value is true if and only if the returned address is valid.
func (per PartialExpandedResource) AbsResource() (AbsResource, bool) {
if len(per.module.unexpandedSuffix) != 0 {
return AbsResource{}, false
}
return AbsResource{
Module: per.module.expandedPrefix,
Resource: per.resource,
}, true
}
// ConfigResource returns the unexpanded resource address that this
// partially-expanded resource address originates from.
func (per PartialExpandedResource) ConfigResource() ConfigResource {
return ConfigResource{
Module: per.module.Module(),
Resource: per.resource,
}
}
// Resource returns just the leaf resource address that this partially-expanded
// resource address uses, discarding the containing module instance information
// altogether.
func (per PartialExpandedResource) Resource() Resource {
return per.resource
}
// KnownModuleInstancePrefix returns the longest possible ModuleInstance address
// made of known segments of the module instances that this set of resource
// instances all belong to.
//
// If the whole module instance address is known and only the resource
// instances are not then this returns the full prefix, which will be the same
// as the module from a successful return value from
// [PartialExpandedResource.AbsResource].
func (per PartialExpandedResource) KnownModuleInstancePrefix() ModuleInstance {
return per.module.KnownPrefix()
}
// ModuleInstance returns the fully-qualified [ModuleInstance] that this
// partial-expanded resource belongs to, but only if its module instance
// address is fully known.
//
// The second return value is false if the module instance address is not
// fully expanded, in which case the first return value is invalid. Use
// [PartialExpandedResource.PartialExpandedModule] instead in that case.
func (per PartialExpandedResource) ModuleInstance() (ModuleInstance, bool) {
if len(per.module.unexpandedSuffix) != 0 {
return nil, false
}
return per.module.expandedPrefix, true
}
// PartialExpandedModule returns a [PartialExpandedModule] address describing
// the partially-unknown module instance address that the resource belongs to,
// but only if the module instance address is not fully known.
//
// The second return value is false if the module instance address is actually
// fully expanded, in which case the first return value is invalid. Use
// [PartialExpandedResource.ModuleInstance] instead in that case.
func (per PartialExpandedResource) PartialExpandedModule() (PartialExpandedModule, bool) {
if len(per.module.unexpandedSuffix) == 0 {
return PartialExpandedModule{}, false
}
return per.module, true
}
// IsTargetedBy returns true if and only if the given targetable address might
// target the resource instances that could exist if the receiver were fully
// expanded.
func (per PartialExpandedResource) IsTargetedBy(addr Targetable) bool {
compareModule := func(module Module) bool {
// We'll step through each step in the module address and compare it
// to the known prefix and unexpanded suffix of the receiver. If we
// find a mismatch then we know the receiver can't be targeted by this
// address.
for ix, step := range module {
if ix >= len(per.module.expandedPrefix) {
ix = ix - len(per.module.expandedPrefix)
if ix >= len(per.module.unexpandedSuffix) {
// Then the target address has more steps than the receiver
// and so can't possibly target it.
return false
}
if step != per.module.unexpandedSuffix[ix] {
// Then the target address has a different step at this
// position than the receiver does, so it can't target it.
return false
}
} else {
if step != per.module.expandedPrefix[ix].Name {
// Then the target address has a different step at this
// position than the receiver does, so it can't target it.
return false
}
}
}
// If we make it here then the target address is a prefix of the
// receivers module address, so it could potentially target the
// receiver.
return true
}
compareModuleInstance := func(inst ModuleInstance) bool {
// We'll step through each step in the module address and compare it
// to the known prefix and unexpanded suffix of the receiver. If we
// find a mismatch then we know the receiver can't be targeted by this
// address.
for ix, step := range inst {
if ix >= len(per.module.expandedPrefix) {
ix = ix - len(per.module.expandedPrefix)
if ix >= len(per.module.unexpandedSuffix) {
// Then the target address has more steps than the receiver
// and so can't possibly target it.
return false
}
if step.Name != per.module.unexpandedSuffix[ix] {
// Then the target address has a different step at this
// position than the receiver does, so it can't target it.
return false
}
} else {
if step.Name != per.module.expandedPrefix[ix].Name || (step.InstanceKey != NoKey && step.InstanceKey != per.module.expandedPrefix[ix].InstanceKey) {
// Then the target address has a different step at this
// position than the receiver does, so it can't target it.
return false
}
}
}
// If we make it here then the target address is a prefix of the
// receivers module address, so it could potentially target the
// receiver.
return true
}
switch addr.AddrType() {
case ConfigResourceAddrType:
addr := addr.(ConfigResource)
if !compareModule(addr.Module) {
return false
}
return addr.Resource.Equal(per.resource)
case AbsResourceAddrType:
addr := addr.(AbsResource)
if !compareModuleInstance(addr.Module) {
return false
}
return addr.Resource.Equal(per.resource)
case AbsResourceInstanceAddrType:
addr := addr.(AbsResourceInstance)
if !compareModuleInstance(addr.Module) {
return false
}
return addr.Resource.Resource.Equal(per.resource)
case ModuleAddrType:
return compareModule(addr.(Module))
case ModuleInstanceAddrType:
return compareModuleInstance(addr.(ModuleInstance))
}
return false
}
// String returns a string representation of the pattern which uses the special
// placeholder "[*]" to represent positions where instance keys are not yet
// known.
func (per PartialExpandedResource) String() string {
moduleAddr := per.module.String()
if len(moduleAddr) != 0 {
return moduleAddr + "." + per.resource.String() + "[*]"
}
return per.resource.String() + "[*]"
}
func (per PartialExpandedResource) UniqueKey() UniqueKey {
// If this address is equivalent to an AbsResource address then we'll
// return its instance key here so that function Equivalent will consider
// the two as equivalent.
if ar, ok := per.AbsResource(); ok {
return ar.UniqueKey()
}
// For not-fully-expanded module paths we'll use a distinct address type
// since there is no other address type equivalent to those.
return partialExpandedResourceKey(per.String())
}
type partialExpandedResourceKey string
var _ UniqueKey = partialExpandedModuleKey("")
func (partialExpandedResourceKey) uniqueKeySigil() {}
// InPartialExpandedModule is a generic type used for all address types that
// represent objects that exist inside module instances but do not have any
// expansion capability of their own beyond just the containing module
// expansion.
//
// Although not enforced by the type system, this type should be used only for
// address types T that are combined with a ModuleInstance value in a type
// whose name starts with "Abs". For example, [LocalValue] is a reasonable T
// because [AbsLocalValue] represents a local value inside a particular module
// instance. InPartialExpandedModule[LocalValue] is therefore like an
// [AbsLocalValue] whose module path isn't fully known yet.
//
// This type is here primarily just to have implementations of [UniqueKeyer]
// so we can store partially-evaluated objects from unexpanded modules in
// collections for later reference downstream.
type InPartialExpandedModule[T interface {
UniqueKeyer
fmt.Stringer
}] struct {
Module PartialExpandedModule
Local T
}
// ObjectInPartialExpandedModule is a constructor for [InPartialExpandedModule]
// that's here primarily just to benefit from function type parameter inference
// to avoid manually writing out type T when constructing such a value.
func ObjectInPartialExpandedModule[T interface {
UniqueKeyer
fmt.Stringer
}](module PartialExpandedModule, local T) InPartialExpandedModule[T] {
return InPartialExpandedModule[T]{
Module: module,
Local: local,
}
}
var _ UniqueKeyer = InPartialExpandedModule[LocalValue]{}
// ModuleLevelsKnown returns the number of module path segments of the address
// that have known instance keys.
//
// This might be useful, for example, for preferring a more-specifically-known
// address over a less-specifically-known one when selecting a placeholder
// value to use to represent an object beneath an unexpanded module address.
func (in InPartialExpandedModule[T]) ModuleLevelsKnown() int {
return in.Module.LevelsKnown()
}
// String returns a string representation of the pattern which uses the special
// placeholder "[*]" to represent positions where module instance keys are not
// yet known.
func (in InPartialExpandedModule[T]) String() string {
moduleAddr := in.Module.String()
if len(moduleAddr) != 0 {
return moduleAddr + "." + in.Local.String()
}
return in.Local.String()
}
func (in InPartialExpandedModule[T]) UniqueKey() UniqueKey {
return inPartialExpandedModuleUniqueKey{
moduleKey: in.Module.UniqueKey(),
localKey: in.Local.UniqueKey(),
}
}
type inPartialExpandedModuleUniqueKey struct {
moduleKey UniqueKey
localKey UniqueKey
}
func (inPartialExpandedModuleUniqueKey) uniqueKeySigil() {}