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 }