Repository navigation
Proposal: Partial type inference #7467
Replies: 3 comments 2 replies
|
Thanks for trying to show the proposed change in context of the existing spec. It would be helpful to further highlight the portions that changes ( The way I see it, there's three layers to this proposal:
Thoughts on part 1 (method type inference)I'll need to read this in more details. I expected to see the changes to candidate selection and type parameter fixing (type inference phase 2), but I didn't quite get why we need the new concepts of shape-dependence and type-dependence. Thoughts on parts 2 and 3 (object creation)Applying method type inference to object creation
This probably needs more details. The current spec handles object creation in the following way: "The set of candidate instance constructors consists of all accessible instance constructors declared in T, which are applicable with respect to A." But in the In such case, the constructors from multiple types have to be considered in one go, so that text needs to be amended. Also, the inference (from arguments to type arguments) won't apply to constructors, but to the type itself, which is different than method invocation. So we would infer When going down that line, it raises the question: would type inference apply to containing types? e.g. Clarifying an early exampleI was confused by an early example: That seems wrong. The type parameter |
|
Is there a strong reason we are coupling constructor generic type inference, with this much more advanced proposal for partial generic type inference? It would be super nice if we could decouple those 2 things as work on "standard" generic type inference for constructors could start right away while the more advanced aspects of this proposal here are discussed and defined in more detail. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Partial type inference
Summary
Partial type inference introduces a syntax skipping obvious type arguments in the argument list of
and allowing to specify just ambiguous ones.
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.
Besides the changes described above, the proposal mentions further interactions and possibilities to extend the partial type inference.
Motivation
If the compiler is not able to infer command call type arguments, the user has to specify all of them.
This requirement can be verbose, noisy, and unnecessary in cases where the compiler can infer almost all type arguments and need just to specify ambiguous ones.
However, we can't just change the current behavior of the type inference because it would introduce breaking changes.
What we can do is to introduce improved type inference in places, where it was not before like object_creation_expression.
It is a nice chance to push the type inference to the next level without introducing breaking changes.
And then wait for the time, when C# would be ready to introduce breaking changes without any major disadvantages.
No matter how the partial type inference would work, we should be careful about the following things.
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.
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.
So will want to look ahead to other potential directions, which can be done after this feature.
Detailed design
Grammar
The following changes are made in tokens located in the grammar section.
_depends on the context in which it appears: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.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
We change the meaning of the content of type_argument_list in two contexts.
inferred_type_argument represents an unknown type, which will be resolved during type inference.
_identifier is considered to represent inferred_type_argument when: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:
Namespace and type names
Determining the meaning of a namespace_or_type_name is changed as follow.
If there is an ambiguity in the current scope, a compilation-time error occurs.
Method invocations
The binding-time processing of a method invocation of the form
M(A), whereMis a method group (possibly including a type_argument_list), andAis an optional argument_list is changed in the following way.The initial set of candidate methods for is changed by adding new condition.
Fis non-generic,Fis a candidate when:Mhas no type argument list, andFis applicable with respect toA(Β§12.6.4.2).Fis generic andMhas no type argument list,Fis a candidate when:Fsatisfy their constraints (Β§8.4.5), and the parameter list ofFis applicable with respect toA(Β§12.6.4.2)Fis generic andMhas type argument list containing at least one inferred_type_argument,Fis a candidate when:Fsatisfy their constraints (Β§8.4.5), and the parameter list ofFis applicable with respect toA(Β§12.6.4.2)Fis generic andMincludes a type argument list,Fis a candidate when:Fhas the same number of method type parameters as were supplied in the type argument list, andFsatisfy their constraints (Β§8.4.5), and the parameter list ofFis applicable with respect toA(Β§12.6.4.2).Object creation expressions
The binding-time processing of an object_creation_expression of the form new
T(A), whereTis a class_type, or a value_type, andAis 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), whereTis a class_type, or a value_type, andAis an optional argument_list, consists of the following steps:Tis a value_type andAis not present:T, namely the default value forTas defined in Β§8.3.3.Tis a type_parameter andAis not present:T, a binding-time error occurs.Tis a class_type or a struct_type:Tis an abstract or static class_type, a compile-time error occurs.Tis not inferrred (generic_inferred or partially_inferred), the constructor is accessible inT, and is applicable with respect toA(Β§12.6.4.2).Tis generic_constructed or partially_constructed and the constructor is accessible inT, 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 toA(Β§12.6.4.2).T, namely the value produced by invoking the instance constructor determined in the two steps above.Type inference
We change the type inference as follows.
Type inference for generic method invocation is performed when the invocation:
Type inference for constructors is performed when the generic type of object_creation_expression:
When the method invocation contains a type argument list containing inferred type argument, the input for type inference is extended as follows:
_identifier with a new type variableX.Inputs for constructor type inference are constructed as follows:
Addmethod, 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.Arguments binding
Type inference algorithm change
Shape dependence
Xα΅’shape-depends directly on an unfixed type variableXβifXβrepresents inferred_type_argument and it is contained in shape bound of the type variableXα΅’.Xβshape-depends onXα΅’ifXβshape-depends directly onXα΅’or ifXα΅’shape-depends directly onXα΅₯andXα΅₯shape-depends onXβ. Thus βshape-depends onβ is the transitive but not reflexive closure of βshape-depends directly onβ.Type dependence
Xα΅’type-depends directly on an unfixed type variableXβifXβoccurs in any bound of type variableXα΅’.Xβtype-depends onXα΅’ifXβtype-depends directly onXα΅’or ifXα΅’type-depends directly onXα΅₯andXα΅₯type-depends onXβ. Thus βtype-depends onβ is the transitive but not reflexive closure of βtype-depends directly onβ.Shape inference
Uto a typeVis made as follows:Vis one of the unfixedXα΅’thenUis a shape bound ofV.UofVis set:Uto all lower-bounds ofV, which contains an unfixed type variableUto all exact-bounds ofV, which contains an unfixed type variable.Uto all upper-bounds ofV, which contains an unfixed type variable.VtoUifUcontains an unfixed type variable.VtoUifUcontains unfixed type variable.VtoUifUcontains an unfixed type variable.Lower-bound inference
Uis added to the set of lower-bounds ofV:Uto the shape ofV, if it has any and the shape contains an unfixed type variable.VtoU, ifVhas a shape andUcontains an unfixed type variable.Uto all lower-bounds ofV, which contains an unfixed type variableUto all exact-bounds and upper-bounds ofV, which contains an unfixed type variable.VtoUifUcontains an unfixed type variableVtoUifUcontains unfixed type variable.Upper-bound inference
Uis added to the set of upper-bounds ofV:Uto the shape ofV, if it has any and the shape contains an unfixed type variable.VtoU, ifVhas a shape andUcontains an unfixed type variable.Uto all upper-bounds ofV, which contains an unfixed type variableUto all exact-bounds and lower-bounds ofV, which contains an unfixed type variable.VtoUifUcontains an unfixed type variableVtoUifUcontains unfixed type variable.Exact inference
Uis added to the set of lower-bounds ofV:Uto the shape ofV, if it has any and the shape contains an unfixed type variable.VtoU, ifVhas a shape andUcontains an unfixed type variable.Uto all exact-bounds ofV, which contains an unfixed type variableUto all lower-bounds ofV, which contains an unfixed type variableUto all upper-bounds ofV, which contains an unfixed type variableVtoU, which contains an unfixed type variableVtoU, which contains an unfixed type variableVtoU, which contains an unfixed type variableSecond phase
Xα΅’which do not depend on (Β§12.6.3.6), shape-depend on, and type-depend on anyXβare fixed (Β§12.6.3.12).Xα΅’are fixed for which all of the following hold:Xβthat depends on, shape-depends on, or type-depends onXα΅’Xβon whichXα΅’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.Fixing
Xα΅’with a set of bounds is fixed as follows:Type inference for constructor
Compile-time checking of dynamic member invocation
We change the compile-time checking in order to be useful during partial type inferece.
Fis 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.Nullability
We can use an examination mark
?to say that the inferred type argument should be a nullable type (e.g.F<_?>(...)).Drawbacks
Why should we not do this?
Alternatives
What other designs have been considered? What is the impact of not doing this?
Unresolved questions
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
varkeyword 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.Type inference for casting
This can be useful with combination with already preparing collection literals.
Is there a better choice for choosing the placeholder for inferred type argument ?
Potentional resolution: My choice contained in the detailed design is based on the following.
Details
We base our choice on the usages specified below.
Foo<T1, T2>(...))new Bar<T1, T2>(...))Bar<T1, T2> temp = ...)T1[])T1in local variableDiamond operator
varand looks wierd.Whitespace seperated by commas
varand probably introduce many implementation problems in the parser._ seperated by commas
varand seems to be wierd.var seperated by commas
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 asvar.Design meetings
Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to.
All reactions