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/compile: type constraint containing any used directly as type #68710

Open
godcong opened this issue Aug 2, 2024 · 16 comments
Open

cmd/compile: type constraint containing any used directly as type #68710

godcong opened this issue Aug 2, 2024 · 16 comments
Assignees
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. ExpertNeeded NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@godcong
Copy link

godcong commented Aug 2, 2024

Proposal Details

type AnyType interface {
	any | []uint8
}

type KeySet struct {
	Keys []AnyType 
}

some issue content about this:
golang-jwt/jwt#401

The defined generic type AnyType can be used directly as a type and is compiled.

If you delete any, you will be prompted with

cannot use type AnyType outside a type constraint: interface contains type constraints

I haven't found anything about it, so I don't know if it's intentional or not.

If so, could you provide some relevant information?

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Aug 2, 2024
@seankhliao seankhliao changed the title cmd/compile: can compilation use any to bypass go's generic checking? cmd/compile: type constraint containing any used directly as type Aug 2, 2024
@prattmic
Copy link
Member

prattmic commented Aug 2, 2024

cc @griesemer

@prattmic
Copy link
Member

prattmic commented Aug 2, 2024

Simple case: https://go.dev/play/p/t8l45ebTndw

package main

import (
	"fmt"
)

type Foo interface {
	any
}

func main() {
	var f Foo = 42
	fmt.Println(f)
}

It seems that interface{ any } is treated the same as interface{}. I am also unsure whether this is intended or a bug.

@mateusz834
Copy link
Member

I think that interface{ any } being the same as interface{} makes sense, same as interface{io.Reader} is the same as io.Reader.

@prattmic
Copy link
Member

prattmic commented Aug 2, 2024

@mateusz834 Good point, thanks. With a single interface this is clearly an embedded interface.

Then the remaining question is with the original example, where any | []uint8 is effectively "simplified" to any, which then allows embedding.

@atdiar
Copy link

atdiar commented Aug 2, 2024

I think we might want a different definition of type identity for basic interface as opposed to union ones.

If we ever end up with union implemented as interfaces this will simplify discrimination.

In that sense, the union here should not be usable as a type yet, just like any union we have nowadays. (because crypto.PublicKey is a defined interface type and the union with the slice type creates a full-blown 'union' interface type.

That should be relevant when embedding union interface types.
The "union" part remains part of the type identity.

Probably a bug. (but something to think over)

@mateusz834
Copy link
Member

This might make #57644 unintuitive.

Consider:

type Foo interface {
	any | int
}

type Bar interface {
	Foo | string
}

func main() {
	var _ Bar = struct{}{}
}

@cuonglm
Copy link
Member

cuonglm commented Aug 2, 2024

Then the remaining question is with the original example, where any | []uint8 is effectively "simplified" to any, which then allows embedding.

IIUC, this is the right behavior because:

type AnyType interface {
	any | []uint8
}

is a Basic Interface. While:

type AnyType interface {
	[]uint8
}

is not, since its type sets can not be defined entirely by a list of methods.

@atdiar
Copy link

atdiar commented Aug 2, 2024

To add, the clearer semantics would be that Bar accepts either interface values or string values. It's definitely weird to say that, given our current definition of type set and since interfaces have a dynamic type which is never an interface.

But, if a is a regular type, it implements itself. Where it's unclear nowadays is whether a implements a | b because a | b would be an interface and that would come with implementing operations that only interface types have (assertions, comparisons : a might not be comparable etc)
In theory it does, so we would have to think about it.

That's the type identity part.

So I guess we might need to wrap union typed values before assigning them. (no need, the issue is simply about the definition of unions and disjointness, having to explicitly mention the intersection as a separate case)

@Merovius
Copy link
Contributor

Merovius commented Aug 2, 2024

I'll note that any (or rather, empty interfaces) is the only interface this works with. IMO this is pretty clearly a bug.

IIUC, this is the right behavior because: […] is a Basic Interface.

I don't think the spec is all that clear about whether it is actually a basic interface. It says

In its most basic form an interface specifies a (possibly empty) list of methods. The type set defined by such an interface is the set of types which implement all of those methods, and the corresponding method set consists exactly of the methods specified by the interface. Interfaces whose type sets can be defined entirely by a list of methods are called basic interfaces.

Now, on the one hand, the type set of any | []uint8 is "all types", which can be defined by the empty "list of methods", so sure, strictly speaking it fulfills the definition. But ISTM the intent here is a syntactical one, i.e. "if it is an interface which neither embeds another interface nor contains any other type elements, except listing methods". The section headings of "Basic interfaces", "Embedded interfaces" and "General interfaces" also make that clear.

I just can't imagine anyone thinking it is intended for any | []uint8 to be interpreted the way it is.

@zigo101
Copy link

zigo101 commented Aug 3, 2024

type AnyType interface {
	any | []uint8
}

is equivalent to and automatically simplified by compiler as

type AnyType interface {
	any
}

So, it is a basic interface type. Both spec and implementation have no problems here.

@atdiar
Copy link

atdiar commented Aug 3, 2024

@zigo101 that's arguable. The interface is defined as a set of methods that is empty AND a union of terms.

any | uint8 is equivalent to either any other type that is not uint8 OR uint8 only OR uint8 and any (which is still uint8 in terms of constraints).

So the union might not be simplifiable?
In that case it would not be a basic interface.

@cuonglm
Copy link
Member

cuonglm commented Aug 3, 2024

@zigo101 that's arguable. The interface is defined as a set of methods that is empty AND a union of terms.

any | uint8 is equivalent to either any other type that is not uint8 OR uint8 only OR uint8 and any (which is still uint8 in terms of constraints).

So the union might not be simplifiable?
In that case it would not be a basic interface.

any denotes all non-interface type, including uint8. So any | <anything> | ... is just any.

@atdiar
Copy link

atdiar commented Aug 3, 2024

@cuonglm I understand what you mean. But in terms of sets, the union is decomposable in disjoint sets.
That might be the definition that we need when/if interface types are involved.

Otherwise, that would mean that we could consider the type set of the union term {[]uint8} to be empty/uninhabited since all types satisfy any. (to be understood as it not bringing anything to the union, being a redundant term).
That would make terms of a union non commutable.

[edit] It's perhaps easier to see that it is not true if we replace the interface by a union where each term is a distinct element of the type set: we'd get after simplification: {any\{[]uint8}} ∪ {[]uint8}

The decomposition in disjoint sets should be preferable.

@godcong
Copy link
Author

godcong commented Aug 7, 2024

type AnyType interface {
	any | []uint8
}

One question.
Because of the use of |, it is possible to identify the behavior as a type definition.
At this point, should any be a union?

So this any, then, should be recognized as an

type AnyType interface {
	interface{ /* nothing */ } | []uint8
}

Or should be recognized as

type AnyType interface {
	interface { /* all types */ } | []uint8
}

Should this any not be recognized as an interface type.
If you want to add an interface type, I think it should be in the following format:

type AnyType interface {
	interface{ /* types all or nothing? */ } | []uint8
	any // interface type
}

@dr2chase dr2chase added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Aug 7, 2024
@mknyszek mknyszek added this to the Backlog milestone Aug 7, 2024
@mknyszek
Copy link
Contributor

mknyszek commented Aug 7, 2024

Just since I don't think this has been stated yet, this is not new behavior -- the behavior described by OP appears to be present in Go 1.21+.

@zigo101
Copy link

zigo101 commented Aug 8, 2024

It has been present since Go 1.18.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. ExpertNeeded NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
Development

No branches or pull requests