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

proposal: go/types: adopt "defined type" terminology consistently #65855

Open
adonovan opened this issue Feb 21, 2024 · 7 comments
Open

proposal: go/types: adopt "defined type" terminology consistently #65855

adonovan opened this issue Feb 21, 2024 · 7 comments
Assignees
Labels
Milestone

Comments

@adonovan
Copy link
Member

The spec uses the term "defined type" for a type created by a declaration such as type T int; a defined type is capable of bearing declared methods. Historically, this was called a Named type, but the advent of type aliases made this confusing and ambiguous.

Though I was initially lukewarm about the term "defined type" (aren't all types defined?), recent work auditing dense type-checking code in x/tools as made me appreciate it, because there are now three kinds of "named" types:

  • defined types (*Named)
  • type parameters (*TypeParam, go1.18), and
  • type aliases (*Alias in go1.22).

In some cases, their namedness is a meaningful commonality that can be used by tooling. For example, they all have an associated TypeName symbol that provides their location and logical name for use in a tool's UI.

I propose that we follow through with the renaming, using the name "Named" as little as possible. Obviously we can't get rid of Named, or make it an alias for Defined (as its reflect.Type.String would change), but we can make Defined an alias for it:

// A Defined type is a type created by a declaration such as `type T underlying`,
// capable of bearing method declarations.
//
// Along with TypeParam and Alias, it is one of three types that are named
// and have an associated TypeName symbol.
// Named is a historical and deprecated term for Defined.
type Defined = types.Named
func NewDefined(obj *TypeName, underlying Type, methods []*Func) *Defined

// Named is the historical term for a Defined type.
// Use Defined in new code.
type Named struct { ... }

// Deprecated: use NewDefined.
func NewNamed( ... ) { return NewDefined ... }
@gopherbot gopherbot added this to the Proposal milestone Feb 21, 2024
@findleyr
Copy link
Contributor

Interesting. I think we've long considered "Named" to have been a misnomer, but not considered changing it. FWIW, I'm not sure that compatibility prohibits Named from being an alias for Defined. Also, predeclared basic types are also named (https://go.dev/ref/spec#Types).

I guess the question is whether this is worth trying to change at this point, or whether it is simpler to continue to live with Named. I think there are many things we would change in the go/types API if we could, and such a superficial change may not be worth the churn.

@zigo101
Copy link

zigo101 commented Feb 23, 2024

It looks the latest spec has almost cleared the confusions. Named types include predeclared types, defined types (including ordinary and generic ones), and type parameters. An instantiated type is an ordinary defined type (so also a named type). An alias to an unnamed composite type is not a named type.

@findleyr findleyr assigned timothy-king and unassigned findleyr Aug 6, 2024
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/604476 mentions this issue: go/types: clarify Named, Alias, TypeName, Object

@griesemer
Copy link
Contributor

As an aside, in the past, we did consider introducing

type Defined = Named

but we also concluded that it would only add more confusion (I'm pretty sure @findleyr and I discussed this). Maybe that's not the case anymore, now that "named types" are essentially types with names (but for alias types that denote type literals).

On the other hand, as you say, Defined type is also not a great term (we don't have "undefined" types). In retrospect, the correct term probably should have been "nominal type" from the start because they behave like in a nominal type system (https://en.wikipedia.org/wiki/Nominal_type_system). And, "nominal" nicely refers to the name of the type but also implies a certain behavior. In fact we have nominal (defined) and structural types (type literals) in Go. That said, I don't know that we/the community can stomach another terminology change just for the sake of being clearer in the spec.

@atdiar
Copy link

atdiar commented Aug 13, 2024

Perhaps that we could even go a bit further.
Even nominal seem to imply that the name is part of the set of constraints wrt type identity.

It's not really true for interfaces, which behave structurally. Perhaps that it could even be reconciliated with aliases?
An interface type definition would define one of many aliases for that type.

Only non-interface type definitions would be able to define a name for a type.

A type name is almost a unique identifier for that type, at least within a compilation unit, if we don't take into account types defined within function scopes.

Obviously, interface types are identified differently, disregarding where they are defined.

A type thus being a "kind" of interface.

Just thinking.

@griesemer
Copy link
Contributor

griesemer commented Aug 13, 2024

@atdiar A defined interface type (a "nominal interface type") still behaves differently from an interface literal (a "structural interface"): two otherwise identical signatures are different types if one of them contains a defined interface type and the other contains an interface literal (link).

In other words, "nominal" is in fact exactly the right word because the name does matter wrt to type identity.

Even non-interface types allow assignment of a value of structural type to a variable of nominal type if the underlying types are identical.

There's a (different) proposal that would make that difference go away for interfaces, but that's somewhat orthogonal. What you are suggesting is interesting but I am not sure we can make this change in a backward-compatible way. Rather than say that a type definition of an interface produces one of many aliases, I think it would be cleaner to say that the RHS of a type definition cannot be an interface. One can only define alias types for interfaces.

@atdiar
Copy link

atdiar commented Aug 13, 2024

Oh, you're right. That wouldn't be backward compatible because type assertions/switches. That would require the version-based migration process.

Perhaps is it semi-nominal only still? Because variable assignability seem to not mind about the interface name?

https://go.dev/play/p/GYnd66YcmWz

The composite non interface types seem to be strict about the type definition of each of their arguments indeed.
In return position, doesn't seem that the name matters.

Interesting. (that's not a huge issue, it doesn't make the code wrong, just pondering)

gopherbot pushed a commit that referenced this issue Sep 20, 2024
Updates #65855
Updates #66890

Change-Id: I167c9de818049cae02f0d99f8e0fb4017e07bea9
Reviewed-on: https://go-review.googlesource.com/c/go/+/604476
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

7 participants