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