Skip to content

go/types, types2: document predicates on generic types #50887

Closed
@findleyr

Description

@findleyr

Right now, go/types has limited functionality for computing predicates "modulo type parameters": signatures are considered identical modulo renaming of type parameters in their type parameter list:

func F[A ~int](A) {}
func G[B ~int](B) {}
func H[C int](C) {}

type T[D ~int] func(D)

In this example, the types of F and G are considered identical, because they have pairwise identical type parameter constraints and their signatures are identical after substituting type parameters. However, the signature of F is NOT identical to the signature of H (constraints don't match), and NOT identical to the underlying type of T (the signature itself does not have a type parameter list -- it uses type parameters from the declaration for T).

CL 379540 was intended to clean up some logic that existed to work around an earlier limitation that we didn't have instantiated methods. However, it inadvertently removed a "feature" that APIs like AssignableTo would somewhat work with uninstantiated types:

We ran into this example in kythe:

type Interface[T any] interface {
        Accept(T)
}

type Container[T any] struct {
        Element T
}

func (c Container[T]) Accept(t T) { c.Element = t }

Previously, we would report that Container is assignable to Interface (AssignableTo(Container, Interface) == true), because they would unify. However, this could be wrong, as in the following example:

type InterfaceInt[T ~int] interface {
        Accept(T)
}

type ContainerString[T ~string] struct {
        Element T
}

func (c ContainerString[T]) Accept(t T) { c.Element = t }

We would also have AssignableTo(ContainerString, InterfaceInt) == true, even though there is no instantiation for which this holds.

Related:

Alternatively, callers could "instantiate" the types with identical arguments, but then the burden is pushed onto the caller to find type arguments that satisfy both type parameter lists, which is arbitrarily complicated. (EDIT: this wouldn't even really work: type parameters could be in arbitrary order, and so for the caller to even know which type parameters should correspond, they'd have to do the unification themselves).

I don't believe this affects type-checking, but matters for the API and for tools. We need to reconsider what is most useful here.

My initial thoughts are as follows: rather than what we currently do for function signatures, generalize to a limited form of unification in Identical, where type parameters are joined and then substituted in constraints.

This runs into a problem of "free type parameters"

func _[A *C, B *C, C any]() {
  var X func(A)
  var Y func(B)
}

type I[P *Q, Q any] interface{
  m(P)
}

The type of X and Y should not be identical. What about the type of X and the signature of I.m? (EDIT: X and Y do NOT have identical types).

CC @griesemer @mdempsky

Metadata

Metadata

Assignees

No one assigned

    Labels

    DocumentationIssues describing a change to documentation.FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions