From: Robert Griesemer Date: Tue, 30 Nov 2021 17:29:45 +0000 (-0800) Subject: spec: add section on type inference X-Git-Tag: go1.18beta2~9 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=654d5f4b5dfc30167bbffd0d7aeba3c1e29277c8;p=gostls13.git spec: add section on type inference Change-Id: Ic338788d6410ed0d09ad129811377ee9ce5ed496 Reviewed-on: https://go-review.googlesource.com/c/go/+/367954 Trust: Robert Griesemer Reviewed-by: Ian Lance Taylor --- diff --git a/doc/go_spec.html b/doc/go_spec.html index b25cf5fa6e..c653cbffc0 100644 --- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -1,6 +1,6 @@ @@ -4128,8 +4128,8 @@ with the same underlying array.

Instantiations

-A parameterized function or type is instantiated by substituting -type arguments for the type parameters. +A parameterized function or type is instantiated by substituting type arguments +for the type parameters. Instantiation proceeds in two phases:

@@ -4201,6 +4201,385 @@ b := min[float64](2.0, 3) // b has value 2.0 of type float64 c := min(b, -1) // c has value -1.0 of type float64 +

Type inference

+ +

+Missing type arguments may be inferred by a series of steps, described below. +Each step attempts to use known information to infer additional type arguments. +Type inference stops as soon as all type arguments are known. +After type inference is complete, it is still necessary to substitute all type arguments +for type parameters and verify that each type argument implements the relevant constraint; +it is possible for an inferred type argument to fail to implement a constraint, in which +case instantiation fails. +

+ +

+Type inference is based on +

+ +
    +
  • + a type parameter list +
  • +
  • + a substitution map M initialized with the known type arguments, if any +
  • +
  • + a (possibly empty) list of ordinary function arguments (in case of a function call only) +
  • +
+ +

+and then proceeds with the following steps: +

+ +
    +
  1. + apply function argument type inference + to all typed ordinary function arguments +
  2. +
  3. + apply constraint type inference +
  4. +
  5. + apply function argument type inference to all untyped ordinary function arguments + using the default type for each of the untyped function arguments +
  6. +
  7. + apply constraint type inference +
  8. +
+ +

+If there are no ordinary or untyped function arguments, the respective steps are skipped. +Constraint type inference is skipped if the previous step didn't infer any new type arguments, +but it is run at least once if there are missing type arguments. +

+ +

+The substitution map M is carried through all steps, and each step may add entries to M. +The process stops as soon as M has a type argument for each type parameter or if an inference step fails. +If an inference step fails, or if M is still missing type arguments after the last step, type inference fails. +

+ +

Type unification

+ +

+Type inference is based on type unification. A single unification step +applies to a substitution map and two types, either +or both of which may be or contain type parameters. The substitution map tracks +the known (explicitly provided or already inferred) type arguments: the map +contains an entry PA for each type +parameter P and corresponding known type argument A. +During unification, known type arguments take the place of their corresponding type +parameters when comparing types. Unification is the process of finding substitution +map entries that make the two types equivalent. +

+ +

+For unification, two types that don't contain any type parameters from the current type +parameter list are equivalent +if they are identical, or if they are channel types that are identical ignoring channel +direction, or if their underlying types are equivalent. +

+ +

+Unification works by comparing the structure of pairs of types: their structure +disregarding type parameters must be identical, and types other than type parameters +must be equivalent. +A type parameter in one type may match any complete subtype in the other type; +each successful match causes an entry to be added to the substitution map. +If the structure differs, or types other than type parameters are not equivalent, +unification fails. +

+ + + +

+For example, if T1 and T2 are type parameters, +[]map[int]bool can be unified with any of the following: +

+ +
+[]map[int]bool   // types are identical
+T1               // adds T1 → []map[int]bool to substitution map
+[]T1             // adds T1 → map[int]bool to substitution map
+[]map[T1]T2      // adds T1 → int and T2 → bool to substitution map
+
+ +

+On the other hand, []map[int]bool cannot be unified with any of +

+ +
+int              // int is not a slice
+struct{}         // a struct is not a slice
+[]struct{}       // a struct is not a map
+[]map[T1]string  // map element types don't match
+
+ +

+As an exception to this general rule, because a defined type +D and a type literal L are never equivalent, +unification compares the underlying type of D with L instead. +For example, given the defined type +

+ +
+type Vector []float64
+
+ +

+and the type literal []E, unification compares []float64 with +[]E and adds an entry Efloat64 to +the substitution map. +

+ +

Function argument type inference

+ + + +

+Function argument type inference infers type arguments from function arguments: +if a function parameter is declared with a type T that uses +type parameters, +unifying the type of the corresponding +function argument with T may infer type arguments for the type +parameters used by T. +

+ +

+For instance, given the type-parameterized function +

+ +
+func scale[Number ~int64|~float64|~complex128](v []Number, s Number) []Number
+
+ +

