From df50582cf5cae88d064ce5b15ae3f33673a2475c Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sat, 7 Oct 2023 13:24:04 +0200 Subject: [PATCH 01/29] Partial type inference --- proposals/partial-type-inference.md | 743 ++++++++++++++++++++++++++++ 1 file changed, 743 insertions(+) create mode 100644 proposals/partial-type-inference.md diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md new file mode 100644 index 0000000000..b0b498fcaa --- /dev/null +++ b/proposals/partial-type-inference.md @@ -0,0 +1,743 @@ +# Partial type inference + +* [x] Proposed +* [ ] Prototype +* [ ] Implementation +* [ ] Specification + +> Note: This proposal was created because of championed [Partial type inference](https://github.com/dotnet/csharplang/issues/1349). It is a continuation of the proposed second version published in [csharplang/discussions/7467](https://github.com/dotnet/csharplang/discussions/7467) and the first version published in [csharplang/discussions/7286](https://github.com/dotnet/csharplang/discussions/7286) + +## Summary +[summary]: #summary + +Partial type inference introduces a syntax skipping obvious type arguments in the argument list of + +1. *invocation_expression* +2. *object_creation_expresssion* + +and allowing to specify just ambiguous ones. + +> Example of skipping obvious type arguments +> +> ```csharp +> M<_, object>(42, null); +> void M(T1 t1, T2 t2) { ... } +> ``` + +It also improves the type inference in the case of *object_creation_expression* by leveraging type bounds obtained from the target, *object_or_collection_initializer*, and *type_parameter_constraints_clauses*. + +> Example of inferring type arguments of type from the initializer list +> +> ```csharp +> using System.Collections.Generic; +> var statistics = new Dictionary<,>(){["Joe"] = 20}; +> ``` + +Besides the changes described above, the proposal mentions further interactions and possibilities to extend the partial type inference. + +## Motivation +[motivation]: #motivation + +* The current method type inference has two possible areas for improvement. + The first improvement regards the strength of the method type inference, which uses only arguments' types to deduce the method's type arguments. + Concretely, we can see the weakness in cases where type arguments only depend on target type or type parameter restrictions. + + > Example + > + > ```csharp + > object data = database.fetch(); + > int count = data.Field("count"); // Error, method type inference can't infer the return type. We have to specify the type argument. + > + > public static class Extensions + > { + > public static TReturn Field(this object inst, string fieldName) { ... } + > } + > ``` + > + > ```csharp + > test(new MyData()); // Error, method type inference can't infer T. We have to specify all type arguments. + > + > public void test(U data) where T : TestCaseDefault { ... } + > ``` + + The second improvement regards the "all or nothing" principle, where the method type inference infers either all of the type arguments or nothing. + + > Example + > ```csharp + > log<, object>(new Message( ... ), null); // Error, impossible to specify just one type argument. We have to specify all of them. + > + > public void log(T message, U appendix) { ... } + > ``` + + The first improvement, which would improve the method type inference algorithm, has a significant disadvantage of the breaking change. + On the other hand, the second improvement, which would enable specifying some of the method's type arguments, does not influence old code, solves problems regarding the "all or nothing" principle, and reduces the first weakness. + + > Example + > + > ```csharp + > test, _>(new MyData()); // We can use _ to mark type arguments which should be inferred by the compiler. + > + > public void test(U data) where T : TestCaseDefault { ... } + > ``` + > + > ```csharp + > log<_, object>(new Message( ... ), null); // We can use _ to mark type arguments which should be inferred by the compiler. + > + > public void log(T message, U appendix) { ... } + > ``` + +* The next motivation is constructor type inference. + Method type inference is not defined on *object_creation_expression*, prohibiting taking advantage of type inference. + We divide use cases into the following categories, where type inference would help the programmer. + + 1. Cases where the method type inference would success. + + > Example + > + > ```csharp + > var wrappedData = Create(new MyData()); + > + > public static Wrapper Create(T item) { return new Wrapper(item); } + > + > class Wrapper { public Wrapper(T item) { ... } } + > ``` + + 2. Cases where the method type inference would be weak. (Using type info from target type, or type arguments' constrains) + + > Example + > + > ```csharp + > var alg = Create(new MyData()); // Method type inference can't infer TLogger because it doesn't use type constrains specified by `where` clauses + > + > public static Algorithm Create(TData data) where TLogger : Logger { return new Algorithm(data); } + > class Algorithm where TLogger : Logger { public Algorithm(TData data) { ... }} + > ``` + + 3. Cases where the method type inference is not defined. (Using type info from initializers) + + > Example + > + > ```csharp + > var numbers = new List() {1,2,3,4}; // I would like to infer List type argument based on the initializer. + > ``` + + 4. Other cases + + > Example + > + > ```csharp + > new Dictionary() {[""] = null}; // The first type argument is unnecessary since we can deduce it from the initializer. + > ``` + + An existing solution can be seen in `Create()` method wrappers of constructors enabling a type inference through method type inference as you can se in the examples above. + However, we can't use it with *object_or_collection_initializer*; we are limited by method type inference strength, and it adds unnecessary boiler code. + + Adding constructor type inference as we will describe in the following section would solve above mentioned examples. + + > Example + > + > ```csharp + > var wrappedData = new Wrapper<>(new MyData); + > class Wrapper { public Wrapper(T item) { ... } } + > ``` + > + > ```csharp + > var alg = new Algorithm<>(new MyData()); + > var algWithSpecialLogger = new Algorithm<_ , SpecialLogger<_>>(new MyData()); + > + > class Algorithm where TLogger : Logger { public Algorithm(TData data) { ... }} + > ``` + > + > ```csharp + > var numbers = new List<>() {1,2,3,4}; + > ``` + > + > ```csharp + > new Dictionary<_, string>() {[""] = null}; + > ``` + +No matter how the partial type inference would work, we should be careful about the following things. + +- **Convenience** - We want an easy and intuitive syntax that we can skip the obvious type arguments. +- **Performance** - Type inference is a complicated problem when we introduce subtyping and overloading in a type system. +Although it can be done, the computation can take exponential time which we don't want. +So it has to be restricted to cases, where the problem can be solved effectively but it still has practical usage. +- **IDE** - Improvement of the type inference can complicate IDE hints during coding. +We should give the user clear and not overwhelming errors when there will be an error and try to provide info that helps him to fix it. +- **Extensions** - We don't want to make this change blocker for another potential feature in the future. +So will want to look ahead to other potential directions, which can be done after this feature. + +## Detailed design +[design]: #detailed-design + +### Grammar + +> Specification: Original section changed in the following way + +The following changes are made in [tokens](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#64-tokens) located in the [grammar](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#62-grammars) section. + +> [Identifiers](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#643-identifiers) + +- The semantics of an identifier named `_` depends on the context in which it appears: + - It can denote a named program element, such as a variable, class, or method, or + - It can denote a discard ([§9.2.9.1](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/variables.md#9291-discards)). + - **It can denote an inferred type argument avoiding specifying type arguments which can be inferred by the compiler.** + +> [Keywords](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#644-keywords) + +* A ***contextual keyword*** is an identifier-like sequence of characters that has special meaning in certain contexts, but is not reserved, and can be used as an identifier outside of those contexts as well as when prefaced by the `@` character. + + ```diff + contextual_keyword + : 'add' | 'alias' | 'ascending' | 'async' | 'await' + | 'by' | 'descending' | 'dynamic' | 'equals' | 'from' + | 'get' | 'global' | 'group' | 'into' | 'join' + | 'let' | 'nameof' | 'on' | 'orderby' | 'partial' + | 'remove' | 'select' | 'set' | 'unmanaged' | 'value' + + | 'var' | 'when' | 'where' | 'yield' | '_' + - | 'var' | 'when' | 'where' | 'yield' + ; + ``` + +### Type arguments + +> Specification: Original section changed in the following way + +* We change the meaning of the content of *type_argument_list* in two contexts. + * [Constructed types](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#84-constructed-types) occuring in [*object_creation_expression*](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#128162-object-creation-expressions) + * Constructed types and type arguments occuring in method [invocation](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12892-method-invocations) + +* ***inferred_type_argument*** represents an unknown type, which will be resolved during type inference. + +* `_` identifier is considered to represent *inferred_type_argument* when: + * It occurs in *type_argument_list* of a method group during method invocation. + * It occurs in *type_argument_list* of a type in *object_creation_expression*. + * It occurs as an arbitrary nested identifier in the expressions mentioned above. + > Example + > + > ```csharp + > F<_, int>( ... ); // _ represents an inferred type argument. + > new C<_, int>( ... ); // _ represents an inferred type argument. + > F, int>( ... ); // _ represents an inferred type argument. + > new C, int>( ... ); // _ represents an inferred type argument. + > C<_> temp = ...; // _ doesn't represent an inferred type argument. + > new _( ... ) // _ doesn't represent an inferred type argument. + > Container<_>.Method<_>(arg); // _ of Container<_> doesn't represent an inferred type argument. (Containing type's type argument won't be inferred) + > ``` + +* A method group and type are said to be *partial_inferred* if it contains at least one *inferred_type_argument*. + +* A type is said to be *generic_inferred* when all the following hold: + * It has an empty *type_argument_list*. + * It occurs as a *type* of *object_creation_expression*. + > Example + > + > ```csharp + > new C<>(...) // Valid code, C is generic_inferred. + > new C>(...) // Invalid code, C nor G are generic_inferred. + > F<>(...) // Invalid code, F isn't generic_inferred. + > ``` + +### Namespace and type names + +> Specification: Original section changed in the following way + +Determining the [meaning](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/basic-concepts.md#781-general) of a *namespace_or_type_name* is changed as follow. + +* If a type is a *generic_inferred*, then we resolve the identifier in the same manner except ignoring the arity of type parameters (Types of arity 0 is ignored). +If there is an ambiguity in the current scope, a compilation-time error occurs. + > Example + > + > ```csharp + > class P1 + > { + > void M() + > { + > new C1<>( ... ); // Refers generic_inferred type C1 + > new C2<>( ... ); // Refers generic_inferred type C2 + > } + > class C1 { ... } + > class C2 { ... } + > } + > class P2 + > { + > void M() + > { + > new C1<>( ... ); // Compile-time error occurs because of ambiguity between C1 and C1 + > } + > class C1 { ... } + > class C1 { ... } + > } + > ``` + +### Method invocations + +> Specification: Original section changed in the following way + +The binding-time processing of a [method invocation](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12892-method-invocations) of the form `M(A)`, where `M` is a method group (possibly including a *type_argument_list*), and `A` is an optional *argument_list* is changed in the following way. + +The initial set of candidate methods for is changed by adding new condition. + +- If `F` is non-generic, `F` is a candidate when: + - `M` has no type argument list, and + - `F` is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)). +- If `F` is generic and `M` has no type argument list, `F` is a candidate when: + - Type inference ([§12.6.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1263-type-inference)) succeeds, inferring a list of type arguments for the call, and + - Once the inferred type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)) +- **If `F` is generic and `M` has type argument list containing at least one *inferred_type_argument*, `F` is a candidate when:** + - **Type inference ([§12.6.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1263-type-inference)) succeeds, inferring a list of *inferred_type_arguments* for the call, and** + - **Once the *inferred_type_arguments* are inferred and together with remaining type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member))** +- If `F` is generic and `M` includes a type argument list, `F` is a candidate when: + - `F` has the same number of method type parameters as were supplied in the type argument list, and + - Once the type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)). + +### Object creation expressions + +> Specification: Original section changed in the following way + +The binding-time processing of an [*object_creation_expression*](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#128162-object-creation-expressions) of the form new `T(A)`, where `T` is a *class_type*, or a *value_type*, and `A` is an optional *argument_list*, is changed in the following way. + +> Note: Type inference of constructor is described later in the type inference section. + +The binding-time processing of an *object_creation_expression* of the form new `T(A)`, where `T` is a *class_type*, or a *value_type*, and `A` is an optional *argument_list*, consists of the following steps: + +- If `T` is a *value_type* and `A` is not present: + - **The *object_creation_expression* is a default constructor invocation.** + - **If the type is *generic_inferred* or *partially_inferred*, type inference of the default constructor occurs to determine the type arguments. If it succeeded, construct the type using inferred type arguments. If it failed and there is no chance to get the target type now or later, the binding-time error occurs. Otherwise, repeat the binding when the target type will be determined and add it to the inputs of type inference.** + - **If the type inference above succeeded or the type is not inferred, the result of the *object_creation_expression* is a value of (constructed) type `T`, namely the default value for `T` as defined in [§8.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#833-default-constructors).** +- Otherwise, if `T` is a *type_parameter* and `A` is not present: + - If no value type constraint or constructor constraint ([§15.2.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/classes.md#1525-type-parameter-constraints)) has been specified for `T`, a binding-time error occurs. + - The result of the *object_creation_expression* is a value of the run-time type that the type parameter has been bound to, namely the result of invoking the default constructor of that type. The run-time type may be a reference type or a value type. +- Otherwise, if `T` is a *class_type* or a *struct_type*: + - If `T` is an abstract or static *class_type*, a compile-time error occurs. + - **The instance constructor to invoke is determined using the overload resolution rules of [§12.6.4](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1264-overload-resolution). The set of candidate instance constructors is determined as follows:** + - **`T` is not inferrred (*generic_inferred* or *partially_inferred*), the constructor is accessible in `T`, and is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** + - **If `T` is *generic_constructed* or *partially_constructed* and the constructor is accessible in `T`, type inference of the constructor is performed. Once the *inferred_type_arguments* are inferred and together with the remaining type arguments are substituted for the corresponding type parameters, all constructed types in the parameter list of the constructor satisfy their constraints, and the parameter list of the constructor is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** + - **A binding-time error occurs when:** + - **The set of candidate instance constructors is empty, or if a single best instance constructor cannot be identified, and there is no chance to know the target type now or later.** + - **If the set of candidate instance constructors is still empty, or if a single best instance constructor cannot be identified, repeat the binding of the *object_creation_expression* to the time, when target type will be known and add it to inputs of type inference.** + - The result of the *object_creation_expression* is a value of type `T`, namely the value produced by invoking the instance constructor determined in the two steps above. + - Otherwise, the *object_creation_expression* is invalid, and a binding-time error occurs. + +### Type inference + +> Specification: Original section changed in the following way + +We change the [type inference](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1263-type-inference) as follows. + +* Type inference for generic method invocation is performed when the invocation: + * Doesn't have a *type_argument_list*. + * The type argument list contains at least one *inferred_type_argument*. + > Example + > + > ```csharp + > M( ... ); // Type inference is invoked. + > M<_, string>( ... ); // Type inference is invoked. + > M, string>( ... ); // Type inference is invoked. + > ``` + +* **Type inference for constructors** is performed when the generic type of *object_creation_expression*: + * Has a diamond operator. + * Its *type_argument_list* contains at least one *inferred_type_argument*. + > Example + > + > ```csharp + > new C<>( ... ); // Type inference is invoked. + > new C<_, string>( ... ); // Type inference is invoked. + > new C, string>( ... ); // Type inference is invoked. + > ``` + +* In the case of *method type inference*, we infer method type parameters. + In the case of *constructor type inference*, we infer type parameters of a type defining the constructors. + The previous sentence prohibits inferring type parameters of an outside type that contains the inferred type. (e.g. inference of `new Containing<>.Nested<>(42)` is not allowed) + +* When the method invocation contains a type argument list containing inferred type argument, the input for type inference is extended as follows: + * We replace each `_` identifier with a new type variable `X`. + * We perform *shape inference* from each type argument to the corresponding type parameter. + +* Inputs for **constructor type inference** are constructed as follows: + * If the inferred type contains a nonempty *type_argument_list*. + * We replace each `_` identifier with a new type variable `X`. + * We perform *shape inference* from each type argument to the corresponding type parameter. + * If the target type should be used based on the expression binding, perform *upper-bound inference* from it to the type containing the constructor + * If the expression contains an *object_initializer_list*, for each *initializer_element* of the list perform *lower-bound inference* from the type of the element to the type of *initializer_target*. If the binding of the element fails, skip it. + * If the expression contains *where* clauses defining type constraints of type parameters of the type containing constructor, for each constraint not representing *constructor* constrain, *reference type constraint*, *value type constraint* and *unmanaged type constraint* perform *lower-bound inference* from the constraint to the corresponding type parameter. + * If the expression contains a *collection_initializer_list* and the type doesn't have overloads of the `Add` method, for each *initializer_element* of the list perform *lower-bound inference* from the types of the elements contained in the *initializer_element* to the types of the method's parameters. If the binding of any element fails, skip it. + * If the expression contains a *collection_initializer_list* using an indexer, use the indexer defined in the type and perform *lower_bound_inference* from the types in *initializer_element* to types of matching parameters of the indexer. + +* Arguments binding + * It can happen that an argument of an expression will be *object_creation_expression*, which needs a target type to be successful binded. + * In these situations, we behave like the type of the argument is unknown and bind it when we will know the target type. + * We treat it in the same manner as an unconverted *new()* operator. + +#### Type inference algorithm change + +> Specification: Original section changed in the following way. + +* Shape dependence + * An *unfixed* type variable `Xᵢ` *shape-depends directly on* an *unfixed* type variable `Xₑ` if `Xₑ` represents *inferred_type_argument* and it is contained in *shape bound* of the type variable `Xᵢ`. + * `Xₑ` *shape-depends on* `Xᵢ` if `Xₑ` *shape-depends directly on* `Xᵢ` or if `Xᵢ` *shape-depends directly on* `Xᵥ` and `Xᵥ` *shape-depends on* `Xₑ`. Thus “*shape-depends on*” is the transitive but not reflexive closure of “*shape-depends directly on*”. + +* Type dependence + * An *unfixed* type variable `Xᵢ` *type-depends directly on* an *unfixed* type variable `Xₑ` if `Xₑ` occurs in any bound of type variable `Xᵢ`. + * `Xₑ` *type-depends on* `Xᵢ` if `Xₑ` *type-depends directly on* `Xᵢ` or if `Xᵢ` *type-depends directly on* `Xᵥ` and `Xᵥ` *type-depends on* `Xₑ`. Thus “*type-depends on*” is the transitive but not reflexive closure of “*type-depends directly on*”. + +* Shape inference + * A *shape* inference from a type `U` to a type `V` is made as follows: + * If `V` is one of the *unfixed* `Xᵢ` then `U` is a shape bound of `V`. + * When a shape bound `U` of `V` is set: + * We perform *upper-bound* inference from `U` to all lower-bounds of `V`, which contains an unfixed type variable + * We perform *exact* inference from `U` to all exact-bounds of `V`, which contains an unfixed type variable. + * We perform *lower-bound* inference from `U` to all upper-bounds of `V`, which contains an unfixed type variable. + * We perform *lower-bound* inference from all lower-bounds of `V` to `U` if `U` contains an unfixed type variable. + * We perform *exact* inference from all exact-bounds of `V` to `U` if `U` contains unfixed type variable. + * We perform *upper-type* inference from all upper-bounds of `V` to `U` if `U` contains an unfixed type variable. + * Otherwise, on inferences are made + +* Lower-bound inference + * When a new bound `U` is added to the set of lower-bounds of `V`: + * We perform *lower-bound* inference from `U` to the shape of `V` , if it has any and the shape contains an unfixed type variable. + * We perform *upper-bound* inference from the shape of `V` to `U`, if `V` has a shape and `U` contains an unfixed type variable. + * We perform *exact* inference from `U` to all lower-bounds of `V`, which contains an unfixed type variable. + * We perform *lower-bound* inference from `U` to all exact-bounds and upper-bounds of `V`, which contains an unfixed type variable. + * We perform *exact* inference from all lower-bounds of `V` to `U` if `U` contains an unfixed type variable. + * We perform *upper-bound* type inference from all exact-bounds and upper-bounds of `V` to `U` if `U` contains unfixed type variable. + +* Upper-bound inference + * When new bound `U` is added to the set of upper-bounds of `V`: + * We perform *upper-bound* inference from `U` to the shape of `V` , if it has any and the shape contains an unfixed type variable. + * We perform *lower-bound* inference from the shape of `V` to `U`, if `V` has a a shape and `U` contains an unfixed type variable. + * We perform *exact* inference from `U` to all upper-bounds of `V`, which contains an unfixed type variable. + * We perform *upper-bound* inference from `U` to all exact-bounds and lower-bounds of `V`, which contains an unfixed type variable. + * We perform *exact* inference from all upper-bounds of `V` to `U` if `U` contains an unfixed type variable. + * We perform *lower-bound* type inference from all exact-bounds and lower-bounds of `V` to `U` if `U` contains unfixed type variable. + +* Exact inference + * When new bound `U` is added to the set of lower-bounds of `V`: + * We perform *exact-bound* inference from `U` to the shape of `V` , if has any and the shape contains an unfixed type variable. + * We perform *exact* inference from the shape of `V` to `U`, if `V` has a shape and `U` contains an unfixed type variable. + * We perform *exact* inference from `U` to all exact-bounds of `V`, which contains an unfixed type variable. + * We perform *lower-bound* inference from `U` to all lower-bounds of `V`, which contains an unfixed type variable. + * We perform *upper-bound* inference from `U` to all upper-bounds of `V`, which contains an unfixed type variable. + * We perform *exact* inference from all exact-bounds of `V` to `U`, which contains an unfixed type variable. + * We perform *upper-bound* inference from all lower-bounds of `V` to `U`, which contains an unfixed type variable. + * We perform *lower-bound* inference from all upper-bounds of `V` to `U`, which contains an unfixed type variable. + +* Second phase + * **Firstly, all *unfixed* type variables `Xᵢ` which do not *depend on* ([§12.6.3.6](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12636-dependence)), *shape-depend on*, and *type-depend on* any `Xₑ` are fixed ([§12.6.3.12](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#126312-fixing)).** + * **If no such type variables exist, all *unfixed* type variables `Xᵢ` are *fixed* for which all of the following hold:** + * **There is at least one type variable `Xₑ` that *depends on*, *shape-depends on*, or *type-depends on* `Xᵢ`** + * **There is no type variable `Xₑ` on which `Xᵢ` *shape-depends on*.** + * **`Xᵢ` has a non-empty set of bounds and has at least on bound which doesn't contain any *unfixed* type variable.** + * If no such type variables exist and there are still unfixed type variables, type inference fails. + * [...] + +* Fixing + * **An *unfixed* type variable `Xᵢ` with a set of bounds is *fixed* as follows:** + * **If the type variable has a shape bound, check the type has no conflicts with other bounds of that type variable in the same way as the standard says. It it has no conflicts, the type variable is *fixed* to that type. Otherwise type inference failed.** + * Otherwise, fix it as the standard says. + +> Explanation of inference improvements +> +> Now, the inferred bounds can contain other unfixed type variables. +> So we have to propagate the type info also through these bounds. +> +> > Example +> > +> > ```csharp +> > void F (T1 p1) { ... } +> > ... +> > F>(new List()); +> > ``` +> > We have now two type variables `T1` and `_`. From the first bound, we get that `IList<_>` is a shape bound of `T1`(Ignore now the type of bound, it would be the same in other types of bound). +> > When we investigate the second bound `List`, we will figure out that it would be a lower bound of `T1`. +> > But now, we have to somehow propagate the int type to the `_` type variable, because it relates to it. +> > That means, in the process of adding new bounds, we have to also propagate this info through bounds, which contain unfixed type variables. +> > In this case, we do additional inference of `IList<_>` and `List` yielding exact bound `int` of `_`. + +> Explanation of type-dependence +> +> Type-dependence is required because, till this time when a type variable had any bounds, it didn't contain any unfixed type variable. +> It was important because we could do the parameter fixation, where we work with exact types(not unfixed type variables). +> However now, the type variable can contain bounds containing unfixed type variables. +> We have to ensure that we will not start fixing the type variable till these unfixed type variables are unfixed(In some cases, we can be in a situation, where this dependency will form a cycle. In this case, we will allow the fixation earlier). +> +> > Example +> > +> > We use the previous example. +> > After the first phase. `T1` has bounds `IList<_>` and `List`. `_` has bound `int`. +> > In this situation, we can't start to fix `T1` because `_` is not fixed yet. +> > `T1` is type-dependent on `_`. +> > So, we will first fix `_`, which becomes `int`. +> > Then, `T1` is not type-dependent anymore, because all bounds don't contain any unfixed type variables. +> > `IList<_>` is now `IList` after the `_` fixation. +> > We can fix `T1` now. + +> Explanation of shape-dependence +> +> A similar thing is for shape-dependence. +> Although it cares about bounds received from the type argument list. +> We want a shape bound to be exact (not containing any unfixed type variables) because it is later important for the fixation. +> An intention is to keep the exact form of the given hint(`IList<_>`). +> +> > Example +> > +> > Given `IList<_>` as a type argument, when we treat nullability, we want the hinted type parameter to be non-nullable(not `IList<_>?`). +> > It can happen, other bounds would infer the nullable version, and although `IList<_>` can be converted to `IList<_>?`, it is not the user's intention. + + +> Explanation of inference restriction during constructor type inference +> +> Because performing type inference can even take exponential time when a type system contains overloading, the restriction was made above to avoid it. +> It regards to permit only one method `Add` in the collections and binding arguments before the overload resolution when we bind all *object_creation_expressions* without target info and then in case of overload resolution success and some of these arguments failed in the binding, we try to bind it again with already known target type information. + +### Compile-time checking of dynamic member invocation + +> Specification: Original section changed in the following way + +We change the [compile-time checking](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1265-compile-time-checking-of-dynamic-member-invocation) in order to be useful during partial type inferece. + +- First, if `F` is a generic method and type arguments were provided, then those **, that aren't *inferred_type_argument*** are substituted for the type parameters in the parameter list. However, if type arguments were not provided, no such substitution happens. +- Then, any parameter whose type is open (i.e., contains a type parameter; see [§8.4.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#843-open-and-closed-types)) is elided, along with its corresponding parameter(s). + +### Nullability + +> Specification: New section + +We can use an examination mark `?` to say that the inferred type argument should be a nullable type (e.g. `F<_?>(...)`). + +## Drawbacks +[drawbacks]: #drawbacks + +Difference between constructor type inference and method type inference could confuse the programmer. + +Construct's like `new List<>(){1,2,3}` can look weird when we focus on the pairs of empty braces. + +## Alternatives +[alternatives]: #alternatives + +What other designs have been considered? What is the impact of not doing this? + +* Alternative: Strong type inference as we can see in Haskell or Rust without restrictions mentioned above. + + Reason not to do that: It would introduce breaking changes. It would take an exponential time to deal with overloading. + + +* Alternative: Default type parameters values([link](https://github.com/dotnet/csharplang/discussions/280)): + + Reason not to do that: It would need to change old code to take advantage of it + +* Alternative: Named type arguments([link](https://github.com/dotnet/csharplang/discussions/280)): + + Reason not to do that: We would like to have less type annotations in the code. + +## Unresolved questions +[unresolved]: #unresolved-questions + +* Would it be confusing to have the constructor type inference to work differently in subtle ways than the method call type inference ? + + Potential resolution: We could restrict the power of constructor type inference to use just parameters and initializers which doesn't apear in method call expressions. + +* Type inference for arrays + + In a similar way as we propose partial type inference in method type inference. + It can be used in *array_creation_expression* as well (e.g. `new C<_>[]{...}`). + However, It has the following complication. + To avoid a breaking change, the type inference has to be as powerful as in method type inference. There is a question if it is still as valuable as in cases with methods. + +* Type inference for delegates + + We can do the same thing for `delegate_creation_expression`. However, these expressions seems to be used rarely, so is it valuable to add the type inference for them as well ? + +* Type inference for local variables + + Sometimes `var` keyword as a variable declaration is not sufficient. + We would like to be able to specify more the type information about variable but still have some implementation details hidden. + With the `_` placeholder we would be able to specify more the shape of the variable avoiding unnecessary specification of type arguments. + + ```csharp + Wrapper<_> wrapper = ... // I get an wrapper, which I'm interested in, but I don't care about the type arguments, because I don't need them in my code. + wrapper.DoSomething( ... ); + ``` + +* Type inference for casting + + This can be useful with combination with already preparing collection literals. + + ```csharp + var temp = (Span<_>)[1,2,3]; + ``` + +* Should we allow type inference of nested types like this `new Containing<>.Nested<>(42)` ? + + Potentional resolution: In my opinion, it looks weird and I wouldn't allow it, because then it would be coherent with method type inference and I don't think it would be common usage. + I use nested type as a helper which contains logic of some part of the outside type. + That means it does not usually use all type parameters defined in the outside type. + Hence, there can be a lack of type info to infer type parameters of outside type when we try to use the constructor of nested type. + From the theoretical point of view, it can be done. + +* Is there a better choice for choosing the placeholder for inferred type argument ? + + Potentional resolution: My choice contained in the [detailed design](#detailed-design) is based on the following. + +
+ +We base our choice on the usages specified below. + +1. Type argument list of generic method call (e.g. `Foo(...)`) +2. Type argument list of type creation (e.g. `new Bar(...)`) +3. Type argument list of local variable (e.g. `Bar temp = ...`) +4. Expressing array type (e.g. `T1[]`) +5. Expressing inferred type alone `T1` in local variable + +**Diamond operator** + +1. In the case of generic method calls it doesn't much make sense since method type inference is enabled by default without using angle brackets. + +```csharp +Foo<>(arg1, arg2, arg3); // Doesn't bring us any additional info +``` + +2. There is an advantage. It can turn on the type inference. However, it would complicate overload resolution because we would have to search for every generic type of the same name no matter what arity. But could make a restriction. Usually, there is not more than one generic type with the same name. So when there will be just one type of that name, we can turn the inference on. + +```csharp +new Bar<>(...); // Many constructors which we have to investigate for applicability +new Baz<>(...); // Its OK, we know what set of constructors to investigate. + +class Bar { ... } +class Bar { ... } +class Bar { ... } + +class Baz { ... } +``` + +3. It could make sense to specify just a wrapper of some type that gives us general API that doesn't involve its type arguments. It would say that the part of the code just cares about the wrapper. However, we think that it doesn't give us much freedom because type arguments usually appear in public API and only a few of them are for internal use. + +```csharp +Wrapper<> temp = ... +``` + +4. It doesn't seem very well. + +```csharp +<>[] temp = ... +``` + +5. It clashes with `var` and looks wierd. + +```csharp +<> temp = ... // equivalent to `var temp = ...` +``` + +**Whitespace seperated by commas** + +1. It is able to specify the arity of the generic method. However, it seems to be messy when it is used in generic methods with many generic type parameters. Also, it already has its own meaning of expressing open generic type. + +```csharp +Foo<, string, List<>, >(arg1, arg2, arg3); +``` + +1. The same reasoning as above. However, it could be use as an option to determining an arity of inferred type. `new Klass<,,,>(...)` determines exact class, which type arguments we want to infer. We wouldn't permit it with hinting the type arguments (e.g. `new Klass<,,int>(...)` would be invalid code). + +```csharp +new Bar<, string, List<>, >(arg1, arg2) { arg3 }; +``` + +1. It doesn't work with array type. + +```csharp +Bar<, string, List<>, > temp = ... +``` + +4. It doesn't seems very well. + +```csharp +[] temp = ... +Foo<, [], >(arg1, arg2) +``` + +5. It looks like CSharp would not be a statically-typed language, clashed with `var` and probably introduce many implementation problems in the parser. + +```csharp +temp = ... +``` + +**_ seperated by commas** + +1. It specifies the arity of the generic method. It explicitly says that we want to infer this type argument. It seems to be less messy. + +```csharp +Foo<_, string, List<_>, _>(arg1, arg2, arg3); +``` + +2. The same reasons as above. + +```csharp +new Bar<_, string, List<_>, _>(arg1, arg2, arg3); +``` + +3. The same reasons as above. + +```csharp +Bar<_, string, List<_>, _>(arg1, arg2); +``` + +4. Looks quite OK. + +```csharp +_[] temp = ... +``` + +5. Clashes with `var` and seems to be wierd. + +```csharp +_ temp = ... +``` + +**var seperated by commas** + +1. More keystrokes. It starts to raise the question if it brings the advantage of saving keystrokes. + +```csharp +Foo, var>(arg1, arg2, arg3); +``` + +2. The same reasons as above + +```csharp +new Bar, var>(arg1, arg2, arg3); +``` + +3. The same reasons as above. + +```csharp +Bar, var>(arg1, arg2); +``` + +1. Looks OK. + +```csharp +var[] temp = ... +``` + +5. State of the art. + +```csharp +var temp = ... +``` + +**Something else seperated by commas** + +Doesn't make a lot of sense because it needs to assign new meaning to that character in comparison with `_`, `var`, `<>`, `<,,,>`. +Asterisk `*` can be considered, however, it can remind a pointer. + +**Conslusion** + +I prefer `_` character with enabling `<>` operator in the case of constructor inference when there is only one generic type with that name. +Additionally to that, I would prohibit using `_` in the same places as `var`. + +
+ +## Design meetings + +Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to. \ No newline at end of file From 6e3c0cb08edd330471f55b84803e2d45c28a4faa Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 2 Nov 2023 15:09:47 -0700 Subject: [PATCH 02/29] Update partial-type-inference.md --- proposals/partial-type-inference.md | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index b0b498fcaa..ef1291a118 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -180,8 +180,8 @@ The following changes are made in [tokens](https://github.com/dotnet/csharpstand - The semantics of an identifier named `_` depends on the context in which it appears: - It can denote a named program element, such as a variable, class, or method, or - - It can denote a discard ([§9.2.9.1](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/variables.md#9291-discards)). - - **It can denote an inferred type argument avoiding specifying type arguments which can be inferred by the compiler.** + - It can denote a discard (§9.2.9.1). + - \***It can denote an inferred type argument avoiding specifying type arguments which can be inferred by the compiler.** > [Keywords](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#644-keywords) @@ -280,16 +280,16 @@ The initial set of candidate methods for is changed by adding new condition. - If `F` is non-generic, `F` is a candidate when: - `M` has no type argument list, and - - `F` is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)). + - `F` is applicable with respect to `A` (§12.6.4.2). - If `F` is generic and `M` has no type argument list, `F` is a candidate when: - - Type inference ([§12.6.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1263-type-inference)) succeeds, inferring a list of type arguments for the call, and - - Once the inferred type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)) -- **If `F` is generic and `M` has type argument list containing at least one *inferred_type_argument*, `F` is a candidate when:** + - Type inference (§12.6.3) succeeds, inferring a list of type arguments for the call, and + - Once the inferred type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` (§12.6.4.2) +- \***If `F` is generic and `M` has type argument list containing at least one *inferred_type_argument*, `F` is a candidate when:** - **Type inference ([§12.6.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1263-type-inference)) succeeds, inferring a list of *inferred_type_arguments* for the call, and** - **Once the *inferred_type_arguments* are inferred and together with remaining type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member))** - If `F` is generic and `M` includes a type argument list, `F` is a candidate when: - `F` has the same number of method type parameters as were supplied in the type argument list, and - - Once the type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)). + - Once the type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints (§8.4.5), and the parameter list of `F` is applicable with respect to `A` (§12.6.4.2). ### Object creation expressions @@ -302,20 +302,20 @@ The binding-time processing of an [*object_creation_expression*](https://github. The binding-time processing of an *object_creation_expression* of the form new `T(A)`, where `T` is a *class_type*, or a *value_type*, and `A` is an optional *argument_list*, consists of the following steps: - If `T` is a *value_type* and `A` is not present: - - **The *object_creation_expression* is a default constructor invocation.** + - \***The *object_creation_expression* is a default constructor invocation.** - **If the type is *generic_inferred* or *partially_inferred*, type inference of the default constructor occurs to determine the type arguments. If it succeeded, construct the type using inferred type arguments. If it failed and there is no chance to get the target type now or later, the binding-time error occurs. Otherwise, repeat the binding when the target type will be determined and add it to the inputs of type inference.** - **If the type inference above succeeded or the type is not inferred, the result of the *object_creation_expression* is a value of (constructed) type `T`, namely the default value for `T` as defined in [§8.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#833-default-constructors).** - Otherwise, if `T` is a *type_parameter* and `A` is not present: - - If no value type constraint or constructor constraint ([§15.2.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/classes.md#1525-type-parameter-constraints)) has been specified for `T`, a binding-time error occurs. + - If no value type constraint or constructor constraint (§15.2.5) has been specified for `T`, a binding-time error occurs. - The result of the *object_creation_expression* is a value of the run-time type that the type parameter has been bound to, namely the result of invoking the default constructor of that type. The run-time type may be a reference type or a value type. - Otherwise, if `T` is a *class_type* or a *struct_type*: - If `T` is an abstract or static *class_type*, a compile-time error occurs. - - **The instance constructor to invoke is determined using the overload resolution rules of [§12.6.4](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1264-overload-resolution). The set of candidate instance constructors is determined as follows:** + - \***The instance constructor to invoke is determined using the overload resolution rules of [§12.6.4](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1264-overload-resolution). The set of candidate instance constructors is determined as follows:** - **`T` is not inferrred (*generic_inferred* or *partially_inferred*), the constructor is accessible in `T`, and is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** - **If `T` is *generic_constructed* or *partially_constructed* and the constructor is accessible in `T`, type inference of the constructor is performed. Once the *inferred_type_arguments* are inferred and together with the remaining type arguments are substituted for the corresponding type parameters, all constructed types in the parameter list of the constructor satisfy their constraints, and the parameter list of the constructor is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** - - **A binding-time error occurs when:** + - \***A binding-time error occurs when:** - **The set of candidate instance constructors is empty, or if a single best instance constructor cannot be identified, and there is no chance to know the target type now or later.** - - **If the set of candidate instance constructors is still empty, or if a single best instance constructor cannot be identified, repeat the binding of the *object_creation_expression* to the time, when target type will be known and add it to inputs of type inference.** + - \***If the set of candidate instance constructors is still empty, or if a single best instance constructor cannot be identified, repeat the binding of the *object_creation_expression* to the time, when target type will be known and add it to inputs of type inference.** - The result of the *object_creation_expression* is a value of type `T`, namely the value produced by invoking the instance constructor determined in the two steps above. - Otherwise, the *object_creation_expression* is invalid, and a binding-time error occurs. @@ -424,8 +424,8 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra * We perform *lower-bound* inference from all upper-bounds of `V` to `U`, which contains an unfixed type variable. * Second phase - * **Firstly, all *unfixed* type variables `Xᵢ` which do not *depend on* ([§12.6.3.6](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12636-dependence)), *shape-depend on*, and *type-depend on* any `Xₑ` are fixed ([§12.6.3.12](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#126312-fixing)).** - * **If no such type variables exist, all *unfixed* type variables `Xᵢ` are *fixed* for which all of the following hold:** + * \***Firstly, all *unfixed* type variables `Xᵢ` which do not *depend on* ([§12.6.3.6](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12636-dependence)), *shape-depend on*, and *type-depend on* any `Xₑ` are fixed ([§12.6.3.12](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#126312-fixing)).** + * \***If no such type variables exist, all *unfixed* type variables `Xᵢ` are *fixed* for which all of the following hold:** * **There is at least one type variable `Xₑ` that *depends on*, *shape-depends on*, or *type-depends on* `Xᵢ`** * **There is no type variable `Xₑ` on which `Xᵢ` *shape-depends on*.** * **`Xᵢ` has a non-empty set of bounds and has at least on bound which doesn't contain any *unfixed* type variable.** @@ -433,7 +433,7 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra * [...] * Fixing - * **An *unfixed* type variable `Xᵢ` with a set of bounds is *fixed* as follows:** + * \***An *unfixed* type variable `Xᵢ` with a set of bounds is *fixed* as follows:** * **If the type variable has a shape bound, check the type has no conflicts with other bounds of that type variable in the same way as the standard says. It it has no conflicts, the type variable is *fixed* to that type. Otherwise type inference failed.** * Otherwise, fix it as the standard says. @@ -497,8 +497,8 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra We change the [compile-time checking](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1265-compile-time-checking-of-dynamic-member-invocation) in order to be useful during partial type inferece. -- First, if `F` is a generic method and type arguments were provided, then those **, that aren't *inferred_type_argument*** are substituted for the type parameters in the parameter list. However, if type arguments were not provided, no such substitution happens. -- Then, any parameter whose type is open (i.e., contains a type parameter; see [§8.4.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#843-open-and-closed-types)) is elided, along with its corresponding parameter(s). +- First, if `F` is a generic method and type arguments were provided, then those \***, that aren't *inferred_type_argument*** are substituted for the type parameters in the parameter list. However, if type arguments were not provided, no such substitution happens. +- Then, any parameter whose type is open (i.e., contains a type parameter; see §8.4.3) is elided, along with its corresponding parameter(s). ### Nullability @@ -740,4 +740,4 @@ Additionally to that, I would prohibit using `_` in the same places as `var`. ## Design meetings -Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to. \ No newline at end of file +Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to. From afc1eca54319d996510be2dc69c3d238bcf246f4 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 2 Nov 2023 15:10:30 -0700 Subject: [PATCH 03/29] Remove header template --- proposals/partial-type-inference.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index ef1291a118..060152c240 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -1,10 +1,5 @@ # Partial type inference -* [x] Proposed -* [ ] Prototype -* [ ] Implementation -* [ ] Specification - > Note: This proposal was created because of championed [Partial type inference](https://github.com/dotnet/csharplang/issues/1349). It is a continuation of the proposed second version published in [csharplang/discussions/7467](https://github.com/dotnet/csharplang/discussions/7467) and the first version published in [csharplang/discussions/7286](https://github.com/dotnet/csharplang/discussions/7286) ## Summary From 44bf3a2c96a274800f275cc8b7e8845d09c41d35 Mon Sep 17 00:00:00 2001 From: Tomas Husak <49864539+TomatorCZ@users.noreply.github.com> Date: Sat, 4 Nov 2023 22:47:52 +0100 Subject: [PATCH 04/29] fix typo Co-authored-by: Julien Couvreur --- proposals/partial-type-inference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 060152c240..236d46f180 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -85,7 +85,7 @@ Besides the changes described above, the proposal mentions further interactions Method type inference is not defined on *object_creation_expression*, prohibiting taking advantage of type inference. We divide use cases into the following categories, where type inference would help the programmer. - 1. Cases where the method type inference would success. + 1. Cases where the method type inference would succeed. > Example > From 54ee1c4b41d80e7e3568b7c34c63db3d76382949 Mon Sep 17 00:00:00 2001 From: Tomas Husak <49864539+TomatorCZ@users.noreply.github.com> Date: Sat, 4 Nov 2023 22:51:13 +0100 Subject: [PATCH 05/29] fix typo Co-authored-by: Jared Parsons --- proposals/partial-type-inference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 236d46f180..82a4cfc612 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -64,7 +64,7 @@ Besides the changes described above, the proposal mentions further interactions > public void log(T message, U appendix) { ... } > ``` - The first improvement, which would improve the method type inference algorithm, has a significant disadvantage of the breaking change. + The first improvement, which would improve the method type inference algorithm, has a significant disadvantage of introducing a breaking change. On the other hand, the second improvement, which would enable specifying some of the method's type arguments, does not influence old code, solves problems regarding the "all or nothing" principle, and reduces the first weakness. > Example From 469a1125d8d4a30be7798b80a8debf765d17b22b Mon Sep 17 00:00:00 2001 From: Tomas Husak <49864539+TomatorCZ@users.noreply.github.com> Date: Sun, 5 Nov 2023 11:39:50 +0100 Subject: [PATCH 06/29] change wording Co-authored-by: Julien Couvreur --- proposals/partial-type-inference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 82a4cfc612..4ef75e7621 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -169,7 +169,7 @@ So will want to look ahead to other potential directions, which can be done afte > Specification: Original section changed in the following way -The following changes are made in [tokens](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#64-tokens) located in the [grammar](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#62-grammars) section. +We modify [Identifiers](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#643-identifiers) as follows: > [Identifiers](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#643-identifiers) From 33b71f0c489eaa03018d18a9cabf8b2cdf74a43a Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sun, 5 Nov 2023 11:59:27 +0100 Subject: [PATCH 07/29] change wording and fix typo --- proposals/partial-type-inference.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 4ef75e7621..fc7fd0e7ea 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -5,14 +5,14 @@ ## Summary [summary]: #summary -Partial type inference introduces a syntax skipping obvious type arguments in the argument list of +Partial type inference introduces a syntax skipping inferrable type arguments in the argument list of 1. *invocation_expression* 2. *object_creation_expresssion* and allowing to specify just ambiguous ones. -> Example of skipping obvious type arguments +> Example of skipping inferrable type arguments > > ```csharp > M<_, object>(42, null); @@ -97,12 +97,12 @@ Besides the changes described above, the proposal mentions further interactions > class Wrapper { public Wrapper(T item) { ... } } > ``` - 2. Cases where the method type inference would be weak. (Using type info from target type, or type arguments' constrains) + 2. Cases where the method type inference would be weak. (Using type info from target type, or type arguments' constraints) > Example > > ```csharp - > var alg = Create(new MyData()); // Method type inference can't infer TLogger because it doesn't use type constrains specified by `where` clauses + > var alg = Create(new MyData()); // Method type inference can't infer TLogger because it doesn't use type constraints specified by `where` clauses > > public static Algorithm Create(TData data) where TLogger : Logger { return new Algorithm(data); } > class Algorithm where TLogger : Logger { public Algorithm(TData data) { ... }} @@ -153,7 +153,7 @@ Besides the changes described above, the proposal mentions further interactions No matter how the partial type inference would work, we should be careful about the following things. -- **Convenience** - We want an easy and intuitive syntax that we can skip the obvious type arguments. +- **Convenience** - We want an easy and intuitive syntax that we can skip the inferrable type arguments. - **Performance** - Type inference is a complicated problem when we introduce subtyping and overloading in a type system. Although it can be done, the computation can take exponential time which we don't want. So it has to be restricted to cases, where the problem can be solved effectively but it still has practical usage. @@ -169,16 +169,14 @@ So will want to look ahead to other potential directions, which can be done afte > Specification: Original section changed in the following way -We modify [Identifiers](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#643-identifiers) as follows: - -> [Identifiers](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#643-identifiers) +> We modify [Identifiers](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#643-identifiers) as follows: - The semantics of an identifier named `_` depends on the context in which it appears: - It can denote a named program element, such as a variable, class, or method, or - It can denote a discard (§9.2.9.1). - - \***It can denote an inferred type argument avoiding specifying type arguments which can be inferred by the compiler.** + - **It will denote a type argument to be inferred.** -> [Keywords](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#644-keywords) +> We modify [Keywords](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#644-keywords) as follows: * A ***contextual keyword*** is an identifier-like sequence of characters that has special meaning in certain contexts, but is not reserved, and can be used as an identifier outside of those contexts as well as when prefaced by the `@` character. @@ -356,7 +354,7 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra * We perform *shape inference* from each type argument to the corresponding type parameter. * If the target type should be used based on the expression binding, perform *upper-bound inference* from it to the type containing the constructor * If the expression contains an *object_initializer_list*, for each *initializer_element* of the list perform *lower-bound inference* from the type of the element to the type of *initializer_target*. If the binding of the element fails, skip it. - * If the expression contains *where* clauses defining type constraints of type parameters of the type containing constructor, for each constraint not representing *constructor* constrain, *reference type constraint*, *value type constraint* and *unmanaged type constraint* perform *lower-bound inference* from the constraint to the corresponding type parameter. + * If the expression contains *where* clauses defining type constraints of type parameters of the type containing constructor, for each constraint not representing *constructor* constraint, *reference type constraint*, *value type constraint* and *unmanaged type constraint* perform *lower-bound inference* from the constraint to the corresponding type parameter. * If the expression contains a *collection_initializer_list* and the type doesn't have overloads of the `Add` method, for each *initializer_element* of the list perform *lower-bound inference* from the types of the elements contained in the *initializer_element* to the types of the method's parameters. If the binding of any element fails, skip it. * If the expression contains a *collection_initializer_list* using an indexer, use the indexer defined in the type and perform *lower_bound_inference* from the types in *initializer_element* to types of matching parameters of the indexer. From 5e59e96d98c7c7c12678c293b04d5e20d8b8f037 Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sun, 5 Nov 2023 12:17:43 +0100 Subject: [PATCH 08/29] delete unnecessary part of example --- proposals/partial-type-inference.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index fc7fd0e7ea..25be64e6d0 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -90,8 +90,6 @@ Besides the changes described above, the proposal mentions further interactions > Example > > ```csharp - > var wrappedData = Create(new MyData()); - > > public static Wrapper Create(T item) { return new Wrapper(item); } > > class Wrapper { public Wrapper(T item) { ... } } From b6081e37e9a7e3b6892d3b0164e5d91b3a61cfc3 Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sun, 5 Nov 2023 12:27:48 +0100 Subject: [PATCH 09/29] change formulation of type_name meaning --- proposals/partial-type-inference.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 25be64e6d0..ad42d679be 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -244,8 +244,8 @@ If there is an ambiguity in the current scope, a compilation-time error occurs. > { > void M() > { - > new C1<>( ... ); // Refers generic_inferred type C1 - > new C2<>( ... ); // Refers generic_inferred type C2 + > new C1<>( ... ); // generic_inferred type C1<> refers to generic type C where the type argument will be inferred from inspecting candidate constructors of C + > new C2<>( ... ); // generic_inferred type C1<> refers to generic type C where the type arguments will be inferred from inspecting candidate constructors of C > } > class C1 { ... } > class C2 { ... } From 6039b9fcc71f26a254f65ce4261200d04ba90e17 Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sun, 5 Nov 2023 12:58:19 +0100 Subject: [PATCH 10/29] change grammar of type argument section --- proposals/partial-type-inference.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index ad42d679be..ba1bc29bd2 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -194,12 +194,34 @@ So will want to look ahead to other potential directions, which can be done afte > Specification: Original section changed in the following way -* We change the meaning of the content of *type_argument_list* in two contexts. - * [Constructed types](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#84-constructed-types) occuring in [*object_creation_expression*](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#128162-object-creation-expressions) - * Constructed types and type arguments occuring in method [invocation](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12892-method-invocations) +> We change [type arguments](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/types.md#842-type-arguments) section as follows: * ***inferred_type_argument*** represents an unknown type, which will be resolved during type inference. +* Each argument in a type argument list is ~~simply a type~~ **a type or an inferred type**. + +```diff +type_argument_list + : '<' type_arguments '>' + ; + +type_arguments +- : type_argument (',' type_argument)* ++ : type_argument (',' type_arguments) ++ | inferred_type_argument (',' type_arguments) + ; + +type_argument + : type + ; + ++inferred_type_argument ++ : '_' ++ ; +``` + +* When a type with `_` identifier is presented in the scope where **inferred_type_argument** is used, a warning should appear since the **inferred_type_argument** hides the type's name causing a breaking change. + * `_` identifier is considered to represent *inferred_type_argument* when: * It occurs in *type_argument_list* of a method group during method invocation. * It occurs in *type_argument_list* of a type in *object_creation_expression*. From ea87f2f973aff156f9467c56d4e1861e3c7306bb Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sun, 5 Nov 2023 19:30:00 +0100 Subject: [PATCH 11/29] make type inference using initializers and types with floating arity during type inference as alternatives --- proposals/partial-type-inference.md | 230 ++++++++++++++++------------ 1 file changed, 134 insertions(+), 96 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index ba1bc29bd2..78fad7010b 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -19,13 +19,13 @@ and allowing to specify just ambiguous ones. > void M(T1 t1, T2 t2) { ... } > ``` -It also improves the type inference in the case of *object_creation_expression* by leveraging type bounds obtained from the target, *object_or_collection_initializer*, and *type_parameter_constraints_clauses*. +It also improves the type inference in the case of *object_creation_expression* by leveraging type bounds obtained from the target and *type_parameter_constraints_clauses*. > Example of inferring type arguments of type from the initializer list > > ```csharp > using System.Collections.Generic; -> var statistics = new Dictionary<,>(){["Joe"] = 20}; +> IList temp = new List<_>(); > ``` Besides the changes described above, the proposal mentions further interactions and possibilities to extend the partial type inference. @@ -105,24 +105,8 @@ Besides the changes described above, the proposal mentions further interactions > public static Algorithm Create(TData data) where TLogger : Logger { return new Algorithm(data); } > class Algorithm where TLogger : Logger { public Algorithm(TData data) { ... }} > ``` - - 3. Cases where the method type inference is not defined. (Using type info from initializers) - - > Example - > - > ```csharp - > var numbers = new List() {1,2,3,4}; // I would like to infer List type argument based on the initializer. - > ``` - - 4. Other cases - - > Example - > - > ```csharp - > new Dictionary() {[""] = null}; // The first type argument is unnecessary since we can deduce it from the initializer. - > ``` - An existing solution can be seen in `Create()` method wrappers of constructors enabling a type inference through method type inference as you can se in the examples above. + An existing solution can be seen in `Create()` method wrappers of constructors enabling a type inference through method type inference as you can ses in the examples above. However, we can't use it with *object_or_collection_initializer*; we are limited by method type inference strength, and it adds unnecessary boiler code. Adding constructor type inference as we will describe in the following section would solve above mentioned examples. @@ -130,24 +114,16 @@ Besides the changes described above, the proposal mentions further interactions > Example > > ```csharp - > var wrappedData = new Wrapper<>(new MyData); + > var wrappedData = new Wrapper<_>(new MyData()); > class Wrapper { public Wrapper(T item) { ... } } > ``` > > ```csharp - > var alg = new Algorithm<>(new MyData()); + > var alg = new Algorithm<_, _>(new MyData()); > var algWithSpecialLogger = new Algorithm<_ , SpecialLogger<_>>(new MyData()); > > class Algorithm where TLogger : Logger { public Algorithm(TData data) { ... }} > ``` - > - > ```csharp - > var numbers = new List<>() {1,2,3,4}; - > ``` - > - > ```csharp - > new Dictionary<_, string>() {[""] = null}; - > ``` No matter how the partial type inference would work, we should be careful about the following things. @@ -240,49 +216,6 @@ type_argument * A method group and type are said to be *partial_inferred* if it contains at least one *inferred_type_argument*. -* A type is said to be *generic_inferred* when all the following hold: - * It has an empty *type_argument_list*. - * It occurs as a *type* of *object_creation_expression*. - > Example - > - > ```csharp - > new C<>(...) // Valid code, C is generic_inferred. - > new C>(...) // Invalid code, C nor G are generic_inferred. - > F<>(...) // Invalid code, F isn't generic_inferred. - > ``` - -### Namespace and type names - -> Specification: Original section changed in the following way - -Determining the [meaning](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/basic-concepts.md#781-general) of a *namespace_or_type_name* is changed as follow. - -* If a type is a *generic_inferred*, then we resolve the identifier in the same manner except ignoring the arity of type parameters (Types of arity 0 is ignored). -If there is an ambiguity in the current scope, a compilation-time error occurs. - > Example - > - > ```csharp - > class P1 - > { - > void M() - > { - > new C1<>( ... ); // generic_inferred type C1<> refers to generic type C where the type argument will be inferred from inspecting candidate constructors of C - > new C2<>( ... ); // generic_inferred type C1<> refers to generic type C where the type arguments will be inferred from inspecting candidate constructors of C - > } - > class C1 { ... } - > class C2 { ... } - > } - > class P2 - > { - > void M() - > { - > new C1<>( ... ); // Compile-time error occurs because of ambiguity between C1 and C1 - > } - > class C1 { ... } - > class C1 { ... } - > } - > ``` - ### Method invocations > Specification: Original section changed in the following way @@ -297,7 +230,7 @@ The initial set of candidate methods for is changed by adding new condition. - If `F` is generic and `M` has no type argument list, `F` is a candidate when: - Type inference (§12.6.3) succeeds, inferring a list of type arguments for the call, and - Once the inferred type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` (§12.6.4.2) -- \***If `F` is generic and `M` has type argument list containing at least one *inferred_type_argument*, `F` is a candidate when:** +- **If `F` is generic and `M` has type argument list containing at least one *inferred_type_argument*, `F` is a candidate when:** - **Type inference ([§12.6.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1263-type-inference)) succeeds, inferring a list of *inferred_type_arguments* for the call, and** - **Once the *inferred_type_arguments* are inferred and together with remaining type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member))** - If `F` is generic and `M` includes a type argument list, `F` is a candidate when: @@ -315,20 +248,20 @@ The binding-time processing of an [*object_creation_expression*](https://github. The binding-time processing of an *object_creation_expression* of the form new `T(A)`, where `T` is a *class_type*, or a *value_type*, and `A` is an optional *argument_list*, consists of the following steps: - If `T` is a *value_type* and `A` is not present: - - \***The *object_creation_expression* is a default constructor invocation.** - - **If the type is *generic_inferred* or *partially_inferred*, type inference of the default constructor occurs to determine the type arguments. If it succeeded, construct the type using inferred type arguments. If it failed and there is no chance to get the target type now or later, the binding-time error occurs. Otherwise, repeat the binding when the target type will be determined and add it to the inputs of type inference.** + - **The *object_creation_expression* is a default constructor invocation.** + - **If the type is *partially_inferred*, type inference of the default constructor occurs to determine the type arguments. If it succeeded, construct the type using inferred type arguments. If it failed and there is no chance to get the target type now or later, the binding-time error occurs. Otherwise, repeat the binding when the target type will be determined and add it to the inputs of type inference.** - **If the type inference above succeeded or the type is not inferred, the result of the *object_creation_expression* is a value of (constructed) type `T`, namely the default value for `T` as defined in [§8.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#833-default-constructors).** - Otherwise, if `T` is a *type_parameter* and `A` is not present: - If no value type constraint or constructor constraint (§15.2.5) has been specified for `T`, a binding-time error occurs. - The result of the *object_creation_expression* is a value of the run-time type that the type parameter has been bound to, namely the result of invoking the default constructor of that type. The run-time type may be a reference type or a value type. - Otherwise, if `T` is a *class_type* or a *struct_type*: - If `T` is an abstract or static *class_type*, a compile-time error occurs. - - \***The instance constructor to invoke is determined using the overload resolution rules of [§12.6.4](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1264-overload-resolution). The set of candidate instance constructors is determined as follows:** - - **`T` is not inferrred (*generic_inferred* or *partially_inferred*), the constructor is accessible in `T`, and is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** - - **If `T` is *generic_constructed* or *partially_constructed* and the constructor is accessible in `T`, type inference of the constructor is performed. Once the *inferred_type_arguments* are inferred and together with the remaining type arguments are substituted for the corresponding type parameters, all constructed types in the parameter list of the constructor satisfy their constraints, and the parameter list of the constructor is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** - - \***A binding-time error occurs when:** + - **The instance constructor to invoke is determined using the overload resolution rules of [§12.6.4](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1264-overload-resolution). The set of candidate instance constructors is determined as follows:** + - **`T` is not inferrred (*partially_inferred*), the constructor is accessible in `T`, and is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** + - **If `T` is *partially_constructed* and the constructor is accessible in `T`, type inference of the constructor is performed. Once the *inferred_type_arguments* are inferred and together with the remaining type arguments are substituted for the corresponding type parameters, all constructed types in the parameter list of the constructor satisfy their constraints, and the parameter list of the constructor is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** + - **A binding-time error occurs when:** - **The set of candidate instance constructors is empty, or if a single best instance constructor cannot be identified, and there is no chance to know the target type now or later.** - - \***If the set of candidate instance constructors is still empty, or if a single best instance constructor cannot be identified, repeat the binding of the *object_creation_expression* to the time, when target type will be known and add it to inputs of type inference.** + - **If the set of candidate instance constructors is still empty, or if a single best instance constructor cannot be identified, repeat the binding of the *object_creation_expression* to the time, when target type will be known and add it to inputs of type inference.** - The result of the *object_creation_expression* is a value of type `T`, namely the value produced by invoking the instance constructor determined in the two steps above. - Otherwise, the *object_creation_expression* is invalid, and a binding-time error occurs. @@ -350,19 +283,17 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra > ``` * **Type inference for constructors** is performed when the generic type of *object_creation_expression*: - * Has a diamond operator. * Its *type_argument_list* contains at least one *inferred_type_argument*. > Example > > ```csharp - > new C<>( ... ); // Type inference is invoked. > new C<_, string>( ... ); // Type inference is invoked. > new C, string>( ... ); // Type inference is invoked. > ``` * In the case of *method type inference*, we infer method type parameters. In the case of *constructor type inference*, we infer type parameters of a type defining the constructors. - The previous sentence prohibits inferring type parameters of an outside type that contains the inferred type. (e.g. inference of `new Containing<>.Nested<>(42)` is not allowed) + The previous sentence prohibits inferring type parameters of an outside type that contains the inferred type. (e.g. inference of `new Containing<_>.Nested<_>(42)` is not allowed) * When the method invocation contains a type argument list containing inferred type argument, the input for type inference is extended as follows: * We replace each `_` identifier with a new type variable `X`. @@ -373,10 +304,7 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra * We replace each `_` identifier with a new type variable `X`. * We perform *shape inference* from each type argument to the corresponding type parameter. * If the target type should be used based on the expression binding, perform *upper-bound inference* from it to the type containing the constructor - * If the expression contains an *object_initializer_list*, for each *initializer_element* of the list perform *lower-bound inference* from the type of the element to the type of *initializer_target*. If the binding of the element fails, skip it. * If the expression contains *where* clauses defining type constraints of type parameters of the type containing constructor, for each constraint not representing *constructor* constraint, *reference type constraint*, *value type constraint* and *unmanaged type constraint* perform *lower-bound inference* from the constraint to the corresponding type parameter. - * If the expression contains a *collection_initializer_list* and the type doesn't have overloads of the `Add` method, for each *initializer_element* of the list perform *lower-bound inference* from the types of the elements contained in the *initializer_element* to the types of the method's parameters. If the binding of any element fails, skip it. - * If the expression contains a *collection_initializer_list* using an indexer, use the indexer defined in the type and perform *lower_bound_inference* from the types in *initializer_element* to types of matching parameters of the indexer. * Arguments binding * It can happen that an argument of an expression will be *object_creation_expression*, which needs a target type to be successful binded. @@ -437,8 +365,8 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra * We perform *lower-bound* inference from all upper-bounds of `V` to `U`, which contains an unfixed type variable. * Second phase - * \***Firstly, all *unfixed* type variables `Xᵢ` which do not *depend on* ([§12.6.3.6](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12636-dependence)), *shape-depend on*, and *type-depend on* any `Xₑ` are fixed ([§12.6.3.12](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#126312-fixing)).** - * \***If no such type variables exist, all *unfixed* type variables `Xᵢ` are *fixed* for which all of the following hold:** + * **Firstly, all *unfixed* type variables `Xᵢ` which do not *depend on* ([§12.6.3.6](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12636-dependence)), *shape-depend on*, and *type-depend on* any `Xₑ` are fixed ([§12.6.3.12](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#126312-fixing)).** + * **If no such type variables exist, all *unfixed* type variables `Xᵢ` are *fixed* for which all of the following hold:** * **There is at least one type variable `Xₑ` that *depends on*, *shape-depends on*, or *type-depends on* `Xᵢ`** * **There is no type variable `Xₑ` on which `Xᵢ` *shape-depends on*.** * **`Xᵢ` has a non-empty set of bounds and has at least on bound which doesn't contain any *unfixed* type variable.** @@ -446,7 +374,7 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra * [...] * Fixing - * \***An *unfixed* type variable `Xᵢ` with a set of bounds is *fixed* as follows:** + * **An *unfixed* type variable `Xᵢ` with a set of bounds is *fixed* as follows:** * **If the type variable has a shape bound, check the type has no conflicts with other bounds of that type variable in the same way as the standard says. It it has no conflicts, the type variable is *fixed* to that type. Otherwise type inference failed.** * Otherwise, fix it as the standard says. @@ -502,7 +430,7 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra > Explanation of inference restriction during constructor type inference > > Because performing type inference can even take exponential time when a type system contains overloading, the restriction was made above to avoid it. -> It regards to permit only one method `Add` in the collections and binding arguments before the overload resolution when we bind all *object_creation_expressions* without target info and then in case of overload resolution success and some of these arguments failed in the binding, we try to bind it again with already known target type information. +> It regards binding arguments before the overload resolution when we bind all *object_creation_expressions* without target info and then in case of overload resolution success and some of these arguments failed in the binding, we try to bind it again with already known target type information. ### Compile-time checking of dynamic member invocation @@ -510,7 +438,7 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra We change the [compile-time checking](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1265-compile-time-checking-of-dynamic-member-invocation) in order to be useful during partial type inferece. -- First, if `F` is a generic method and type arguments were provided, then those \***, that aren't *inferred_type_argument*** are substituted for the type parameters in the parameter list. However, if type arguments were not provided, no such substitution happens. +- First, if `F` is a generic method and type arguments were provided, then those ***, that aren't *inferred_type_argument*** are substituted for the type parameters in the parameter list. However, if type arguments were not provided, no such substitution happens. - Then, any parameter whose type is open (i.e., contains a type parameter; see §8.4.3) is elided, along with its corresponding parameter(s). ### Nullability @@ -524,8 +452,6 @@ We can use an examination mark `?` to say that the inferred type argument should Difference between constructor type inference and method type inference could confuse the programmer. -Construct's like `new List<>(){1,2,3}` can look weird when we focus on the pairs of empty braces. - ## Alternatives [alternatives]: #alternatives @@ -544,12 +470,124 @@ What other designs have been considered? What is the impact of not doing this? Reason not to do that: We would like to have less type annotations in the code. +* Alternative: Allow type inference in object creation with floating arity(`new C<>(...)`): + + Reason not to do that: Maybe much controversial ? + +
+ + This change was initially a part of the proposal. To support this feature, following sections of this proposal would be changed. + + ### Type arguments + + ... + + * A type is said to be *generic_inferred* when all the following hold: + * It has an empty *type_argument_list*. + * It occurs as a *type* of *object_creation_expression*. + > Example + > + > ```csharp + > new C<>(...) // Valid code, C is generic_inferred. + > new C>(...) // Invalid code, C nor G are generic_inferred. + > F<>(...) // Invalid code, F isn't generic_inferred. + > ``` + + ### Namespace and type names + + > Specification: Original section changed in the following way + + Determining the [meaning](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/basic-concepts.md#781-general) of a *namespace_or_type_name* is changed as follow. + + * If a type is a *generic_inferred*, then we resolve the identifier in the same manner except ignoring the arity of type parameters (Types of arity 0 is ignored). + If there is an ambiguity in the current scope, a compilation-time error occurs. + > Example + > + > ```csharp + > class P1 + > { + > void M() + > { + > new C1<>( ... ); // generic_inferred type C1<> refers to generic type C where the type argument will be inferred from inspecting candidate constructors of C + > new C2<>( ... ); // generic_inferred type C1<> refers to generic type C where the type arguments will be inferred from inspecting candidate constructors of C + > } + > class C1 { ... } + > class C2 { ... } + > } + > class P2 + > { + > void M() + > { + > new C1<>( ... ); // Compile-time error occurs because of ambiguity between C1 and C1 + > } + > class C1 { ... } + > class C1 { ... } + > } + > ``` + + ### Object creation expressions + + ... + + - If the type is ***generic_inferred* or** *partially_inferred*, type inference of the default constructor occurs to determine the type arguments. If it succeeded, construct the type using inferred type arguments. If it failed and there is no chance to get the target type now or later, the binding-time error occurs. Otherwise, repeat the binding when the target type will be determined and add it to the inputs of type inference. + + ... + + - The instance constructor to invoke is determined using the overload resolution rules of [§12.6.4](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1264-overload-resolution). The set of candidate instance constructors is determined as follows: + - `T` is not inferrred (***generic_inferred* or** *partially_inferred*), the constructor is accessible in `T`, and is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)). + - If `T` is ***generic_constructed* or** *partially_constructed* and the constructor is accessible in `T`, type inference of the constructor is performed. Once the *inferred_type_arguments* are inferred and together with the remaining type arguments are substituted for the corresponding type parameters, all constructed types in the parameter list of the constructor satisfy their constraints, and the parameter list of the constructor is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)). + - ... + + ### Type inference + + ... + + * Type inference for constructors is performed when the generic type of *object_creation_expression*: + * **Has a diamond operator.** + * Its *type_argument_list* contains at least one *inferred_type_argument*. + > Example + > + > ```diff + > +new C<>( ... ); // Type inference is invoked. + > new C<_, string>( ... ); // Type inference is invoked. + > new C, string>( ... ); // Type inference is invoked. + > ``` + +
+ +* Alternative: Allowing object initializers contribute to the inference in object creation: + + > Example of inferring type arguments of type from the initializer list + > + > ```csharp + > using System.Collections.Generic; + > var statistics = new Dictionary<,>(){["Joe"] = 20}; // Inferred: Dictionary + > ``` + + Reason not to do that: Maybe much controversial ? + +
+ + This change was initially a part of the proposal. To support this feature, following sections of this proposal would be changed. + + ### Type inference + + ... + + * Inputs for **constructor type inference** are constructed as follows: + * ... + * If the expression contains an *object_initializer_list*, for each *initializer_element* of the list perform *lower-bound inference* from the type of the element to the type of *initializer_target*. If the binding of the element fails, skip it. + * If the expression contains a *collection_initializer_list* and the type doesn't have overloads of the `Add` method, for each *initializer_element* of the list perform *lower-bound inference* from the types of the elements contained in the *initializer_element* to the types of the method's parameters. If the binding of any element fails, skip it. + * If the expression contains a *collection_initializer_list* using an indexer, use the indexer defined in the type and perform *lower_bound_inference* from the types in *initializer_element* to types of matching parameters of the indexer. + +
+ ## Unresolved questions [unresolved]: #unresolved-questions * Would it be confusing to have the constructor type inference to work differently in subtle ways than the method call type inference ? - Potential resolution: We could restrict the power of constructor type inference to use just parameters and initializers which doesn't apear in method call expressions. + Potential resolution: We could restrict the power of constructor type inference to use just parameters. * Type inference for arrays @@ -581,7 +619,7 @@ What other designs have been considered? What is the impact of not doing this? var temp = (Span<_>)[1,2,3]; ``` -* Should we allow type inference of nested types like this `new Containing<>.Nested<>(42)` ? +* Should we allow type inference of nested types like this `new Containing<_>.Nested<_>(42)` ? Potentional resolution: In my opinion, it looks weird and I wouldn't allow it, because then it would be coherent with method type inference and I don't think it would be common usage. I use nested type as a helper which contains logic of some part of the outside type. @@ -746,7 +784,7 @@ Asterisk `*` can be considered, however, it can remind a pointer. **Conslusion** -I prefer `_` character with enabling `<>` operator in the case of constructor inference when there is only one generic type with that name. +I prefer `_` character. Additionally to that, I would prohibit using `_` in the same places as `var`. From 8c7eadb5ffafaa2b2dd98681bffe85c23d474764 Mon Sep 17 00:00:00 2001 From: Tomas Husak <49864539+TomatorCZ@users.noreply.github.com> Date: Sat, 3 Feb 2024 11:48:23 +0100 Subject: [PATCH 12/29] typo Co-authored-by: Rikki Gibson --- proposals/partial-type-inference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 78fad7010b..42d7a91288 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -333,7 +333,7 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra * We perform *lower-bound* inference from all lower-bounds of `V` to `U` if `U` contains an unfixed type variable. * We perform *exact* inference from all exact-bounds of `V` to `U` if `U` contains unfixed type variable. * We perform *upper-type* inference from all upper-bounds of `V` to `U` if `U` contains an unfixed type variable. - * Otherwise, on inferences are made + * Otherwise, no inferences are made * Lower-bound inference * When a new bound `U` is added to the set of lower-bounds of `V`: From ebe9eafcc3f8811bbfa2fae20a31091df1fa3018 Mon Sep 17 00:00:00 2001 From: Tomas Husak <49864539+TomatorCZ@users.noreply.github.com> Date: Sat, 3 Feb 2024 11:48:49 +0100 Subject: [PATCH 13/29] typo Co-authored-by: Rikki Gibson --- proposals/partial-type-inference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 42d7a91288..8da3c7c638 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -337,7 +337,7 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra * Lower-bound inference * When a new bound `U` is added to the set of lower-bounds of `V`: - * We perform *lower-bound* inference from `U` to the shape of `V` , if it has any and the shape contains an unfixed type variable. + * We perform *lower-bound* inference from `U` to the shape of `V`, if it has any and the shape contains an unfixed type variable. * We perform *upper-bound* inference from the shape of `V` to `U`, if `V` has a shape and `U` contains an unfixed type variable. * We perform *exact* inference from `U` to all lower-bounds of `V`, which contains an unfixed type variable. * We perform *lower-bound* inference from `U` to all exact-bounds and upper-bounds of `V`, which contains an unfixed type variable. From 47524aeec1d6e1c1079ee35a5db30967cfdc5b61 Mon Sep 17 00:00:00 2001 From: Tomas Husak <49864539+TomatorCZ@users.noreply.github.com> Date: Sat, 3 Feb 2024 11:49:09 +0100 Subject: [PATCH 14/29] type Co-authored-by: Rikki Gibson --- proposals/partial-type-inference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 8da3c7c638..4d019c0b42 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -355,7 +355,7 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra * Exact inference * When new bound `U` is added to the set of lower-bounds of `V`: - * We perform *exact-bound* inference from `U` to the shape of `V` , if has any and the shape contains an unfixed type variable. + * We perform *exact-bound* inference from `U` to the shape of `V`, if has any and the shape contains an unfixed type variable. * We perform *exact* inference from the shape of `V` to `U`, if `V` has a shape and `U` contains an unfixed type variable. * We perform *exact* inference from `U` to all exact-bounds of `V`, which contains an unfixed type variable. * We perform *lower-bound* inference from `U` to all lower-bounds of `V`, which contains an unfixed type variable. From 9daa67297dab71be1ea9bd35ddb9b16ef9e97fe9 Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sat, 3 Feb 2024 13:58:29 +0100 Subject: [PATCH 15/29] typo --- proposals/partial-type-inference.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 4d019c0b42..319d316e57 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -106,7 +106,7 @@ Besides the changes described above, the proposal mentions further interactions > class Algorithm where TLogger : Logger { public Algorithm(TData data) { ... }} > ``` - An existing solution can be seen in `Create()` method wrappers of constructors enabling a type inference through method type inference as you can ses in the examples above. + An existing solution can be seen in `Create()` method wrappers of constructors enabling a type inference through method type inference as you can see in the examples above. However, we can't use it with *object_or_collection_initializer*; we are limited by method type inference strength, and it adds unnecessary boiler code. Adding constructor type inference as we will describe in the following section would solve above mentioned examples. @@ -134,7 +134,7 @@ So it has to be restricted to cases, where the problem can be solved effectively - **IDE** - Improvement of the type inference can complicate IDE hints during coding. We should give the user clear and not overwhelming errors when there will be an error and try to provide info that helps him to fix it. - **Extensions** - We don't want to make this change blocker for another potential feature in the future. -So will want to look ahead to other potential directions, which can be done after this feature. +So we will want to look ahead to other potential directions, which can be done after this feature. ## Detailed design [design]: #detailed-design From e973d80d053e19997a8dde270eb8579e8d167a82 Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sat, 3 Feb 2024 14:11:23 +0100 Subject: [PATCH 16/29] shrink comments in code examples --- proposals/partial-type-inference.md | 68 +++++++++++++++++++---------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 319d316e57..89ada31dcf 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -41,7 +41,8 @@ Besides the changes described above, the proposal mentions further interactions > > ```csharp > object data = database.fetch(); - > int count = data.Field("count"); // Error, method type inference can't infer the return type. We have to specify the type argument. + > // Error below, method type inference can't infer the return type. We have to specify the type argument. + > int count = data.Field("count"); > > public static class Extensions > { @@ -50,7 +51,8 @@ Besides the changes described above, the proposal mentions further interactions > ``` > > ```csharp - > test(new MyData()); // Error, method type inference can't infer T. We have to specify all type arguments. + > // Error below, method type inference can't infer T. We have to specify all type arguments. + > test(new MyData()); > > public void test(U data) where T : TestCaseDefault { ... } > ``` @@ -59,7 +61,8 @@ Besides the changes described above, the proposal mentions further interactions > Example > ```csharp - > log<, object>(new Message( ... ), null); // Error, impossible to specify just one type argument. We have to specify all of them. + > // Error below, impossible to specify just one type argument. We have to specify all of them. + > log<, object>(new Message( ... ), null); > > public void log(T message, U appendix) { ... } > ``` @@ -70,13 +73,15 @@ Besides the changes described above, the proposal mentions further interactions > Example > > ```csharp - > test, _>(new MyData()); // We can use _ to mark type arguments which should be inferred by the compiler. + > // We can use _ to mark type arguments which should be inferred by the compiler. + > test, _>(new MyData()); > > public void test(U data) where T : TestCaseDefault { ... } > ``` > > ```csharp - > log<_, object>(new Message( ... ), null); // We can use _ to mark type arguments which should be inferred by the compiler. + > // We can use _ to mark type arguments which should be inferred by the compiler. + > log<_, object>(new Message( ... ), null); > > public void log(T message, U appendix) { ... } > ``` @@ -100,10 +105,18 @@ Besides the changes described above, the proposal mentions further interactions > Example > > ```csharp - > var alg = Create(new MyData()); // Method type inference can't infer TLogger because it doesn't use type constraints specified by `where` clauses + > // Method type inference can't infer TLogger because it doesn't use type constraints specified by `where` clauses + > var alg = Create(new MyData()); > - > public static Algorithm Create(TData data) where TLogger : Logger { return new Algorithm(data); } - > class Algorithm where TLogger : Logger { public Algorithm(TData data) { ... }} + > public static Algorithm Create(TData data) where TLogger : Logger + > { + > return new Algorithm(data); + > } + > + > class Algorithm where TLogger : Logger + > { + > public Algorithm(TData data) { ... } + > } > ``` An existing solution can be seen in `Create()` method wrappers of constructors enabling a type inference through method type inference as you can see in the examples above. @@ -211,7 +224,10 @@ type_argument > new C, int>( ... ); // _ represents an inferred type argument. > C<_> temp = ...; // _ doesn't represent an inferred type argument. > new _( ... ) // _ doesn't represent an inferred type argument. - > Container<_>.Method<_>(arg); // _ of Container<_> doesn't represent an inferred type argument. (Containing type's type argument won't be inferred) + > + > // _ of Container<_> doesn't represent an inferred type argument. + > // (Containing type's type argument won't be inferred) + > Container<_>.Method<_>(arg); > ``` * A method group and type are said to be *partial_inferred* if it contains at least one *inferred_type_argument*. @@ -506,22 +522,26 @@ What other designs have been considered? What is the impact of not doing this? > ```csharp > class P1 > { - > void M() - > { - > new C1<>( ... ); // generic_inferred type C1<> refers to generic type C where the type argument will be inferred from inspecting candidate constructors of C - > new C2<>( ... ); // generic_inferred type C1<> refers to generic type C where the type arguments will be inferred from inspecting candidate constructors of C - > } - > class C1 { ... } - > class C2 { ... } + > void M() + > { + > // generic_inferred type C1<> refers to generic type C where + > // the type argument will be inferred from inspecting candidate constructors of C + > new C1<>( ... ); + > // generic_inferred type C1<> refers to generic type C where + > // the type arguments will be inferred from inspecting candidate constructors of C + > new C2<>( ... ); + > } + > class C1 { ... } + > class C2 { ... } > } > class P2 > { - > void M() - > { - > new C1<>( ... ); // Compile-time error occurs because of ambiguity between C1 and C1 - > } - > class C1 { ... } - > class C1 { ... } + > void M() + > { + > new C1<>( ... ); // Compile-time error occurs because of ambiguity between C1 and C1 + > } + > class C1 { ... } + > class C1 { ... } > } > ``` @@ -607,7 +627,9 @@ What other designs have been considered? What is the impact of not doing this? With the `_` placeholder we would be able to specify more the shape of the variable avoiding unnecessary specification of type arguments. ```csharp - Wrapper<_> wrapper = ... // I get an wrapper, which I'm interested in, but I don't care about the type arguments, because I don't need them in my code. + // I get an wrapper, which I'm interested in, but I don't care about the type arguments, + // because I don't need them in my code. + Wrapper<_> wrapper = ... wrapper.DoSomething( ... ); ``` From 4485ab9ea0bdb6a4c0b04c3575728caf54e1a19d Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sat, 3 Feb 2024 14:42:25 +0100 Subject: [PATCH 17/29] add possible resolutions of _ used as a typename --- proposals/partial-type-inference.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 89ada31dcf..0fe8003a39 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -209,7 +209,13 @@ type_argument + ; ``` -* When a type with `_` identifier is presented in the scope where **inferred_type_argument** is used, a warning should appear since the **inferred_type_argument** hides the type's name causing a breaking change. +* When a type with `_` identifier is presented in the scope where **inferred_type_argument** is used, a warning should appear since the **inferred_type_argument** hides the type's name causing a breaking change. + There are two possible resolutions of this warning. + If the `_` identifier should represent **inferred_type_argument**, the user should suppress the warning or should rename the type or alias declaration. + If the `_` identifier should represent a type, the user should use `@_` to explicitly reference a typename. + +* When there is a type or alias declaration with the `_` identifier, a warning should appear since it is a contextual keyword. + A possible resolution would be to rename the definition or suppress the warning when the declaration can't be renamed. * `_` identifier is considered to represent *inferred_type_argument* when: * It occurs in *type_argument_list* of a method group during method invocation. From 524a4918684eb5b97ef0da0fe43bd67ddcb8681b Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sat, 3 Feb 2024 14:43:54 +0100 Subject: [PATCH 18/29] typo --- proposals/partial-type-inference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 0fe8003a39..864674b06c 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -215,7 +215,7 @@ type_argument If the `_` identifier should represent a type, the user should use `@_` to explicitly reference a typename. * When there is a type or alias declaration with the `_` identifier, a warning should appear since it is a contextual keyword. - A possible resolution would be to rename the definition or suppress the warning when the declaration can't be renamed. + A possible resolution would be to rename the declaration or suppress the warning when the declaration can't be renamed. * `_` identifier is considered to represent *inferred_type_argument* when: * It occurs in *type_argument_list* of a method group during method invocation. From 9dfa24f073118a81555a11db2edda8de4cc6846c Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sat, 3 Feb 2024 18:04:33 +0100 Subject: [PATCH 19/29] make changes regarding type inference more clear --- proposals/partial-type-inference.md | 66 ++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index 864674b06c..adbddae7cc 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -291,7 +291,7 @@ The binding-time processing of an *object_creation_expression* of the form new ` > Specification: Original section changed in the following way -We change the [type inference](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1263-type-inference) as follows. +We replace the [type inference/general](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#12631-general) section with the following section. * Type inference for generic method invocation is performed when the invocation: * Doesn't have a *type_argument_list*. @@ -304,7 +304,9 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra > M, string>( ... ); // Type inference is invoked. > ``` -* **Type inference for constructors** is performed when the generic type of *object_creation_expression*: +* Type inference is applied to each generic method in the method group. + +* Type inference for constructors is performed when the generic type of *object_creation_expression*: * Its *type_argument_list* contains at least one *inferred_type_argument*. > Example > @@ -313,26 +315,59 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra > new C, string>( ... ); // Type inference is invoked. > ``` +* Type inference is applied to each constructor which is contained in the type. + +* When one of the cases appears, a ***type inference*** process attempts to infer type arguments for the call. + The presence of type inference allows a more convenient syntax to be used for calling a generic method or creating an object of a generic type, and allows the programmer to avoid specifying redundant type information. + * In the case of *method type inference*, we infer method type parameters. In the case of *constructor type inference*, we infer type parameters of a type defining the constructors. The previous sentence prohibits inferring type parameters of an outside type that contains the inferred type. (e.g. inference of `new Containing<_>.Nested<_>(42)` is not allowed) -* When the method invocation contains a type argument list containing inferred type argument, the input for type inference is extended as follows: - * We replace each `_` identifier with a new type variable `X`. - * We perform *shape inference* from each type argument to the corresponding type parameter. +* Type inference occurs as part of the binding-time processing of a [method invocation](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12892-method-invocations) or an [object_creation_expression](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#128162-object-creation-expressions) and takes place before the overload resolution step of the invocation. + +* If type inference succeeds, then the inferred type arguments are used to determine the types of arguments for subsequent overload resolution. + If overload resolution chooses a generic method or constructor as the one to invoke, then the inferred type arguments are used as the type arguments for the invocation or for the type containing the constructor. + If type inference for a particular method or constructor fails, that method or constructor does not participate in overload resolution. + The failure of type inference, in and of itself, does not cause a binding-time error. However, it often leads to a binding-time error when overload resolution then fails to find any applicable methods or constructors. + +* Arguments binding + * It can happen that an argument of an expression will be *object_creation_expression*, which needs a target type to be successful binded. + * In these situations, we behave like the type of the argument is unknown and bind it when we will know the target type. + * We treat it in the same manner as an unconverted *new()* operator. + +* If each supplied argument does not correspond to exactly one parameter in the method or constructor [corresponding-parameters](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#12622-corresponding-parameters), or there is a non-optional parameter with no corresponding argument, then inference immediately fails. + +Otherwise, assume that the generic method has the following signature: + +`Tₑ M(T₁ p₁ ... Tₓ pₓ)` + +With a method call of the form `M(E₁ ...Eₓ)` the task of type inference is to find unique type arguments `S₁...Sᵥ` for each of the type parameters `X₁...Xᵥ` so that the call `M(E₁...Eₓ)` becomes valid. + +In case of construtor, assume the following signature: + +`M..ctor(T₁ p₁ ... Tₓ pₓ)` + +With a constructor call of the form `new M<...>(E₁ ...Eₓ)` the task of type inference is to find unique type arguments `S₁...Sᵥ` for each of the type parameters `X₁...Xᵥ` so that the call `new M(E₁...Eₓ)` becomes valid. -* Inputs for **constructor type inference** are constructed as follows: +* The process of type inference is described below as an algorithm. A conformant compiler may be implemented using an alternative approach, provided it reaches the same result in all cases. + +* During the process of inference each type parameter `Xᵢ` is either *fixed* to a particular type `Sᵢ` or *unfixed* with an associated set of *bounds.* Each of the bounds is some type `T`. Initially each type variable `Xᵢ` is unfixed with an empty set of bounds. + +* Type inference takes place in phases. Each phase will try to infer type arguments for more type variables based on the findings of the previous phase. The first phase makes some initial inferences of bounds, whereas the second phase fixes type variables to specific types and infers further bounds. The second phase may have to be repeated a number of times. + +* Additional inferences of method type inference algorithm are made as follows: + * If the inferred method group contains a nonempty *type_argument_list*. + * We replace each `_` identifier with a new type variable `X`. + * We perform *shape inference* from each type argument to the corresponding type parameter. + +* Additional inferences of constructor type inference algorithm are made as follows: * If the inferred type contains a nonempty *type_argument_list*. * We replace each `_` identifier with a new type variable `X`. * We perform *shape inference* from each type argument to the corresponding type parameter. * If the target type should be used based on the expression binding, perform *upper-bound inference* from it to the type containing the constructor * If the expression contains *where* clauses defining type constraints of type parameters of the type containing constructor, for each constraint not representing *constructor* constraint, *reference type constraint*, *value type constraint* and *unmanaged type constraint* perform *lower-bound inference* from the constraint to the corresponding type parameter. -* Arguments binding - * It can happen that an argument of an expression will be *object_creation_expression*, which needs a target type to be successful binded. - * In these situations, we behave like the type of the argument is unknown and bind it when we will know the target type. - * We treat it in the same manner as an unconverted *new()* operator. - #### Type inference algorithm change > Specification: Original section changed in the following way. @@ -394,6 +429,11 @@ We change the [type inference](https://github.com/dotnet/csharpstandard/blob/dra * **`Xᵢ` has a non-empty set of bounds and has at least on bound which doesn't contain any *unfixed* type variable.** * If no such type variables exist and there are still unfixed type variables, type inference fails. * [...] + +* First phase + + * For each of the method **/constructor** arguments `Eᵢ`: + * [...] * Fixing * **An *unfixed* type variable `Xᵢ` with a set of bounds is *fixed* as follows:** @@ -462,6 +502,10 @@ We change the [compile-time checking](https://github.com/dotnet/csharpstandard/b - First, if `F` is a generic method and type arguments were provided, then those ***, that aren't *inferred_type_argument*** are substituted for the type parameters in the parameter list. However, if type arguments were not provided, no such substitution happens. - Then, any parameter whose type is open (i.e., contains a type parameter; see §8.4.3) is elided, along with its corresponding parameter(s). +- **If the method group contains inferred type arguments, a warning should appear since partial type inference is not supported by runtime.** + +We add the following +- If an object creation expression is inferred and the argument list contains a dynamic value, an error should appear since constructor type inference is not supported by runtime. ### Nullability From 7aa277e63f69c12f3bd27cae6c98a7a965fa6958 Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Sat, 3 Feb 2024 22:53:46 +0100 Subject: [PATCH 20/29] remove block quotes --- proposals/partial-type-inference.md | 464 +++++++++++++--------------- 1 file changed, 211 insertions(+), 253 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index adbddae7cc..d4993c81f0 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -1,6 +1,6 @@ # Partial type inference -> Note: This proposal was created because of championed [Partial type inference](https://github.com/dotnet/csharplang/issues/1349). It is a continuation of the proposed second version published in [csharplang/discussions/7467](https://github.com/dotnet/csharplang/discussions/7467) and the first version published in [csharplang/discussions/7286](https://github.com/dotnet/csharplang/discussions/7286) +Note: This proposal was created because of championed [Partial type inference](https://github.com/dotnet/csharplang/issues/1349). It is a continuation of the proposed second version published in [csharplang/discussions/7467](https://github.com/dotnet/csharplang/discussions/7467) and the first version published in [csharplang/discussions/7286](https://github.com/dotnet/csharplang/discussions/7286) ## Summary [summary]: #summary @@ -12,21 +12,17 @@ Partial type inference introduces a syntax skipping inferrable type arguments in and allowing to specify just ambiguous ones. -> Example of skipping inferrable type arguments -> -> ```csharp -> M<_, object>(42, null); -> void M(T1 t1, T2 t2) { ... } -> ``` +```csharp +M<_, object>(42, null); +void M(T1 t1, T2 t2) { ... } +``` It also improves the type inference in the case of *object_creation_expression* by leveraging type bounds obtained from the target and *type_parameter_constraints_clauses*. -> Example of inferring type arguments of type from the initializer list -> -> ```csharp -> using System.Collections.Generic; -> IList temp = new List<_>(); -> ``` +```csharp +using System.Collections.Generic; +IList temp = new List<_>(); +``` Besides the changes described above, the proposal mentions further interactions and possibilities to extend the partial type inference. @@ -37,54 +33,49 @@ Besides the changes described above, the proposal mentions further interactions The first improvement regards the strength of the method type inference, which uses only arguments' types to deduce the method's type arguments. Concretely, we can see the weakness in cases where type arguments only depend on target type or type parameter restrictions. - > Example - > - > ```csharp - > object data = database.fetch(); - > // Error below, method type inference can't infer the return type. We have to specify the type argument. - > int count = data.Field("count"); - > - > public static class Extensions - > { - > public static TReturn Field(this object inst, string fieldName) { ... } - > } - > ``` - > - > ```csharp - > // Error below, method type inference can't infer T. We have to specify all type arguments. - > test(new MyData()); - > - > public void test(U data) where T : TestCaseDefault { ... } - > ``` + ```csharp + object data = database.fetch(); + // Error below, method type inference can't infer the return type. We have to specify the type argument. + int count = data.Field("count"); + + public static class Extensions + { + public static TReturn Field(this object inst, string fieldName) { ... } + } + ``` + + ```csharp + // Error below, method type inference can't infer T. We have to specify all type arguments. + test(new MyData()); + + public void test(U data) where T : TestCaseDefault { ... } + ``` The second improvement regards the "all or nothing" principle, where the method type inference infers either all of the type arguments or nothing. - > Example - > ```csharp - > // Error below, impossible to specify just one type argument. We have to specify all of them. - > log<, object>(new Message( ... ), null); - > - > public void log(T message, U appendix) { ... } - > ``` + ```csharp + // Error below, impossible to specify just one type argument. We have to specify all of them. + log<, object>(new Message( ... ), null); + + public void log(T message, U appendix) { ... } + ``` The first improvement, which would improve the method type inference algorithm, has a significant disadvantage of introducing a breaking change. On the other hand, the second improvement, which would enable specifying some of the method's type arguments, does not influence old code, solves problems regarding the "all or nothing" principle, and reduces the first weakness. - > Example - > - > ```csharp - > // We can use _ to mark type arguments which should be inferred by the compiler. - > test, _>(new MyData()); - > - > public void test(U data) where T : TestCaseDefault { ... } - > ``` - > - > ```csharp - > // We can use _ to mark type arguments which should be inferred by the compiler. - > log<_, object>(new Message( ... ), null); - > - > public void log(T message, U appendix) { ... } - > ``` + ```csharp + // We can use _ to mark type arguments which should be inferred by the compiler. + test, _>(new MyData()); + + public void test(U data) where T : TestCaseDefault { ... } + ``` + + ```csharp + // We can use _ to mark type arguments which should be inferred by the compiler. + log<_, object>(new Message( ... ), null); + + public void log(T message, U appendix) { ... } + ``` * The next motivation is constructor type inference. Method type inference is not defined on *object_creation_expression*, prohibiting taking advantage of type inference. @@ -92,51 +83,46 @@ Besides the changes described above, the proposal mentions further interactions 1. Cases where the method type inference would succeed. - > Example - > - > ```csharp - > public static Wrapper Create(T item) { return new Wrapper(item); } - > - > class Wrapper { public Wrapper(T item) { ... } } - > ``` + + ```csharp + public static Wrapper Create(T item) { return new Wrapper(item); } + + class Wrapper { public Wrapper(T item) { ... } } + ``` 2. Cases where the method type inference would be weak. (Using type info from target type, or type arguments' constraints) - > Example - > - > ```csharp - > // Method type inference can't infer TLogger because it doesn't use type constraints specified by `where` clauses - > var alg = Create(new MyData()); - > - > public static Algorithm Create(TData data) where TLogger : Logger - > { - > return new Algorithm(data); - > } - > - > class Algorithm where TLogger : Logger - > { - > public Algorithm(TData data) { ... } - > } - > ``` + ```csharp + // Method type inference can't infer TLogger because it doesn't use type constraints specified by `where` clauses + var alg = Create(new MyData()); + + public static Algorithm Create(TData data) where TLogger : Logger + { + return new Algorithm(data); + } + + class Algorithm where TLogger : Logger + { + public Algorithm(TData data) { ... } + } + ``` An existing solution can be seen in `Create()` method wrappers of constructors enabling a type inference through method type inference as you can see in the examples above. However, we can't use it with *object_or_collection_initializer*; we are limited by method type inference strength, and it adds unnecessary boiler code. Adding constructor type inference as we will describe in the following section would solve above mentioned examples. - - > Example - > - > ```csharp - > var wrappedData = new Wrapper<_>(new MyData()); - > class Wrapper { public Wrapper(T item) { ... } } - > ``` - > - > ```csharp - > var alg = new Algorithm<_, _>(new MyData()); - > var algWithSpecialLogger = new Algorithm<_ , SpecialLogger<_>>(new MyData()); - > - > class Algorithm where TLogger : Logger { public Algorithm(TData data) { ... }} - > ``` + + ```csharp + var wrappedData = new Wrapper<_>(new MyData()); + class Wrapper { public Wrapper(T item) { ... } } + ``` + + ```csharp + var alg = new Algorithm<_, _>(new MyData()); + var algWithSpecialLogger = new Algorithm<_ , SpecialLogger<_>>(new MyData()); + + class Algorithm where TLogger : Logger { public Algorithm(TData data) { ... }} + ``` No matter how the partial type inference would work, we should be careful about the following things. @@ -154,16 +140,14 @@ So we will want to look ahead to other potential directions, which can be done a ### Grammar -> Specification: Original section changed in the following way - -> We modify [Identifiers](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#643-identifiers) as follows: +We modify [Identifiers](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#643-identifiers) as follows: - The semantics of an identifier named `_` depends on the context in which it appears: - It can denote a named program element, such as a variable, class, or method, or - It can denote a discard (§9.2.9.1). - **It will denote a type argument to be inferred.** -> We modify [Keywords](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#644-keywords) as follows: +We modify [Keywords](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#644-keywords) as follows: * A ***contextual keyword*** is an identifier-like sequence of characters that has special meaning in certain contexts, but is not reserved, and can be used as an identifier outside of those contexts as well as when prefaced by the `@` character. @@ -181,9 +165,7 @@ So we will want to look ahead to other potential directions, which can be done a ### Type arguments -> Specification: Original section changed in the following way - -> We change [type arguments](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/types.md#842-type-arguments) section as follows: +We change [type arguments](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/types.md#842-type-arguments) section as follows: * ***inferred_type_argument*** represents an unknown type, which will be resolved during type inference. @@ -221,27 +203,24 @@ type_argument * It occurs in *type_argument_list* of a method group during method invocation. * It occurs in *type_argument_list* of a type in *object_creation_expression*. * It occurs as an arbitrary nested identifier in the expressions mentioned above. - > Example - > - > ```csharp - > F<_, int>( ... ); // _ represents an inferred type argument. - > new C<_, int>( ... ); // _ represents an inferred type argument. - > F, int>( ... ); // _ represents an inferred type argument. - > new C, int>( ... ); // _ represents an inferred type argument. - > C<_> temp = ...; // _ doesn't represent an inferred type argument. - > new _( ... ) // _ doesn't represent an inferred type argument. - > - > // _ of Container<_> doesn't represent an inferred type argument. - > // (Containing type's type argument won't be inferred) - > Container<_>.Method<_>(arg); - > ``` + + ```csharp + F<_, int>( ... ); // _ represents an inferred type argument. + new C<_, int>( ... ); // _ represents an inferred type argument. + F, int>( ... ); // _ represents an inferred type argument. + new C, int>( ... ); // _ represents an inferred type argument. + C<_> temp = ...; // _ doesn't represent an inferred type argument. + new _( ... ) // _ doesn't represent an inferred type argument. + + // _ of Container<_> doesn't represent an inferred type argument. + // (Containing type's type argument won't be inferred) + Container<_>.Method<_>(arg); + ``` * A method group and type are said to be *partial_inferred* if it contains at least one *inferred_type_argument*. ### Method invocations -> Specification: Original section changed in the following way - The binding-time processing of a [method invocation](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12892-method-invocations) of the form `M(A)`, where `M` is a method group (possibly including a *type_argument_list*), and `A` is an optional *argument_list* is changed in the following way. The initial set of candidate methods for is changed by adding new condition. @@ -261,11 +240,9 @@ The initial set of candidate methods for is changed by adding new condition. ### Object creation expressions -> Specification: Original section changed in the following way - The binding-time processing of an [*object_creation_expression*](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#128162-object-creation-expressions) of the form new `T(A)`, where `T` is a *class_type*, or a *value_type*, and `A` is an optional *argument_list*, is changed in the following way. -> Note: Type inference of constructor is described later in the type inference section. +Note: Type inference of constructor is described later in the type inference section. The binding-time processing of an *object_creation_expression* of the form new `T(A)`, where `T` is a *class_type*, or a *value_type*, and `A` is an optional *argument_list*, consists of the following steps: @@ -289,31 +266,27 @@ The binding-time processing of an *object_creation_expression* of the form new ` ### Type inference -> Specification: Original section changed in the following way - We replace the [type inference/general](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#12631-general) section with the following section. * Type inference for generic method invocation is performed when the invocation: * Doesn't have a *type_argument_list*. * The type argument list contains at least one *inferred_type_argument*. - > Example - > - > ```csharp - > M( ... ); // Type inference is invoked. - > M<_, string>( ... ); // Type inference is invoked. - > M, string>( ... ); // Type inference is invoked. - > ``` + + ```csharp + M( ... ); // Type inference is invoked. + M<_, string>( ... ); // Type inference is invoked. + M, string>( ... ); // Type inference is invoked. + ``` * Type inference is applied to each generic method in the method group. * Type inference for constructors is performed when the generic type of *object_creation_expression*: * Its *type_argument_list* contains at least one *inferred_type_argument*. - > Example - > - > ```csharp - > new C<_, string>( ... ); // Type inference is invoked. - > new C, string>( ... ); // Type inference is invoked. - > ``` + + ```csharp + new C<_, string>( ... ); // Type inference is invoked. + new C, string>( ... ); // Type inference is invoked. + ``` * Type inference is applied to each constructor which is contained in the type. @@ -337,31 +310,30 @@ We replace the [type inference/general](https://github.com/dotnet/csharpstandard * We treat it in the same manner as an unconverted *new()* operator. * If each supplied argument does not correspond to exactly one parameter in the method or constructor [corresponding-parameters](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#12622-corresponding-parameters), or there is a non-optional parameter with no corresponding argument, then inference immediately fails. + Otherwise, assume that the generic method has the following signature: -Otherwise, assume that the generic method has the following signature: + `Tₑ M(T₁ p₁ ... Tₓ pₓ)` -`Tₑ M(T₁ p₁ ... Tₓ pₓ)` + With a method call of the form `M<...>(E₁ ...Eₓ)` the task of type inference is to find unique type arguments `S₁...Sᵥ` for each of the type parameters `X₁...Xᵥ` so that the call `M(E₁...Eₓ)` becomes valid. -With a method call of the form `M(E₁ ...Eₓ)` the task of type inference is to find unique type arguments `S₁...Sᵥ` for each of the type parameters `X₁...Xᵥ` so that the call `M(E₁...Eₓ)` becomes valid. - -In case of construtor, assume the following signature: + In case of construtor, assume the following signature: -`M..ctor(T₁ p₁ ... Tₓ pₓ)` + `M..ctor(T₁ p₁ ... Tₓ pₓ)` -With a constructor call of the form `new M<...>(E₁ ...Eₓ)` the task of type inference is to find unique type arguments `S₁...Sᵥ` for each of the type parameters `X₁...Xᵥ` so that the call `new M(E₁...Eₓ)` becomes valid. + With a constructor call of the form `new M<...>(E₁ ...Eₓ)` the task of type inference is to find unique type arguments `S₁...Sᵥ` for each of the type parameters `X₁...Xᵥ` so that the call `new M(E₁...Eₓ)` becomes valid. * The process of type inference is described below as an algorithm. A conformant compiler may be implemented using an alternative approach, provided it reaches the same result in all cases. -* During the process of inference each type parameter `Xᵢ` is either *fixed* to a particular type `Sᵢ` or *unfixed* with an associated set of *bounds.* Each of the bounds is some type `T`. Initially each type variable `Xᵢ` is unfixed with an empty set of bounds. +* During the process of inference each type variable `Xᵢ` is either *fixed* to a particular type `Sᵢ` or *unfixed* with an associated set of *bounds.* Each of the bounds is some type `T`. Initially each type variable `Xᵢ` is unfixed with an empty set of bounds. * Type inference takes place in phases. Each phase will try to infer type arguments for more type variables based on the findings of the previous phase. The first phase makes some initial inferences of bounds, whereas the second phase fixes type variables to specific types and infers further bounds. The second phase may have to be repeated a number of times. -* Additional inferences of method type inference algorithm are made as follows: +* Additional changes of method type inference algorithm are made as follows: * If the inferred method group contains a nonempty *type_argument_list*. * We replace each `_` identifier with a new type variable `X`. * We perform *shape inference* from each type argument to the corresponding type parameter. -* Additional inferences of constructor type inference algorithm are made as follows: +* Additional changes of constructor type inference algorithm are made as follows: * If the inferred type contains a nonempty *type_argument_list*. * We replace each `_` identifier with a new type variable `X`. * We perform *shape inference* from each type argument to the corresponding type parameter. @@ -370,7 +342,7 @@ With a constructor call of the form `new M<...>(E₁ ...Eₓ)` the task of type #### Type inference algorithm change -> Specification: Original section changed in the following way. +We change the type inference algorithm contained in the [type inference](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1263-type-inference) section as follows and continue with an explanation of the changes at the end. * Shape dependence * An *unfixed* type variable `Xᵢ` *shape-depends directly on* an *unfixed* type variable `Xₑ` if `Xₑ` represents *inferred_type_argument* and it is contained in *shape bound* of the type variable `Xᵢ`. @@ -440,64 +412,53 @@ With a constructor call of the form `new M<...>(E₁ ...Eₓ)` the task of type * **If the type variable has a shape bound, check the type has no conflicts with other bounds of that type variable in the same way as the standard says. It it has no conflicts, the type variable is *fixed* to that type. Otherwise type inference failed.** * Otherwise, fix it as the standard says. -> Explanation of inference improvements -> -> Now, the inferred bounds can contain other unfixed type variables. -> So we have to propagate the type info also through these bounds. -> -> > Example -> > -> > ```csharp -> > void F (T1 p1) { ... } -> > ... -> > F>(new List()); -> > ``` -> > We have now two type variables `T1` and `_`. From the first bound, we get that `IList<_>` is a shape bound of `T1`(Ignore now the type of bound, it would be the same in other types of bound). -> > When we investigate the second bound `List`, we will figure out that it would be a lower bound of `T1`. -> > But now, we have to somehow propagate the int type to the `_` type variable, because it relates to it. -> > That means, in the process of adding new bounds, we have to also propagate this info through bounds, which contain unfixed type variables. -> > In this case, we do additional inference of `IList<_>` and `List` yielding exact bound `int` of `_`. - -> Explanation of type-dependence -> -> Type-dependence is required because, till this time when a type variable had any bounds, it didn't contain any unfixed type variable. -> It was important because we could do the parameter fixation, where we work with exact types(not unfixed type variables). -> However now, the type variable can contain bounds containing unfixed type variables. -> We have to ensure that we will not start fixing the type variable till these unfixed type variables are unfixed(In some cases, we can be in a situation, where this dependency will form a cycle. In this case, we will allow the fixation earlier). -> -> > Example -> > -> > We use the previous example. -> > After the first phase. `T1` has bounds `IList<_>` and `List`. `_` has bound `int`. -> > In this situation, we can't start to fix `T1` because `_` is not fixed yet. -> > `T1` is type-dependent on `_`. -> > So, we will first fix `_`, which becomes `int`. -> > Then, `T1` is not type-dependent anymore, because all bounds don't contain any unfixed type variables. -> > `IList<_>` is now `IList` after the `_` fixation. -> > We can fix `T1` now. - -> Explanation of shape-dependence -> -> A similar thing is for shape-dependence. -> Although it cares about bounds received from the type argument list. -> We want a shape bound to be exact (not containing any unfixed type variables) because it is later important for the fixation. -> An intention is to keep the exact form of the given hint(`IList<_>`). -> -> > Example -> > -> > Given `IList<_>` as a type argument, when we treat nullability, we want the hinted type parameter to be non-nullable(not `IList<_>?`). -> > It can happen, other bounds would infer the nullable version, and although `IList<_>` can be converted to `IList<_>?`, it is not the user's intention. - - -> Explanation of inference restriction during constructor type inference -> -> Because performing type inference can even take exponential time when a type system contains overloading, the restriction was made above to avoid it. -> It regards binding arguments before the overload resolution when we bind all *object_creation_expressions* without target info and then in case of overload resolution success and some of these arguments failed in the binding, we try to bind it again with already known target type information. +* Explanation of inference improvements + * Now, the inferred bounds can contain other unfixed type variables. + So we have to propagate the type info also through these bounds. + + ```csharp + void F (T1 p1) { ... } + ... + F>(new List()); + ``` + + * Consider an example above. + We have now two type variables `T1` and `_`. From the first bound, we get that `IList<_>` is a shape bound of `T1`(Ignore now the type of bound, it would be the same in other types of bound). + When we investigate the second bound `List`, we will figure out that it would be a lower bound of `T1`. + But now, we have to somehow propagate the int type to the `_` type variable, because it relates to it. + That means, in the process of adding new bounds, we have to also propagate this info through bounds, which contain unfixed type variables. + In this case, we do additional inference of `IList<_>` and `List` yielding exact bound `int` of `_`. + +* Explanation of type-dependence + * Type-dependence is required because, till this time when a type variable had any bounds, it didn't contain any unfixed type variable. + It was important because we could do the parameter fixation, where we work with exact types(not unfixed type variables). + However now, the type variable can contain bounds containing unfixed type variables. + We have to ensure that we will not start fixing the type variable till these unfixed type variables are unfixed(In some cases, we can be in a situation, where this dependency will form a cycle. In this case, we will allow the fixation earlier). + + * We use the previous example. + After the first phase. `T1` has bounds `IList<_>` and `List`. `_` has bound `int`. + In this situation, we can't start to fix `T1` because `_` is not fixed yet. + `T1` is type-dependent on `_`. + So, we will first fix `_`, which becomes `int`. + Then, `T1` is not type-dependent anymore, because all bounds don't contain any unfixed type variables. + `IList<_>` is now `IList` after the `_` fixation. + We can fix `T1` now. + +* Explanation of shape-dependence + * A similar thing is for shape-dependence. + Although it cares about bounds received from the type argument list. + We want a shape bound to be exact (not containing any unfixed type variables) because it is later important for the fixation. + An intention is to keep the exact form of the given hint(`IList<_>`). + + * Example: Given `IList<_>` as a type argument, when we treat nullability, we want the hinted type parameter to be non-nullable(not `IList<_>?`). + It can happen, other bounds would infer the nullable version, and although `IList<_>` can be converted to `IList<_>?`, it is not the user's intention. + +* Explanation of inference restriction during constructor type inference + * Because performing type inference can even take exponential time when a type system contains overloading, the restriction was made above to avoid it. + It regards binding arguments before the overload resolution when we bind all *object_creation_expressions* without target info and then in case of overload resolution success and some of these arguments failed in the binding, we try to bind it again with already known target type information. ### Compile-time checking of dynamic member invocation -> Specification: Original section changed in the following way - We change the [compile-time checking](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1265-compile-time-checking-of-dynamic-member-invocation) in order to be useful during partial type inferece. - First, if `F` is a generic method and type arguments were provided, then those ***, that aren't *inferred_type_argument*** are substituted for the type parameters in the parameter list. However, if type arguments were not provided, no such substitution happens. @@ -509,14 +470,16 @@ We add the following ### Nullability -> Specification: New section +We add the following section about nullability. -We can use an examination mark `?` to say that the inferred type argument should be a nullable type (e.g. `F<_?>(...)`). +* We can use an examination mark `?` to say that the inferred type argument should be a nullable type (e.g. `F<_?>(...)`). ## Drawbacks [drawbacks]: #drawbacks -Difference between constructor type inference and method type inference could confuse the programmer. +Difference between constructor type inference and method type inference could confuse the programmer. +However, a generic type doesn't usually have constructors containing parameters which types contain all type parameters of the generic type. +So original method type inference would be too weak to infer the type arguments of the generic type. ## Alternatives [alternatives]: #alternatives @@ -551,49 +514,45 @@ What other designs have been considered? What is the impact of not doing this? * A type is said to be *generic_inferred* when all the following hold: * It has an empty *type_argument_list*. * It occurs as a *type* of *object_creation_expression*. - > Example - > - > ```csharp - > new C<>(...) // Valid code, C is generic_inferred. - > new C>(...) // Invalid code, C nor G are generic_inferred. - > F<>(...) // Invalid code, F isn't generic_inferred. - > ``` + + ```csharp + new C<>(...) // Valid code, C is generic_inferred. + new C>(...) // Invalid code, C nor G are generic_inferred. + F<>(...) // Invalid code, F isn't generic_inferred. + ``` ### Namespace and type names - > Specification: Original section changed in the following way - - Determining the [meaning](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/basic-concepts.md#781-general) of a *namespace_or_type_name* is changed as follow. + Determining the [meaning](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/basic-concepts.md#781-general) of a *namespace_or_type_name* is changed as follows. * If a type is a *generic_inferred*, then we resolve the identifier in the same manner except ignoring the arity of type parameters (Types of arity 0 is ignored). If there is an ambiguity in the current scope, a compilation-time error occurs. - > Example - > - > ```csharp - > class P1 - > { - > void M() - > { - > // generic_inferred type C1<> refers to generic type C where - > // the type argument will be inferred from inspecting candidate constructors of C - > new C1<>( ... ); - > // generic_inferred type C1<> refers to generic type C where - > // the type arguments will be inferred from inspecting candidate constructors of C - > new C2<>( ... ); - > } - > class C1 { ... } - > class C2 { ... } - > } - > class P2 - > { - > void M() - > { - > new C1<>( ... ); // Compile-time error occurs because of ambiguity between C1 and C1 - > } - > class C1 { ... } - > class C1 { ... } - > } - > ``` + + ```csharp + class P1 + { + void M() + { + // generic_inferred type C1<> refers to generic type C where + // the type argument will be inferred from inspecting candidate constructors of C + new C1<>( ... ); + // generic_inferred type C1<> refers to generic type C where + // the type arguments will be inferred from inspecting candidate constructors of C + new C2<>( ... ); + } + class C1 { ... } + class C2 { ... } + } + class P2 + { + void M() + { + new C1<>( ... ); // Compile-time error occurs because of ambiguity between C1 and C1 + } + class C1 { ... } + class C1 { ... } + } + ``` ### Object creation expressions @@ -615,24 +574,23 @@ What other designs have been considered? What is the impact of not doing this? * Type inference for constructors is performed when the generic type of *object_creation_expression*: * **Has a diamond operator.** * Its *type_argument_list* contains at least one *inferred_type_argument*. - > Example - > - > ```diff - > +new C<>( ... ); // Type inference is invoked. - > new C<_, string>( ... ); // Type inference is invoked. - > new C, string>( ... ); // Type inference is invoked. - > ``` + + ```diff + +new C<>( ... ); // Type inference is invoked. + new C<_, string>( ... ); // Type inference is invoked. + new C, string>( ... ); // Type inference is invoked. + ``` * Alternative: Allowing object initializers contribute to the inference in object creation: - > Example of inferring type arguments of type from the initializer list - > - > ```csharp - > using System.Collections.Generic; - > var statistics = new Dictionary<,>(){["Joe"] = 20}; // Inferred: Dictionary - > ``` + Example of inferring type arguments of type from the initializer list: + + ```csharp + using System.Collections.Generic; + var statistics = new Dictionary<,>(){["Joe"] = 20}; // Inferred: Dictionary + ``` Reason not to do that: Maybe much controversial ? @@ -644,7 +602,7 @@ What other designs have been considered? What is the impact of not doing this? ... - * Inputs for **constructor type inference** are constructed as follows: + * Additional changes of constructor type inference algorithm are made as follows: * ... * If the expression contains an *object_initializer_list*, for each *initializer_element* of the list perform *lower-bound inference* from the type of the element to the type of *initializer_target*. If the binding of the element fails, skip it. * If the expression contains a *collection_initializer_list* and the type doesn't have overloads of the `Add` method, for each *initializer_element* of the list perform *lower-bound inference* from the types of the elements contained in the *initializer_element* to the types of the method's parameters. If the binding of any element fails, skip it. @@ -844,7 +802,7 @@ var[] temp = ... ``` 5. State of the art. - + ```csharp var temp = ... ``` From 87cc926ef56eeba8d4ea7f39ec0af1fe1ddf6b17 Mon Sep 17 00:00:00 2001 From: Tomas Husak <49864539+TomatorCZ@users.noreply.github.com> Date: Wed, 13 Mar 2024 16:24:55 +0100 Subject: [PATCH 21/29] Update proposals/partial-type-inference.md Co-authored-by: Rikki Gibson --- proposals/partial-type-inference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index d4993c81f0..c0ffbc9601 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -472,7 +472,7 @@ We add the following We add the following section about nullability. -* We can use an examination mark `?` to say that the inferred type argument should be a nullable type (e.g. `F<_?>(...)`). +* We can use a question mark `?` to say that the inferred type argument should be a nullable type (e.g. `F<_?>(...)`). ## Drawbacks [drawbacks]: #drawbacks From 98d835e3dcbd67efbb7ae1c87cf54ac19578d68f Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Wed, 13 Mar 2024 15:42:16 +0100 Subject: [PATCH 22/29] move nullability to type arguments section --- proposals/partial-type-inference.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index c0ffbc9601..c791a96f21 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -217,6 +217,8 @@ type_argument Container<_>.Method<_>(arg); ``` +* We can use a question mark `?` to say that the inferred type argument should be a nullable type (e.g. `F<_?>(...)`). + * A method group and type are said to be *partial_inferred* if it contains at least one *inferred_type_argument*. ### Method invocations @@ -468,12 +470,6 @@ We change the [compile-time checking](https://github.com/dotnet/csharpstandard/b We add the following - If an object creation expression is inferred and the argument list contains a dynamic value, an error should appear since constructor type inference is not supported by runtime. -### Nullability - -We add the following section about nullability. - -* We can use a question mark `?` to say that the inferred type argument should be a nullable type (e.g. `F<_?>(...)`). - ## Drawbacks [drawbacks]: #drawbacks From e9b827b18a70ca365ff6ac8cd5db81d1c16b6d69 Mon Sep 17 00:00:00 2001 From: Tomas Husak Date: Wed, 13 Mar 2024 15:45:41 +0100 Subject: [PATCH 23/29] fix code example --- proposals/partial-type-inference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/partial-type-inference.md b/proposals/partial-type-inference.md index c791a96f21..a8cc65437a 100644 --- a/proposals/partial-type-inference.md +++ b/proposals/partial-type-inference.md @@ -65,7 +65,7 @@ Besides the changes described above, the proposal mentions further interactions ```csharp // We can use _ to mark type arguments which should be inferred by the compiler. - test, _>(new MyData()); + test, _>(new MyData()); public void test(U data) where T : TestCaseDefault { ... } ``` From be609af81dbd71988dcc0f219299bb3066b89e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hus=C3=A1k?= Date: Tue, 16 Apr 2024 18:46:27 +0200 Subject: [PATCH 24/29] Add proposal containing just 1. top level _ in method inv and creating object expr 2. nested _ in method inv and creating object expr --- proposals/partial-type-inference-v2.md | 398 +++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 proposals/partial-type-inference-v2.md diff --git a/proposals/partial-type-inference-v2.md b/proposals/partial-type-inference-v2.md new file mode 100644 index 0000000000..3971329fc2 --- /dev/null +++ b/proposals/partial-type-inference-v2.md @@ -0,0 +1,398 @@ +# Partial type inference + +## Summary +[summary]: #summary + +Partial type inference introduces a syntax skipping inferrable type arguments in the argument list of + +1. *invocation_expression* +2. *object_creation_expresssion* + +and allowing to specify just ambiguous ones. + +An example of the partial type inference in *invocation_expression* can be seen below where the first type argument of the `M` method call is inferred by the compiler and the second type parameter is given in the type argument list. + +```csharp +void M(T1 t1) { ... } +... +M<_, int>("text"); // Inferred as void M(string) +``` + +An example of partial type inference in *object_creation_expression* can be seen below where the first type argument of the `C` object is inferred by the compiler and the second type parameter is given in the type argument list. + + +```csharp +class C +{ + public C(T1 p1) { ... } +} +... +new C<_, int>("text"); // Inferred as C.ctor(string) +``` + +## Motivation + +Skipped since it was given in the previous document. + +## Detailed design + +### Grammar + +We modify [Identifiers](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#643-identifiers) as follows: + +- The semantics of an identifier named `_` depends on the context in which it appears: + - It can denote a named program element, such as a variable, class, or method, or + - It can denote a discard (§9.2.9.1)**, or** + - **It can denote a type to be inferred (See [Types/Inferred Type] section).** + +We modify [Keywords](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/lexical-structure.md#644-keywords) as follows: + +* A ***contextual keyword*** is an identifier-like sequence of characters that has special meaning in certain contexts, but is not reserved, and can be used as an identifier outside of those contexts as well as when prefaced by the `@` character. + + ```diff + contextual_keyword + : 'add' | 'alias' | 'ascending' | 'async' | 'await' + | 'by' | 'descending' | 'dynamic' | 'equals' | 'from' + | 'get' | 'global' | 'group' | 'into' | 'join' + | 'let' | 'nameof' | 'on' | 'orderby' | 'partial' + | 'remove' | 'select' | 'set' | 'unmanaged' | 'value' + + | 'var' | 'when' | 'where' | 'yield' | '_' + - | 'var' | 'when' | 'where' | 'yield' + ; + ``` + +### Types + +We change the [8.1. General](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/types.md#81-general) section in the following way. + +```diff +type + : reference_type + | value_type + | type_parameter + | pointer_type // unsafe code support ++ | inferred_type_placeholder + ; + ++inferred_type_placeholder ++ : '_' ++ ; +``` + +*inferred_type_placeholder* can be only used in the *constructor type inference* and the *method type inference*. (See the [Type Inference] section) + +We change the [8.2.1 General](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/types.md#821-general) section in the following way. + +```diff +non_array_type + : value_type + | class_type + | interface_type + | delegate_type + | 'dynamic' + | type_parameter + | pointer_type // unsafe code support ++ | inferred_type_placeholder + ; +``` + +We add the following new section to the [Types](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/types.md#8-types) section. + +#### Inferred types + +Inferred types represent a user explicitly written types, which have to be resolved during type inference. + +Currently, inferred types can be used only in: +* a type argument of *invocation_expression* during method type inference +* a type of *object_creation_expression* during constructor type inference. This type can be only a generic type which is *partial_inferred_type*. + +```csharp +F<_, int>( ... ); // _ represents an inferred type. +new C<_, int>( ... ); // C<_, int> represents an inferred type. +F, int>( ... ); // C<_> represents an inferred type. +new C, int>( ... ); // C, int> represents an inferred type. +C<_> temp = ...; // _ nor C<_> doesn't represent an inferred type. +new _( ... ); // _ doesn't represent an inferred type. +new _[ ... ]; // _[ ... ] doesn't represent an inferred type. +new _?( ... ); // _? doesn't represent an inferred type. + +// Container<_> doesn't represent an inferred type. +// (Containing type's or method's type won't be inferred) +Container<_>.Method<_>(arg); +new Container<_>.Type<_>(arg); +``` + +* *inferred_type_placeholder* represents an unknown type denoted by the `_` identifier, which will be resolved during type inference. + +* *partial_inferred_type* represents a type which contains *inferred_type_placeholder* in its syntax but it is not *inferred_type_placeholder*. +The contained uknown type(s) are resolved during type inference assembling closed type. + +```csharp +_ // inferred_type_placeholder, but not partial_inferred_type +_? // partial_inferred_type, but not inferred_type +List<_> // partial_inferred_type, but not inferred_type +_[] //partial_inferred_type, but not inferred_type +``` + +* We can use a question mark `?` to say that the inferred type should be a nullable type (e.g. `F<_?>(...)`). + +* A method group is said to be *partial_inferred* if it contains at least one *inferred_type_placeholder* or *partial_inferred_type* in its type argument list. + +* A type is said to be *partial_inferred* if it is *inferred_type_placeholder* or *partial_inferred_type*. + +* When a type with the `_` identifier is presented in the scope where *inferred_type_placeholder* is used, a warning should appear since the *inferred_type_placeholder* hides the type's name causing a breaking change. +There are two possible resolutions of this warning. +If the `_` identifier should represent *inferred_type_placeholder*, the user should suppress the warning or should rename the type or alias declaration. +If the `_` identifier should represent a type, the user should use `@_` to explicitly reference a typename. + +* When there is a type or alias declaration with the `_` identifier, a warning should appear since it is a contextual keyword. +A possible resolution would be to rename the declaration or suppress the warning when the declaration can't be renamed. + +### Method invocations + +The binding-time processing of a [method invocation](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12892-method-invocations) of the form `M(A)`, where `M` is a method group (possibly including a *type_argument_list*), and `A` is an optional *argument_list* is changed in the following way. + +The initial set of candidate methods for is changed by adding new condition. + +- If `F` is non-generic, `F` is a candidate when: + - `M` has no type argument list, and + - `F` is applicable with respect to `A` (§12.6.4.2). +- If `F` is generic and `M` has no type argument list, `F` is a candidate when: + - Type inference (§12.6.3) succeeds, inferring a list of type arguments for the call, and + - Once the inferred type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` (§12.6.4.2) +- **If `F` is generic and `M` has a type argument list containing at least one *inferred_type* or *partial_inferred_type*, `F` is a candidate when:** + - **Method type inference (See the [Type Inference] section) succeeds, inferring the type arguments list for the call, and** + - **Once the inferred type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member))** +- If `F` is generic and `M` includes a type argument list, `F` is a candidate when: + - `F` has the same number of method type parameters as were supplied in the type argument list, and + - Once the type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints (§8.4.5), and the parameter list of `F` is applicable with respect to `A` (§12.6.4.2). + +### Object creation expressions + +The binding-time processing of an [*object_creation_expression*](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#128162-object-creation-expressions) of the form new `T(A)`, where `T` is a *class_type*, or a *value_type*, and `A` is an optional *argument_list*, is changed in the following way. + +The binding-time processing of an *object_creation_expression* of the form new `T(A)`, where `T` is a *class_type*, or a *value_type*, and `A` is an optional *argument_list*, consists of the following steps: + +- If `T` is a *value_type* and `A` is not present: + - **The *object_creation_expression* is a default constructor invocation.** + - **If the type is *partially_inferred*, Constructor type inference (See the [Type Inference] section) of the default constructor occurs to determine closed type. If it succeeded, construct the type using the inferred type arguments. If it failed the binding-time error occurs.** + - **If the type inference above succeeded or the type is not inferred, the result of the *object_creation_expression* is a value of (constructed) type `T`, namely the default value for `T` as defined in [§8.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#833-default-constructors).** +- Otherwise, if `T` is a *type_parameter* and `A` is not present: + - If no value type constraint or constructor constraint (§15.2.5) has been specified for `T`, a binding-time error occurs. + - The result of the *object_creation_expression* is a value of the run-time type that the type parameter has been bound to, namely the result of invoking the default constructor of that type. The run-time type may be a reference type or a value type. +- Otherwise, if `T` is a *class_type* or a *struct_type*: + - If `T` is an abstract or static *class_type*, a compile-time error occurs. + - **The instance constructor to invoke is determined using the overload resolution rules of [§12.6.4](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1264-overload-resolution). The set of candidate instance constructors is determined as follows:** + - **`T` is not inferrred (*partially_inferred*), the constructor is accessible in `T`, and is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** + - **If `T` is *partially_constructed* and the constructor is accessible in `T`, partial constructor type inference of the constructor is performed. Once the type arguments are inferred and substituted for the corresponding type parameters, all constructed types in the parameter list of the constructor satisfy their constraints, and the parameter list of the constructor is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** + - A binding-time error occurs when: + - The set of candidate instance constructors is empty, or if a single best instance constructor cannot be identified. + - The result of the *object_creation_expression* is a value of type `T`, namely the value produced by invoking the instance constructor determined in the two steps above. + - Otherwise, the *object_creation_expression* is invalid, and a binding-time error occurs. + +### Type inference + +We replace the [type inference/general](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#12631-general) section with the following section. + +* Type inference for generic method invocation is performed when the invocation: + * Doesn't have a *type_argument_list*. + * The type argument list contains at least one *partial_inferred* type. + + ```csharp + M( ... ); // Type inference is invoked. + M<_, string>( ... ); // Type inference is invoked. + M, string>( ... ); // Type inference is invoked. + ``` + +* Type inference is applied to each generic method in the method group. + +* Type inference for constructors is performed when the generic type of *object_creation_expression* is *partially_inferred*: + + ```csharp + new C<_, string>( ... ); // Type inference is invoked. + new C, string>( ... ); // Type inference is invoked. + ``` + +* Type inference is applied to each constructor which is contained in the type. + +* When one of the cases appears, a ***type inference*** process attempts to infer type arguments for the call or type. + The presence of type inference allows a more convenient syntax to be used for calling a generic method or creating an object of a generic type, and allows the programmer to avoid specifying redundant type information. + +* In the case of *method type inference*, we infer method type parameters. + In the case of *constructor type inference*, we infer type parameters of a type defining the constructors. + The previous sentence prohibits inferring type parameters of an outside type that contains the inferred type. (e.g. inference of `new Containing<_>.Nested<_>(42)` is not allowed) + +* Type inference occurs as part of the binding-time processing of a [method invocation](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12892-method-invocations) or an [object_creation_expression](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#128162-object-creation-expressions) and takes place before the overload resolution step of the invocation. + +* If type inference succeeds, then the inferred type arguments are used to determine the types of arguments for subsequent overload resolution. + If overload resolution chooses a generic method or constructor as the one to invoke, then the inferred type arguments are used as the type arguments for the invocation or for the type containing the constructor. + If type inference for a particular method or constructor fails, that method or constructor does not participate in overload resolution. + The failure of type inference, in and of itself, does not cause a binding-time error. However, it often leads to a binding-time error when overload resolution then fails to find any applicable methods or constructors. + +* If each supplied argument does not correspond to exactly one parameter in the method or constructor [corresponding-parameters](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#12622-corresponding-parameters), or there is a non-optional parameter with no corresponding argument, then inference immediately fails. + Otherwise, assume that the generic method has the following signature: + + `Tₑ M(T₁ p₁ ... Tₓ pₓ)` + + With a method call of the form `M<...>(E₁ ...Eₓ)` the task of type inference is to find unique type arguments `S₁...Sᵥ` for each of the type parameters `X₁...Xᵥ` so that the call `M(E₁...Eₓ)` becomes valid. + + In case of construtor, assume the following signature: + + `M..ctor(T₁ p₁ ... Tₓ pₓ)` + + With a constructor call of the form `new M<...>(E₁ ...Eₓ)` the task of type inference is to find unique type arguments `S₁...Sᵥ` for each of the type parameters `X₁...Xᵥ` so that the call `new M(E₁...Eₓ)` becomes valid. + +* The process of type inference is described below as an algorithm. A conformant compiler may be implemented using an alternative approach, provided it reaches the same result in all cases. + +* During the process of inference each type variable `Xᵢ` is either *fixed* to a particular type `Sᵢ` or *unfixed* with an associated set of *bounds.* Each of the bounds is some type `T`. Initially each type variable `Xᵢ` is unfixed with an empty set of bounds. + +* Type inference takes place in phases. Each phase will try to infer types for more type variables based on the findings of the previous phase. The first phase makes some initial inferences of bounds, whereas the second phase fixes type variables to specific types and infers further bounds. The second phase may have to be repeated a number of times. + +* Additional changes of method type inference algorithm are made as follows: + * If the inferred method group contains a nonempty *type_argument_list*. + * We replace each *inferred_type_placeholder* with a new type variable `X`. + * We perform *shape inference* from each type argument to the corresponding type parameter. + +* Additional changes of constructor type inference algorithm are made as follows: + * If the inferred type contains a nonempty *type_argument_list*. + * We replace each *inferred_type_placeholder* with a new type variable `X`. + * We perform *shape inference* from each type argument to the corresponding type parameter. + +#### Type inference algorithm change + +We change the type inference algorithm contained in the [type inference](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1263-type-inference) section as follows and continue with an explanation of the changes at the end. + +* Shape dependence + * An *unfixed* type variable `Xᵢ` *shape-depends directly on* an *unfixed* type variable `Xₑ` if `Xₑ` represents *inferred_type_argument* and it is contained in *shape bound* of the type variable `Xᵢ`. + * `Xₑ` *shape-depends on* `Xᵢ` if `Xₑ` *shape-depends directly on* `Xᵢ` or if `Xᵢ` *shape-depends directly on* `Xᵥ` and `Xᵥ` *shape-depends on* `Xₑ`. Thus “*shape-depends on*” is the transitive but not reflexive closure of “*shape-depends directly on*”. + +* Type dependence + * An *unfixed* type variable `Xᵢ` *type-depends directly on* an *unfixed* type variable `Xₑ` if `Xₑ` occurs in any bound of type variable `Xᵢ`. + * `Xₑ` *type-depends on* `Xᵢ` if `Xₑ` *type-depends directly on* `Xᵢ` or if `Xᵢ` *type-depends directly on* `Xᵥ` and `Xᵥ` *type-depends on* `Xₑ`. Thus “*type-depends on*” is the transitive but not reflexive closure of “*type-depends directly on*”. + +* Shape inference + * A *shape* inference from a type `U` to a type `V` is made as follows: + * If `V` is one of the *unfixed* `Xᵢ` then `U` is a shape bound of `V`. + * When a shape bound `U` of `V` is set: + * We perform *upper-bound* inference from `U` to all lower-bounds of `V`, which contains an unfixed type variable + * We perform *exact* inference from `U` to all exact-bounds of `V`, which contains an unfixed type variable. + * We perform *lower-bound* inference from `U` to all upper-bounds of `V`, which contains an unfixed type variable. + * We perform *lower-bound* inference from all lower-bounds of `V` to `U` if `U` contains an unfixed type variable. + * We perform *exact* inference from all exact-bounds of `V` to `U` if `U` contains unfixed type variable. + * We perform *upper-type* inference from all upper-bounds of `V` to `U` if `U` contains an unfixed type variable. + * Otherwise, no inferences are made + +* Lower-bound inference + * When a new bound `U` is added to the set of lower-bounds of `V`: + * We perform *lower-bound* inference from `U` to the shape of `V`, if it has any and the shape contains an unfixed type variable. + * We perform *upper-bound* inference from the shape of `V` to `U`, if `V` has a shape and `U` contains an unfixed type variable. + * We perform *exact* inference from `U` to all lower-bounds of `V`, which contains an unfixed type variable. + * We perform *lower-bound* inference from `U` to all exact-bounds and upper-bounds of `V`, which contains an unfixed type variable. + * We perform *exact* inference from all lower-bounds of `V` to `U` if `U` contains an unfixed type variable. + * We perform *upper-bound* type inference from all exact-bounds and upper-bounds of `V` to `U` if `U` contains unfixed type variable. + +* Upper-bound inference + * When new bound `U` is added to the set of upper-bounds of `V`: + * We perform *upper-bound* inference from `U` to the shape of `V` , if it has any and the shape contains an unfixed type variable. + * We perform *lower-bound* inference from the shape of `V` to `U`, if `V` has a a shape and `U` contains an unfixed type variable. + * We perform *exact* inference from `U` to all upper-bounds of `V`, which contains an unfixed type variable. + * We perform *upper-bound* inference from `U` to all exact-bounds and lower-bounds of `V`, which contains an unfixed type variable. + * We perform *exact* inference from all upper-bounds of `V` to `U` if `U` contains an unfixed type variable. + * We perform *lower-bound* type inference from all exact-bounds and lower-bounds of `V` to `U` if `U` contains unfixed type variable. + +* Exact inference + * When new bound `U` is added to the set of lower-bounds of `V`: + * We perform *exact-bound* inference from `U` to the shape of `V`, if has any and the shape contains an unfixed type variable. + * We perform *exact* inference from the shape of `V` to `U`, if `V` has a shape and `U` contains an unfixed type variable. + * We perform *exact* inference from `U` to all exact-bounds of `V`, which contains an unfixed type variable. + * We perform *lower-bound* inference from `U` to all lower-bounds of `V`, which contains an unfixed type variable. + * We perform *upper-bound* inference from `U` to all upper-bounds of `V`, which contains an unfixed type variable. + * We perform *exact* inference from all exact-bounds of `V` to `U`, which contains an unfixed type variable. + * We perform *upper-bound* inference from all lower-bounds of `V` to `U`, which contains an unfixed type variable. + * We perform *lower-bound* inference from all upper-bounds of `V` to `U`, which contains an unfixed type variable. + +* Second phase + * **Firstly, all *unfixed* type variables `Xᵢ` which do not *depend on* ([§12.6.3.6](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12636-dependence)), *shape-depend on*, and *type-depend on* any `Xₑ` are fixed ([§12.6.3.12](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#126312-fixing)).** + * **If no such type variables exist, all *unfixed* type variables `Xᵢ` are *fixed* for which all of the following hold:** + * **There is at least one type variable `Xₑ` that *depends on*, *shape-depends on*, or *type-depends on* `Xᵢ`** + * **There is no type variable `Xₑ` on which `Xᵢ` *shape-depends on*.** + * **`Xᵢ` has a non-empty set of bounds and has at least on bound which doesn't contain any *unfixed* type variable.** + * If no such type variables exist and there are still unfixed type variables, type inference fails. + * [...] + +* First phase + + * For each of the method **/constructor** arguments `Eᵢ`: + * [...] + +* Fixing + * **An *unfixed* type variable `Xᵢ` with a set of bounds is *fixed* as follows:** + * **If the type variable has a shape bound, check the type has no conflicts with other bounds of that type variable in the same way as the standard says. It it has no conflicts, the type variable is *fixed* to that type. Otherwise type inference failed.** + * Otherwise, fix it as the standard says. + +* Explanation of inference improvements + * Now, the inferred bounds can contain other unfixed type variables. + So we have to propagate the type info also through these bounds. + + ```csharp + void F (T1 p1) { ... } + ... + F>(new List()); + ``` + + * Consider an example above. + We have now two type variables `T1` and `_`. From the first bound, we get that `IList<_>` is a shape bound of `T1`(Ignore now the type of bound, it would be the same in other types of bound). + When we investigate the second bound `List`, we will figure out that it would be a lower bound of `T1`. + But now, we have to somehow propagate the int type to the `_` type variable, because it relates to it. + That means, in the process of adding new bounds, we have to also propagate this info through bounds, which contain unfixed type variables. + In this case, we do additional inference of `IList<_>` and `List` yielding exact bound `int` of `_`. + +* Explanation of type-dependence + * Type-dependence is required because, till this time when a type variable had any bounds, it didn't contain any unfixed type variable. + It was important because we could do the parameter fixation, where we work with exact types(not unfixed type variables). + However now, the type variable can contain bounds containing unfixed type variables. + We have to ensure that we will not start fixing the type variable till these unfixed type variables are unfixed(In some cases, we can be in a situation, where this dependency will form a cycle. In this case, we will allow the fixation earlier). + + * We use the previous example. + After the first phase. `T1` has bounds `IList<_>` and `List`. `_` has bound `int`. + In this situation, we can't start to fix `T1` because `_` is not fixed yet. + `T1` is type-dependent on `_`. + So, we will first fix `_`, which becomes `int`. + Then, `T1` is not type-dependent anymore, because all bounds don't contain any unfixed type variables. + `IList<_>` is now `IList` after the `_` fixation. + We can fix `T1` now. + +* Explanation of shape-dependence + * A similar thing is for shape-dependence. + Although it cares about bounds received from the type argument list. + We want a shape bound to be exact (not containing any unfixed type variables) because it is later important for the fixation. + An intention is to keep the exact form of the given hint(`IList<_>`). + + * Example: Given `IList<_>` as a type argument, when we treat nullability, we want the hinted type parameter to be non-nullable(not `IList<_>?`). + It can happen, other bounds would infer the nullable version, and although `IList<_>` can be converted to `IList<_>?`, it is not the user's intention. + +* Explanation of inference restriction during constructor type inference + * Because performing type inference can even take exponential time when a type system contains overloading, the restriction was made above to avoid it. + It regards binding arguments before the overload resolution when we bind all *object_creation_expressions* without target info and then in case of overload resolution success and some of these arguments failed in the binding, we try to bind it again with already known target type information. + +### Compile-time checking of dynamic member invocation + +We change the [compile-time checking](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1265-compile-time-checking-of-dynamic-member-invocation). + +- First, if `F` is a generic method and type arguments were provided, then if the type argument list contains an *partially_inferred* type, an binding error occurs since it is not supported in the runtime binding. +- Then, any parameter whose type is open (i.e., contains a type parameter; see §8.4.3) is elided, along with its corresponding parameter(s). + +We add the following +- If an object creation expression is inferred and the argument list contains a dynamic value, an binding error appears since constructor type inference is not supported by runtime. + +## Drawbacks + +Skipped since it was given in the previous document. + +## Alternatives + +Skipped since it was given in the previous document. + +## Unresolved questions + +Skipped since it was given in the previous document. \ No newline at end of file From 2537f535d7f5d6c9d056421449c8db62bf7c2729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hus=C3=A1k?= Date: Tue, 16 Apr 2024 19:01:31 +0200 Subject: [PATCH 25/29] typo --- proposals/partial-type-inference-v2.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposals/partial-type-inference-v2.md b/proposals/partial-type-inference-v2.md index 3971329fc2..7c41ada4e2 100644 --- a/proposals/partial-type-inference-v2.md +++ b/proposals/partial-type-inference-v2.md @@ -160,7 +160,7 @@ The initial set of candidate methods for is changed by adding new condition. - If `F` is generic and `M` has no type argument list, `F` is a candidate when: - Type inference (§12.6.3) succeeds, inferring a list of type arguments for the call, and - Once the inferred type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` (§12.6.4.2) -- **If `F` is generic and `M` has a type argument list containing at least one *inferred_type* or *partial_inferred_type*, `F` is a candidate when:** +- **If `F` is generic and `M` is *partial_inferred*, `F` is a candidate when:** - **Method type inference (See the [Type Inference] section) succeeds, inferring the type arguments list for the call, and** - **Once the inferred type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of `F` satisfy their constraints ([§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#845-satisfying-constraints)), and the parameter list of `F` is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member))** - If `F` is generic and `M` includes a type argument list, `F` is a candidate when: @@ -175,7 +175,7 @@ The binding-time processing of an *object_creation_expression* of the form new ` - If `T` is a *value_type* and `A` is not present: - **The *object_creation_expression* is a default constructor invocation.** - - **If the type is *partially_inferred*, Constructor type inference (See the [Type Inference] section) of the default constructor occurs to determine closed type. If it succeeded, construct the type using the inferred type arguments. If it failed the binding-time error occurs.** + - **If the type is *partial_inferred*, Constructor type inference (See the [Type Inference] section) of the default constructor occurs to determine closed type. If it succeeded, construct the type using the inferred type arguments. If it failed the binding-time error occurs.** - **If the type inference above succeeded or the type is not inferred, the result of the *object_creation_expression* is a value of (constructed) type `T`, namely the default value for `T` as defined in [§8.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#833-default-constructors).** - Otherwise, if `T` is a *type_parameter* and `A` is not present: - If no value type constraint or constructor constraint (§15.2.5) has been specified for `T`, a binding-time error occurs. @@ -183,8 +183,8 @@ The binding-time processing of an *object_creation_expression* of the form new ` - Otherwise, if `T` is a *class_type* or a *struct_type*: - If `T` is an abstract or static *class_type*, a compile-time error occurs. - **The instance constructor to invoke is determined using the overload resolution rules of [§12.6.4](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1264-overload-resolution). The set of candidate instance constructors is determined as follows:** - - **`T` is not inferrred (*partially_inferred*), the constructor is accessible in `T`, and is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** - - **If `T` is *partially_constructed* and the constructor is accessible in `T`, partial constructor type inference of the constructor is performed. Once the type arguments are inferred and substituted for the corresponding type parameters, all constructed types in the parameter list of the constructor satisfy their constraints, and the parameter list of the constructor is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** + - **`T` is not inferrred (*partial_inferred*), the constructor is accessible in `T`, and is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** + - **If `T` is *partial_inferred* and the constructor is accessible in `T`, constructor type inference of the constructor is performed. Once the type arguments are inferred and substituted for the corresponding type parameters, all constructed types in the parameter list of the constructor satisfy their constraints, and the parameter list of the constructor is applicable with respect to `A` ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#12642-applicable-function-member)).** - A binding-time error occurs when: - The set of candidate instance constructors is empty, or if a single best instance constructor cannot be identified. - The result of the *object_creation_expression* is a value of type `T`, namely the value produced by invoking the instance constructor determined in the two steps above. From 254b137af7d2397459bc9a976934465e7fa2db0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hus=C3=A1k?= Date: Tue, 16 Apr 2024 19:10:07 +0200 Subject: [PATCH 26/29] typo --- proposals/partial-type-inference-v2.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposals/partial-type-inference-v2.md b/proposals/partial-type-inference-v2.md index 7c41ada4e2..34c73d0996 100644 --- a/proposals/partial-type-inference-v2.md +++ b/proposals/partial-type-inference-v2.md @@ -125,7 +125,7 @@ new Container<_>.Type<_>(arg); * *inferred_type_placeholder* represents an unknown type denoted by the `_` identifier, which will be resolved during type inference. * *partial_inferred_type* represents a type which contains *inferred_type_placeholder* in its syntax but it is not *inferred_type_placeholder*. -The contained uknown type(s) are resolved during type inference assembling closed type. +The contained uknown type(s) are inferred during type inference assembling the closed type. ```csharp _ // inferred_type_placeholder, but not partial_inferred_type @@ -176,7 +176,7 @@ The binding-time processing of an *object_creation_expression* of the form new ` - If `T` is a *value_type* and `A` is not present: - **The *object_creation_expression* is a default constructor invocation.** - **If the type is *partial_inferred*, Constructor type inference (See the [Type Inference] section) of the default constructor occurs to determine closed type. If it succeeded, construct the type using the inferred type arguments. If it failed the binding-time error occurs.** - - **If the type inference above succeeded or the type is not inferred, the result of the *object_creation_expression* is a value of (constructed) type `T`, namely the default value for `T` as defined in [§8.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#833-default-constructors).** + - **If the type inference above succeeded, the result of the *object_creation_expression* is a value of (constructed) type `T`, namely the default value for `T` as defined in [§8.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#833-default-constructors).** - Otherwise, if `T` is a *type_parameter* and `A` is not present: - If no value type constraint or constructor constraint (§15.2.5) has been specified for `T`, a binding-time error occurs. - The result of the *object_creation_expression* is a value of the run-time type that the type parameter has been bound to, namely the result of invoking the default constructor of that type. The run-time type may be a reference type or a value type. @@ -196,7 +196,7 @@ We replace the [type inference/general](https://github.com/dotnet/csharpstandard * Type inference for generic method invocation is performed when the invocation: * Doesn't have a *type_argument_list*. - * The type argument list contains at least one *partial_inferred* type. + * The method group is *partial_inferred*. ```csharp M( ... ); // Type inference is invoked. @@ -206,7 +206,7 @@ We replace the [type inference/general](https://github.com/dotnet/csharpstandard * Type inference is applied to each generic method in the method group. -* Type inference for constructors is performed when the generic type of *object_creation_expression* is *partially_inferred*: +* Type inference for constructors is performed when the generic type of *object_creation_expression* is *partially_inferred*. ```csharp new C<_, string>( ... ); // Type inference is invoked. From 4f015638106fa54b34364aa06958a1f28a460e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hus=C3=A1k?= Date: Tue, 16 Apr 2024 19:26:08 +0200 Subject: [PATCH 27/29] adjust object creation expression binding --- proposals/partial-type-inference-v2.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/proposals/partial-type-inference-v2.md b/proposals/partial-type-inference-v2.md index 34c73d0996..101e78bf58 100644 --- a/proposals/partial-type-inference-v2.md +++ b/proposals/partial-type-inference-v2.md @@ -173,11 +173,7 @@ The binding-time processing of an [*object_creation_expression*](https://github. The binding-time processing of an *object_creation_expression* of the form new `T(A)`, where `T` is a *class_type*, or a *value_type*, and `A` is an optional *argument_list*, consists of the following steps: -- If `T` is a *value_type* and `A` is not present: - - **The *object_creation_expression* is a default constructor invocation.** - - **If the type is *partial_inferred*, Constructor type inference (See the [Type Inference] section) of the default constructor occurs to determine closed type. If it succeeded, construct the type using the inferred type arguments. If it failed the binding-time error occurs.** - - **If the type inference above succeeded, the result of the *object_creation_expression* is a value of (constructed) type `T`, namely the default value for `T` as defined in [§8.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/types.md#833-default-constructors).** -- Otherwise, if `T` is a *type_parameter* and `A` is not present: +- if `T` is a *type_parameter* and `A` is not present: - If no value type constraint or constructor constraint (§15.2.5) has been specified for `T`, a binding-time error occurs. - The result of the *object_creation_expression* is a value of the run-time type that the type parameter has been bound to, namely the result of invoking the default constructor of that type. The run-time type may be a reference type or a value type. - Otherwise, if `T` is a *class_type* or a *struct_type*: From fe311859ba3122098ff29836b388664de78d9db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hus=C3=A1k?= Date: Tue, 16 Apr 2024 19:32:45 +0200 Subject: [PATCH 28/29] typo --- proposals/partial-type-inference-v2.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proposals/partial-type-inference-v2.md b/proposals/partial-type-inference-v2.md index 101e78bf58..07e0e0dbc5 100644 --- a/proposals/partial-type-inference-v2.md +++ b/proposals/partial-type-inference-v2.md @@ -375,8 +375,7 @@ We change the type inference algorithm contained in the [type inference](https:/ We change the [compile-time checking](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1265-compile-time-checking-of-dynamic-member-invocation). -- First, if `F` is a generic method and type arguments were provided, then if the type argument list contains an *partially_inferred* type, an binding error occurs since it is not supported in the runtime binding. -- Then, any parameter whose type is open (i.e., contains a type parameter; see §8.4.3) is elided, along with its corresponding parameter(s). +- First, if `F` is a generic method and type arguments were provided, then if the method group is *partial_inferred*, an binding error occurs since it is not supported in the runtime binding. We add the following - If an object creation expression is inferred and the argument list contains a dynamic value, an binding error appears since constructor type inference is not supported by runtime. From e20b17a501c599137b6b4a2d4ebc340f27432f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hus=C3=A1k?= Date: Tue, 16 Apr 2024 19:35:53 +0200 Subject: [PATCH 29/29] remove outdated explanation --- proposals/partial-type-inference-v2.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/proposals/partial-type-inference-v2.md b/proposals/partial-type-inference-v2.md index 07e0e0dbc5..d1de55eb0e 100644 --- a/proposals/partial-type-inference-v2.md +++ b/proposals/partial-type-inference-v2.md @@ -367,10 +367,6 @@ We change the type inference algorithm contained in the [type inference](https:/ * Example: Given `IList<_>` as a type argument, when we treat nullability, we want the hinted type parameter to be non-nullable(not `IList<_>?`). It can happen, other bounds would infer the nullable version, and although `IList<_>` can be converted to `IList<_>?`, it is not the user's intention. -* Explanation of inference restriction during constructor type inference - * Because performing type inference can even take exponential time when a type system contains overloading, the restriction was made above to avoid it. - It regards binding arguments before the overload resolution when we bind all *object_creation_expressions* without target info and then in case of overload resolution success and some of these arguments failed in the binding, we try to bind it again with already known target type information. - ### Compile-time checking of dynamic member invocation We change the [compile-time checking](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1265-compile-time-checking-of-dynamic-member-invocation).