// Copyright IBM Corp. 2014, 2026 // 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, } } // Action returns the address of an action within the receiver. func (pem PartialExpandedModule) Action(action Action) PartialExpandedAction { return PartialExpandedAction{ module: pem, action: action, } } // 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() {} // PartialExpandedAction represents a partially-expanded action address. // See PartialExpandedResource for more information. type PartialExpandedAction struct { module PartialExpandedModule action Action } func (per PartialExpandedAction) AbsAction() (AbsAction, bool) { if len(per.module.unexpandedSuffix) != 0 { return AbsAction{}, false } return AbsAction{ Module: per.module.expandedPrefix, Action: per.action, }, true } // ConfigAction returns the unexpanded action address that this // partially-expanded action address originates from. func (per PartialExpandedAction) ConfigAction() ConfigAction { return ConfigAction{ Module: per.module.Module(), Action: per.action, } } func (per PartialExpandedAction) ModuleInstance() (ModuleInstance, bool) { if len(per.module.unexpandedSuffix) != 0 { return nil, false } return per.module.expandedPrefix, true } func (m ModuleInstance) UnexpandedAction(action Action) PartialExpandedAction { return PartialExpandedAction{ module: PartialExpandedModule{ expandedPrefix: m, }, action: action, } } func (a *AbsAction) UnexpandedAction(action Action) PartialExpandedAction { return PartialExpandedAction{ module: PartialExpandedModule{ expandedPrefix: a.Module, }, action: action, } } // UnknownActionInstance returns an [AbsActionInstance] that represents the // same action as the receiver but with all instance keys replaced with a // wildcard value. func (per PartialExpandedAction) UnknownActionInstance() AbsActionInstance { return AbsActionInstance{ Module: per.module.UnknownModuleInstance(), Action: per.action.Instance(WildcardKey), } } func (pea PartialExpandedAction) String() string { moduleAddr := pea.module.String() if len(moduleAddr) != 0 { return moduleAddr + "." + pea.action.String() + "[*]" } return pea.action.String() + "[*]" } func (pea PartialExpandedAction) Equal(other PartialExpandedAction) bool { return pea.module.MatchesPartial(other.module.expandedPrefix.PartialModule()) && pea.action.Equal(other.action) } func (pea PartialExpandedAction) UniqueKey() UniqueKey { // If this address is equivalent to an AbsAction address then we'll // return its instance key here so that function Equivalent will consider // the two as equivalent. if ar, ok := pea.AbsAction(); 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 partialExpandedActionKey(pea.String()) } type partialExpandedActionKey string func (partialExpandedActionKey) uniqueKeySigil() {} // PartialExpandedModule returns a [PartialExpandedModule] address describing // the partially-unknown module instance address that the action 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 // [PartialExpandedAction.ModuleInstance] instead in that case. func (per PartialExpandedAction) PartialExpandedModule() (PartialExpandedModule, bool) { if len(per.module.unexpandedSuffix) == 0 { return PartialExpandedModule{}, false } return per.module, true }