+and the call +

+ +
+var vector []float64
+scaledVector := scale(vector, 42)
+
+ +

+the type argument for Number can be inferred from the function argument +vector by unifying the type of vector with the corresponding +parameter type: []float64 and []Number +match in structure and float64 matches with Number. +This adds the entry Numberfloat64 to the +substitution map. +Untyped arguments, such as the second function argument 42 here, are ignored +in the first round of function argument type inference and only considered if there are +unresolved type parameters left. +

+ +

+Function argument type inference can be used when the function has ordinary parameters +whose types are defined using the function's type parameters. Inference happens in two +separate phases; each phase operates on a specific list of (parameter, argument) pairs: +

+ +
    +
  1. + The list Lt contains all (parameter, argument) pairs where the parameter + type uses type parameters and where the function argument is typed. +
  2. +
  3. + The list Lu contains all remaining pairs where the parameter type is a single + type parameter. In this list, the respective function arguments are untyped. +
  4. +
+ +

+Any other (parameter, argument) pair is ignored. +

+ +

+By construction, the arguments of the pairs in Lu are untyped constants +(or the untyped boolean result of a comparison). And because default types +of untyped values are always predeclared non-composite types, they can never match against +a composite type, so it is sufficient to only consider parameter types that are single type +parameters. +

+ +

+Each list is processed in a separate phase: +

+ +
    +
  1. + In the first phase, the parameter and argument types of each pair in Lt + are unified. If unification succeeds for a pair, it may yield new entries that + are added to the substitution map M. If unification fails, type inference + fails. +
  2. +
  3. + The second phase considers the entries of list Lu. Type parameters for + which the type argument has already been determined are ignored in this phase. + For each remaining pair, the parameter type (which is a single type parameter) and + the default type of the corresponding untyped argument is + unified. If unification fails, type inference fails. +
  4. +
+ +

+Example: +

+ +
+func min[T constraints.Ordered](x, y T) T
+
+var x int
+min(x, 2.0)    // T is int, inferred from typed argument x; 2.0 is assignable to int
+min(1.0, 2.0)  // T is float64, inferred from default type for 1.0 and matches default type for 2.0
+min(1.0, 2)    // illegal: default type float64 (for 1.0) doesn't match default type int (for 2)
+
+ +

Constraint type inference

+ + + +

+Constraint type inference infers type arguments from already known +type arguments by considering structural type constraints: +if the structural type T of a structural constraint is parameterized, +unifying a known type argument with T may +infer type arguments for other type parameters used by the structural type. +

+ +

+For instance, consider the type parameter list with type parameters List and +Elem: +

+ +
+[List ~[]Elem, Elem any]
+
+ +

+Constraint type inference can deduce the type of Elem from the type argument +for List because Elem is a type parameter in the structural constraint +~[]Elem for List. +If the type argument is Bytes: +

+ +
+type Bytes []byte
+
+ +

+unifying the underlying type of Bytes with the structural constraint means +unifying []byte with []Elem. That unification succeeds and yields +the substitution map entry +Elembyte. +Thus, in this example, constraint type inference can infer the second type argument from the +first one. +

+ +

+Generally, constraint type inference proceeds in two phases: Starting with a given +substitution map M +

+ +
    +
  1. +For all type parameters with a structural constraint, unify the type parameter with the structural +type of its constraint. If any unification fails, constraint type inference fails. +
  2. + +
  3. +At this point, some entries in M may map type parameters to other +type parameters or to types containing type parameters. For each entry +PA in M where A is or +contains type parameters Q for which there exist entries +QB in M, substitute those +Q with the respective B in A. +Stop when no further substitution is possible. +
  4. +
+ +

+The result of constraint type inference is the final substitution map M from type +parameters P to type arguments A where no type parameter P +appears in any of the A. +

+ +

+For instance, given the type parameter list +

+ +
+[A any, B []C, C *A]
+
+ +

+and the single provided type argument int for type parameter A, +the initial substitution map M contains the entry Aint. +

+ +

+In the first phase, the type parameters B and C are unified +with the structural type of their respective constraints. This adds the entries +B[]C and C*A +to M. + +

+At this point there are two entries in M where the right-hand side +is or contains type parameters for which there exists other entries in M: +[]C and *A. +In the second phase, these type parameters are replaced with their respective +types. It doesn't matter in which order this happens. Starting with the state +of M after the first phase: +

+ +

+Aint, +B[]C, +C*A +

+ +

+Replace A on the right-hand side of → with int: +

+ +

+Aint, +B[]C, +C*int +

+ +

+Replace C on the right-hand side of → with *int: +

+ +

+Aint, +B[]*int, +C*int +

+ +

+At this point no further substitution is possible and the map is full. +Therefore, M represents the final map of type parameters +to type arguments for the given type parameter list. +

+

Operators