-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
proposal: go/types: add Info.ImplicitConversions mapping #70638
Comments
Related Issues
Related Code Changes (Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.) |
I think this would simplify quite a bit of reverse-engineering in tools. Nits:
|
Does this primarily apply to expressions of untyped constants? Assignments of values to interfaces? Something else? The proposal should be a bit more specific and perhaps provides some concrete examples. |
The satisfy package cares only about concrete-to-interface and interface-to-interface conversions. The inliner cares about all kinds of implicit conversions (untyped-to-typed; unnamed-to-named and vice versa; concrete-to-interface and interface-to-interface; and even channel direction restrictions). I elaborated the examples a little. |
Change https://go.dev/cl/632935 mentions this issue: |
Add more precision (and hopefully clarity) to the detection of redundant conversions, by using more granular analysis of references. We now record, for each reference, (1) whether it appears in an assignment context, perhaps subject to implicit conversions, (2) whether this assignment is to an interface value, and (3) whether this assignment may affect type inference. With these conditions, we can more precisely detect scenarios where conversion is necessary, as annotated in the code. Notably, this avoids unnecessary concrete-to-concrete conversions. Also: - Fix a crash in falcon analysis for named literal types. - Handle index expression assignability (previously missing). - Avoid the print builtin as an example of a function that takes 'any', as the type checker records the effective type for print, and the spec is unclear about whether implementations are allowed to specialize for effective types. For golang/go#70599 Updates golang/go#70638 Change-Id: I9730deba54d864928a1dc02a1ab00481a2ce9998 Reviewed-on: https://go-review.googlesource.com/c/tools/+/632935 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Alan Donovan <adonovan@google.com>
Change https://go.dev/cl/633711 mentions this issue: |
This proposal has been added to the active column of the proposals project |
Not to bikeshed, but we may also want to include some helper functions on types.Info with this proposal. Sometimes we want to know the type of an expression, absent its syntactic context. Other times, we want to know the type of the hole the expression fills. Unfortunately the type checker is inconsistent in how it records this information. For example, the type checker updates untyped expression types once their effective type is computed, except for untyped nil. How about
(suggestions for better names are welcome). I'd love to just make |
Three *TypeOf functions seems like at least two too many. What would the doc comments and implementations of these functions look like? |
I think it's one too many. If you're reacting to the names, I agree they are not great :) The implementation(s) would go like this: // InitialTypeOf returns the type of expr, before any implicit conversions have been applied.
func (info *Info) InitialTypeOf(expr ast.Expr) types.Type {
if imp, ok := info.ImplicitConversions[expr]; ok {
return imp.From
}
return info.TypeOf(expr)
} The problem is that |
OK, that function looks useful. I don't like the name |
An alternative approach I just wanted to throw out there is exposing the algorithm rather than its conclusions (or maybe both). Are there cases where tools need to "speculatively" ask about implicit conversions in an expression/statement? Or would that require fully type-checking a complete snippet anyway, so it doesn't really help? |
The algorithm is really no less than full type checking. One might well want to query ImplicitConversions for a given expression, but the CheckExpr API is too limited to allow that; one would need to create a bogus |
It sounds like we're going to focus on the new map in
Given that 2/3rds of the documentation here is a parenthetical explaining a subtle caveat, is this an opportunity to fix that caveat or lift it to a non-parenthetical level? Could we have a third field? E.g., |
We can certainly lift the doc: type Info struct {
// ImplicitConversions records, for an expression appearing in (say) an assignment
// context, any implicit conversion applied to it.
//
// For historical reasons, ImplicitConversions[e].From is not necessarily identical to
// Types[e].Type for a constant expression e, as the latter may reflect an implicit
// untyped-to-typed conversion.
ImplicitConversions map[ast.Expr]ImplicitConversion
}
// An ImplicitConversion records, for an expression appearing in (say) an assignment
// context, an implicit conversion applied to it. See notes at Info.ImplicitConversions.
type ImplicitConversion struct {
From, To types.Type
} I'll have a think about adding an Untyped field. FWIW, I think the proposed API provides all the necessary information (in both senses: commentary and computed data). |
In proposal review, a question arose about whether "From" is actually necessary since it only differs from Info.Types for untyped constants. If we drop that, the map value will be half the size, but also the map will have far fewer entries. How useful is "From"? Can you infer the untyped type from other information? Should we provide another map for untyped-to-typed conversions? |
After discussion, @griesemer and I agreed that--name bike-shedding aside--the following would be a better interface: package types
type TypeAndValue struct { ... }
// AssignedTo returns, for a corresponding expression subject to an assignment conversion
// such as the right side of an assignment lhs = rhs, the type of the variable lhs on the left side.
// For all other expressions it returns nil.
+ func (TypeAndValue) AssignedTo() Type
// Untyped reports whether the corresponding expression has an "untyped" constant type.
//
// For unfortunate historical reasons, TypeAndValue.Type reports the [Default] type of
// the corresponding expression when it is the immediate right-hand operand of an assignment.
// For example, given var x any = 1 + 2, the types of 1, 2, and 1+2 are all 'untyped int',
// but TypeAndValue.Type reports 'int' for 1 + 2. The Untyped method returns true for all three.
+ func (TypeAndValue) Untyped() bool The implementation requires an extra (optional) Type field within TypeAndValue for AssignedTo, plus an additional bit to record Untyped. Fortunately this should not bump TypeAndValue to a larger size class. AssignedTo is a method, not a field, as we have finally learned our lesson in go/types about painting ourselves into a corner by exposing representation details. |
Just to be clear that this isn't only for
I'd suggest that this should return a bool and the untyped |
Based on the discussion above, this proposal seems like a likely accept. The proposal is to add the following to package package types
// AssignedTo returns, for a corresponding expression subject to an assignment conversion
// such as the right side of an assignment lhs = rhs, the type of the variable lhs on the left side.
// For all other expressions it returns nil.
func (TypeAndValue) AssignedTo() Type
// Untyped returns the "untyped" constant type of this expression, or nil if this expression
// does not have an untyped constant type.
//
// For unfortunate historical reasons, TypeAndValue.Type reports the [Default] type of
// the corresponding expression when it is the immediate right-hand operand of an assignment.
// For example, given var x any = 1 + 2, the types of 1, 2, and 1+2 are all 'untyped int',
// but TypeAndValue.Type reports 'int' for 1 + 2. The Untyped method returns
// [UntypedInt], true for all three.
func (TypeAndValue) Untyped() Type (The documentation of the |
Background: One of the many tricky subtasks of the type checker is to compute the conversions implicitly applied to an expression that is used on the right-hand side of an assignment (or passed as an argument, etc), arithmetic, and shifts. This information is hard to compute and useful to clients. Our refactoring tools have needed this information on several occasions.
For example:
f(x)
tofunc f(y Y) { g(y) }
, whether it is safe to elide the implicit conversionY(x)
, and this depends on both the implicit conversion from x to Y and on the implicit conversion from y to g's parameter type. Ascertaining that x and y are both subject to implicit conversions in general requires logic of many cases (duplicating the type checker), and is further complicated by the loss of untyped type information when x is a constant.Proposal: We add a new field to
types.Info
to record all implicit conversions.[1] historical reasons
Related:
@findleyr @timothy-king @griesemer
The text was updated successfully, but these errors were encountered: