github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/addrs/move_endpoint_module.go (about)

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