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 }