Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expression evaluator decides module call values using instances.Expander and namedvals.State #34545

Merged
merged 4 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions internal/addrs/module_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,23 @@ func (c AbsModuleCall) absMoveableSigil() {
// AbsModuleCall is "moveable".
}

// StaticModule returns the static module path for the receiver.
//
// In other words, it effectively discards all of the dynamic instance keys
// along the path to this call, while retaining the static module names.
//
// Given a representation of module.a["foo"].module.b, this would return
// the [Module]-based representation of module.a.module.b, discarding the
// first step's dynamic instance key "foo".
func (c AbsModuleCall) StaticModule() Module {
ret := make(Module, len(c.Module), len(c.Module)+1)
for i, step := range c.Module {
ret[i] = step.Name
}
ret = append(ret, c.Call.Name)
return ret
}

func (c AbsModuleCall) String() string {
if len(c.Module) == 0 {
return "module." + c.Call.Name
Expand Down
11 changes: 11 additions & 0 deletions internal/addrs/module_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,17 @@ func (m ModuleInstance) Call() (ModuleInstance, ModuleCall) {
}
}

// AbsCall returns the same information as [ModuleInstance.Call], but returns
// it as a single [AbsModuleCall] value rather than the containing module
// and the local call address separately.
func (m ModuleInstance) AbsCall() AbsModuleCall {
container, call := m.Call()
return AbsModuleCall{
Module: container,
Call: call,
}
}

// CallInstance returns the module call instance address that corresponds to
// the given module instance, along with the address of the module instance
// that contains it.
Expand Down
39 changes: 39 additions & 0 deletions internal/instances/expander.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,45 @@ func (e *Expander) ExpandModule(addr addrs.Module) []addrs.ModuleInstance {
return e.expandModule(addr, false)
}

// ExpandAbsModuleCall is similar to [Expander.ExpandModule] except that it
// filters the result to include only the instances that belong to the
// given module call instance, and therefore returns just instance keys
// since the rest of the module address is implied by the given argument.
//
// For example, passing an address representing module.a["foo"].module.b
// would include only instances under module.a["foo"], and disregard instances
// under other dynamic paths like module.a["bar"].
//
// If the requested module call has an unknown expansion (e.g. because it
// had an unknown value for count or for_each) then the second result is
// false and the other results are meaningless. If the second return value is
// true, then the set of module instances is complete, and all of the instances
// have instance keys matching the returned keytype.
//
// The instances are returned in the typical sort order for the returned
// key type: integer keys are sorted numerically, and string keys are sorted
// lexically.
func (e *Expander) ExpandAbsModuleCall(addr addrs.AbsModuleCall) (keyType addrs.InstanceKeyType, insts []addrs.InstanceKey, known bool) {
expParent, ok := e.findModule(addr.Module)
if !ok {
// This module call lives under an unknown-expansion prefix, so we
// cannot answer this question.
return addrs.NoKeyType, nil, false
}

expCall, ok := expParent.moduleCalls[addr.Call]
if !ok {
// This indicates a bug, since we should've calculated the expansions
// (even if unknown) before any caller asks for the results.
panic(fmt.Sprintf("no expansion has been registered for %s", addr.String()))
}
keyType, instKeys, deferred := expCall.instanceKeys()
if deferred {
return addrs.NoKeyType, nil, false
}
return keyType, instKeys, true
}

// expandModule allows skipping unexpanded module addresses by setting skipUnregistered to true.
// This is used by instances.Set, which is only concerned with the expanded
// instances, and should not panic when looking up unknown addresses.
Expand Down
27 changes: 27 additions & 0 deletions internal/instances/expander_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,24 @@ func TestExpander(t *testing.T) {
t.Errorf("wrong result\n%s", diff)
}
})
t.Run("module count2[0] module count2 instances", func(t *testing.T) {
instAddr := mustModuleInstanceAddr(`module.count2[0].module.count2[0]`)
callAddr := instAddr.AbsCall() // discards the final [0] instance key from the above
keyType, got, known := ex.ExpandAbsModuleCall(callAddr)
if !known {
t.Fatal("expansion unknown; want known")
}
if keyType != addrs.IntKeyType {
t.Fatalf("wrong key type %#v; want %#v", keyType, addrs.IntKeyType)
}
want := []addrs.InstanceKey{
addrs.IntKey(0),
addrs.IntKey(1),
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong result\n%s", diff)
}
})
t.Run("module count2 module count2 GetDeepestExistingModuleInstance", func(t *testing.T) {
t.Run("first step invalid", func(t *testing.T) {
got := ex.GetDeepestExistingModuleInstance(mustModuleInstanceAddr(`module.count2["nope"].module.count2[0]`))
Expand Down Expand Up @@ -596,6 +614,15 @@ func TestExpanderWithUnknowns(t *testing.T) {
ex.SetResourceCount(module1Inst1Module2Inst0, resourceAddrKnownExp, 2)
ex.SetResourceCountUnknown(module1Inst1Module2Inst0, resourceAddrUnknownExp)

module2Call := addrs.AbsModuleCall{
Module: module1Inst0,
Call: moduleCallAddr2,
}
_, _, instsKnown := ex.ExpandAbsModuleCall(module2Call)
if instsKnown {
t.Fatalf("instances of %s are known; should be unknown", module2Call.String())
}

gotKnown := ex.ExpandModule(module2)
wantKnown := []addrs.ModuleInstance{
module1Inst1.Child("bar", addrs.IntKey(0)),
Expand Down
48 changes: 0 additions & 48 deletions internal/namedvals/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,54 +107,6 @@ func (s *State) GetOutputValue(addr addrs.AbsOutputValue) cty.Value {
return s.outputs.GetExactResult(addr)
}

func (s *State) GetOutputValuesForModuleCall(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall) addrs.Map[addrs.AbsOutputValue, cty.Value] {
s.mu.Lock()
defer s.mu.Unlock()

// HACK: The "values" data structure isn't really designed to support
// this operation, since it tries to be general over all different named
// value address types but that makes it unable to generically handle
// the problem of finding the module instance for a particular absolute
// address. We'd need a ModuleInstance equivalent of
// addrs.InPartialExpandedModule to achieve that, but our "Abs" address
// types are all hand-written and predate Go having support for generic
// types.
//
// This operation is just a stop-gap until we make the evaluator work
// in a different way to handle placeholder values, so we'll accept it
// being clunky and slow just as a checkpoint to make everything still
// work similarly to how it used to, and then delete this function again
// later once we can implement what we need using just
// [State.GetOutputValue] by having the caller determine which output
// values it should be asking for using the configuration.

ret := addrs.MakeMap[addrs.AbsOutputValue, cty.Value]()
all := s.outputs.GetExactResults()

for _, elem := range all.Elems {
outputMod := elem.Key.Module
if outputMod.IsRoot() {
// We cannot enumerate the root module output values with this
// function, because the root module has no "call".
continue
}
callingMod, call := outputMod.Call()
if call != callAddr {
continue
}
if !callingMod.Equal(parentAddr) {
continue
}

// If we get here then the output value we're holding belongs to
// one of the instances of the call indicated in this function's
// arguments.
ret.PutElement(elem)
}

return ret
}

func (s *State) HasOutputValue(addr addrs.AbsOutputValue) bool {
s.mu.Lock()
defer s.mu.Unlock()
Expand Down
Loading
Loading