Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@AlekseyTs
Copy link
Contributor

No description provided.

@AlekseyTs
Copy link
Contributor Author

@333fred, @RikkiGibson, @dotnet/roslyn-compiler Please review


if (CheckDisallowedNullAssignment(operandType, parameterAnnotations, conversionOperand.Syntax))
{
LearnFromNonNullTest(conversionOperand, ref State);
Copy link
Contributor Author

@AlekseyTs AlekseyTs Dec 17, 2025

Choose a reason for hiding this comment

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

;

I'll be adding a PROTOTYPE comment to cover this code path #Closed

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

Done review pass (commit 1)

@AlekseyTs AlekseyTs requested a review from 333fred December 18, 2025 16:19
@AlekseyTs AlekseyTs requested a review from 333fred December 18, 2025 17:18
@AlekseyTs AlekseyTs requested a review from a team December 18, 2025 19:31
@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler For a second review

2 similar comments
@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler For a second review

@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler For a second review

return property is
{
IsStatic: false,
DeclaredAccessibility: Accessibility.Public,
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Dec 22, 2025

Choose a reason for hiding this comment

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

just checking, it's intentional that this is public, and not just accessible to the caller? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it is intentional. Definition of the interface from the spec:

public interface IUnion
{
    // The value of the union or null
    object? Value { get; }
}

BTW, this is not a new code, a member method got simply changed to a local function

Copy link
Member

Choose a reason for hiding this comment

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

Yes it is intentional. Definition of the interface from the spec:

Thanks :)

BTW, this is not a new code, a member method got simply changed to a local function

Understood. It was still something that i wanted to double check :)

DeclaredAccessibility: Accessibility.Public,
IsAbstract: true,
GetMethod: not null,
SetMethod: null,
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Dec 22, 2025

Choose a reason for hiding this comment

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

intentional that it definitely has no set method, not that it doesn't matter if it does/doesn't have it? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

intentional that it definitely has no set method, not that it doesn't matter if it does/doesn't have it?

Same response, intentional ,,,

Copy link
Member

Choose a reason for hiding this comment

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

Ok. This part was a bit odd to me though. it wasn't clear that a member that is a superset of what is needed is inadmissible. IMO, this would be worth bringing to an email/ldm for clarity. I'm totally fine if the resolution is "no, it can't have a setter". but i def want to know if that's the intent, vs "we don't care if it has a setter or not".

_variables[containingSlot].Symbol.GetTypeOrReturnType().Type is NamedTypeSymbol { IsUnionTypeNoUseSiteDiagnostics: true, UnionCaseTypes: not [] } unionType &&
Binder.GetUnionTypeValuePropertyNoUseSiteDiagnostics(unionType) == (object)property:
{
// For union types where none of the case types are nullable, the default state for Value is "not null" rather than "maybe null".
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Dec 22, 2025

Choose a reason for hiding this comment

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

is this only referring to 'nullable' for nullable-reference-types, or does this also encompass nullable value types? tahnks. #Resolved

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 this only referring to 'nullable' for nullable-reference-types, or does this also encompass nullable value types?

This is referring to nullable in terms of nullable analysis, and covers both kinds of types. BTW, this quotes the feature spec.

{
SetState(ref this.State, valueSlot, operandState);
}
}
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Dec 22, 2025

Choose a reason for hiding this comment

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

i'm curious if this code could be shared with the similar code on like 4276 #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i'm curious if this code could be shared with the similar code on lik

I will evaluate the possibility while working on the following changes

if (valueSlot > 0)
{
SetState(ref this.State, valueSlot, operandState);
}
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Dec 22, 2025

Choose a reason for hiding this comment

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

similar question here. there seems to be a common pattern of reading the operand state, making a new slot, and then storing state. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

similar question here.

The next change in the pipeline moves this code into VisitUnionConversion and it gets shared across the callers this way.

