From 6087b1bdb9ab99fd6170ca02c0f1a5472efa2f1b Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 5 Aug 2021 15:56:31 -0400 Subject: [PATCH] CanChainFrom and NestedWithin Add implementations of CanChainFrom and NestedWithin for MoveEndpointInModule. CanChainFrom allows the linking of move statements of the same address, which means the prior destination address must equal the following source address. If the destination and source addresses are of different types, they must be covered by NestedWithin rather than CanChainFrom. NestedWithin checks if the destination contains the source address. Any matching types would be covered by CanChainFrom. --- internal/addrs/move_endpoint_module.go | 77 ++++++++- internal/addrs/move_endpoint_module_test.go | 176 ++++++++++++++++++++ 2 files changed, 251 insertions(+), 2 deletions(-) diff --git a/internal/addrs/move_endpoint_module.go b/internal/addrs/move_endpoint_module.go index 1ed6fbee1..aea1e643a 100644 --- a/internal/addrs/move_endpoint_module.go +++ b/internal/addrs/move_endpoint_module.go @@ -218,7 +218,35 @@ func (e *MoveEndpointInModule) SelectsModule(addr ModuleInstance) bool { // 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 { - // TODO: implement + eSub := e.relSubject + oSub := other.relSubject + + switch oSub := oSub.(type) { + case AbsModuleCall: + switch eSub := eSub.(type) { + case AbsModuleCall: + return eSub.Equal(oSub) + } + + case ModuleInstance: + switch eSub := eSub.(type) { + case ModuleInstance: + return eSub.Equal(oSub) + } + + case AbsResource: + switch eSub := eSub.(type) { + case AbsResource: + return eSub.Equal(oSub) + } + + case AbsResourceInstance: + switch eSub := eSub.(type) { + case AbsResourceInstance: + return eSub.Equal(oSub) + } + } + return false } @@ -226,7 +254,52 @@ func (e *MoveEndpointInModule) CanChainFrom(other *MoveEndpointInModule) bool { // contained within one of the objects that the given other address could // select. func (e *MoveEndpointInModule) NestedWithin(other *MoveEndpointInModule) bool { - // TODO: implement + eSub := e.relSubject + oSub := other.relSubject + + switch oSub := oSub.(type) { + case AbsModuleCall: + withinModuleCall := func(mod ModuleInstance, call AbsModuleCall) bool { + // parent modules don't match at all + if !call.Module.IsAncestor(mod) { + return false + } + + rem := mod[len(call.Module):] + return rem[0].Name == call.Call.Name + } + + // Module calls can contain module instances, resources, and resource + // instances. + switch eSub := eSub.(type) { + case AbsResource: + return withinModuleCall(eSub.Module, oSub) + + case AbsResourceInstance: + return withinModuleCall(eSub.Module, oSub) + + case ModuleInstance: + return withinModuleCall(eSub, oSub) + } + + case ModuleInstance: + // Module instances can contain resources and resource instances. + switch eSub := eSub.(type) { + case AbsResource: + return eSub.Module.Equal(oSub) || oSub.IsAncestor(eSub.Module) + + case AbsResourceInstance: + return eSub.Module.Equal(oSub) || oSub.IsAncestor(eSub.Module) + } + + case AbsResource: + // A resource can only contain a resource instance. + switch eSub := eSub.(type) { + case AbsResourceInstance: + return eSub.ContainingResource().Equal(oSub) + } + } + return false } diff --git a/internal/addrs/move_endpoint_module_test.go b/internal/addrs/move_endpoint_module_test.go index 1e52eea23..ff772aba1 100644 --- a/internal/addrs/move_endpoint_module_test.go +++ b/internal/addrs/move_endpoint_module_test.go @@ -1074,3 +1074,179 @@ func TestAbsResourceMoveDestination(t *testing.T) { ) } } + +func TestMoveEndpointChainAndNested(t *testing.T) { + tests := []struct { + Endpoint, Other AbsMoveable + CanChainFrom, NestedWithin bool + }{ + { + Endpoint: AbsModuleCall{ + Module: mustParseModuleInstanceStr("module.foo[2]"), + Call: ModuleCall{Name: "bar"}, + }, + Other: AbsModuleCall{ + Module: mustParseModuleInstanceStr("module.foo[2]"), + Call: ModuleCall{Name: "bar"}, + }, + CanChainFrom: true, + NestedWithin: false, + }, + + { + Endpoint: mustParseModuleInstanceStr("module.foo[2]"), + Other: AbsModuleCall{ + Module: mustParseModuleInstanceStr("module.foo[2]"), + Call: ModuleCall{Name: "bar"}, + }, + CanChainFrom: false, + NestedWithin: false, + }, + + { + Endpoint: mustParseModuleInstanceStr("module.foo[2].module.bar[2]"), + Other: AbsModuleCall{ + Module: RootModuleInstance, + Call: ModuleCall{Name: "foo"}, + }, + CanChainFrom: false, + NestedWithin: true, + }, + + { + Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz").ContainingResource(), + Other: AbsModuleCall{ + Module: mustParseModuleInstanceStr("module.foo[2]"), + Call: ModuleCall{Name: "bar"}, + }, + CanChainFrom: false, + NestedWithin: true, + }, + + { + Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar[3].resource.baz[2]"), + Other: AbsModuleCall{ + Module: mustParseModuleInstanceStr("module.foo[2]"), + Call: ModuleCall{Name: "bar"}, + }, + CanChainFrom: false, + NestedWithin: true, + }, + + { + Endpoint: AbsModuleCall{ + Module: mustParseModuleInstanceStr("module.foo[2]"), + Call: ModuleCall{Name: "bar"}, + }, + Other: mustParseModuleInstanceStr("module.foo[2]"), + CanChainFrom: false, + NestedWithin: false, + }, + + { + Endpoint: mustParseModuleInstanceStr("module.foo[2]"), + Other: mustParseModuleInstanceStr("module.foo[2]"), + CanChainFrom: true, + NestedWithin: false, + }, + + { + Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), + Other: mustParseModuleInstanceStr("module.foo[2]"), + CanChainFrom: false, + NestedWithin: true, + }, + + { + Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz"), + Other: mustParseModuleInstanceStr("module.foo[2]"), + CanChainFrom: false, + NestedWithin: true, + }, + + { + Endpoint: AbsModuleCall{ + Module: mustParseModuleInstanceStr("module.foo[2]"), + Call: ModuleCall{Name: "bar"}, + }, + Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), + CanChainFrom: false, + NestedWithin: false, + }, + + { + Endpoint: mustParseModuleInstanceStr("module.foo[2]"), + Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), + CanChainFrom: false, + NestedWithin: false, + }, + + { + Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), + Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), + CanChainFrom: true, + NestedWithin: false, + }, + + { + Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), + Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz[2]").ContainingResource(), + CanChainFrom: false, + NestedWithin: true, + }, + + { + Endpoint: AbsModuleCall{ + Module: mustParseModuleInstanceStr("module.foo[2]"), + Call: ModuleCall{Name: "bar"}, + }, + Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), + CanChainFrom: false, + }, + { + Endpoint: mustParseModuleInstanceStr("module.foo[2]"), + Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), + CanChainFrom: false, + }, + { + Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), + Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), + CanChainFrom: false, + }, + { + Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), + Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), + CanChainFrom: true, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("[%02d]%s.CanChainFrom(%s)", i, test.Endpoint, test.Other), + func(t *testing.T) { + endpoint := &MoveEndpointInModule{ + relSubject: test.Endpoint, + } + + other := &MoveEndpointInModule{ + relSubject: test.Other, + } + + if endpoint.CanChainFrom(other) != test.CanChainFrom { + t.Errorf("expected %s CanChainFrom %s == %t", test.Endpoint, test.Other, test.CanChainFrom) + } + + if endpoint.NestedWithin(other) != test.NestedWithin { + t.Errorf("expected %s NestedWithin %s == %t", test.Endpoint, test.Other, test.NestedWithin) + } + }, + ) + } +} + +func mustParseAbsResourceInstanceStr(s string) AbsResourceInstance { + r, diags := ParseAbsResourceInstanceStr(s) + if diags.HasErrors() { + panic(diags.ErrWithWarnings().Error()) + } + return r +}