Skip to content

Conversation

@AlekseyTs
Copy link
Contributor

Fixes #80518.

…ject creation should be expanded to an argument list immediately enclosing the object creation

Fixes dotnet#80518.
@AlekseyTs AlekseyTs requested a review from a team as a code owner October 2, 2025 16:06
@AlekseyTs AlekseyTs added Area-Compilers Feature - Target-Typed New `new (args)` gets the created type inferred from context labels Oct 2, 2025
@CyrusNajmabadi
Copy link
Member

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;
}
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Oct 2, 2025

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

Copy link
Contributor Author

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.
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Oct 2, 2025

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

Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wfm.

CyrusNajmabadi
CyrusNajmabadi previously approved these changes Oct 2, 2025
@AlekseyTs
Copy link
Contributor Author

does this fall out from the rules of the lang already spec'ed?

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 new() is converted, but the new() needs overload resolution to complete in order to get converted).

@CyrusNajmabadi
Copy link
Member

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 new() is converted, but the new() needs overload resolution to complete in order to get converted).

That makes perfect sense. Thanks.

@AlekseyTs AlekseyTs marked this pull request as draft October 2, 2025 21:29
@AlekseyTs AlekseyTs closed this Oct 3, 2025
…on variables declared within implicit object creation expressions.
@AlekseyTs AlekseyTs reopened this Oct 8, 2025
@AlekseyTs AlekseyTs changed the title Forbidden zone for an out var used as an argument of an implicit object creation should be expanded to an argument list immediately enclosing the object creation Improve detection of invalid references for implicitly typed expression variables declared within implicit object creation expressions. Oct 8, 2025
@AlekseyTs AlekseyTs marked this pull request as ready for review October 8, 2025 21:12
//
// 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,
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Oct 8, 2025

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

@CyrusNajmabadi
Copy link
Member

Can review fully tomorrow. Thanks.

@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler Please review

@AlekseyTs AlekseyTs requested a review from jcouv October 9, 2025 13:15
@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler Please review

@AlekseyTs
Copy link
Contributor Author

@jcouv, @dotnet/roslyn-compiler Please review

@jcouv jcouv self-assigned this Oct 13, 2025
internal virtual ErrorCode ForbiddenDiagnostic => ErrorCode.ERR_VariableUsedBeforeDeclaration;
/// </param>
/// <returns></returns>
internal virtual bool IsForbiddenReference(SyntaxNode reference, out ErrorCode forbiddenDiagnostic)
Copy link
Member

@jcouv jcouv Oct 13, 2025

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);
Copy link
Member

@jcouv jcouv Oct 13, 2025

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? #Closed

Copy link
Contributor Author

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,
Copy link
Member

@jcouv jcouv Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dealind

typo: dealing #Closed

@AlekseyTs AlekseyTs requested review from a team and jcouv October 16, 2025 15:19
@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler Please review

@AlekseyTs
Copy link
Contributor Author

@jcouv, @dotnet/roslyn-compiler Please review

{
get
{
return field ??= new ExtendedErrorTypeSymbol(this, name: "var", arity: 0, errorInfo: null, variableUsedBeforeDeclaration: true);
Copy link
Member

@jcouv jcouv Oct 20, 2025

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

Copy link
Contributor Author

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)?

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;
Copy link
Member

@jcouv jcouv Oct 20, 2025

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

Copy link
Contributor Author

@AlekseyTs AlekseyTs Oct 20, 2025

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.

Copy link
Member

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.

Copy link
Member

@jcouv jcouv left a 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

Copy link
Member

@jcouv jcouv left a 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)

@AlekseyTs AlekseyTs requested review from a team and jcouv October 20, 2025 19:24
@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler For a second review

1 similar comment
@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler For a second review

@RikkiGibson
Copy link
Member

Taking a look today.

if (_forbiddenReferences?.Contains(reference) == true)
{
diagnostics.Add(ForbiddenDiagnostic, reference.Location, reference);
return TypeWithAnnotations.Create(this.DeclaringCompilation.ImplicitlyTypedVariableUsedInForbiddenZoneType);
Copy link
Member

@RikkiGibson RikkiGibson Oct 21, 2025

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

Copy link
Member

@RikkiGibson RikkiGibson Oct 21, 2025

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.

Copy link
Contributor Author

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.

Copy link
Member

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>
@AlekseyTs AlekseyTs enabled auto-merge (squash) October 22, 2025 13:38
@AlekseyTs AlekseyTs merged commit f230001 into dotnet:main Oct 22, 2025
23 of 24 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Oct 22, 2025
333fred added a commit to 333fred/roslyn that referenced this pull request Oct 22, 2025
* 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
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Compilers Feature - Target-Typed New `new (args)` gets the created type inferred from context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stack overflow in compiler binding out-vars in implicit object creation.

4 participants