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

cmd/go2go: poor diagnostics for type-lists of interface types used as type constraints #39711

Closed
bcmills opened this issue Jun 19, 2020 · 7 comments

Comments

@bcmills
Copy link
Contributor

bcmills commented Jun 19, 2020

I was experimenting a bit with type-lists as type constraints, since a type-list containing only interface types is conceptually itself an interface.

The program in https://go2goplay.golang.org/p/oobylbuDcfp does not produce an error today. Should it?

The program in https://go2goplay.golang.org/p/Y2yzWbOOS9t produces a misleading T is not an interface error (even though T is necessarily an interface).

The program in https://go2goplay.golang.org/p/82ff4-1eW5- produces a spurious duplicate type … in type list error.

(CC @ianlancetaylor @griesemer)

@griesemer
Copy link
Contributor

package main

import (
	"fmt"
)

type EmptyInterface interface {
	type interface{}
}

func Return(type T EmptyInterface, T2 T)(x T2) T2 {
	return x
}

func main() {
	fmt.Println(Return(interface{}, interface{})(0))
}

is valid.

Return is instantiated with two empty interfaces as types. First, the type parameters T and T2 are substituted leading to the type parameter signature (type interface{} EmptyInterface, interface{} interface{}). Then we check constraints: interface{} is a type that's in the type list of EmptyInterface, and the 2nd interface{} implements its constraint which is interface{}. So the instantiation is valid. Now we check the argument passing: x is of type interface{}, and we can assign a 0 to it. The result is of type interface{} and contains the int value 0. LGTM!

@griesemer
Copy link
Contributor

griesemer commented Jun 19, 2020

package main

import (
	"fmt"
	"syscall"
)

type Stringy interface {
	type fmt.Stringer, error
}

func Return(type T Stringy, T2 T)(x T2) T2 {
	return x
}

func main() {
	fmt.Println(Return(error, syscall.Errno)(syscall.ENOSYS))
}

is valid and the error is incorrect.

Return is instantiated with T = error, and T2 = syscall.Errno. Then we check constraints: error must satisfy Stringy which it does because there's no methods but error is in the type list. syscall.Errno must satisfy error (because T was substituted with error), which it does because syscall.Errno implements error.

Edited: Upon further reflection (and looking at the implementation), I believe this program should be considered incorrect: A function is always type-checked independent of instantiation (it may be exported and we don't know where it is used). For Return to be correct, the constraint for T2 must be an interface, but it is type parameter T which may or may not be an interface. Now in this case, the constraint for T is Stringy which enumerates two interfaces in its type list. Therefore one could argue that T is known to be an interface, eventually. But that seems really pushing it. In any case, the current prototype would require some significant changes (constraints are expected to be interfaces early on).

2nd edit: It's more complicated than that: Sometimes this actually works, e.g. in this case:

func _(type A interface{ type interface{} }, B A)()

So there are some open issues here.

I'd say the result here is a question: Should this be permitted or not? I suggest that it be left out; it could always be added if it were important.

@griesemer
Copy link
Contributor

The last program

package main

import (
	"fmt"
	"syscall"
)

type Stringy interface {
	type interface{ Error() string }, interface{ String() string }
}

func Return(type T Stringy, T2 T)(x T2) T2 {
	return x
}

func main() {
	fmt.Println(Return(error, syscall.Errno)(syscall.ENOSYS))
}

is valid (same reasons as before) and the error message is incorrect (the reason is that the interface are compared before they are "completed" and then they look the same).

@griesemer
Copy link
Contributor

In summary, the last two programs shouldn't produce an error, but the first one is correct (and actually runs).

@griesemer
Copy link
Contributor

Correction: The first program is correct, the 2nd one (likely) incorrect per the current design (see edited comment above), and the last program is correct and shouldn't report an error.

@gopherbot
Copy link

Change https://golang.org/cl/239158 mentions this issue: [dev.go2go] go/types: check for duplicate types after interfaces were completed

gopherbot pushed a commit that referenced this issue Jun 20, 2020
… completed

Fixes #39711.

Change-Id: Id0efb854a42bd88a66be8165fa3444e33d1c8f8d
Reviewed-on: https://go-review.googlesource.com/c/go/+/239158
Reviewed-by: Robert Griesemer <gri@golang.org>
@griesemer
Copy link
Contributor

Fixed error for last program on dev.go2go. Filed separate issue #39723 for 2nd program. Closing.

@golang golang locked and limited conversation to collaborators Jun 20, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants