Skip to content

Fix result and slot of conditional receiver#33997

Merged
jcouv merged 3 commits intodotnet:masterfrom
jcouv:cond-nullability
Mar 12, 2019
Merged

Fix result and slot of conditional receiver#33997
jcouv merged 3 commits intodotnet:masterfrom
jcouv:cond-nullability

Conversation

@jcouv
Copy link
Member

@jcouv jcouv commented Mar 9, 2019

As the Dump() for a conditional access (excerpted below) shows, the call or member access uses a placeholder ("conditional receiver") in place of the actual receiver.

There are two parts of this PR:

  • we ensure that visiting a conditional receiver produces the correct result type,
  • we ensure that asking for a slot for a conditional receiver produces the correct slot.

To do that, we store some information (the result and the slot) about the real receiver when we visit a conditional access.

conditionalAccess
    ├─receiver
    │ └─local
    │   ├─localSymbol: Container<System.String> x
    │   ├─type: Container<System.String>
    │   └─isSuppressed: False
    ├─accessExpression
    │ └─call
    │   ├─receiverOpt
    │   │ └─fieldAccess
    │   │   ├─receiverOpt
    │   │   │ └─conditionalReceiver
    │   │   │   ├─id: 0
    │   │   │   ├─type: Container<System.String>
    │   │   │   └─isSuppressed: False
    │   │   ├─fieldSymbol: System.String Container<System.String>.Field
    │   │   └─isSuppressed: False
    │   ├─method: System.String System.String.ToString()
    │   ├─type: System.String
    │   └─isSuppressed: False
    ├─type: System.String
    └─isSuppressed: False

@jcouv jcouv added this to the 16.1.P1 milestone Mar 9, 2019
@jcouv jcouv self-assigned this Mar 9, 2019
@jcouv jcouv force-pushed the cond-nullability branch from 1b382a9 to 948e974 Compare March 10, 2019 01:40
@jcouv jcouv changed the title Fix result of conditional receiver Fix result and slot of conditional receiver Mar 10, 2019
@jcouv jcouv marked this pull request as ready for review March 10, 2019 03:44
@jcouv jcouv requested a review from a team as a code owner March 10, 2019 03:44
@jcouv jcouv force-pushed the cond-nullability branch from 948e974 to 69f7810 Compare March 10, 2019 03:50
@jcouv jcouv force-pushed the cond-nullability branch from 69f7810 to 2e410b8 Compare March 10, 2019 03:58
// so we reset before leaving the function.
_lastConditionalAccessSlot = -1;

_lastConditionalAccessSlot = previousConditionalAccessSlot;
Copy link
Member

@jaredpar jaredpar Mar 10, 2019

Choose a reason for hiding this comment

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

Did you consider a finally block here? Whenever we have resets like this I worry about the risk of a later change inserting a return in the middle. I may be over paranoid here. #Pending

Copy link
Member Author

@jcouv jcouv Mar 11, 2019

Choose a reason for hiding this comment

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

I don't think we need a try/finally here because any exception will terminate the analysis, so the state we leave it in won't matter.
We have very limited exception handling in flow analysis (only one finally to free the pooled object when hitting stackguard). #Pending

Copy link
Contributor

Choose a reason for hiding this comment

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

The concern is with returning early rather than exceptions. I think I'd prefer to have this reset in a finally block.


In reply to: 264336845 [](ancestors = 264336845)

Copy link
Member Author

@jcouv jcouv Mar 11, 2019

Choose a reason for hiding this comment

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

Sorry, I'd misunderstood. Will fix #Pending


var receiver = node.Receiver;
var receiverType = VisitRvalueWithState(receiver);
_currentConditionalReceiverVisitResult = _visitResult;
Copy link
Member

@jaredpar jaredpar Mar 10, 2019

Choose a reason for hiding this comment

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

Consider a Debug.Assert(_currentConditionalReceiverVisitResult == default predicate to this method. #Resolved

Copy link
Member Author

@jcouv jcouv Mar 11, 2019

Choose a reason for hiding this comment

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

I think that wouldn't be the case in x?.y?.f when we get the y. I'll double-check. #Resolved

Copy link
Member

@jaredpar jaredpar left a comment

Choose a reason for hiding this comment

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

:shipit:

@jcouv
Copy link
Member Author

jcouv commented Mar 11, 2019

@dotnet/roslyn-compiler for a second review. Thanks #Resolved

var rvalueType = _currentConditionalReceiverVisitResult.RValueType.Type;
if (rvalueType?.IsNullableType() == true)
{
rvalueType = rvalueType.GetNullableUnderlyingTypeWithAnnotations().TypeSymbol;
Copy link
Contributor

@cston cston Mar 11, 2019

Choose a reason for hiding this comment

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

GetNullableUnderlyingTypeWithAnnotations().TypeSymbol [](start = 40, length = 53)

GetNullableUnderlyingType() #Pending

var x = Create(s1);
var y = Create(s2);
x?.Field /*1*/
.Extension(y?.Field.ToString());
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this test that the conditional access receiver field is reset? Is there overlap between the two receivers?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think any scenario can currently hit the "reset". I've included the "reset" to be safe for future changes.
The reason we can't hit it now is that to have a nested conditional, you must have some invocation or indexer with arguments, but then we're no longer dealing with fields (ie. slots).


In reply to: 264353430 [](ancestors = 264353430)

public C? f;
int? Test1(C? c) => c?.f.M(c.f.ToString()); // nested use of `c.f` is safe
int? Test2(C? c) => c.f.M(c.f.ToString());
int M(string s) => s.Length;
Copy link
Contributor

Choose a reason for hiding this comment

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

int M(string s) => s.Length; [](start = 4, length = 28)

Can this be removed?

Copy link
Member Author

Choose a reason for hiding this comment

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

I've simplified M a bit, but otherwise left it. I need it to test access on c.f nested in expression with c?.f.

void Test2(C? c) => c.Nested?.M(c.Nested.ToString());
void Test3(C c) => c?.Nested?.M(c.Nested.ToString());

void M(string s) => throw null!;
Copy link
Contributor

Choose a reason for hiding this comment

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

void M(string s) => throw null!; [](start = 4, length = 32)

Can this be removed?

Copy link
Member Author

Choose a reason for hiding this comment

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

I didn't understand. M is used above.


In reply to: 264355329 [](ancestors = 264355329)

@jcouv jcouv closed this Mar 11, 2019
@jcouv jcouv reopened this Mar 11, 2019
@jcouv jcouv closed this Mar 11, 2019
@jcouv jcouv reopened this Mar 11, 2019
@jcouv jcouv merged commit 74502ba into dotnet:master Mar 12, 2019
@jcouv jcouv deleted the cond-nullability branch March 12, 2019 03:07
@gafter
Copy link
Member

gafter commented Apr 12, 2019

We can see from these three lines that the underlying issue has not been fixed.


Refers to: src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs:2265 in 0df246e. [](commit_id = 0df246e, deletion_comment = False)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

4 participants