-
Notifications
You must be signed in to change notification settings - Fork 0
fix(scan,plan): make injector-generated providers visible cross-package #8
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
Changes from all commits
0dfba16
88562bc
0bdec5e
0f1f216
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,12 +111,14 @@ func Build(c ir.Container, idx *Index, opts Options) (Plan, []diag.Diag) { | |
| diags = append(diags, emDiags...) | ||
|
|
||
| r := &resolver{ | ||
| idx: idx, | ||
| inputs: inputs, | ||
| overrides: overrides, | ||
| embeds: embeds, | ||
| stepByKey: map[string]int{}, | ||
| active: map[string]bool{}, | ||
| idx: idx, | ||
| inputs: inputs, | ||
| overrides: overrides, | ||
| embeds: embeds, | ||
| stepByKey: map[string]int{}, | ||
| active: map[string]bool{}, | ||
| selfPkgPath: c.PkgPath, | ||
| selfFuncName: constructorName, | ||
| } | ||
|
|
||
| var outputs []Output | ||
|
|
@@ -175,6 +177,15 @@ type resolver struct { | |
| steps []Step | ||
| stepByKey map[string]int | ||
| active map[string]bool | ||
|
|
||
| // selfPkgPath and selfFuncName identify this container's own | ||
| // generated constructor. They are used to filter the by-type lookup | ||
| // so a container whose `inject:"returns"` declares an interface that | ||
| // matches an unrelated provider does not see its own previously | ||
| // emitted constructor as a candidate (a self-loop that would also | ||
| // produce a spurious "multiple providers" error). | ||
| selfPkgPath string | ||
| selfFuncName string | ||
| } | ||
|
|
||
| // embedSource describes one exported field of an inject:"embed" input that | ||
|
|
@@ -192,6 +203,44 @@ func (r *resolver) resolveField(f ir.Field) (int, []diag.Diag) { | |
| return r.resolveByType(f.Type, f.Pos, "field "+f.Name) | ||
| } | ||
|
|
||
| // excludeSelfProvider drops the container's own previously generated | ||
| // constructor from the candidate list so a `inject:"returns"` field does | ||
| // not pick itself up via type lookup. Callers that name a provider | ||
| // explicitly via inject:"with=..." are not affected. | ||
| // | ||
| // The common case is that the self-provider is not in the candidate | ||
| // list at all (most fields request types unrelated to the container's | ||
| // own return type), so the function returns the original slice without | ||
| // allocating when there is nothing to exclude. | ||
| func (r *resolver) excludeSelfProvider(candidates []*ir.Provider) []*ir.Provider { | ||
| if r.selfFuncName == "" { | ||
| return candidates | ||
| } | ||
| selfIdx := -1 | ||
| for i, c := range candidates { | ||
| if c.PkgPath == r.selfPkgPath && c.FuncName == r.selfFuncName { | ||
| selfIdx = i | ||
| break | ||
| } | ||
| } | ||
| if selfIdx < 0 { | ||
| return candidates | ||
| } | ||
| // Boundary cases — the self-provider sits at one end of the slice, | ||
| // so a sub-slice is enough and no allocation is needed. This covers | ||
| // the very common case of a single matching candidate. | ||
| if selfIdx == 0 { | ||
| return candidates[1:] | ||
| } | ||
| if selfIdx == len(candidates)-1 { | ||
| return candidates[:selfIdx] | ||
| } | ||
| kept := make([]*ir.Provider, 0, len(candidates)-1) | ||
| kept = append(kept, candidates[:selfIdx]...) | ||
| kept = append(kept, candidates[selfIdx+1:]...) | ||
| return kept | ||
| } | ||
|
Comment on lines
+215
to
+242
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the common case, the container's own constructor is not present in the candidate list. The current implementation always allocates a new slice (via We can optimize this by first checking if the self-provider is present in the candidates. If it is not, we can return the original func (r *resolver) excludeSelfProvider(candidates []*ir.Provider) []*ir.Provider {
if r.selfFuncName == "" {
return candidates
}
found := false
for _, c := range candidates {
if c.PkgPath == r.selfPkgPath && c.FuncName == r.selfFuncName {
found = true
break
}
}
if !found {
return candidates
}
kept := make([]*ir.Provider, 0, len(candidates)-1)
for _, c := range candidates {
if c.PkgPath == r.selfPkgPath && c.FuncName == r.selfFuncName {
continue
}
kept = append(kept, c)
}
return kept
}
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applied in selfIdx := -1
for i, c := range candidates {
if c.PkgPath == r.selfPkgPath && c.FuncName == r.selfFuncName {
selfIdx = i
break
}
}
if selfIdx < 0 {
return candidates
}
kept := make([]*ir.Provider, 0, len(candidates)-1)
kept = append(kept, candidates[:selfIdx]...)
kept = append(kept, candidates[selfIdx+1:]...)
return kept |
||
|
|
||
| func (r *resolver) resolveByType(want types.Type, pos token.Position, parent string) (int, []diag.Diag) { | ||
| tk := TypeKey(want) | ||
|
|
||
|
|
@@ -210,6 +259,7 @@ func (r *resolver) resolveByType(want types.Type, pos token.Position, parent str | |
| } | ||
|
|
||
| candidates := r.idx.LookupByType(want) | ||
| candidates = r.excludeSelfProvider(candidates) | ||
| if len(candidates) == 0 { | ||
| return -1, []diag.Diag{ | ||
| diag.Errorf(pos, "no provider for %s (required by %s)", TypeString(want), parent), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can optimize
excludeSelfProviderto avoid allocations when the self-provider is at the very beginning or the very end of the candidates list (including the common case where it is the only candidate). By returning a sub-slice ofcandidatesdirectly, we avoid allocating a new slice.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applied in
0f1f216. Boundary checks added before the middle-of-slice case:This catches the most common shape — a single matching candidate that is itself the self-provider — without any allocation, plus the head/tail cases. The interior case still allocates
len-1exactly.