github.com/opentofu/opentofu@v1.7.1/internal/addrs/move_endpoint_module.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package addrs
     7  
     8  import (
     9  	"fmt"
    10  	"reflect"
    11  	"strings"
    12  
    13  	"github.com/zclconf/go-cty/cty"
    14  
    15  	"github.com/opentofu/opentofu/internal/tfdiags"
    16  )
    17  
    18  // anyKeyImpl is the InstanceKey representation indicating a wildcard, which
    19  // matches all possible keys. This is only used internally for matching
    20  // combinations of address types, where only portions of the path contain key
    21  // information.
    22  type anyKeyImpl rune
    23  
    24  func (k anyKeyImpl) instanceKeySigil() {
    25  }
    26  
    27  func (k anyKeyImpl) String() string {
    28  	return fmt.Sprintf("[%s]", string(k))
    29  }
    30  
    31  func (k anyKeyImpl) Value() cty.Value {
    32  	return cty.StringVal(string(k))
    33  }
    34  
    35  // anyKey is the only valid value of anyKeyImpl
    36  var anyKey = anyKeyImpl('*')
    37  
    38  // MoveEndpointInModule annotates a MoveEndpoint with the address of the
    39  // module where it was declared, which is the form we use for resolving
    40  // whether move statements chain from or are nested within other move
    41  // statements.
    42  type MoveEndpointInModule struct {
    43  	// SourceRange is the location of the physical endpoint address
    44  	// in configuration, if this MoveEndpoint was decoded from a
    45  	// configuration expresson.
    46  	SourceRange tfdiags.SourceRange
    47  
    48  	// The internals are unexported here because, as with MoveEndpoint,
    49  	// we're somewhat abusing AbsMoveable here to represent an address
    50  	// relative to the module, rather than as an absolute address.
    51  	// Conceptually, the following two fields represent a matching pattern
    52  	// for AbsMoveables where the elements of "module" behave as
    53  	// ModuleInstanceStep values with a wildcard instance key, because
    54  	// a moved block in a module affects all instances of that module.
    55  	// Unlike MoveEndpoint, relSubject in this case can be any of the
    56  	// address types that implement AbsMoveable.
    57  	module     Module
    58  	relSubject AbsMoveable
    59  }
    60  
    61  // ImpliedMoveStatementEndpoint is a special constructor for MoveEndpointInModule
    62  // which is suitable only for constructing "implied" move statements, which
    63  // means that we inferred the statement automatically rather than building it
    64  // from an explicit block in the configuration.
    65  //
    66  // Implied move endpoints, just as for the statements they are embedded in,
    67  // have somewhat-related-but-imprecise source ranges, typically referring to
    68  // some general configuration construct that implied the statement, because
    69  // by definition there is no explicit move endpoint expression in this case.
    70  func ImpliedMoveStatementEndpoint(addr AbsResourceInstance, rng tfdiags.SourceRange) *MoveEndpointInModule {
    71  	// implied move endpoints always belong to the root module, because each
    72  	// one refers to a single resource instance inside a specific module
    73  	// instance, rather than all instances of the module where the resource
    74  	// was declared.
    75  	return &MoveEndpointInModule{
    76  		SourceRange: rng,
    77  		module:      RootModule,
    78  		relSubject:  addr,
    79  	}
    80  }
    81  
    82  func (e *MoveEndpointInModule) ObjectKind() MoveEndpointKind {
    83  	return absMoveableEndpointKind(e.relSubject)
    84  }
    85  
    86  // String produces a string representation of the object matching pattern
    87  // represented by the reciever.
    88  //
    89  // Since there is no direct syntax for representing such an object matching
    90  // pattern, this function uses a splat-operator-like representation to stand
    91  // in for the wildcard instance keys.
    92  func (e *MoveEndpointInModule) String() string {
    93  	if e == nil {
    94  		return ""
    95  	}
    96  	var buf strings.Builder
    97  	for _, name := range e.module {
    98  		buf.WriteString("module.")
    99  		buf.WriteString(name)
   100  		buf.WriteString("[*].")
   101  	}
   102  	buf.WriteString(e.relSubject.String())
   103  
   104  	// For consistency we'll also use the splat-like wildcard syntax to
   105  	// represent the final step being either a resource or module call
   106  	// rather than an instance, so we can more easily distinguish the two
   107  	// in the string representation.
   108  	switch e.relSubject.(type) {
   109  	case AbsModuleCall, AbsResource:
   110  		buf.WriteString("[*]")
   111  	}
   112  
   113  	return buf.String()
   114  }
   115  
   116  // Equal returns true if the reciever represents the same matching pattern
   117  // as the other given endpoint, ignoring the source location information.
   118  //
   119  // This is not an optimized function and is here primarily to help with
   120  // writing concise assertions in test code.
   121  func (e *MoveEndpointInModule) Equal(other *MoveEndpointInModule) bool {
   122  	if (e == nil) != (other == nil) {
   123  		return false
   124  	}
   125  	if !e.module.Equal(other.module) {
   126  		return false
   127  	}
   128  	// This assumes that all of our possible "movables" are trivially
   129  	// comparable with reflect, which is true for all of them at the time
   130  	// of writing.
   131  	return reflect.DeepEqual(e.relSubject, other.relSubject)
   132  }
   133  
   134  // Module returns the address of the module where the receiving address was
   135  // declared.
   136  func (e *MoveEndpointInModule) Module() Module {
   137  	return e.module
   138  }
   139  
   140  // InModuleInstance returns an AbsMoveable address which concatenates the
   141  // given module instance address with the receiver's relative object selection
   142  // to produce one example of an instance that might be affected by this
   143  // move statement.
   144  //
   145  // The result is meaningful only if the given module instance is an instance
   146  // of the same module returned by the method Module. InModuleInstance doesn't
   147  // fully verify that (aside from some cheap/easy checks), but it will produce
   148  // meaningless garbage if not.
   149  func (e *MoveEndpointInModule) InModuleInstance(modInst ModuleInstance) AbsMoveable {
   150  	if len(modInst) != len(e.module) {
   151  		// We don't check all of the steps to make sure that their names match,
   152  		// because it would be expensive to do that repeatedly for every
   153  		// instance of a module, but if the lengths don't match then that's
   154  		// _obviously_ wrong.
   155  		panic("given instance address does not match module address")
   156  	}
   157  	switch relSubject := e.relSubject.(type) {
   158  	case ModuleInstance:
   159  		ret := make(ModuleInstance, 0, len(modInst)+len(relSubject))
   160  		ret = append(ret, modInst...)
   161  		ret = append(ret, relSubject...)
   162  		return ret
   163  	case AbsModuleCall:
   164  		retModAddr := make(ModuleInstance, 0, len(modInst)+len(relSubject.Module))
   165  		retModAddr = append(retModAddr, modInst...)
   166  		retModAddr = append(retModAddr, relSubject.Module...)
   167  		return relSubject.Call.Absolute(retModAddr)
   168  	case AbsResourceInstance:
   169  		retModAddr := make(ModuleInstance, 0, len(modInst)+len(relSubject.Module))
   170  		retModAddr = append(retModAddr, modInst...)
   171  		retModAddr = append(retModAddr, relSubject.Module...)
   172  		return relSubject.Resource.Absolute(retModAddr)
   173  	case AbsResource:
   174  		retModAddr := make(ModuleInstance, 0, len(modInst)+len(relSubject.Module))
   175  		retModAddr = append(retModAddr, modInst...)
   176  		retModAddr = append(retModAddr, relSubject.Module...)
   177  		return relSubject.Resource.Absolute(retModAddr)
   178  	default:
   179  		panic(fmt.Sprintf("unexpected move subject type %T", relSubject))
   180  	}
   181  }
   182  
   183  // ModuleCallTraversals returns both the address of the module where the
   184  // receiver was declared and any other module calls it traverses through
   185  // while selecting a particular object to move.
   186  //
   187  // This is a rather special-purpose function here mainly to support our
   188  // validation rule that a module can only traverse down into child modules.
   189  func (e *MoveEndpointInModule) ModuleCallTraversals() (Module, []ModuleCall) {
   190  	// We're returning []ModuleCall rather than Module here to make it clearer
   191  	// that this is a relative sequence of calls rather than an absolute
   192  	// module path.
   193  
   194  	var steps []ModuleInstanceStep
   195  	switch relSubject := e.relSubject.(type) {
   196  	case ModuleInstance:
   197  		// We want all of the steps except the last one here, because the
   198  		// last one is always selecting something declared in the same module
   199  		// even though our address structure doesn't capture that.
   200  		steps = []ModuleInstanceStep(relSubject[:len(relSubject)-1])
   201  	case AbsModuleCall:
   202  		steps = []ModuleInstanceStep(relSubject.Module)
   203  	case AbsResourceInstance:
   204  		steps = []ModuleInstanceStep(relSubject.Module)
   205  	case AbsResource:
   206  		steps = []ModuleInstanceStep(relSubject.Module)
   207  	default:
   208  		panic(fmt.Sprintf("unexpected move subject type %T", relSubject))
   209  	}
   210  
   211  	ret := make([]ModuleCall, len(steps))
   212  	for i, step := range steps {
   213  		ret[i] = ModuleCall{Name: step.Name}
   214  	}
   215  	return e.module, ret
   216  }
   217  
   218  // synthModuleInstance constructs a module instance out of the module path and
   219  // any module portion of the relSubject, substituting Module and Call segments
   220  // with ModuleInstanceStep using the anyKey value.
   221  // This is only used internally for comparison of these complete paths, but
   222  // does not represent how the individual parts are handled elsewhere in the
   223  // code.
   224  func (e *MoveEndpointInModule) synthModuleInstance() ModuleInstance {
   225  	var inst ModuleInstance
   226  
   227  	for _, mod := range e.module {
   228  		inst = append(inst, ModuleInstanceStep{Name: mod, InstanceKey: anyKey})
   229  	}
   230  
   231  	switch sub := e.relSubject.(type) {
   232  	case ModuleInstance:
   233  		inst = append(inst, sub...)
   234  	case AbsModuleCall:
   235  		inst = append(inst, sub.Module...)
   236  		inst = append(inst, ModuleInstanceStep{Name: sub.Call.Name, InstanceKey: anyKey})
   237  	case AbsResource:
   238  		inst = append(inst, sub.Module...)
   239  	case AbsResourceInstance:
   240  		inst = append(inst, sub.Module...)
   241  	default:
   242  		panic(fmt.Sprintf("unhandled relative address type %T", sub))
   243  	}
   244  
   245  	return inst
   246  }
   247  
   248  // SelectsModule returns true if the reciever directly selects either
   249  // the given module or a resource nested directly inside that module.
   250  //
   251  // This is a good function to use to decide which modules in a state
   252  // to consider when processing a particular move statement. For a
   253  // module move the given module itself is what will move, while a
   254  // resource move indicates that we should search each of the resources in
   255  // the given module to see if they match.
   256  func (e *MoveEndpointInModule) SelectsModule(addr ModuleInstance) bool {
   257  	synthInst := e.synthModuleInstance()
   258  
   259  	// In order to match the given module instance, our combined path must be
   260  	// equal in length.
   261  	if len(synthInst) != len(addr) {
   262  		return false
   263  	}
   264  
   265  	for i, step := range synthInst {
   266  		switch step.InstanceKey {
   267  		case anyKey:
   268  			// we can match any key as long as the name matches
   269  			if step.Name != addr[i].Name {
   270  				return false
   271  			}
   272  		default:
   273  			if step != addr[i] {
   274  				return false
   275  			}
   276  		}
   277  	}
   278  	return true
   279  }
   280  
   281  // SelectsResource returns true if the receiver directly selects either
   282  // the given resource or one of its instances.
   283  func (e *MoveEndpointInModule) SelectsResource(addr AbsResource) bool {
   284  	// Only a subset of subject types can possibly select a resource, so
   285  	// we'll take care of those quickly before we do anything more expensive.
   286  	switch e.relSubject.(type) {
   287  	case AbsResource, AbsResourceInstance:
   288  		// okay
   289  	default:
   290  		return false // can't possibly match
   291  	}
   292  
   293  	if !e.SelectsModule(addr.Module) {
   294  		return false
   295  	}
   296  
   297  	// If we get here then we know the module part matches, so we only need
   298  	// to worry about the relative resource part.
   299  	switch relSubject := e.relSubject.(type) {
   300  	case AbsResource:
   301  		return addr.Resource.Equal(relSubject.Resource)
   302  	case AbsResourceInstance:
   303  		// We intentionally ignore the instance key, because we consider
   304  		// instances to be part of the resource they belong to.
   305  		return addr.Resource.Equal(relSubject.Resource.Resource)
   306  	default:
   307  		// We should've filtered out all other types above
   308  		panic(fmt.Sprintf("unsupported relSubject type %T", relSubject))
   309  	}
   310  }
   311  
   312  // moduleInstanceCanMatch indicates that modA can match modB taking into
   313  // account steps with an anyKey InstanceKey as wildcards. The comparison of
   314  // wildcard steps is done symmetrically, because varying portions of either
   315  // instance's path could have been derived from configuration vs evaluation.
   316  // The length of modA must be equal or shorter than the length of modB.
   317  func moduleInstanceCanMatch(modA, modB ModuleInstance) bool {
   318  	for i, step := range modA {
   319  		switch {
   320  		case step.InstanceKey == anyKey || modB[i].InstanceKey == anyKey:
   321  			// we can match any key as long as the names match
   322  			if step.Name != modB[i].Name {
   323  				return false
   324  			}
   325  		default:
   326  			if step != modB[i] {
   327  				return false
   328  			}
   329  		}
   330  	}
   331  	return true
   332  }
   333  
   334  // CanChainFrom returns true if the reciever describes an address that could
   335  // potentially select an object that the other given address could select.
   336  //
   337  // In other words, this decides whether the move chaining rule applies, if
   338  // the reciever is the "to" from one statement and the other given address
   339  // is the "from" of another statement.
   340  func (e *MoveEndpointInModule) CanChainFrom(other *MoveEndpointInModule) bool {
   341  	eMod := e.synthModuleInstance()
   342  	oMod := other.synthModuleInstance()
   343  
   344  	// if the complete paths are different lengths, these cannot refer to the
   345  	// same value.
   346  	if len(eMod) != len(oMod) {
   347  		return false
   348  	}
   349  	if !moduleInstanceCanMatch(oMod, eMod) {
   350  		return false
   351  	}
   352  
   353  	eSub := e.relSubject
   354  	oSub := other.relSubject
   355  
   356  	switch oSub := oSub.(type) {
   357  	case AbsModuleCall, ModuleInstance:
   358  		switch eSub.(type) {
   359  		case AbsModuleCall, ModuleInstance:
   360  			// we already know the complete module path including any final
   361  			// module call name is equal.
   362  			return true
   363  		}
   364  
   365  	case AbsResource:
   366  		switch eSub := eSub.(type) {
   367  		case AbsResource:
   368  			return eSub.Resource.Equal(oSub.Resource)
   369  		}
   370  
   371  	case AbsResourceInstance:
   372  		switch eSub := eSub.(type) {
   373  		case AbsResourceInstance:
   374  			return eSub.Resource.Equal(oSub.Resource)
   375  		}
   376  	}
   377  
   378  	return false
   379  }
   380  
   381  // NestedWithin returns true if the receiver describes an address that is
   382  // contained within one of the objects that the given other address could
   383  // select.
   384  func (e *MoveEndpointInModule) NestedWithin(other *MoveEndpointInModule) bool {
   385  	eMod := e.synthModuleInstance()
   386  	oMod := other.synthModuleInstance()
   387  
   388  	// In order to be nested within the given endpoint, the module path must be
   389  	// shorter or equal.
   390  	if len(oMod) > len(eMod) {
   391  		return false
   392  	}
   393  
   394  	if !moduleInstanceCanMatch(oMod, eMod) {
   395  		return false
   396  	}
   397  
   398  	eSub := e.relSubject
   399  	oSub := other.relSubject
   400  
   401  	switch oSub := oSub.(type) {
   402  	case AbsModuleCall:
   403  		switch eSub.(type) {
   404  		case AbsModuleCall:
   405  			// we know the other endpoint selects our module, but if we are
   406  			// also a module call our path must be longer to be nested.
   407  			return len(eMod) > len(oMod)
   408  		}
   409  
   410  		return true
   411  
   412  	case ModuleInstance:
   413  		switch eSub.(type) {
   414  		case ModuleInstance, AbsModuleCall:
   415  			// a nested module must have a longer path
   416  			return len(eMod) > len(oMod)
   417  		}
   418  
   419  		return true
   420  
   421  	case AbsResource:
   422  		if len(eMod) != len(oMod) {
   423  			// these resources are from different modules
   424  			return false
   425  		}
   426  
   427  		// A resource can only contain a resource instance.
   428  		switch eSub := eSub.(type) {
   429  		case AbsResourceInstance:
   430  			return eSub.Resource.Resource.Equal(oSub.Resource)
   431  		}
   432  	}
   433  
   434  	return false
   435  }
   436  
   437  // matchModuleInstancePrefix is an internal helper to decide whether the given
   438  // module instance address refers to either the module where the move endpoint
   439  // was declared or some descendent of that module.
   440  //
   441  // If so, it will split the given address into two parts: the "prefix" part
   442  // which corresponds with the module where the statement was declared, and
   443  // the "relative" part which is the remainder that the relSubject of the
   444  // statement might match against.
   445  //
   446  // The second return value is another example of our light abuse of
   447  // ModuleInstance to represent _relative_ module references rather than
   448  // absolute: it's a module instance address relative to the same return value.
   449  // Because the exported idea of ModuleInstance represents only _absolute_
   450  // module instance addresses, we mustn't expose that value through any exported
   451  // API.
   452  func (e *MoveEndpointInModule) matchModuleInstancePrefix(instAddr ModuleInstance) (ModuleInstance, ModuleInstance, bool) {
   453  	if len(e.module) > len(instAddr) {
   454  		return nil, nil, false // to short to possibly match
   455  	}
   456  	for i := range e.module {
   457  		if e.module[i] != instAddr[i].Name {
   458  			return nil, nil, false
   459  		}
   460  	}
   461  	// If we get here then we have a match, so we'll slice up the input
   462  	// to produce the prefix and match segments.
   463  	return instAddr[:len(e.module)], instAddr[len(e.module):], true
   464  }
   465  
   466  // MoveDestination considers a an address representing a module
   467  // instance in the context of source and destination move endpoints and then,
   468  // if the module address matches the from endpoint, returns the corresponding
   469  // new module address that the object should move to.
   470  //
   471  // MoveDestination will return false in its second return value if the receiver
   472  // doesn't match fromMatch, indicating that the given move statement doesn't
   473  // apply to this object.
   474  //
   475  // Both of the given endpoints must be from the same move statement and thus
   476  // must have matching object types. If not, MoveDestination will panic.
   477  func (m ModuleInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (ModuleInstance, bool) {
   478  	// NOTE: This implementation assumes the invariant that fromMatch and
   479  	// toMatch both belong to the same configuration statement, and thus they
   480  	// will both have the same address type and the same declaration module.
   481  
   482  	// The root module instance is not itself moveable.
   483  	if m.IsRoot() {
   484  		return nil, false
   485  	}
   486  
   487  	// The two endpoints must either be module call or module instance
   488  	// addresses, or else this statement can never match.
   489  	if fromMatch.ObjectKind() != MoveEndpointModule {
   490  		return nil, false
   491  	}
   492  
   493  	// The rest of our work will be against the part of the reciever that's
   494  	// relative to the declaration module. mRel is a weird abuse of
   495  	// ModuleInstance that represents a relative module address, similar to
   496  	// what we do for MoveEndpointInModule.relSubject.
   497  	mPrefix, mRel, match := fromMatch.matchModuleInstancePrefix(m)
   498  	if !match {
   499  		return nil, false
   500  	}
   501  
   502  	// Our next goal is to split mRel into two parts: the match (if any) and
   503  	// the suffix. Our result will then replace the match with the replacement
   504  	// in toMatch while preserving the prefix and suffix.
   505  	var mSuffix, mNewMatch ModuleInstance
   506  
   507  	switch relSubject := fromMatch.relSubject.(type) {
   508  	case ModuleInstance:
   509  		if len(relSubject) > len(mRel) {
   510  			return nil, false // too short to possibly match
   511  		}
   512  		for i := range relSubject {
   513  			if relSubject[i] != mRel[i] {
   514  				return nil, false // this step doesn't match
   515  			}
   516  		}
   517  		// If we get to here then we've found a match. Since the statement
   518  		// addresses are already themselves ModuleInstance fragments we can
   519  		// just slice out the relevant parts.
   520  		mNewMatch = toMatch.relSubject.(ModuleInstance)
   521  		mSuffix = mRel[len(relSubject):]
   522  	case AbsModuleCall:
   523  		// The module instance part of relSubject must be a prefix of
   524  		// mRel, and mRel must be at least one step longer to account for
   525  		// the call step itself.
   526  		if len(relSubject.Module) > len(mRel)-1 {
   527  			return nil, false
   528  		}
   529  		for i := range relSubject.Module {
   530  			if relSubject.Module[i] != mRel[i] {
   531  				return nil, false // this step doesn't match
   532  			}
   533  		}
   534  		// The call name must also match the next step of mRel, after
   535  		// the relSubject.Module prefix.
   536  		callStep := mRel[len(relSubject.Module)]
   537  		if callStep.Name != relSubject.Call.Name {
   538  			return nil, false
   539  		}
   540  		// If we get to here then we've found a match. We need to construct
   541  		// a new mNewMatch that's an instance of the "new" relSubject with
   542  		// the same key as our call.
   543  		mNewMatch = toMatch.relSubject.(AbsModuleCall).Instance(callStep.InstanceKey)
   544  		mSuffix = mRel[len(relSubject.Module)+1:]
   545  	default:
   546  		panic("invalid address type for module-kind move endpoint")
   547  	}
   548  
   549  	ret := make(ModuleInstance, 0, len(mPrefix)+len(mNewMatch)+len(mSuffix))
   550  	ret = append(ret, mPrefix...)
   551  	ret = append(ret, mNewMatch...)
   552  	ret = append(ret, mSuffix...)
   553  	return ret, true
   554  }
   555  
   556  // MoveDestination considers a an address representing a resource
   557  // in the context of source and destination move endpoints and then,
   558  // if the resource address matches the from endpoint, returns the corresponding
   559  // new resource address that the object should move to.
   560  //
   561  // MoveDestination will return false in its second return value if the receiver
   562  // doesn't match fromMatch, indicating that the given move statement doesn't
   563  // apply to this object.
   564  //
   565  // Both of the given endpoints must be from the same move statement and thus
   566  // must have matching object types. If not, MoveDestination will panic.
   567  func (r AbsResource) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (AbsResource, bool) {
   568  	switch fromMatch.ObjectKind() {
   569  	case MoveEndpointModule:
   570  		// If we've moving a module then any resource inside that module
   571  		// moves too.
   572  		fromMod := r.Module
   573  		toMod, match := fromMod.MoveDestination(fromMatch, toMatch)
   574  		if !match {
   575  			return AbsResource{}, false
   576  		}
   577  		return r.Resource.Absolute(toMod), true
   578  
   579  	case MoveEndpointResource:
   580  		fromRelSubject, ok := fromMatch.relSubject.(AbsResource)
   581  		if !ok {
   582  			// The only other possible type for a resource move is
   583  			// AbsResourceInstance, and that can never match an AbsResource.
   584  			return AbsResource{}, false
   585  		}
   586  
   587  		// fromMatch can only possibly match the reciever if the resource
   588  		// portions are identical, regardless of the module paths.
   589  		if fromRelSubject.Resource != r.Resource {
   590  			return AbsResource{}, false
   591  		}
   592  
   593  		// The module path portion of relSubject must have a prefix that
   594  		// matches the module where our endpoints were declared.
   595  		mPrefix, mRel, match := fromMatch.matchModuleInstancePrefix(r.Module)
   596  		if !match {
   597  			return AbsResource{}, false
   598  		}
   599  
   600  		// The remaining steps of the module path must _exactly_ match
   601  		// the relative module path in the "fromMatch" address.
   602  		if len(mRel) != len(fromRelSubject.Module) {
   603  			return AbsResource{}, false // can't match if lengths are different
   604  		}
   605  		for i := range mRel {
   606  			if mRel[i] != fromRelSubject.Module[i] {
   607  				return AbsResource{}, false // all of the steps must match
   608  			}
   609  		}
   610  
   611  		// If we got here then we have a match, and so our result is the
   612  		// module instance where the statement was declared (mPrefix) followed
   613  		// by the "to" relative address in toMatch.
   614  		toRelSubject := toMatch.relSubject.(AbsResource)
   615  		var mNew ModuleInstance
   616  		if len(mPrefix) > 0 || len(toRelSubject.Module) > 0 {
   617  			mNew = make(ModuleInstance, 0, len(mPrefix)+len(toRelSubject.Module))
   618  			mNew = append(mNew, mPrefix...)
   619  			mNew = append(mNew, toRelSubject.Module...)
   620  		}
   621  		ret := toRelSubject.Resource.Absolute(mNew)
   622  		return ret, true
   623  
   624  	default:
   625  		panic("unexpected object kind")
   626  	}
   627  }
   628  
   629  // MoveDestination considers a an address representing a resource
   630  // instance in the context of source and destination move endpoints and then,
   631  // if the instance address matches the from endpoint, returns the corresponding
   632  // new instance address that the object should move to.
   633  //
   634  // MoveDestination will return false in its second return value if the receiver
   635  // doesn't match fromMatch, indicating that the given move statement doesn't
   636  // apply to this object.
   637  //
   638  // Both of the given endpoints must be from the same move statement and thus
   639  // must have matching object types. If not, MoveDestination will panic.
   640  func (r AbsResourceInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (AbsResourceInstance, bool) {
   641  	switch fromMatch.ObjectKind() {
   642  	case MoveEndpointModule:
   643  		// If we've moving a module then any resource inside that module
   644  		// moves too.
   645  		fromMod := r.Module
   646  		toMod, match := fromMod.MoveDestination(fromMatch, toMatch)
   647  		if !match {
   648  			return AbsResourceInstance{}, false
   649  		}
   650  		return r.Resource.Absolute(toMod), true
   651  
   652  	case MoveEndpointResource:
   653  		switch fromMatch.relSubject.(type) {
   654  		case AbsResource:
   655  			oldResource := r.ContainingResource()
   656  			newResource, match := oldResource.MoveDestination(fromMatch, toMatch)
   657  			if !match {
   658  				return AbsResourceInstance{}, false
   659  			}
   660  			return newResource.Instance(r.Resource.Key), true
   661  		case AbsResourceInstance:
   662  			fromRelSubject, ok := fromMatch.relSubject.(AbsResourceInstance)
   663  			if !ok {
   664  				// The only other possible type for a resource move is
   665  				// AbsResourceInstance, and that can never match an AbsResource.
   666  				return AbsResourceInstance{}, false
   667  			}
   668  
   669  			// fromMatch can only possibly match the reciever if the resource
   670  			// portions are identical, regardless of the module paths.
   671  			if fromRelSubject.Resource != r.Resource {
   672  				return AbsResourceInstance{}, false
   673  			}
   674  
   675  			// The module path portion of relSubject must have a prefix that
   676  			// matches the module where our endpoints were declared.
   677  			mPrefix, mRel, match := fromMatch.matchModuleInstancePrefix(r.Module)
   678  			if !match {
   679  				return AbsResourceInstance{}, false
   680  			}
   681  
   682  			// The remaining steps of the module path must _exactly_ match
   683  			// the relative module path in the "fromMatch" address.
   684  			if len(mRel) != len(fromRelSubject.Module) {
   685  				return AbsResourceInstance{}, false // can't match if lengths are different
   686  			}
   687  			for i := range mRel {
   688  				if mRel[i] != fromRelSubject.Module[i] {
   689  					return AbsResourceInstance{}, false // all of the steps must match
   690  				}
   691  			}
   692  
   693  			// If we got here then we have a match, and so our result is the
   694  			// module instance where the statement was declared (mPrefix) followed
   695  			// by the "to" relative address in toMatch.
   696  			toRelSubject := toMatch.relSubject.(AbsResourceInstance)
   697  			var mNew ModuleInstance
   698  			if len(mPrefix) > 0 || len(toRelSubject.Module) > 0 {
   699  				mNew = make(ModuleInstance, 0, len(mPrefix)+len(toRelSubject.Module))
   700  				mNew = append(mNew, mPrefix...)
   701  				mNew = append(mNew, toRelSubject.Module...)
   702  			}
   703  			ret := toRelSubject.Resource.Absolute(mNew)
   704  			return ret, true
   705  		default:
   706  			panic("invalid address type for resource-kind move endpoint")
   707  		}
   708  	default:
   709  		panic("unexpected object kind")
   710  	}
   711  }
   712  
   713  // IsModuleReIndex takes the From and To endpoints from a single move
   714  // statement, and returns true if the only changes are to module indexes, and
   715  // all non-absolute paths remain the same.
   716  func (from *MoveEndpointInModule) IsModuleReIndex(to *MoveEndpointInModule) bool {
   717  	// The statements must originate from the same module.
   718  	if !from.module.Equal(to.module) {
   719  		panic("cannot compare move expressions from different modules")
   720  	}
   721  
   722  	switch f := from.relSubject.(type) {
   723  	case AbsModuleCall:
   724  		switch t := to.relSubject.(type) {
   725  		case ModuleInstance:
   726  			// Generate a synthetic module to represent the full address of
   727  			// the module call. We're not actually comparing indexes, so the
   728  			// instance doesn't matter.
   729  			callAddr := f.Instance(NoKey).Module()
   730  			return callAddr.Equal(t.Module())
   731  		}
   732  
   733  	case ModuleInstance:
   734  		switch t := to.relSubject.(type) {
   735  		case AbsModuleCall:
   736  			callAddr := t.Instance(NoKey).Module()
   737  			return callAddr.Equal(f.Module())
   738  
   739  		case ModuleInstance:
   740  			return t.Module().Equal(f.Module())
   741  		}
   742  	}
   743  
   744  	return false
   745  }