-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Improve detection of invalid references for implicitly typed expression variables declared within implicit object creation expressions. #80546
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
Conversation
…ject creation should be expanded to an argument list immediately enclosing the object creation Fixes dotnet#80518.
|
Fix seems reasonable to me. I'm curious if this needs LDM check? Or does this fall out from the rules of the lang already spec'ed? -- Note: the above is not blocking. I think addressing the crash is important. Just not sure if this means we compile a subset of what the lang thinks should be legal. |
| while (forbiddenZoneOpt?.Parent is ImplicitObjectCreationExpressionSyntax { Parent: ArgumentSyntax { Parent: BaseArgumentListSyntax expanded } }) | ||
| { | ||
| forbiddenZoneOpt = expanded; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so, this seems reasonable to me. i'm curious what the right path is when we do with(...) elements. IN other words, we likely will want to expand this to cover [new(out var x), x]. This code seems fairly straightforward to update. But it seems a bit fragile in that i don't get a good sense of what the general rule should be for something being updated here. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I'll see if these scenarios need special treatment too.
| // (9,9): error CS0103: The name 'M1' does not exist in the current context | ||
| // M1(new(out var x), x); | ||
| Diagnostic(ErrorCode.ERR_NameNotInContext, "M1").WithArguments("M1").WithLocation(9, 9), | ||
| // (9,28): error CS8196: Reference to an implicitly-typed out variable 'x' is not permitted in the same argument list. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
diagnostic is a bit incorrect now. but i suppose it's still clear enough?
it seems like it may need tweaking with collection expression work. perhaps make it vaguer as "reference to implicitly-typed out variable 'x' is not permitted here"? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
diagnostic is a bit incorrect now. but i suppose it's still clear enough?
Looks sufficiently good to me for the given scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wfm.
I think it does. An implicit object creation expression is converted to its target type after overload resolution succeeds, types of any out vars declared within the object creation argument list cannot be inferred until then. Therefore, the language cannot know the type of the vars referenced in the same argument list (the type is needed to overload resolution before the |
That makes perfect sense. Thanks. |
…on variables declared within implicit object creation expressions.
out var used as an argument of an implicit object creation should be expanded to an argument list immediately enclosing the object creation| // | ||
| // For simplicity, we may assign nodes that themselves don't represent expressions to 'typeIsKnownAfterBinding'. | ||
|
|
||
| SyntaxNode? typeIsKnownAfterBinding = nodeEnclosingDeclaration; // Strictly speaking, when we are dealind with an argument list, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| SyntaxNode? typeIsKnownAfterBinding = nodeEnclosingDeclaration; // Strictly speaking, when we are dealind with an argument list, | |
| SyntaxNode? typeIsKnownAfterBinding = nodeEnclosingDeclaration; // Strictly speaking, when we are dealing with an argument list, | |
| ``` #Closed |
| // the type is known after an overload resolution for an operation | ||
| // owning the argument list is performed and the arguments are converted | ||
| // accordingly. But for our purposes simply pretending that this happens | ||
| // when we bind the argument list itself works pretty well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the formatting of this comment leaves a lot to be desired. Not a fan of multiline done this way after the actual statement. #Closed
|
Can review fully tomorrow. Thanks. |
|
@dotnet/roslyn-compiler Please review |
|
@dotnet/roslyn-compiler Please review |
|
@jcouv, @dotnet/roslyn-compiler Please review |
| internal virtual ErrorCode ForbiddenDiagnostic => ErrorCode.ERR_VariableUsedBeforeDeclaration; | ||
| /// </param> | ||
| /// <returns></returns> | ||
| internal virtual bool IsForbiddenReference(SyntaxNode reference, out ErrorCode forbiddenDiagnostic) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we only care about this for SourceLocalSymbol and all the call-sites already do such a type check. Consider moving this down into SourceLocalSymbol
Then the call-sites can do if (localSymbol is SourceLocalSymbol { IsVar : true } sourceLocal && sourceLocal.IsForbiddenReference(...)) #Closed
| // inside an argument of a 'new()' have known type before the 'new()' is converted. For example, | ||
| // the following code is legal because of that: | ||
| // | ||
| // M1(new() (M2(out var x)), x); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these extra parens intentional?
No. Will remove
| // | ||
| // For simplicity, we may assign nodes that themselves don't represent expressions to 'typeIsKnownAfterBinding'. | ||
|
|
||
| SyntaxNode? typeIsKnownAfterBinding = nodeEnclosingDeclaration; // Strictly speaking, when we are dealind with an argument list, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
@dotnet/roslyn-compiler Please review |
|
@jcouv, @dotnet/roslyn-compiler Please review |
| { | ||
| get | ||
| { | ||
| return field ??= new ExtendedErrorTypeSymbol(this, name: "var", arity: 0, errorInfo: null, variableUsedBeforeDeclaration: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any risk of race condition (two threads concurrently see a null field and decide to set, but to different values, which would mess up downstream phases which check for singleton value)? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any risk of race condition (two threads concurrently see a
nullfield and decide to set, but to different values, which would mess up downstream phases which check for singleton value)?
Good observation. I'll make an adjustment.
| private TypeWithAnnotations.Boxed? _type; | ||
|
|
||
| // Please don't use thread local storage widely. This should be one of only a few uses. | ||
| [ThreadStatic] private static PooledHashSet<LocalTypeInferenceInProgressKey>? s_LocalTypeInferenceInProgress; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't understand why we need two data structures and why we need to track syntax nodes (LocalTypeInferenceInProgressKey.Reference and _forbiddenReferences).
Why isn't tracking the set of "locals being bound" (a PooledHashSet<SourceLocalSymbol> or ConsList<SourceLocalSymbol>) and saving the error type into _type sufficient to break the cycles? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without going to much into details:
- Only specific references are illegal, other references are fine
- If we ignore illegal references (that is what we used to do, and this is what I think we should keep doing), the inference might still succeed (quite possibly as part of an error recovery), therefore failing the inference once we detect an invalid reference is undesirable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I gave the alternative design a quick try to understand the problem better, and now understand your second bullet: In a scenario like var x1 = Dummy(TakeOutParam(true, out var x1), x1); (Scope_LocalDeclarationStmt_03) we'd set the type for x1 to object (because of coercion to parameter type from TakeOutParam) before we detect the circularity and thus before we get to record an error type.
jcouv
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (commit 11). May take another look after clarifying comment is addressed. Thanks
jcouv
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (commit 11)
|
@dotnet/roslyn-compiler For a second review |
1 similar comment
|
@dotnet/roslyn-compiler For a second review |
|
Taking a look today. |
src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs
Outdated
Show resolved
Hide resolved
| if (_forbiddenReferences?.Contains(reference) == true) | ||
| { | ||
| diagnostics.Add(ForbiddenDiagnostic, reference.Location, reference); | ||
| return TypeWithAnnotations.Create(this.DeclaringCompilation.ImplicitlyTypedVariableUsedInForbiddenZoneType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we SetTypeWithAnnotations at this point? Otherwise, is there a risk that the next caller to ask about the type, will cause the work of detecting the forbidden reference to be done over again? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like SetTypeWithAnnotations would not set the ImplicitlyTapedVariableUsedInForbiddenZoneType anyway. So, this suggestion might be irrelevant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we SetTypeWithAnnotations at this point?
I intentionally don't do that. Very often, even in presence of an illegal reference, during error recovery we are able to infer the type of the local pretty accurately. Basically I am trying maintain the previous behavior where the fact of detecting an illegal reference has no impact on the inferred type.
Otherwise, is there a risk that the next caller to ask about the type, will cause the work of detecting the forbidden reference to be done over again?
We should not be doing the work again for the same location since we recorded it in _forbiddenReferences, which is checked on entry and we return right away. It is possible that we will do the work for another location until we infer the type or explicitly fail the inference. Since the error should not be reported for legal locations, even in presence of an illegal reference, we have to check each distinct location. Eventually all of them get recorded (it is not an unbound set) and we complete the inference. After that we will not attempt the inference for the same local.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe I am following now, thanks.
Co-authored-by: Rikki Gibson <rikkigibson@gmail.com>
* upstream/main: (123 commits) Fix SafeContext of Span-valued collection expressions to match specification (dotnet#80684) Improve detection of invalid references for implicitly typed expression variables declared within implicit object creation expressions. (dotnet#80546) Add test demonstrating behavior of ToMinimalDisplayString. (dotnet#80757) Only set DOTNET_HOST_PATH if something was installed (dotnet#80842) Clean up a Razor external access service (dotnet#80830) Remove unused statement (dotnet#80823) Allow foreach on typed null enumerables (dotnet#80783) Update documentation for DeclaringSyntaxReferences and Locations to clarify partial members behavior (dotnet#80704) Fix issue converting an auto prop to a full prop when 'field' and 'initializers' are involved Rename childIsSimple to innerExpressionHasPrimaryPrecedence and add more test cases Remove placeholder WorkItem attributes from new tests Fix RemoveUnnecessaryParentheses to detect simple expressions in bitwise operations [main] Update dependencies from dotnet/arcade (dotnet#80828) Fix ITypeSymbol.BaseType documentation for type parameters (dotnet#80770) soft-select select camelcase matched item if user might be typing an undefined type parameter (dotnet#80809) Allow semantic tokens in Razor to be better behaved (dotnet#80815) Rebase Remove using Update test Add fix all test ...
Fixes #80518.