github.com/hugorut/terraform@v1.1.3/src/addrs/move_endpoint_module.go (about)

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