github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/addrs/move_endpoint_module.go (about)

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