Skip to content
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

spec: type inference should be more lenient about untyped numeric literals #58671

Closed
jimmyfrasche opened this issue Feb 23, 2023 · 16 comments
Closed
Assignees
Labels
generics Issue is related to generics Proposal Proposal-Accepted TypeInference Issue is related to generic type inference
Milestone

Comments

@jimmyfrasche
Copy link
Member

Forked off #58650 per @ianlancetaylor's request

Currently

func Add[T addable](x, y T) T {
  return x + y
}
var x = 1 + 2.5
var y = Add(1, 2.5)

assigns x the type float64 but on the next line reports the error default type float64 of 2.5 does not match inferred type int for T.

Go takes the default type based on the literal form before applying type inference so it's the same as writing Add(int(1), float64(2.5)).

In the special case of multiple untyped numeric literals for a single type parameter, type inference should select the "largest" one provided to use as the default type.

("largest" in the sense that complex128 is larger than float64 is larger than int)

template

Author background

  • Would you consider yourself a novice, intermediate, or experienced Go programmer? experienced
  • What other languages do you have experience with? Many.

Related proposals

  • Has this idea, or one like it, been proposed before? not to my knowledge
    • If so, how does this proposal differ?
  • Does this affect error handling? no
    • If so, how does this differ from previous error handling proposals?
  • Is this about generics? yes
    • If so, how does this relate to the accepted design and other generics proposals? tweaks the inference algorithm

Proposal

  • What is the proposed change? see above
  • Who does this proposal help, and why? Currently the eagerness to jump to default types makes inference behave more rigidly than the rest of the language. This would loosen the restriction to make both behave the same so there's less to remember.
  • Please describe as precisely as possible the change to the language. see above
  • What would change in the language spec? inference algorithm
  • Please also describe the change informally, as in a class teaching Go. The same explanation that's used to explain the default type of 1 + 2.5
  • Is this change backward compatible? yes, all old code would still be admissible, this would just allow code to work that is currently an error
    • Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit.
      Show example code before and after the change.
    • Before Add(1.0, 2.5)
    • After Add(1, 2.5)
  • Orthogonality: how does this change interact or overlap with existing features? n/a
  • Is the goal of this change a performance improvement? no
    • If so, what quantifiable improvement should we expect?
    • How would we measure it?

Costs

  • Would this change make Go easier or harder to learn, and why? Easier as type inference would work the same as regular math expressions.
  • What is the cost of this proposal? (Every language change has a cost). change to type inference algorithm/spec
  • How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected? go/types would need to implement the changes in addition to the compiler. Nothing else would need to change that I am aware of.
  • What is the compile time cost? extra stage during type inference in corner cases, unlikely to be an issue
  • What is the run time cost? none
  • Can you describe a possible implementation? no
  • Do you have a prototype? (This is not required.) no
@jimmyfrasche jimmyfrasche added LanguageChange v2 A language change or incompatible library change Proposal labels Feb 23, 2023
@jimmyfrasche jimmyfrasche added this to the Proposal milestone Feb 23, 2023
@ianlancetaylor ianlancetaylor added generics Issue is related to generics TypeInference Issue is related to generic type inference labels Feb 24, 2023
@ianlancetaylor
Copy link
Contributor

CC @griesemer

@griesemer
Copy link
Contributor

