From 7d5fd3f267b97fc144c992a6f1d1eddd4261beda Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Wed, 4 Jan 2023 15:51:53 -0800 Subject: [PATCH 1/3] Clarify some aspects of primary constructor parameters capturing --- proposals/primary-constructors.md | 39 ++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/proposals/primary-constructors.md b/proposals/primary-constructors.md index f23e30a21a..9445c5212a 100644 --- a/proposals/primary-constructors.md +++ b/proposals/primary-constructors.md @@ -128,7 +128,7 @@ Primary constructor parameters in class/struct declarations can be declared `ref All instance member initializers in the class body will become assignments in the generated constructor. -If a primary constructor parameter is referenced from within an instance member, it is captured into the state of the enclosing type, so that it remains accessible after the termination of the constructor. A likely implementation strategy is via a private field using a mangled name. +If a primary constructor parameter is referenced from within an instance member, and the refernce is not inside of a `nameof` argument, it is captured into the state of the enclosing type, so that it remains accessible after the termination of the constructor. A likely implementation strategy is via a private field using a mangled name. Capturing is not allowed for `ref`, `in` and `out` parameters. This is similar to a limitation for capturing in lambdas. @@ -187,6 +187,43 @@ Records produce a warning if a primary constructor parameter isn't read within t - for an `in` parameter, if the parameter is not read within any instance initializers or base initializer. - for a `ref` parameter, if the parameter is not read or written to within any instance initializers or base initializer. +### Identical simple names and type names + +There is a special language rule for scenarios often referred to as "Color Color" scenarios - [Identical simple names and type names](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#11762-identical-simple-names-and-type-names). + +>In a member access of the form `E.I`, if `E` is a single identifier, and if the meaning of `E` as a *simple_name* ([§11.7.4](expressions.md#1174-simple-names)) is a constant, field, property, local variable, or parameter with the same type as the meaning of `E` as a *type_name* ([§7.8.1](basic-concepts.md#781-general)), then both possible meanings of `E` are permitted. The member lookup of `E.I` is never ambiguous, since `I` shall necessarily be a member of the type `E` in both cases. In other words, the rule simply permits access to the static members and nested types of `E` where a compile-time error would otherwise have occurred. + +With respect to primary constructors, the rule has effect on whether an identifier within an instance member should be treated as a type reference, or as a primary constructor parameter reference, which, in turn, captures the parameter into the the state of the enclosing type. Even though "the member lookup of `E.I` is never ambiguous", when lookup yields a member group, in some cases it is impossible to make determination whether a member access refers to a static, or to an instance member without fully resolving (binding) the member access. At the same time, the fact of capturing a primary constructor parameter, changes properties of enclosing type in a way that affects semantic analysis. For example, the type might become unmanaged and fail certain constraints because of that. +There are even scenarios for which binding can succeed either way, depending on whether the parameter is considered captured, or not. For example: +``` C# +struct S1(Color Color) +{ + public void Test() + { + Color.M1(this); + } +} + +class Color +{ + public void M1(T x, int y = 0) + { + System.Console.WriteLine(""instance""); + } + + public static void M1(T x) where T : unmanaged + { + System.Console.WriteLine(""static""); + } +} +``` + +If we treat receiver ```Color``` as a value, we capture the parameter and 'S1' becomes managed. Then static method becomes inapplicable due to constraint and we would call instance method. However, if we treat the receiver as a type, we don't capture the parameter and 'S1' remains unmanaged, then both methods are applicable, but the static method is "better" because it doesn't have an optional parameter. Neither choice leads to an error, but each would result in distinct behavior. + +Given this, compiler is going to produce an ambiguity error for a member access `E.I` when all the following conditions are met: +- Member lookup of `E.I` yields a member group containing instance and static members at the same time. Extension methods applicable to the receiver type are treated as instance methods for the purpose of this check. +- If `E` is treated as a simple name, rather than a type name, it would refer to a primary constructor parameter and would capture the parameter into the state of the enclosing type. + ## Primary constructors on records With this proposal, records no longer need to separately specify a primary constructor mechanism. Instead, record (class and struct) declarations that have primary constructors would follow the general rules, with these simple additions: From e07be0ad5db81f890b7667b717e1984215c7a8d5 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Wed, 4 Jan 2023 16:13:32 -0800 Subject: [PATCH 2/3] Update proposals/primary-constructors.md Co-authored-by: Fred Silberberg --- proposals/primary-constructors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/primary-constructors.md b/proposals/primary-constructors.md index 9445c5212a..26864fed68 100644 --- a/proposals/primary-constructors.md +++ b/proposals/primary-constructors.md @@ -128,7 +128,7 @@ Primary constructor parameters in class/struct declarations can be declared `ref All instance member initializers in the class body will become assignments in the generated constructor. -If a primary constructor parameter is referenced from within an instance member, and the refernce is not inside of a `nameof` argument, it is captured into the state of the enclosing type, so that it remains accessible after the termination of the constructor. A likely implementation strategy is via a private field using a mangled name. +If a primary constructor parameter is referenced from within an instance member, and the reference is not inside of a `nameof` argument, it is captured into the state of the enclosing type, so that it remains accessible after the termination of the constructor. A likely implementation strategy is via a private field using a mangled name. Capturing is not allowed for `ref`, `in` and `out` parameters. This is similar to a limitation for capturing in lambdas. From 86ff69e2fc6186b9a0579231d26f1a546e5f6aa2 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Mon, 9 Jan 2023 08:35:53 -0800 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Fred Silberberg Co-authored-by: Jan Jones --- proposals/primary-constructors.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/proposals/primary-constructors.md b/proposals/primary-constructors.md index 26864fed68..faee0a8051 100644 --- a/proposals/primary-constructors.md +++ b/proposals/primary-constructors.md @@ -193,8 +193,8 @@ There is a special language rule for scenarios often referred to as "Color Color >In a member access of the form `E.I`, if `E` is a single identifier, and if the meaning of `E` as a *simple_name* ([§11.7.4](expressions.md#1174-simple-names)) is a constant, field, property, local variable, or parameter with the same type as the meaning of `E` as a *type_name* ([§7.8.1](basic-concepts.md#781-general)), then both possible meanings of `E` are permitted. The member lookup of `E.I` is never ambiguous, since `I` shall necessarily be a member of the type `E` in both cases. In other words, the rule simply permits access to the static members and nested types of `E` where a compile-time error would otherwise have occurred. -With respect to primary constructors, the rule has effect on whether an identifier within an instance member should be treated as a type reference, or as a primary constructor parameter reference, which, in turn, captures the parameter into the the state of the enclosing type. Even though "the member lookup of `E.I` is never ambiguous", when lookup yields a member group, in some cases it is impossible to make determination whether a member access refers to a static, or to an instance member without fully resolving (binding) the member access. At the same time, the fact of capturing a primary constructor parameter, changes properties of enclosing type in a way that affects semantic analysis. For example, the type might become unmanaged and fail certain constraints because of that. -There are even scenarios for which binding can succeed either way, depending on whether the parameter is considered captured, or not. For example: +With respect to primary constructors, the rule affects whether an identifier within an instance member should be treated as a type reference, or as a primary constructor parameter reference, which, in turn, captures the parameter into the the state of the enclosing type. Even though "the member lookup of `E.I` is never ambiguous", when lookup yields a member group, in some cases it is impossible to determine whether a member access refers to a static member or an instance member without fully resolving (binding) the member access. At the same time, capturing a primary constructor parameter changes properties of enclosing type in a way that affects semantic analysis. For example, the type might become unmanaged and fail certain constraints because of that. +There are even scenarios for which binding can succeed either way, depending on whether the parameter is considered captured or not. For example: ``` C# struct S1(Color Color) { @@ -208,19 +208,19 @@ class Color { public void M1(T x, int y = 0) { - System.Console.WriteLine(""instance""); + System.Console.WriteLine("instance"); } public static void M1(T x) where T : unmanaged { - System.Console.WriteLine(""static""); + System.Console.WriteLine("static"); } } ``` -If we treat receiver ```Color``` as a value, we capture the parameter and 'S1' becomes managed. Then static method becomes inapplicable due to constraint and we would call instance method. However, if we treat the receiver as a type, we don't capture the parameter and 'S1' remains unmanaged, then both methods are applicable, but the static method is "better" because it doesn't have an optional parameter. Neither choice leads to an error, but each would result in distinct behavior. +If we treat receiver ```Color``` as a value, we capture the parameter and 'S1' becomes managed. Then the static method becomes inapplicable due to the constraint and we would call instance method. However, if we treat the receiver as a type, we don't capture the parameter and 'S1' remains unmanaged, then both methods are applicable, but the static method is "better" because it doesn't have an optional parameter. Neither choice leads to an error, but each would result in distinct behavior. -Given this, compiler is going to produce an ambiguity error for a member access `E.I` when all the following conditions are met: +Given this, compiler will produce an ambiguity error for a member access `E.I` when all the following conditions are met: - Member lookup of `E.I` yields a member group containing instance and static members at the same time. Extension methods applicable to the receiver type are treated as instance methods for the purpose of this check. - If `E` is treated as a simple name, rather than a type name, it would refer to a primary constructor parameter and would capture the parameter into the state of the enclosing type.