Description
The ObjectFacts available to an analysis.Analyzer currently depends on how the Analyzer is run. When a {single,multi}checker is run as a standalone binary, it can see different ObjectFacts from AllObjectFacts vs. when it is run incrementally (unitchecker) such as go vet
, go vet -vetool=...
and the nogo bazel rule.
High level documentation on Facts.
Object facts can be attached during an analysis Pass
to any types.Object
within the *types.Package
for that Pass
. This includes variables within functions, functions that are not accessible from a *types.Package (func "_" and "init" funcs), types that are local to functions, etc. Relevant documentation:
// ExportObjectFact associates a fact of type *T with the obj,
// replacing any previous fact of that type.
//
// ExportObjectFact panics if it is called after the pass is
// complete, or if obj does not belong to the package being analyzed.
// ExportObjectFact is not concurrency-safe.
ExportObjectFact func(obj types.Object, fact Fact)
This allows the Pass to reason about Facts being placed on type.Objects within the same package and types.Objects from different packages that are accessible from the current package.
AllObjectFacts returns all of the object facts for the current Analyzer including from transitive dependencies.
// AllObjectFacts returns a new slice containing all object facts of the analysis's FactTypes
// in unspecified order.
// WARNING: This is an experimental API and may change in the future.
AllObjectFacts func() []ObjectFact
{single,multi}checker are intended to be easy to use wrappers that support both running as standalone binaries (backed by the library go/analysis/internal/checker) and incrementally (backed by go/analysis/unitchecker). unitchecker supports incremental analysis by serializing Facts for each package. Facts serialization is implemented on top of objectpath. objectpath does not support types.Objects what are not accessible from the *types.Package. There are also places in go/analysis/internal that filter facts: facts.go and checker.go. These two filtering methods are slightly inconsistent.
The upshot of the above is:
- We can get different facts, results , diagnostics, etc. based on whether the Analyzer is run incrementally or not if the analyzer uses AllObjectFacts. (It is not clear using AllObjectFacts is the only such condition.)
AllObjectFacts
's documentation is likely misleading Analyzer designers. It is not "all object facts". If we want to support all object facts, the serialization that objectpath does needs additional information. Alternatively the documentation can be updated to reflect a more narrow contract.
CL showing how to produce different results with incremental and standalone execution: https://go-review.googlesource.com/c/tools/+/342554
Extra Notes:
- There is also a distinction for a standalone checker whether packages
p
andq
which do not transitively import each other can see ObjectFacts from the other via AllObjectFacts. This is architecturally infeasible within incremental builds. It is not clear if go/analysis/internal/checker correctly enforces this. - The "within the same Package" distinction for ExportObjectFact can be subtle as it does not include things like functions coming from embedding an interface from another package. But this is not the focus of this issue.