Given the example above, what should be inferred for T for the call Add(1, 2.0) ? int, float32, and float64 would all be valid answers. And even if we ignore float32 (it's not a default type), we still have two valid choices.

We can come up with rules, but it smells like an unnecessary complication. A minor change to a constant value, possibly defined somewhere else, may suddenly change an inferred type.

@jimmyfrasche
Copy link
Member Author

If I write 1 + 2.0 it gets typed float64 so I would expect the same type for T.

I don't know if this diagram will help but:

On one hand we have 1 : untyped integer + 2.0 : untyped floating point -> (1 + 2.0) : untyped floating point -> (1 + 2.0) : float64

On the other we have Add[T](1 : untyped integer, 2.0 : untyped floating point) -> Add[T](1 : int, 2.0 : float64) -> error.

I'm asking for Add[T](1 : untyped integer, 2.0 : untyped floating point) -> Add[T : untyped floating point](1, 2.0) -> Add[T : float64](1, 2.0). That is, instead of immediately taking the default types retain the untyped type a little longer so that the default type that works for all instances of the parameter is chosen.

@golang golang deleted a comment from gopherbot Feb 25, 2023
@ianlancetaylor ianlancetaylor changed the title proposal: Go 2: type inference should be more lenient about untyped numeric literals proposal: type inference should be more lenient about untyped numeric literals Mar 1, 2023
@ianlancetaylor ianlancetaylor removed LanguageChange v2 A language change or incompatible library change labels Mar 1, 2023
@willfaught
Copy link
Contributor

int, float32, and float64 would all be valid answers

I don't follow why float32 would be a contender.

If I write 1 + 2.0 it gets typed float64 so I would expect the same type for T.

Agreed. Assuming int and float64 for Add(1, 1.2) made sense before generics when Add was:

func Add(x, y interface{}) {...}

because each value could be evaluated in isolation. Now, with type parameters, with the generic Add, the arguments' types are linked, so one must affect the other. It's the same in 1 + 1.2, where the spec requires the operands to have the same type, which is equivalent to the generic function Add. See how Haskell handles apparent mismatched types from different literal kinds in its type checking.

@griesemer griesemer self-assigned this May 4, 2023
@griesemer
Copy link
Contributor

griesemer commented May 4, 2023

I thought a bit more about this and I retract my earlier comment.

I believe @jimmyfrasche is correct that if two arguments of the same type parameter type get assigned different untyped values, an analogue rule should apply as for a constant operation between these two untyped values.

Specifically, the proposal is:

If numeric untyped arguments of different kinds are passed to function parameters of the same (as of yet uninferred) type parameter type, the (combined) argument's kind is the one that appears later in this list: integer, rune, floating-point, complex. The inferred type for the type parameter is the respective default type for that kind.

Example:

func g[P any](...P) P { var x P; return x }

func _() {
	var (
		_ int        = g(1, 2)
		_ rune       = g(1, 'a')
		_ float64    = g(1, 'a', 2.3)
		_ float64    = g('a', 2.3)
		_ complex128 = g(2.3, 'a', 1i)
	)
	g(true, 'a' /* ERROR "mismatched types untyped bool and untyped rune (cannot infer P)" */)
	g(1, "foo" /* ERROR "mismatched types untyped int and untyped string (cannot infer P)" */)
	g(1, 2.3, "bar" /* ERROR "mismatched types untyped float and untyped string (cannot infer P)" */)
}

This is unambiguous, fits nicely with the existing rules for constants (as @jimmyfrasche has pointed out before) and thus feels "natural" for Go. It is fully backward-compatible: existing code will continue to run, and some code that wasn't accepted before is now accepted. It removes possibly annoying type inference errors. As an aside, it is about as complex to implement as what we do now.

I believe we should do this.

@gopherbot
Copy link

Change https://go.dev/cl/492835 mentions this issue: go/types, types2: infer minimum default type for untyped arguments

@ianlancetaylor
Copy link
Contributor

I think cmp.Min, as proposed in #59488, is an argument in favor of this. When build tags are used to give constants different values for different configurations, it becomes reasonable to write cmp.Min(a, b) for untyped constants a and b. We would expect that to work if a and b are arbitrary numeric constants. We would not expected it to fail for const a = 0 and const b = 1.5. Making type inference work for untyped constants is similar to how we can already write a + b for this example.

gopherbot pushed a commit that referenced this issue May 8, 2023
This implements the proposal #58671.
Must be explicitly enabled and requires proposal approval.

For #58671.

Change-Id: I150e78f4f3282d6b7cf9d90feeb5f1c5a36d8c38
Reviewed-on: https://go-review.googlesource.com/c/go/+/492835
Auto-Submit: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Robert Griesemer <gri@google.com>
@rsc
Copy link
Contributor

rsc commented May 10, 2023

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@griesemer griesemer changed the title proposal: type inference should be more lenient about untyped numeric literals proposal: spec: type inference should be more lenient about untyped numeric literals May 10, 2023
@rsc
Copy link
Contributor

rsc commented May 17, 2023

Based on the discussion above, this proposal seems like a likely accept.
— rsc for the proposal review group

@gopherbot
Copy link

Change https://go.dev/cl/495955 mentions this issue: cmd/compile: more lenient type inference for untyped arguments

@gopherbot
Copy link

Change https://go.dev/cl/496035 mentions this issue: cmd/compile: permanently enable more lenient type inference for untyped arguments

gopherbot pushed a commit that referenced this issue May 18, 2023
This enables the implementation for proposal #58671, which is
a likely accept. By enabling it early we get a bit extra soak
time for this feature. The change can be reverted trivially, if
need be.

For #58671.

Change-Id: Id6c27515e45ff79f4f1d2fc1706f3f672ccdd1ab
Reviewed-on: https://go-review.googlesource.com/c/go/+/495955
Run-TryBot: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
gopherbot pushed a commit that referenced this issue May 19, 2023
…r go1.21

This CL permanently enables the new behavior for -lang=go1.21 and
newer, and keeps the existing behavior if -lang=go1.20 or older.

To be submitted once #58671 is accepted.

For #58671.

Change-Id: I83a1d393f0ce7871be8f38ec35742d393946c55f
Reviewed-on: https://go-review.googlesource.com/c/go/+/496035
Run-TryBot: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
@rsc
Copy link
Contributor

rsc commented May 24, 2023

No change in consensus, so accepted. 🎉
This issue now tracks the work of implementing the proposal.
— rsc for the proposal review group

@rsc rsc changed the title proposal: spec: type inference should be more lenient about untyped numeric literals spec: type inference should be more lenient about untyped numeric literals May 24, 2023
@rsc rsc removed this from the Proposal milestone May 24, 2023
@rsc rsc added this to the Backlog milestone May 24, 2023
@griesemer
Copy link
Contributor

This has been implemented and submitted. Leaving this open to track documentation in the spec.

@griesemer griesemer modified the milestones: Backlog, Go1.21 May 25, 2023
@gopherbot
Copy link

Change https://go.dev/cl/499282 mentions this issue: doc/go1.21: document type inference changes

gopherbot pushed a commit that referenced this issue May 31, 2023
For #39661.
For #41176.
For #51593.
For #52397.
For #57192.
For #58645.
For #58650.
For #58671.
For #59338.
For #59750.
For #60353.

Change-Id: Ib731c9f2879beb541f44cb10e40c36a8677d3ad4
Reviewed-on: https://go-review.googlesource.com/c/go/+/499282
TryBot-Bypass: Robert Griesemer <gri@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
@gopherbot
Copy link

Change https://go.dev/cl/499997 mentions this issue: go/types, types2: remove version check for more lenient constant handling in inference

gopherbot pushed a commit that referenced this issue Jun 2, 2023
…ling in inference

For #58671.
Fixes #60566.

Change-Id: I746f99cdfd44b204dc90350fcfb3867e9b8b1da8
Reviewed-on: https://go-review.googlesource.com/c/go/+/499997
Run-TryBot: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
@griesemer
Copy link
Contributor

This is now described in the new section on type inference in the spec, per CL 503920.
Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
generics Issue is related to generics Proposal Proposal-Accepted TypeInference Issue is related to generic type inference
Projects
Status: Accepted
Development

No branches or pull requests

6 participants