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 |
Beta Was this translation helpful? Give feedback.
-
The proposal continue as a PR here |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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.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)
, whereM
is a method group (possibly including a type_argument_list), andA
is an optional argument_list is changed in the following way.The initial set of candidate methods for is changed by adding new condition.
F
is non-generic,F
is a candidate when:M
has no type argument list, andF
is applicable with respect toA
(§12.6.4.2).F
is generic andM
has no type argument list,F
is a candidate when:F
satisfy their constraints (§8.4.5), and the parameter list ofF
is applicable with respect toA
(§12.6.4.2)F
is generic andM
has type argument list containing at least one inferred_type_argument,F
is a candidate when:F
satisfy their constraints (§8.4.5), and the parameter list ofF
is applicable with respect toA
(§12.6.4.2)F
is generic andM
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, andF
satisfy their constraints (§8.4.5), and the parameter list ofF
is 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)
, whereT
is a class_type, or a value_type, andA
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)
, whereT
is a class_type, or a value_type, andA
is an optional argument_list, consists of the following steps:T
is a value_type andA
is not present:T
, namely the default value forT
as defined in §8.3.3.T
is a type_parameter andA
is not present:T
, a binding-time error occurs.T
is a class_type or a struct_type:T
is an abstract or static class_type, a compile-time error occurs.T
is not inferrred (generic_inferred or partially_inferred), the constructor is accessible inT
, and is applicable with respect toA
(§12.6.4.2).T
is 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:
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.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
U
to a typeV
is made as follows:V
is one of the unfixedXᵢ
thenU
is a shape bound ofV
.U
ofV
is set:U
to all lower-bounds ofV
, which contains an unfixed type variableU
to all exact-bounds ofV
, which contains an unfixed type variable.U
to all upper-bounds ofV
, which contains an unfixed type variable.V
toU
ifU
contains an unfixed type variable.V
toU
ifU
contains unfixed type variable.V
toU
ifU
contains an unfixed type variable.Lower-bound inference
U
is added to the set of lower-bounds ofV
:U
to the shape ofV
, if it has any and the shape contains an unfixed type variable.V
toU
, ifV
has a shape andU
contains an unfixed type variable.U
to all lower-bounds ofV
, which contains an unfixed type variableU
to all exact-bounds and upper-bounds ofV
, which contains an unfixed type variable.V
toU
ifU
contains an unfixed type variableV
toU
ifU
contains unfixed type variable.Upper-bound inference
U
is added to the set of upper-bounds ofV
:U
to the shape ofV
, if it has any and the shape contains an unfixed type variable.V
toU
, ifV
has a shape andU
contains an unfixed type variable.U
to all upper-bounds ofV
, which contains an unfixed type variableU
to all exact-bounds and lower-bounds ofV
, which contains an unfixed type variable.V
toU
ifU
contains an unfixed type variableV
toU
ifU
contains unfixed type variable.Exact inference
U
is added to the set of lower-bounds ofV
:U
to the shape ofV
, if it has any and the shape contains an unfixed type variable.V
toU
, ifV
has a shape andU
contains an unfixed type variable.U
to all exact-bounds ofV
, which contains an unfixed type variableU
to all lower-bounds ofV
, which contains an unfixed type variableU
to all upper-bounds ofV
, which contains an unfixed type variableV
toU
, which contains an unfixed type variableV
toU
, which contains an unfixed type variableV
toU
, 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.
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.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
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.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.
We base our choice on the usages specified below.
Foo<T1, T2>(...)
)new Bar<T1, T2>(...)
)Bar<T1, T2> temp = ...
)T1[]
)T1
in local variableDiamond operator
var
and looks wierd.Whitespace seperated by commas
var
and probably introduce many implementation problems in the parser._ seperated by commas
var
and 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.
Beta Was this translation helpful? Give feedback.
All reactions