addrs: Factor out MoveEndpointInModule module prefix matching

All of our MoveDestination methods have the common problem of deciding
whether the receiver is even potentially in the scope of a particular
MoveEndpointInModule, which requires that the receiver belong to an
instance of the module where the move statement was found.

Previously we had this logic inline in all three cases, but now we'll
factor it out into a shared helper function.

At first it seemed like there ought to be more factoring possible for
the AbsResource vs. AbsResourceInstance implementations, since textually
they look very similar, but in practice they only look similar because
those two types have a lot of method names in common, but the Go compiler
sees them as completely distinct and thus we must write the same logic
out twice. I did try some further refactoring to address that but it
made the resulting code significantly more complicated and, by my
judgement, harder to follow. Consequently I decided that a little
duplication was okay and warranted here because this logic is already
quite fiddly to read through and isn't likely to change significantly once
released (due to backward-compatibility promises).
This commit is contained in:
Martin Atkins 2021-07-27 18:05:30 -07:00
parent 45d16b4a2b
commit 5a6d11e375
1 changed files with 39 additions and 28 deletions

View File

@ -145,6 +145,35 @@ func (e *MoveEndpointInModule) NestedWithin(other *MoveEndpointInModule) bool {
return false 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 // MoveDestination considers a an address representing a module
// instance in the context of source and destination move endpoints and then, // instance in the context of source and destination move endpoints and then,
// if the module address matches the from endpoint, returns the corresponding // if the module address matches the from endpoint, returns the corresponding
@ -172,22 +201,14 @@ func (m ModuleInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInModule
return nil, false return nil, false
} }
// The given module instance must have a prefix that matches the
// declaration module of the two endpoints.
if len(fromMatch.module) > len(m) {
return nil, false // too short to possibly match
}
for i := range fromMatch.module {
if fromMatch.module[i] != m[i].Name {
return nil, false // this step doesn't match
}
}
// The rest of our work will be against the part of the reciever that's // 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 // relative to the declaration module. mRel is a weird abuse of
// ModuleInstance that represents a relative module address, similar to // ModuleInstance that represents a relative module address, similar to
// what we do for MoveEndpointInModule.relSubject. // what we do for MoveEndpointInModule.relSubject.
mPrefix, mRel := m[:len(fromMatch.module)], m[len(fromMatch.module):] 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 // 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 // the suffix. Our result will then replace the match with the replacement
@ -282,18 +303,13 @@ func (r AbsResource) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (
// The module path portion of relSubject must have a prefix that // The module path portion of relSubject must have a prefix that
// matches the module where our endpoints were declared. // matches the module where our endpoints were declared.
if len(fromMatch.module) > len(r.Module) { mPrefix, mRel, match := fromMatch.matchModuleInstancePrefix(r.Module)
return AbsResource{}, false // too short to possibly match if !match {
} return AbsResource{}, false
for i := range fromMatch.module {
if fromMatch.module[i] != r.Module[i].Name {
return AbsResource{}, false // this step doesn't match
}
} }
// The remaining steps of the module path must _exactly_ match // The remaining steps of the module path must _exactly_ match
// the relative module path in the "fromMatch" address. // the relative module path in the "fromMatch" address.
mPrefix, mRel := r.Module[:len(fromMatch.module)], r.Module[len(fromMatch.module):]
if len(mRel) != len(fromRelSubject.Module) { if len(mRel) != len(fromRelSubject.Module) {
return AbsResource{}, false // can't match if lengths are different return AbsResource{}, false // can't match if lengths are different
} }
@ -369,18 +385,13 @@ func (r AbsResourceInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInM
// The module path portion of relSubject must have a prefix that // The module path portion of relSubject must have a prefix that
// matches the module where our endpoints were declared. // matches the module where our endpoints were declared.
if len(fromMatch.module) > len(r.Module) { mPrefix, mRel, match := fromMatch.matchModuleInstancePrefix(r.Module)
return AbsResourceInstance{}, false // too short to possibly match if !match {
} return AbsResourceInstance{}, false
for i := range fromMatch.module {
if fromMatch.module[i] != r.Module[i].Name {
return AbsResourceInstance{}, false // this step doesn't match
}
} }
// The remaining steps of the module path must _exactly_ match // The remaining steps of the module path must _exactly_ match
// the relative module path in the "fromMatch" address. // the relative module path in the "fromMatch" address.
mPrefix, mRel := r.Module[:len(fromMatch.module)], r.Module[len(fromMatch.module):]
if len(mRel) != len(fromRelSubject.Module) { if len(mRel) != len(fromRelSubject.Module) {
return AbsResourceInstance{}, false // can't match if lengths are different return AbsResourceInstance{}, false // can't match if lengths are different
} }