blob: d9a5c6fc3b586f29dc2831226df55cc6e76a0cde [file] [log] [blame]
This file serves as a notebook/implementation log.
----------------------------------------------------------------------------------------------------
TODO (implementation issues)
- report a better error when a type is not in a type list (investigate)
- better error message when we need parentheses around a parameterized function parameter type
- review handling of fields of instantiated generic types (do we need to make them non-parameterized?)
- use []*TypeParam for tparams in subst? (unclear)
- should we use nil instead of &emptyInterface for no type bounds (as an optimization)?
- TBD: in prose, should we use "generic" or "parameterized" (try to be consistent)
----------------------------------------------------------------------------------------------------
KNOWN ISSUES
- type parameter constraints are ignored when checking if a parameterized method implements the
matching method in an interface
- iteration over generic variables doesn't report certain channel errors (see TODOs in code)
- cannot handle mutually recursive parameterized interfaces using themselves as type bounds
example: type B(type P B(P)) interface{ m() } (need to delay all checking after seting up declarations)
- invoking a method of a parameterized embedded type doesn't work (cannot properly determine receiver yet)
- pointer designation is incorrectly handled when checking type list constraint satisfaction
----------------------------------------------------------------------------------------------------
OBSERVATIONS
- 2/20/2020: Because we permit parenthesized types anywhere for consistency, also in parameter lists (mea
culpa), we have parsing ambiguities when using instantiated types in parameter lists w/o argument names.
We could disallow the use of parentheses at the top level of type literals and then we might not have
this problem. This is not a backward-compatible change but perhaps worthwhile investigating. Specifically,
will this always work (look specifically at channel types where we need parentheses for disambiguation
and possibly function types).
- 6/3/2020: Observation: gofmt already removes superflous parentheses around types in parameter lists,
so in gofmt'ed code there won't be any such parentheses. Thus we can perhaps make the suggested language
change above without too many problems.
- 2/21/2020: We do need top-level parentheses around types in certain situations such as conversions
or composite literals. We could disallow parentheses around types in parameter lists only, but that
seems quite a bit less elegant.
- 6/13/2020: When comparing against the type list of an interface (to see if the interface is satisfied),
we must either recompute the underlying types of the type list entries after the interface was instantiated,
or we must compute the underlying type of each entry before comparing. (A type list may contain type
parameters which may be substituted with defined types when the interface is instantiated.) With the
latter approach (which is what's implemented now), we could relax some of the constraints that we have
on type lists entries: We could allow any type and just say that for interface satisfaction we always
look at the underlying types.
----------------------------------------------------------------------------------------------------
OPEN QUESTIONS
- Parsing _ = [](a(int)){} requires parentheses around `(a(int))` - should the parser be smarter in
these cases? Another example: []a(b, c){} This cannot be a conversion. Could fix such cases by re-
associating the AST when we see a {. Need to be careful, and need to take into account additional
complexity of spec.
- For len/cap(x) where x is of type parameter type and the bound contains arrays only, should the
result be a constant? (right now it is not). What are the implications for alternative, non-
monomorphizing implementation methods?
- Confirm that it's ok to use inference in missingMethod to compare parameterized methods.
----------------------------------------------------------------------------------------------------
DESIGN/IMPLEMENTATION
- 11/19/2019: For type parameters with interface bounds to work, the scope of all type parameters in
a type parameter list starts at the "type" keyword. This makes all type parameters visible for all
type parameter bounds (interfaces that may be parameterized with the type parameters).
- 12/4/2019: do not allow parenthesized generic uninstantiated types (unless instantiated implicitly)
In other words: generic types must always be instantiated before they can be used in any form
More generally: Only permit type instantiation T(x) in type context, when the type is a named type.
Do not permit it in general in type context: e.g., disallow []T(x) because we consider that a
conversion, in general. Same for ([]T)(x).
- 12/12/2019: represent type bounds always as (possibly unnamed) interfaces
(contracts are user syntactic sugar)
- 12/19/2019: Type parameters don't act like type aliases. For instance:
func f(type T1, T2)(x T1) T2 { return x }
is not valid, no matter how T1 and T2 are instantiated (but if T1 and T2 were type aliases with
both of them having type int, the return x would be valid). In fact, the type parameters act more
like named types with the methods described by their type bound. But type parameters are never
interfaces. To determine: Given a type parameter P, is P == underlying(P) (like for basic types),
or is the the underlying type of P something else (like for defined types). Is there an observable
difference?
- 12/19/2019: Rewrote contract handling: they are now treated as Objects (rather than Types) throughout.
- 12/20/2019: Decided to start moving type parameters to types (from TypeName to Named), need to do the
same for Func. This make more sense as in general any type (conceptually even literal types) could
have type parameters. It's a property of the type, not the type name. It also simplified the code.
- 12/20/2019: Type parameters may be part of type lists in contracts/interfaces. It just falls out
naturally. Added test cases.
- 12/23/2019: Decision: Type parameters and ordinary (value) parameters are in the same block, notably
the function block. The scope of type parameters starts at the 'type' keyword; the scope of ordinary
parameters starts with the (opening '{' of the) function body. Both scopes end with the closing '}'
of the function body (i.e., the end of the function block).
- 1/2/2020: Implementation decision: contracts can only be declared at the package level.
- 1/6/2020: Experimental: First steps towards permitting type parameters in methods as a generalization.
Type-checking problems ooccurring from this are likely to highlight general problematic areas.
First consequence: Scope of type parameters starts at "func" keyword which means that receiver type
name cannot be a type parameter name declared later (or by the receiver type specification). This
seems reasonable and should help avoid confusion which is possible otherwise.
- 1/7/2020: We distinguish embedded instantiated (parameterized) interfaces from methods by enclosing
the embedded interfaces in parentheses (the design draft recommends this change). Since this opens
the possibility for any parenthesized type (that is an interface), we can also allow (parenthesized)
interface literals as it is simpler to permit those than forbid them.
- 1/7/2020: The current implementation permits empty type parameter lists as in: "func f(type)(x int)"
but we cannot call such a function as "f()(1)"; the empty type argument list causes problems.
Document that we allow empty type parameter list declarations, but not empty actual type parameter
lists. (We could allow them for types, but that doesn't seem consistent and probably is confusing).
- 2/19/2020: We accept parenthesized embedded struct fields so we can distinguish between a named
field with a parenthesized type foo (T) and an embedded parameterized type (foo(T)), similarly
to interace embedding.
- 2/19/2020: Permit parentheses around embedded contracts for symmetry with embedding in structs
and interfaces.
- 2/20/2020: Receiver type parameters must always be provided in the receiver parameter list of
a method, even if they are not used by the method. Since the receiver acts like an implicit
declaration of those type parameters, they may be blank, as with any other declaration.
- 3/20/2020: Local type declarations with an underlying type that is a type parameter lose the
methods declared with the type parameter bound. But they don't lose the properties of the
underlying type, i.e., the properties of the type parameter bound's type list.
This is something to consider if we were contemplating moving to a methods-only approach
(no type lists), even though local type declarations are exceedingly rare if they exist at
all in the wild.
- 3/24/2020: Implemented initial support for bidirection type unification which could make
type inference more powerful if we decided to go that route. Specifically, this would
permit type inference for the type parameters of a generic function argument. Given:
func h(f func(int)); func g(type T)(T); one could allow the call: h(g) and the type argument
T of g would be inferred to be int. While not hard to implement, this would be a special case
of the rule that all generic types/functions must be instantiated before they are used except
for function calls where the type arguments can be inferred from the actual arguments.
Decided that for now we leave things as is, since it's not clear the extra complexity is
worth the (probably small) convenience.
- 3/25/2020: We can probably simplify the contract syntax again and only permit one of three
possible constraint entries: 1) an embedded contract, 2) a type parameter followed by a
method signature, and 3) a type parameter followed by a type list. This is what the type
checker currently supports and the printer can print. (The parser still accepts a list of
method signatures or types, freely mixed.)
- 4/24/2020: Permit omission of explicit type bound instantiation if the type bound is an
interface that applies to exactly one type parameter and the type bound expects exactly
one type argument. This makes parameterized interface type bounds easier to use in a common
case.
- 5/?/2020: Relaxed above syntactic sugar and permit omission of explicit type bound instantiation
in any case where the type bound only accepts a single type parameter.
We may not want to permit this in the first version as this is something we can always add later.
- 6/3/2020: A function type parameter acts like a named type. If its type bound has a type list
that type list determines the underlying type of the type parameter. If the bound doesn't have
a type list, the underlying type of a type parameter is itself. (We may decide that in this
latter case there may no underlying type at all which would perhaps more consistent. But this
is not clear cut.)
- 6/6/2020: The underlying type of a type parameter must _always_ be itself, otherwise a declaration
such as: type T(type P interface{type int}) P would have an underlying type int which is wrong.
- 6/7/2020: Introduced the notion of an operational type which is used when determining what
operations are supported by a value of a given type. The operational type of a type parameter
is determined by its type bound. The operational type of any other type is its underlying
type. This approach appears to complete the picture about the nature of type parameters.
- 6/7/2020: Removed support for contracts to match the latest design draft.
- 6/15/2020: Disabled check that types in type lists must only contain basic types or composite types
composed of basic types. It is not needed since we always must recompute the underlying types
after a (possible) instantiation anyway (see observation from 6/3/2020).