golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124.16k stars 17.69k forks source link

go/types: position-independent type checking #69673

Open adonovan opened 1 month ago

adonovan commented 1 month ago

The following go/types APIs all use Pos in semantically significant ways:

Scope.LookupParent(name, pos)
Scope.Contains(pos)
Scope.Innermost(pos)
CheckExpr(fset, pkg, pos, expr, info)
Eval(fset, pkg, pos)

As the doc comment on Innermost says, "The result is guaranteed to be valid only if the type-checked AST has complete position information." This restriction applies equally to all these operations, and should probably be made explicit for all of them.

For example, Scope.LookupParent uses the position of the reference to compute the set of declarations that are in scope. This assumes the position information is accurate, which it is for trees produced by the parser, but not for ones that have been modified by refactoring algorithms or synthesized directly. It should be possible to type-check any syntax tree correctly, even without accurate position information.

In each case, the position is used as a shorthand to indicate the portion of the environment that is accessible at a given point. It would be possible to provide parallel APIs for these 5 functions, without the restriction, that replaces pos with a different parameter that indicates the environment. For example, it could be something like an []ast.Node indicating a path from the root of the tree to the designated node.

Internally, the tree of Scopes could record the environment in a form that is independent of position, similar to my lazy type checker, which mapped each Node to a pair of a Scope and an int that represents the length of the prefix of Scope symbols that are in scope at that point. Ignoring efficiency concerns, I imagine the existing Pos-based functions could become wrappers around a function that resolves a Pos to a []Node (assuming valid pos info), followed by a call to the []Node-based API.

This would open the door to composable refactorings that mutate the tree in a sequence of passes, invalidating position info but still allowing the type checker to be reinvoked after each mutation, before finally formatting the tree to produce the updated output. There are still many open questions about how to build such refactorings: we rely heavily on pos for debugging and error reporting; re-typechecking may require additional packages that were not needed before; and so on. But it is both viable and desirable to make the type checker itself fully independent of syntax positions, so they are used only for error messages, or passed through to Object.Pos, but have no semantics.

gopherbot commented 1 month ago

Change https://go.dev/cl/616259 mentions this issue: go/types, types2: replace 2 uses of Scope.LookupParent with Checker.lookup

gopherbot commented 1 month ago

Change https://go.dev/cl/616337 mentions this issue: go/types, types2: remove remaining internal use of Scope.LookupParent

gopherbot commented 1 month ago

Change https://go.dev/cl/616260 mentions this issue: go/types, types2: remove need for Scope.LookupParent from TestObjectString

gopherbot commented 1 month ago

Change https://go.dev/cl/616261 mentions this issue: go/types, types2: move go/types-only Scope methods into scopes2.go

gopherbot commented 1 month ago

Change https://go.dev/cl/616316 mentions this issue: go/types, types2: remove Checker.pos from types2 code - not needed anymore

griesemer commented 1 month ago

With the above CLs, type-checking is now position-independent. @alandonovan Is there anything left here to do or can this issue be closed?

adonovan commented 1 month ago

With the above CLs, type-checking is now position-independent. @alandonovan Is there anything left here to do or can this issue be closed?

Type-checking of syntax is now position independent (and this happened rather more rapidly than I was expecting!) But we will still need new public API for the above five functions to permit a client to identify a place in the code other than by position, for example as a []ast.Node path from the root node.