{
TrackNullableStateOfTupleConversion(conversionOpt, conversionOperand, conversion, targetType, operandType.Type, slot, valueSlot, assignmentKind, parameterOpt, reportWarnings: reportRemainingWarnings);
int slot = GetOrCreatePlaceholderSlot(conversionOpt);
if (slot > 0)
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Dec 22, 2025

Choose a reason for hiding this comment

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

should this (and the valueslot check above) have assertions that these are greater than 0? or is tehre an expectation that you could get 0/negative? if so, perhaps comment that. #Resolved

Copy link
Contributor Author

@AlekseyTs AlekseyTs Dec 22, 2025

Choose a reason for hiding this comment

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

should this ...

This is an existing code and I am not planning to add asserts or comments to it.

@CyrusNajmabadi
Copy link
Member

Haven't looked at tests ye. want i want to better understnd this null walking code first.

@AlekseyTs
Copy link
Contributor Author

@RikkiGibson, @dotnet/roslyn-compiler For a second review

var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
return HasTopLevelNullabilityImplicitConversion(source, destination) &&
ClassifyImplicitConversionFromType(source.Type, destination.Type, ref discardedUseSiteInfo).Kind != ConversionKind.NoConversion;
Conversion conversion = ClassifyImplicitConversionFromType(source.Type, destination.Type, ref discardedUseSiteInfo);
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

i know this didn't change. but so i understand better. what's the logic used to determine if the use site info can/should be discarded, vs having the caller of this pass in info to fill in that data? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i know this didn't change. but so i understand better. what's the logic used to determine if the use site info can/should be discarded, vs having the caller of this pass in info to fill in that data?

I do not know if this answer is going to help much, but the decision is usually made based on the planned/expected usage pattern for the API and whether we will be interested in the info in the callers.

ClassifyImplicitConversionFromType(source.Type, destination.Type, ref discardedUseSiteInfo).Kind != ConversionKind.NoConversion;
Conversion conversion = ClassifyImplicitConversionFromType(source.Type, destination.Type, ref discardedUseSiteInfo);
return conversion.Kind != ConversionKind.NoConversion &&
(conversion.IsUnion || conversion.IsUserDefined || HasTopLevelNullabilityImplicitConversion(source, destination));
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

i guess i'm not understanding why having a conversion be a union or user-defined means that there are "any nullability implicit conversion". Does this fall out from something in the spec? something else? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i guess i'm not understanding why having a conversion be a union or user-defined means that there are "any nullability implicit conversion". Does this fall out from something in the spec? something else?

The logic in HasTopLevelNullabilityImplicitConversion doesn't look appropriate for user-defined and union conversions because nullability doesn't transfer directly from source to result in these cases. The change was prompted by new tests that I added.

if (!conversions.HasTopLevelNullabilityImplicitConversion(s, d))
Conversion conversion = conversions.ClassifyImplicitConversionFromType(s.Type, d.Type, ref u);

if (!conversion.IsUserDefined && !conversion.IsUnion && !conversions.HasTopLevelNullabilityImplicitConversion(s, d))
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

this check seems to be the negation of the check done in HasAnyNullabilityImplicitConversion. Would it make sense to extract to some named helper that both could use that would then be clearer as to why it is the right check in both locations? #Resolved

Copy link
Contributor Author

@AlekseyTs AlekseyTs Jan 6, 2026

Choose a reason for hiding this comment

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

this check seems to be the negation of the check done in HasAnyNullabilityImplicitConversion. Would it make sense to extract to some named helper that both could use that would then be clearer as to why it is the right check in both locations?

I do not think this is worthwhile and not planning doing that. There are other call sites for HasTopLevelNullabilityImplicitConversion

if (!conversions.HasTopLevelNullabilityImplicitConversion(s, d))
Conversion conversion = conversions.ClassifyConversionFromType(s.Type, d.Type, isChecked: isChecked, ref u, forCast);

if (!conversion.IsUserDefined && !conversion.IsUnion && !conversions.HasTopLevelNullabilityImplicitConversion(s, d))
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

same concern here. doing the same check in 3 places def makes it seem worthwhile to extract out. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

same concern here. doing the same check in 3 places def makes it seem worthwhile to extract out.

I have no plans doing that


return conversions.ClassifyImplicitConversionFromTypeWhenNeitherOrBothFunctionTypes(source, destination, ref useSiteInfo).Exists;
Conversion conversion = conversions.ClassifyImplicitConversionFromTypeWhenNeitherOrBothFunctionTypes(source, destination, ref useSiteInfo);
return conversion.Exists && (conversion.IsUnion || conversion.IsUserDefined || conversions.HasTopLevelNullabilityImplicitConversion(sourceWithAnnotations, destinationWithAnnotations));
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

here as well. #WontFix

Copy link
Member

Choose a reason for hiding this comment

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

having the same logic in 4 places def seems like something worth extracting. especially since, frm my readin ghere, if we updated one, we'd want to update all 4 of these.

case PropertySymbol { Name: WellKnownMemberNames.ValuePropertyName } property when
variable.ContainingSlot is > 0 and var containingSlot &&
property.ContainingType.IsWellKnownTypeIUnion() &&
_variables[containingSlot].Symbol.GetTypeOrReturnType().Type is NamedTypeSymbol { IsUnionTypeNoUseSiteDiagnostics: true, UnionCaseTypes: not [] } unionType &&
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

interesting to have the not [] check there. Does a union require at least one case-type? (this is not a comment about the code, just trying to wrap my head around how the language views things). #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just trying to wrap my head around how the language views things

There is nothing interesting we can possibly do for a union type without case types.

conversionsWithoutNullability.HasIdentityOrImplicitReferenceConversion(possibleDerived, possibleBase, ref discardedUseSiteInfo) ||
conversionsWithoutNullability.HasBoxingConversion(possibleDerived, possibleBase, ref discardedUseSiteInfo);
conversionsWithoutNullability.HasBoxingConversion(possibleDerived, possibleBase, ref discardedUseSiteInfo) ||
(possibleBase.IsInterfaceType() && conversionsWithoutNullability.HasImplicitConversionToOrImplementsVarianceCompatibleInterface(possibleDerived, (NamedTypeSymbol)possibleBase, ref discardedUseSiteInfo, needSupportForRefStructInterfaces: out _));
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

i'm not really understanding the purpose/impact of this code here. Would it be possible to break this up into individual checks, with an explanation of why this case is not a slot member? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i'm not really understanding the purpose/impact of this code here. Would it be possible to break this up into individual checks, with an explanation of why this case is not a slot member?

This change isn't union specific. Ref structs implementing interfaces cannot be boxed. Therefore, the previous check for HasBoxingConversion fails for them even when a ref struct implements the interface. This change simply patches the condition to properly handle ref struct scenarios.

var resultState = NullableFlowState.NotNull;
if (type is object &&
(hasObjectInitializer || type.IsStructType()))
(hasObjectInitializer || type.IsStructType() || isSuitableUnionConstruction(type, constructor, out _)))
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

