| package addrs |
| |
| import ( |
| "fmt" |
| "reflect" |
| "strings" |
| |
| "github.com/zclconf/go-cty/cty" |
| |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| ) |
| |
| // anyKeyImpl is the InstanceKey representation indicating a wildcard, which |
| // matches all possible keys. This is only used internally for matching |
| // combinations of address types, where only portions of the path contain key |
| // information. |
| type anyKeyImpl rune |
| |
| func (k anyKeyImpl) instanceKeySigil() { |
| } |
| |
| func (k anyKeyImpl) String() string { |
| return fmt.Sprintf("[%s]", string(k)) |
| } |
| |
| func (k anyKeyImpl) Value() cty.Value { |
| return cty.StringVal(string(k)) |
| } |
| |
| // anyKey is the only valid value of anyKeyImpl |
| var anyKey = anyKeyImpl('*') |
| |
| // MoveEndpointInModule annotates a MoveEndpoint with the address of the |
| // module where it was declared, which is the form we use for resolving |
| // whether move statements chain from or are nested within other move |
| // statements. |
| type MoveEndpointInModule struct { |
| // SourceRange is the location of the physical endpoint address |
| // in configuration, if this MoveEndpoint was decoded from a |
| // configuration expresson. |
| SourceRange tfdiags.SourceRange |
| |
| // The internals are unexported here because, as with MoveEndpoint, |
| // we're somewhat abusing AbsMoveable here to represent an address |
| // relative to the module, rather than as an absolute address. |
| // Conceptually, the following two fields represent a matching pattern |
| // for AbsMoveables where the elements of "module" behave as |
| // ModuleInstanceStep values with a wildcard instance key, because |
| // a moved block in a module affects all instances of that module. |
| // Unlike MoveEndpoint, relSubject in this case can be any of the |
| // address types that implement AbsMoveable. |
| module Module |
| relSubject AbsMoveable |
| } |
| |
| // ImpliedMoveStatementEndpoint is a special constructor for MoveEndpointInModule |
| // which is suitable only for constructing "implied" move statements, which |
| // means that we inferred the statement automatically rather than building it |
| // from an explicit block in the configuration. |
| // |
| // Implied move endpoints, just as for the statements they are embedded in, |
| // have somewhat-related-but-imprecise source ranges, typically referring to |
| // some general configuration construct that implied the statement, because |
| // by definition there is no explicit move endpoint expression in this case. |
| func ImpliedMoveStatementEndpoint(addr AbsResourceInstance, rng tfdiags.SourceRange) *MoveEndpointInModule { |
| // implied move endpoints always belong to the root module, because each |
| // one refers to a single resource instance inside a specific module |
| // instance, rather than all instances of the module where the resource |
| // was declared. |
| return &MoveEndpointInModule{ |
| SourceRange: rng, |
| module: RootModule, |
| relSubject: addr, |
| } |
| } |
| |
| func (e *MoveEndpointInModule) ObjectKind() MoveEndpointKind { |
| return absMoveableEndpointKind(e.relSubject) |
| } |
| |
| // String produces a string representation of the object matching pattern |
| // represented by the reciever. |
| // |
| // Since there is no direct syntax for representing such an object matching |
| // pattern, this function uses a splat-operator-like representation to stand |
| // in for the wildcard instance keys. |
| func (e *MoveEndpointInModule) String() string { |
| if e == nil { |
| return "" |
| } |
| var buf strings.Builder |
| for _, name := range e.module { |
| buf.WriteString("module.") |
| buf.WriteString(name) |
| buf.WriteString("[*].") |
| } |
| buf.WriteString(e.relSubject.String()) |
| |
| // For consistency we'll also use the splat-like wildcard syntax to |
| // represent the final step being either a resource or module call |
| // rather than an instance, so we can more easily distinguish the two |
| // in the string representation. |
| switch e.relSubject.(type) { |
| case AbsModuleCall, AbsResource: |
| buf.WriteString("[*]") |
| } |
| |
| return buf.String() |
| } |
| |
| // Equal returns true if the reciever represents the same matching pattern |
| // as the other given endpoint, ignoring the source location information. |
| // |
| // This is not an optimized function and is here primarily to help with |
| // writing concise assertions in test code. |
| func (e *MoveEndpointInModule) Equal(other *MoveEndpointInModule) bool { |
| if (e == nil) != (other == nil) { |
| return false |
| } |
| if !e.module.Equal(other.module) { |
| return false |
| } |
| // This assumes that all of our possible "movables" are trivially |
| // comparable with reflect, which is true for all of them at the time |
| // of writing. |
| return reflect.DeepEqual(e.relSubject, other.relSubject) |
| } |
| |
| // Module returns the address of the module where the receiving address was |
| // declared. |
| func (e *MoveEndpointInModule) Module() Module { |
| return e.module |
| } |
| |
| // InModuleInstance returns an AbsMoveable address which concatenates the |
| // given module instance address with the receiver's relative object selection |
| // to produce one example of an instance that might be affected by this |
| // move statement. |
| // |
| // The result is meaningful only if the given module instance is an instance |
| // of the same module returned by the method Module. InModuleInstance doesn't |
| // fully verify that (aside from some cheap/easy checks), but it will produce |
| // meaningless garbage if not. |
| func (e *MoveEndpointInModule) InModuleInstance(modInst ModuleInstance) AbsMoveable { |
| if len(modInst) != len(e.module) { |
| // We don't check all of the steps to make sure that their names match, |
| // because it would be expensive to do that repeatedly for every |
| // instance of a module, but if the lengths don't match then that's |
| // _obviously_ wrong. |
| panic("given instance address does not match module address") |
| } |
| switch relSubject := e.relSubject.(type) { |
| case ModuleInstance: |
| ret := make(ModuleInstance, 0, len(modInst)+len(relSubject)) |
| ret = append(ret, modInst...) |
| ret = append(ret, relSubject...) |
| return ret |
| case AbsModuleCall: |
| retModAddr := make(ModuleInstance, 0, len(modInst)+len(relSubject.Module)) |
| retModAddr = append(retModAddr, modInst...) |
| retModAddr = append(retModAddr, relSubject.Module...) |
| return relSubject.Call.Absolute(retModAddr) |
| case AbsResourceInstance: |
| retModAddr := make(ModuleInstance, 0, len(modInst)+len(relSubject.Module)) |
| retModAddr = append(retModAddr, modInst...) |
| retModAddr = append(retModAddr, relSubject.Module...) |
| return relSubject.Resource.Absolute(retModAddr) |
| case AbsResource: |
| retModAddr := make(ModuleInstance, 0, len(modInst)+len(relSubject.Module)) |
| retModAddr = append(retModAddr, modInst...) |
| retModAddr = append(retModAddr, relSubject.Module...) |
| return relSubject.Resource.Absolute(retModAddr) |
| default: |
| panic(fmt.Sprintf("unexpected move subject type %T", relSubject)) |
| } |
| } |
| |
| // ModuleCallTraversals returns both the address of the module where the |
| // receiver was declared and any other module calls it traverses through |
| // while selecting a particular object to move. |
| // |
| // This is a rather special-purpose function here mainly to support our |
| // validation rule that a module can only traverse down into child modules. |
| func (e *MoveEndpointInModule) ModuleCallTraversals() (Module, []ModuleCall) { |
| // We're returning []ModuleCall rather than Module here to make it clearer |
| // that this is a relative sequence of calls rather than an absolute |
| // module path. |
| |
| var steps []ModuleInstanceStep |
| switch relSubject := e.relSubject.(type) { |
| case ModuleInstance: |
| // We want all of the steps except the last one here, because the |
| // last one is always selecting something declared in the same module |
| // even though our address structure doesn't capture that. |
| steps = []ModuleInstanceStep(relSubject[:len(relSubject)-1]) |
| case AbsModuleCall: |
| steps = []ModuleInstanceStep(relSubject.Module) |
| case AbsResourceInstance: |
| steps = []ModuleInstanceStep(relSubject.Module) |
| case AbsResource: |
| steps = []ModuleInstanceStep(relSubject.Module) |
| default: |
| panic(fmt.Sprintf("unexpected move subject type %T", relSubject)) |
| } |
| |
| ret := make([]ModuleCall, len(steps)) |
| for i, step := range steps { |
| ret[i] = ModuleCall{Name: step.Name} |
| } |
| return e.module, ret |
| } |
| |
| // synthModuleInstance constructs a module instance out of the module path and |
| // any module portion of the relSubject, substituting Module and Call segments |
| // with ModuleInstanceStep using the anyKey value. |
| // This is only used internally for comparison of these complete paths, but |
| // does not represent how the individual parts are handled elsewhere in the |
| // code. |
| func (e *MoveEndpointInModule) synthModuleInstance() ModuleInstance { |
| var inst ModuleInstance |
| |
| for _, mod := range e.module { |
| inst = append(inst, ModuleInstanceStep{Name: mod, InstanceKey: anyKey}) |
| } |
| |
| switch sub := e.relSubject.(type) { |
| case ModuleInstance: |
| inst = append(inst, sub...) |
| case AbsModuleCall: |
| inst = append(inst, sub.Module...) |
| inst = append(inst, ModuleInstanceStep{Name: sub.Call.Name, InstanceKey: anyKey}) |
| case AbsResource: |
| inst = append(inst, sub.Module...) |
| case AbsResourceInstance: |
| inst = append(inst, sub.Module...) |
| default: |
| panic(fmt.Sprintf("unhandled relative address type %T", sub)) |
| } |
| |
| return inst |
| } |
| |
| // SelectsModule returns true if the reciever directly selects either |
| // the given module or a resource nested directly inside that module. |
| // |
| // This is a good function to use to decide which modules in a state |
| // to consider when processing a particular move statement. For a |
| // module move the given module itself is what will move, while a |
| // resource move indicates that we should search each of the resources in |
| // the given module to see if they match. |
| func (e *MoveEndpointInModule) SelectsModule(addr ModuleInstance) bool { |
| synthInst := e.synthModuleInstance() |
| |
| // In order to match the given module instance, our combined path must be |
| // equal in length. |
| if len(synthInst) != len(addr) { |
| return false |
| } |
| |
| for i, step := range synthInst { |
| switch step.InstanceKey { |
| case anyKey: |
| // we can match any key as long as the name matches |
| if step.Name != addr[i].Name { |
| return false |
| } |
| default: |
| if step != addr[i] { |
| return false |
| } |
| } |
| } |
| return true |
| } |
| |
| // SelectsResource returns true if the receiver directly selects either |
| // the given resource or one of its instances. |
| func (e *MoveEndpointInModule) SelectsResource(addr AbsResource) bool { |
| // Only a subset of subject types can possibly select a resource, so |
| // we'll take care of those quickly before we do anything more expensive. |
| switch e.relSubject.(type) { |
| case AbsResource, AbsResourceInstance: |
| // okay |
| default: |
| return false // can't possibly match |
| } |
| |
| if !e.SelectsModule(addr.Module) { |
| return false |
| } |
| |
| // If we get here then we know the module part matches, so we only need |
| // to worry about the relative resource part. |
| switch relSubject := e.relSubject.(type) { |
| case AbsResource: |
| return addr.Resource.Equal(relSubject.Resource) |
| case AbsResourceInstance: |
| // We intentionally ignore the instance key, because we consider |
| // instances to be part of the resource they belong to. |
| return addr.Resource.Equal(relSubject.Resource.Resource) |
| default: |
| // We should've filtered out all other types above |
| panic(fmt.Sprintf("unsupported relSubject type %T", relSubject)) |
| } |
| } |
| |
| // moduleInstanceCanMatch indicates that modA can match modB taking into |
| // account steps with an anyKey InstanceKey as wildcards. The comparison of |
| // wildcard steps is done symmetrically, because varying portions of either |
| // instance's path could have been derived from configuration vs evaluation. |
| // The length of modA must be equal or shorter than the length of modB. |
| func moduleInstanceCanMatch(modA, modB ModuleInstance) bool { |
| for i, step := range modA { |
| switch { |
| case step.InstanceKey == anyKey || modB[i].InstanceKey == anyKey: |
| // we can match any key as long as the names match |
| if step.Name != modB[i].Name { |
| return false |
| } |
| default: |
| if step != modB[i] { |
| return false |
| } |
| } |
| } |
| return true |
| } |
| |
| // CanChainFrom returns true if the reciever describes an address that could |
| // potentially select an object that the other given address could select. |
| // |
| // In other words, this decides whether the move chaining rule applies, if |
| // the reciever is the "to" from one statement and the other given address |
| // is the "from" of another statement. |
| func (e *MoveEndpointInModule) CanChainFrom(other *MoveEndpointInModule) bool { |
| eMod := e.synthModuleInstance() |
| oMod := other.synthModuleInstance() |
| |
| // if the complete paths are different lengths, these cannot refer to the |
| // same value. |
| if len(eMod) != len(oMod) { |
| return false |
| } |
| if !moduleInstanceCanMatch(oMod, eMod) { |
| return false |
| } |
| |
| eSub := e.relSubject |
| oSub := other.relSubject |
| |
| switch oSub := oSub.(type) { |
| case AbsModuleCall, ModuleInstance: |
| switch eSub.(type) { |
| case AbsModuleCall, ModuleInstance: |
| // we already know the complete module path including any final |
| // module call name is equal. |
| return true |
| } |
| |
| case AbsResource: |
| switch eSub := eSub.(type) { |
| case AbsResource: |
| return eSub.Resource.Equal(oSub.Resource) |
| } |
| |
| case AbsResourceInstance: |
| switch eSub := eSub.(type) { |
| case AbsResourceInstance: |
| return eSub.Resource.Equal(oSub.Resource) |
| } |
| } |
| |
| return false |
| } |
| |
| // NestedWithin returns true if the receiver describes an address that is |
| // contained within one of the objects that the given other address could |
| // select. |
| func (e *MoveEndpointInModule) NestedWithin(other *MoveEndpointInModule) bool { |
| eMod := e.synthModuleInstance() |
| oMod := other.synthModuleInstance() |
| |
| // In order to be nested within the given endpoint, the module path must be |
| // shorter or equal. |
| if len(oMod) > len(eMod) { |
| return false |
| } |
| |
| if !moduleInstanceCanMatch(oMod, eMod) { |
| return false |
| } |
| |
| eSub := e.relSubject |
| oSub := other.relSubject |
| |
| switch oSub := oSub.(type) { |
| case AbsModuleCall: |
| switch eSub.(type) { |
| case AbsModuleCall: |
| // we know the other endpoint selects our module, but if we are |
| // also a module call our path must be longer to be nested. |
| return len(eMod) > len(oMod) |
| } |
| |
| return true |
| |
| case ModuleInstance: |
| switch eSub.(type) { |
| case ModuleInstance, AbsModuleCall: |
| // a nested module must have a longer path |
| return len(eMod) > len(oMod) |
| } |
| |
| return true |
| |
| case AbsResource: |
| if len(eMod) != len(oMod) { |
| // these resources are from different modules |
| return false |
| } |
| |
| // A resource can only contain a resource instance. |
| switch eSub := eSub.(type) { |
| case AbsResourceInstance: |
| return eSub.Resource.Resource.Equal(oSub.Resource) |
| } |
| } |
| |
| return false |
| } |
| |
| // matchModuleInstancePrefix is an internal helper to decide whether the given |
| // module instance address refers to either the module where the move endpoint |
| // was declared or some descendent of that module. |
| // |
| // If so, it will split the given address into two parts: the "prefix" part |
| // which corresponds with the module where the statement was declared, and |
| // the "relative" part which is the remainder that the relSubject of the |
| // statement might match against. |
| // |
| // The second return value is another example of our light abuse of |
| // ModuleInstance to represent _relative_ module references rather than |
| // absolute: it's a module instance address relative to the same return value. |
| // Because the exported idea of ModuleInstance represents only _absolute_ |
| // module instance addresses, we mustn't expose that value through any exported |
| // API. |
| func (e *MoveEndpointInModule) matchModuleInstancePrefix(instAddr ModuleInstance) (ModuleInstance, ModuleInstance, bool) { |
| if len(e.module) > len(instAddr) { |
| return nil, nil, false // to short to possibly match |
| } |
| for i := range e.module { |
| if e.module[i] != instAddr[i].Name { |
| return nil, nil, false |
| } |
| } |
| // If we get here then we have a match, so we'll slice up the input |
| // to produce the prefix and match segments. |
| return instAddr[:len(e.module)], instAddr[len(e.module):], true |
| } |
| |
| // MoveDestination considers a an address representing a module |
| // instance in the context of source and destination move endpoints and then, |
| // if the module address matches the from endpoint, returns the corresponding |
| // new module address that the object should move to. |
| // |
| // MoveDestination will return false in its second return value if the receiver |
| // doesn't match fromMatch, indicating that the given move statement doesn't |
| // apply to this object. |
| // |
| // Both of the given endpoints must be from the same move statement and thus |
| // must have matching object types. If not, MoveDestination will panic. |
| func (m ModuleInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (ModuleInstance, bool) { |
| // NOTE: This implementation assumes the invariant that fromMatch and |
| // toMatch both belong to the same configuration statement, and thus they |
| // will both have the same address type and the same declaration module. |
| |
| // The root module instance is not itself moveable. |
| if m.IsRoot() { |
| return nil, false |
| } |
| |
| // The two endpoints must either be module call or module instance |
| // addresses, or else this statement can never match. |
| if fromMatch.ObjectKind() != MoveEndpointModule { |
| return nil, false |
| } |
| |
| // The rest of our work will be against the part of the reciever that's |
| // relative to the declaration module. mRel is a weird abuse of |
| // ModuleInstance that represents a relative module address, similar to |
| // what we do for MoveEndpointInModule.relSubject. |
| mPrefix, mRel, match := fromMatch.matchModuleInstancePrefix(m) |
| if !match { |
| return nil, false |
| } |
| |
| // Our next goal is to split mRel into two parts: the match (if any) and |
| // the suffix. Our result will then replace the match with the replacement |
| // in toMatch while preserving the prefix and suffix. |
| var mSuffix, mNewMatch ModuleInstance |
| |
| switch relSubject := fromMatch.relSubject.(type) { |
| case ModuleInstance: |
| if len(relSubject) > len(mRel) { |
| return nil, false // too short to possibly match |
| } |
| for i := range relSubject { |
| if relSubject[i] != mRel[i] { |
| return nil, false // this step doesn't match |
| } |
| } |
| // If we get to here then we've found a match. Since the statement |
| // addresses are already themselves ModuleInstance fragments we can |
| // just slice out the relevant parts. |
| mNewMatch = toMatch.relSubject.(ModuleInstance) |
| mSuffix = mRel[len(relSubject):] |
| case AbsModuleCall: |
| // The module instance part of relSubject must be a prefix of |
| // mRel, and mRel must be at least one step longer to account for |
| // the call step itself. |
| if len(relSubject.Module) > len(mRel)-1 { |
| return nil, false |
| } |
| for i := range relSubject.Module { |
| if relSubject.Module[i] != mRel[i] { |
| return nil, false // this step doesn't match |
| } |
| } |
| // The call name must also match the next step of mRel, after |
| // the relSubject.Module prefix. |
| callStep := mRel[len(relSubject.Module)] |
| if callStep.Name != relSubject.Call.Name { |
| return nil, false |
| } |
| // If we get to here then we've found a match. We need to construct |
| // a new mNewMatch that's an instance of the "new" relSubject with |
| // the same key as our call. |
| mNewMatch = toMatch.relSubject.(AbsModuleCall).Instance(callStep.InstanceKey) |
| mSuffix = mRel[len(relSubject.Module)+1:] |
| default: |
| panic("invalid address type for module-kind move endpoint") |
| } |
| |
| ret := make(ModuleInstance, 0, len(mPrefix)+len(mNewMatch)+len(mSuffix)) |
| ret = append(ret, mPrefix...) |
| ret = append(ret, mNewMatch...) |
| ret = append(ret, mSuffix...) |
| return ret, true |
| } |
| |
| // MoveDestination considers a an address representing a resource |
| // in the context of source and destination move endpoints and then, |
| // if the resource address matches the from endpoint, returns the corresponding |
| // new resource address that the object should move to. |
| // |
| // MoveDestination will return false in its second return value if the receiver |
| // doesn't match fromMatch, indicating that the given move statement doesn't |
| // apply to this object. |
| // |
| // Both of the given endpoints must be from the same move statement and thus |
| // must have matching object types. If not, MoveDestination will panic. |
| func (r AbsResource) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (AbsResource, bool) { |
| switch fromMatch.ObjectKind() { |
| case MoveEndpointModule: |
| // If we've moving a module then any resource inside that module |
| // moves too. |
| fromMod := r.Module |
| toMod, match := fromMod.MoveDestination(fromMatch, toMatch) |
| if !match { |
| return AbsResource{}, false |
| } |
| return r.Resource.Absolute(toMod), true |
| |
| case MoveEndpointResource: |
| fromRelSubject, ok := fromMatch.relSubject.(AbsResource) |
| if !ok { |
| // The only other possible type for a resource move is |
| // AbsResourceInstance, and that can never match an AbsResource. |
| return AbsResource{}, false |
| } |
| |
| // fromMatch can only possibly match the reciever if the resource |
| // portions are identical, regardless of the module paths. |
| if fromRelSubject.Resource != r.Resource { |
| return AbsResource{}, false |
| } |
| |
| // The module path portion of relSubject must have a prefix that |
| // matches the module where our endpoints were declared. |
| mPrefix, mRel, match := fromMatch.matchModuleInstancePrefix(r.Module) |
| if !match { |
| return AbsResource{}, false |
| } |
| |
| // The remaining steps of the module path must _exactly_ match |
| // the relative module path in the "fromMatch" address. |
| if len(mRel) != len(fromRelSubject.Module) { |
| return AbsResource{}, false // can't match if lengths are different |
| } |
| for i := range mRel { |
| if mRel[i] != fromRelSubject.Module[i] { |
| return AbsResource{}, false // all of the steps must match |
| } |
| } |
| |
| // If we got here then we have a match, and so our result is the |
| // module instance where the statement was declared (mPrefix) followed |
| // by the "to" relative address in toMatch. |
| toRelSubject := toMatch.relSubject.(AbsResource) |
| var mNew ModuleInstance |
| if len(mPrefix) > 0 || len(toRelSubject.Module) > 0 { |
| mNew = make(ModuleInstance, 0, len(mPrefix)+len(toRelSubject.Module)) |
| mNew = append(mNew, mPrefix...) |
| mNew = append(mNew, toRelSubject.Module...) |
| } |
| ret := toRelSubject.Resource.Absolute(mNew) |
| return ret, true |
| |
| default: |
| panic("unexpected object kind") |
| } |
| } |
| |
| // MoveDestination considers a an address representing a resource |
| // instance in the context of source and destination move endpoints and then, |
| // if the instance address matches the from endpoint, returns the corresponding |
| // new instance address that the object should move to. |
| // |
| // MoveDestination will return false in its second return value if the receiver |
| // doesn't match fromMatch, indicating that the given move statement doesn't |
| // apply to this object. |
| // |
| // Both of the given endpoints must be from the same move statement and thus |
| // must have matching object types. If not, MoveDestination will panic. |
| func (r AbsResourceInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (AbsResourceInstance, bool) { |
| switch fromMatch.ObjectKind() { |
| case MoveEndpointModule: |
| // If we've moving a module then any resource inside that module |
| // moves too. |
| fromMod := r.Module |
| toMod, match := fromMod.MoveDestination(fromMatch, toMatch) |
| if !match { |
| return AbsResourceInstance{}, false |
| } |
| return r.Resource.Absolute(toMod), true |
| |
| case MoveEndpointResource: |
| switch fromMatch.relSubject.(type) { |
| case AbsResource: |
| oldResource := r.ContainingResource() |
| newResource, match := oldResource.MoveDestination(fromMatch, toMatch) |
| if !match { |
| return AbsResourceInstance{}, false |
| } |
| return newResource.Instance(r.Resource.Key), true |
| case AbsResourceInstance: |
| fromRelSubject, ok := fromMatch.relSubject.(AbsResourceInstance) |
| if !ok { |
| // The only other possible type for a resource move is |
| // AbsResourceInstance, and that can never match an AbsResource. |
| return AbsResourceInstance{}, false |
| } |
| |
| // fromMatch can only possibly match the reciever if the resource |
| // portions are identical, regardless of the module paths. |
| if fromRelSubject.Resource != r.Resource { |
| return AbsResourceInstance{}, false |
| } |
| |
| // The module path portion of relSubject must have a prefix that |
| // matches the module where our endpoints were declared. |
| mPrefix, mRel, match := fromMatch.matchModuleInstancePrefix(r.Module) |
| if !match { |
| return AbsResourceInstance{}, false |
| } |
| |
| // The remaining steps of the module path must _exactly_ match |
| // the relative module path in the "fromMatch" address. |
| if len(mRel) != len(fromRelSubject.Module) { |
| return AbsResourceInstance{}, false // can't match if lengths are different |
| } |
| for i := range mRel { |
| if mRel[i] != fromRelSubject.Module[i] { |
| return AbsResourceInstance{}, false // all of the steps must match |
| } |
| } |
| |
| // If we got here then we have a match, and so our result is the |
| // module instance where the statement was declared (mPrefix) followed |
| // by the "to" relative address in toMatch. |
| toRelSubject := toMatch.relSubject.(AbsResourceInstance) |
| var mNew ModuleInstance |
| if len(mPrefix) > 0 || len(toRelSubject.Module) > 0 { |
| mNew = make(ModuleInstance, 0, len(mPrefix)+len(toRelSubject.Module)) |
| mNew = append(mNew, mPrefix...) |
| mNew = append(mNew, toRelSubject.Module...) |
| } |
| ret := toRelSubject.Resource.Absolute(mNew) |
| return ret, true |
| default: |
| panic("invalid address type for resource-kind move endpoint") |
| } |
| default: |
| panic("unexpected object kind") |
| } |
| } |
| |
| // IsModuleReIndex takes the From and To endpoints from a single move |
| // statement, and returns true if the only changes are to module indexes, and |
| // all non-absolute paths remain the same. |
| func (from *MoveEndpointInModule) IsModuleReIndex(to *MoveEndpointInModule) bool { |
| // The statements must originate from the same module. |
| if !from.module.Equal(to.module) { |
| panic("cannot compare move expressions from different modules") |
| } |
| |
| switch f := from.relSubject.(type) { |
| case AbsModuleCall: |
| switch t := to.relSubject.(type) { |
| case ModuleInstance: |
| // Generate a synthetic module to represent the full address of |
| // the module call. We're not actually comparing indexes, so the |
| // instance doesn't matter. |
| callAddr := f.Instance(NoKey).Module() |
| return callAddr.Equal(t.Module()) |
| } |
| |
| case ModuleInstance: |
| switch t := to.relSubject.(type) { |
| case AbsModuleCall: |
| callAddr := t.Instance(NoKey).Module() |
| return callAddr.Equal(f.Module()) |
| |
| case ModuleInstance: |
| return t.Module().Equal(f.Module()) |
| } |
| } |
| |
| return false |
| } |