IsModuleMoveReIndex

Add a method for checking if the From and To addresses in a move
statement are only changing the indexes of modules relative to the
statement module.

This is needed because move statement nested within the module will be
able to match against both the From and To addresses, causing cycles in
the order of move operations.
This commit is contained in:
James Bardin 2021-12-21 14:50:53 -05:00
parent cd7277128f
commit 346418e31f
3 changed files with 187 additions and 4 deletions

View File

@ -162,9 +162,9 @@ func TestModuleInstance_IsDeclaredByCall(t *testing.T) {
} }
func mustParseModuleInstanceStr(str string) ModuleInstance { func mustParseModuleInstanceStr(str string) ModuleInstance {
mi, err := ParseModuleInstanceStr(str) mi, diags := ParseModuleInstanceStr(str)
if err != nil { if diags.HasErrors() {
panic(err) panic(diags.ErrWithWarnings())
} }
return mi return mi
} }

View File

@ -373,7 +373,7 @@ func (e *MoveEndpointInModule) CanChainFrom(other *MoveEndpointInModule) bool {
return false return false
} }
// NestedWithin returns true if the reciever describes an address that is // NestedWithin returns true if the receiver describes an address that is
// contained within one of the objects that the given other address could // contained within one of the objects that the given other address could
// select. // select.
func (e *MoveEndpointInModule) NestedWithin(other *MoveEndpointInModule) bool { func (e *MoveEndpointInModule) NestedWithin(other *MoveEndpointInModule) bool {
@ -704,3 +704,53 @@ func (r AbsResourceInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInM
panic("unexpected object kind") panic("unexpected object kind")
} }
} }
// IsModuleMoveReIndex takes the from and to endpoints from a move statement,
// and returns true if the only changes are to module indexes, and all
// non-absolute paths remain the same.
func IsModuleMoveReIndex(from, 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:
if len(t) != 1 {
// An AbsModuleCall only ever has one segment, so the
// ModuleInstance length must match.
return false
}
return f.Call.Name == t[0].Name
}
case ModuleInstance:
switch t := to.relSubject.(type) {
case AbsModuleCall:
if len(f) != 1 {
return false
}
return f[0].Name == t.Call.Name
case ModuleInstance:
// We must have the same number of segments, and the names must all
// match in order for this to solely be an index change operation.
if len(f) != len(t) {
return false
}
for i := range f {
if f[i].Name != t[i].Name {
return false
}
}
return true
}
}
return false
}

View File

@ -1584,6 +1584,139 @@ func TestSelectsResource(t *testing.T) {
} }
} }
func TestIsModuleMoveReIndex(t *testing.T) {
tests := []struct {
from, to AbsMoveable
expect bool
}{
{
from: mustParseModuleInstanceStr(`module.bar`),
to: mustParseModuleInstanceStr(`module.bar`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar`),
to: mustParseModuleInstanceStr(`module.bar[0]`),
expect: true,
},
{
from: AbsModuleCall{
Call: ModuleCall{Name: "bar"},
},
to: mustParseModuleInstanceStr(`module.bar[0]`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar["a"]`),
to: AbsModuleCall{
Call: ModuleCall{Name: "bar"},
},
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.foo`),
to: mustParseModuleInstanceStr(`module.bar`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar`),
to: mustParseModuleInstanceStr(`module.foo[0]`),
expect: false,
},
{
from: AbsModuleCall{
Call: ModuleCall{Name: "bar"},
},
to: mustParseModuleInstanceStr(`module.foo[0]`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar["a"]`),
to: AbsModuleCall{
Call: ModuleCall{Name: "foo"},
},
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
to: mustParseModuleInstanceStr(`module.bar.module.baz`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
to: mustParseModuleInstanceStr(`module.baz.module.baz`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
to: mustParseModuleInstanceStr(`module.baz.module.baz[0]`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
to: mustParseModuleInstanceStr(`module.bar[0].module.baz`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar[0].module.baz`),
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar[0].module.baz`),
to: mustParseModuleInstanceStr(`module.bar[1].module.baz[0]`),
expect: true,
},
{
from: AbsModuleCall{
Call: ModuleCall{Name: "baz"},
},
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
to: AbsModuleCall{
Call: ModuleCall{Name: "baz"},
},
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.baz`),
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
expect: false,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
to: mustParseModuleInstanceStr(`module.baz`),
expect: false,
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("[%02d]IsModuleMoveReIndex(%s, %s)", i, test.from, test.to),
func(t *testing.T) {
from := &MoveEndpointInModule{
relSubject: test.from,
}
to := &MoveEndpointInModule{
relSubject: test.to,
}
if got := IsModuleMoveReIndex(from, to); got != test.expect {
t.Errorf("expected %t, got %t", test.expect, got)
}
},
)
}
}
func mustParseAbsResourceInstanceStr(s string) AbsResourceInstance { func mustParseAbsResourceInstanceStr(s string) AbsResourceInstance {
r, diags := ParseAbsResourceInstanceStr(s) r, diags := ParseAbsResourceInstanceStr(s)
if diags.HasErrors() { if diags.HasErrors() {