rthis is a bit interesting. it seems like we'll almost always call into this a second time below. so maybe it would make sense to just life this out and always call it, always assigning to out PropertySymbol? valueProperty. Then, inside, you can just check if htat value is non-null to go into the innermost if-block. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then, inside, you can just check if htat value is non-null to go into the innermost if-block.

I am more comfortable with the current code flow for now.

}
else
{
operandState = argumentResults[0].RValueType.State;
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

i'm curious why this is the right operand state if the operand slot was <= 0. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i'm curious why this is the right operand state if the operand slot was <= 0.

If we don't have a slot for the argument, this is the only information we have for its state.

constructor.ContainingType.Equals(type, TypeCompareKind.AllIgnoreOptions) &&
type is NamedTypeSymbol { IsUnionTypeNoUseSiteDiagnostics: true } unionType &&
NamedTypeSymbol.IsSuitableUnionConstructor(constructor) &&
(valueProperty = Binder.GetUnionTypeValuePropertyNoUseSiteDiagnostics(unionType)) is { };
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

fwiw, doing the assignment/check to the out param within this complex epression was difficult to discover/understand. I think breaking into a simple assignment and null check would be clearer and more idiomatic. #Resolved

static void Test5(bool? x)
{
#line 500
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

the use of #line is helpful. thanks. #Resolved

[CombinatorialData]
public void NullableAnalysis_05_State_From_Constructor([CombinatorialValues("class", "struct")] string typeKind)
{
var src = @"
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

curious why concat instead of string (or raw string) interpolation. Parsing this out def took more mental effort for me :) #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

curious why concat instead of string (or raw string) interpolation.

Personal preference

static void Test2()
{
#line 200
var s = new S1("""");
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Jan 6, 2026

Choose a reason for hiding this comment

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

raw strings would make internal string literals easier to reason about when reviewing. tnx. #WontFix

}

[Fact]
public void NullableAnalysis_19_State_From_Null_Test()
Copy link
Member

Choose a reason for hiding this comment

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

reached this test. pausing.

RikkiGibson
RikkiGibson previously approved these changes Jan 6, 2026
@RikkiGibson RikkiGibson dismissed their stale review January 6, 2026 23:05

Misclick, not finished yet, sorry.

Copy link
Member

@RikkiGibson RikkiGibson left a comment

Choose a reason for hiding this comment

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

LGTM, comment is not blocking.

Debug.Assert(conversionOperandSlot > 0);
}

if (conversionOperandSlot > 0 && valueFieldSlot > 0)
Copy link
Member

Choose a reason for hiding this comment

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

I didn't follow why we only want to perform the below SetState call when valueFieldSlot > 0. I don't think there is any test in UnionsTests which fails when the condition is removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't follow why we only want to perform the below SetState call when valueFieldSlot > 0.

Probably the thinking was that setting the state is not really useful otherwise. Only when valueFieldSlot > 0 the valueFieldType.State is actually coming from this.State. I am changing this code significantly in the next PR and this logic is changing as well.

@AlekseyTs AlekseyTs merged commit 2e48d2f into dotnet:features/Unions Jan 7, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants