-
Notifications
You must be signed in to change notification settings - Fork 1k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Champion: Name lookup for simple name when target type known #2926
Comments
So with this, would I be able to do something akin to |
Quite useful, but what about the compiler overhead of overload resolution? enum E1 { EA, EB, EC }
enum E2 { EA, EB }
void M(E1 e, int x);
void M(E2 e, string x);
M(EA, someParameter); Does this meet the similar overload resolution algorithm when using number literals? |
Greed is the color of money. |
As per current proposal, I think you would need to qualify at least one operand, GetMethod("Name", BindingFlags.Public | Instance) In order to omit type for all of them, the operator |
That's exactly correct, and we actually brought up the |
I've encountered with an issue with using static, case IBinaryOperation { OperatorKind: Equals } op:
For target-typed lookup, would it make sense to alter the spec as follow?
The lookup itself would have no information about the context and we'll need to add a new bottleneck to check the validity which is defined as being a constant of a particular type in this case. |
@alrz We would need to define a concept of validity for every context in which an expression could appear. I don't think it is worth it. |
To avoid that, as a simpler rule, we could say if an identifier is already found but the conversion fails, we treat as target-typed. In other words, it's defined as the fallback conversion iif the source expression is a simple identifier. Then it wouldn't depend on the context. In the example above, method group fails to convert to enum, so it will be treated as target-typed. edit: I realized the above might be a breaking change in some scenarios involving overload resolution, candidates that were not applicable becomes applicable and could change program's behaviour. |
That would be a breaking change. In an expression such as |
In a design note there was a consideration to separate out overload resolution in target-typing scenarios to be able to tweak conversions without breaking change. I think this one would be another case where that could help. https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-10-28.md I think the situation is almost the same as |
No, this is give a meaning to code that was definitely an error before. There is no breaking change in that case. |
I was responding to:
Target-typing |
If |
The idea of something being treated as target-typed only when the identifier is unqualified is IMO very dangerous, as the simple fact of adding a Any "fallback" usage has this problem. Some of the examples look fairly resilient to this, agreed. But not all. Consider the following, inspired by the examples in the top post: class Outer
{
public interface IFoo {}
}
bool M4(Outer o) => o is IFoo; The intent here is to to test for I wonder whether something as simple as saying "leading period means target typed" is a pragmatic solution here: bool M5(Outer o) => o is .IFoo; |
@mgravell This is something that we had discussed. I apologize that the discussion did not take place here. The expression form (as opposed to the type form) is less susceptible to this issue, so we might consider target-typing only the expression form. Unfortunately, a straightforward application of the language tactic you suggest doesn't work, as it introduces an ambiguity. There are two different syntactically valid parses of the following: _ = A ? . B ? . C : D; (one of the We could probably make a grammar that recognizes that |
Because we're target-typing only as the last resort after any other binding rule has failed, the issue may be still pretty much present. Especially if we don't do anything about #2926 (comment). You may introduce any identical name in the scope in any possible way and all the target-typed identifiers will break. I think having a prefix could help to ensure that an identifier is target-typed regardless of anything else. I think |
In my opinion a problem of this sort arising from this feature would be rare and if it happens would probably bring in a value of the wrong type. You would not give a variable of type |
I guess Flags
|
Static extension methods might provide this for free? (Once you define a ton of extension methods on |
If I understand you correctly, wouldn't we want to use static extension properties? |
I know, I know, everything is complex :) I was wondering if we could limit the scope to just enums to begin with - surely that's not as complicated as static fields and such? |
Enums are static fields. |
@gafter @CyrusNajmabadi I just wanted to follow up on the ambiguity in this example:
Edit: I think I might have gotten this completely wrong. Making a seemingly small and simple syntax rule change like above might not be enough to solve this. Another interesting case to consider is if the name lookup could be extended to also include static methods / properties of public record struct Rect(int X, int Y, int Width, int Height) {
public static Rect FromFoo(FrameworkFoo.Rect rect)
=> new(X: rect.Left, Y: rect.Top, Width: rect.Right - rect.Left, Height: rect.Bottom - rect.Top);
}
// Assume that this method exists: void DrawRect(Rect rect)
FrameworkFoo.Rect rect = new(Left: 50, Right: 100, Top: 25, Bottom: 125);
// Here, the method is looked up based on the target type. I used dot syntax, but dotless could also work
// (but dotless would probably cause more ambiguities in this case).
DrawRect(.FromFoo(rect)); This example was just of the top of my head, but I encounter this kind of code a lot when dealing |
😞
It is the version with a dot that has a syntactic ambiguity. The version without a dot (as in the OP) does not. |
Yes, i mentioned that below:
I'm not saying it's exactly this form. I'm saying i'm willing to champion a form that has no syntactic ambiguity but has some syntactic difference beyond just a simple name. Thanks! |
I have to say, that I don't really get why syntactic ambiguity is so much of a problem here. So meticulously as this topic is approached here, one could argue that properties or fields should also always have a dot in front, when used, to show that they are properties/fields and not class or variable names. But for that kind of ambiguity there is the Or am I totally missing a point here? 🤔 |
@cytoph I'm afraid that would be a breaking change, since code that already has an enum member identified exactly as some other accessible identifier at the same context doesn't issue a warning today, but will issue with the new compiler. |
@Logerfo Ah, I think I get what you mean. In the following case (which is perfectly fine and clear with the current compiler, but with the changes I proposed) the compiler wouldn't be able to tell exactly what I want to assign to the variable
|
Surely if we want this to be a non-breaking change then it's a no-brainer to pick the non-breaking interpretation, that this new interpretation only applies if the name isn't found any other way. Also the new interpretation should only apply if the enum value name is the entire expression; |
@sab39 That would also fit the way as it works with properties/fields and variables: If both are named exactly the same, it's always assumed the variable is used, unless of course you specifically use the |
I think you folks are missing a crucial point. This is not a semantic ambiguity, where we know how to parse the code and just need to do name lookup to decide between one interpretation or another. This is a syntactic ambiguity while trying to parse the code (figure out which punctuation means what). Parsing occurs long before name lookup, and cannot depend on name lookup. |
Hi. Would this work for cases when the outer type is used as a return type? Consider public abstract record Result<TR, TE>
{
public sealed record Ok(TR Value) : Result<TR, TE>;
public sealed record Error(TE Value) : Result<TR, TE>;
} Will this proposal allow me to write some thing like that? Result<int, string> Validate(int value) => value switch
{
> 100 => Ok(value),
_ => Error("Some error text")
};
var result = Validate(101);
int val = result switch
{
Ok(var value) => value,
Error(var value) => throw new InvalidOperationException(value)
}; |
@tuscen I would assume so, since the feature is likely to be built upon the existing target typing rules and return type is considered there. For example, this compiles fine: public class C
{
public object M()
{
return new();
}
} |
Today "using static" solves this problem. If you have to many "using static" - may be it is a time to split code to different classes? :-D Also in this thread I see also problem "simplification of generic names when taget type known". This is other than initial problem! |
No, it doesn't. Consider you initialize GUI tree in code and have HorizontalAlignment and VerticalAlignment enums both having Center entry. |
Interpretation-wise, do you care if it's a local variable or a class member (assuming there is no this. prefix)? If not, then i don't see why you would care if it's an enum value. Hope you elaborate. |
If we end up making |
I would very much prefer explicitness in this case — i.e. use a leading dot (a la Swift) to denote "target-typed" for enums. Makes the rules more straightforward. It also makes things less ambiguous and makes code easier to scan in PRs, etc. |
See #2926 (comment) for a discussion explaining why that proposal leads to a syntactic ambiguity. |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
There are three contexts in which lookup of a simple (not qualified) name would benefit from the fallback of looking up the identifier in a target type.
Simple ID expression
When an expression is an unqualified identifier, and lookup fails to find any accessible binding for that identifier, it is treated as target-typed. When the target type becomes available, the identifier is looked up in the context of the target type. If the identifier in that type designates an accessible constant, static field, or static property whose type is that target type, the identifier binds to that member.
Example:
Object creation expression
When an object creation expression's type is given as an unqualified identifier (possibly with type arguments), and lookup fails to find any accessible binding for that identifier, it is treated as target-typed. When the target type becomes available, the identifier is looked up in the context of the target type (and the rest of the creation expression is bound, like the target-typed new expression). If the identifier in that type designates an accessible type that has an implicit reference conversion to that target type, the identifier binds to that type.
Example:
Type in a pattern
When the type appearing in a pattern is given as an unqualified identifier (possibly with type arguments), and lookup fails to find any accessible binding for that identifier, the identifier is looked up in the context of the pattern's input type. If the identifier in that type designates an accessible type, the identifier binds to that accessible type.
Example:
Design Meetings
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-26.md#discriminated-unions
The text was updated successfully, but these errors were encountered: