Skip to content
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

Flow analysis of with-expressions #44644

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
13e15e1
Use init-only for records and adjust binding
agocke Apr 28, 2020
74e8c4a
Add tests
agocke May 27, 2020
84d9dbc
Implement flow analysis for with expressions
RikkiGibson May 27, 2020
b3569ee
WIP
RikkiGibson May 28, 2020
64ebad9
Reuse object initializer binding + lowering
agocke May 28, 2020
1797b75
Implement some nullable analysis of with-exprs
RikkiGibson May 28, 2020
a8536ce
Reduce diff churn
RikkiGibson May 28, 2020
a8471fe
Respond to PR comments
agocke May 28, 2020
467566d
Merge branch 'features/records' of github.com:dotnet/roslyn into with…
RikkiGibson May 28, 2020
1c7b14c
Merge remote-tracking branch 'upstream/features/records' into with-ex…
agocke May 29, 2020
9f605e2
Respond to PR comments
agocke May 29, 2020
891fd42
Update src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/Synt…
agocke May 29, 2020
09d8ca2
Merge remote-tracking branch 'agocke/with-expr-fix-binding' into with…
RikkiGibson May 29, 2020
bd18ab7
Fix formatting
agocke May 29, 2020
3f35beb
Fix formatting
agocke May 29, 2020
7f7be73
Pull in some changes from with-expr-fix-binding
RikkiGibson May 29, 2020
52fcf20
Merge branch 'with-expr-fix-binding' of github.com:agocke/roslyn into…
RikkiGibson May 29, 2020
67ad61c
Merge branch 'features/records' of github.com:dotnet/roslyn into with…
RikkiGibson May 30, 2020
1b9da52
Reuse object initializer analysis
RikkiGibson Jun 1, 2020
0c77d63
Add simple with-expr dataflow test
RikkiGibson Jun 1, 2020
9812a01
Unskip test, change PROTOTYPE to issue link
RikkiGibson Jun 1, 2020
fdfe365
Add a few more nullable tests
RikkiGibson Jun 1, 2020
dc787b9
Merge branch 'features/records' of github.com:dotnet/roslyn into with…
RikkiGibson Jun 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ private BoundExpression BindWithExpression(WithExpressionSyntax syntax, Diagnost
}
}

var cloneReturnType = cloneMethod?.ReturnType;
// Even if a clone method is not present, we should come up with
// a type to lookup members of the with-expression in for error recovery
var cloneReturnType = cloneMethod?.ReturnType ?? receiverType;
RoslynDebug.AssertNotNull(cloneReturnType);
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved

var args = ArrayBuilder<(Symbol?, BoundExpression)>.GetInstance();
// Bind with expression arguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2026,7 +2026,11 @@ public override BoundNode VisitWhileStatement(BoundWhileStatement node)

public override BoundNode VisitWithExpression(BoundWithExpression node)
{
// PROTOTYPE: This is wrong
VisitRvalue(node.Receiver);
foreach (var (_, expr) in node.Arguments)
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
{
VisitRvalue(expr);
}
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,20 @@ private void VisitBinaryOperatorChildren(BoundBinaryOperatorBase node)
Visit(node.BoundContainingTypeOpt);
return null;
}

public override BoundNode? VisitWithExpression(BoundWithExpression node)
{
Visit(node.Receiver);
// PROTOTYPE: We shouldn't need to implement this method manually here.
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
// Either BoundNodeClassWriter should be made aware of tuples,
// or the `BoundWithExpression.Arguments` should be changed to
// `ImmutableArray<BoundExpression>` as in `BoundObjectInitializerExpressionBase.Initializers`.
foreach (var (_, expr) in node.Arguments)
{
Visit(expr);
}
return null;
}
}
#endif
}
Expand Down
106 changes: 69 additions & 37 deletions src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2222,12 +2222,44 @@ public override BoundNode VisitWhileStatement(BoundWhileStatement node)
return base.VisitWhileStatement(node);
}

public override BoundNode VisitWithExpression(BoundWithExpression expr)
public override BoundNode VisitWithExpression(BoundWithExpression withExpr)
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
{
// PROTOTYPE: This is wrong
SetResultType(expr,
expr.CloneMethod?.ReturnTypeWithAnnotations.ToTypeWithState()
?? TypeWithState.Create(expr.Type, NullableFlowState.NotNull));
Debug.Assert(!IsConditionalState);

var receiver = withExpr.Receiver;
VisitRvalue(receiver);
_ = CheckPossibleNullReceiver(receiver, checkNullableValueType: false);

var resultSlot = GetOrCreatePlaceholderSlot(withExpr);
// carry over the null state of members of 'receiver' to the result of the with-expression.
TrackNullableStateForAssignment(receiver, ResultType.ToTypeWithAnnotations(), resultSlot, ResultType, MakeSlot(receiver));

foreach (var (leftSymbol, right) in withExpr.Arguments)
{
if (leftSymbol is PropertySymbol prop)
{
// PROTOTYPE: attributes on positional record parameters do not propagate
// to the synthesized properties. Perhaps this is by design?
// If we want them to propagate then perhaps we should move
// members such as SourcePropertySymbol.HasAllowNull, etc. to SourceOrRecordPropertySymbol.
var leftAnnotations = GetPropertySetterAnnotations(prop);
var leftLValueType = ApplyLValueAnnotations(prop.TypeWithAnnotations, leftAnnotations);

var rightState = VisitOptionalImplicitConversion(right, targetTypeOpt: leftLValueType, useLegacyWarnings: false, trackMembers: true, AssignmentKind.Assignment);
CheckDisallowedNullAssignment(rightState, leftAnnotations, right.Syntax.Location);
var propSlot = GetOrCreateSlot(prop, resultSlot);
TrackNullableStateForAssignment(right, leftLValueType, propSlot, rightState, MakeSlot(right));

// PROTOTYPE: Should we AdjustSetValue for properties?
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
// e.g. a property with [AllowNull, NotNull] would have a NotNull state after assigning a maybe-null.
// This isn't safe at runtime but it is consistent with what we allow users to do with auto properties.
// AdjustSetValue(left, declaredType, leftLValueType, ref rightState);
}
}

SetResultType(withExpr,
withExpr.CloneMethod?.ReturnTypeWithAnnotations.ToTypeWithState()
?? TypeWithState.Create(withExpr.Type, NullableFlowState.NotNull));
return null;
}

Expand Down Expand Up @@ -6981,6 +7013,34 @@ FlowAnalysisAnnotations getRValueAnnotations(BoundExpression expr)
}
}

private static FlowAnalysisAnnotations GetPropertySetterAnnotations(PropertySymbol property)
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
{
var accessor = property.GetOwnOrInheritedSetMethod();
if (accessor is object)
{
return accessor.Parameters.Last().FlowAnalysisAnnotations;
}
if (property is SourcePropertySymbol sourceProperty)
{
return getPropertyAnnotations(sourceProperty);
}
return FlowAnalysisAnnotations.None;

static FlowAnalysisAnnotations getPropertyAnnotations(SourcePropertySymbol property)
{
var annotations = FlowAnalysisAnnotations.None;
if (property.HasAllowNull)
{
annotations |= FlowAnalysisAnnotations.AllowNull;
}
if (property.HasDisallowNull)
{
annotations |= FlowAnalysisAnnotations.DisallowNull;
}
return annotations;
}
}

private FlowAnalysisAnnotations GetLValueAnnotations(BoundExpression expr)
{
// Annotations are ignored when binding an attribute to avoid cycles. (Members used
Expand All @@ -6992,10 +7052,10 @@ private FlowAnalysisAnnotations GetLValueAnnotations(BoundExpression expr)

var annotations = expr switch
{
BoundPropertyAccess property => getSetterAnnotations(property.PropertySymbol),
BoundIndexerAccess indexer => getSetterAnnotations(indexer.Indexer),
BoundPropertyAccess property => GetPropertySetterAnnotations(property.PropertySymbol),
BoundIndexerAccess indexer => GetPropertySetterAnnotations(indexer.Indexer),
BoundFieldAccess field => getFieldAnnotations(field.FieldSymbol),
BoundObjectInitializerMember { MemberSymbol: PropertySymbol prop } => getSetterAnnotations(prop),
BoundObjectInitializerMember { MemberSymbol: PropertySymbol prop } => GetPropertySetterAnnotations(prop),
BoundObjectInitializerMember { MemberSymbol: FieldSymbol field } => getFieldAnnotations(field),
BoundParameter { ParameterSymbol: ParameterSymbol parameter }
=> ToInwardAnnotations(GetParameterAnnotations(parameter) & ~FlowAnalysisAnnotations.NotNull), // NotNull is enforced upon method exit
Expand All @@ -7007,37 +7067,9 @@ private FlowAnalysisAnnotations GetLValueAnnotations(BoundExpression expr)
static FlowAnalysisAnnotations getFieldAnnotations(FieldSymbol field)
{
return field.AssociatedSymbol is PropertySymbol property ?
getSetterAnnotations(property) :
GetPropertySetterAnnotations(property) :
field.FlowAnalysisAnnotations;
}

static FlowAnalysisAnnotations getSetterAnnotations(PropertySymbol property)
{
var accessor = property.GetOwnOrInheritedSetMethod();
if (accessor is object)
{
return accessor.Parameters.Last().FlowAnalysisAnnotations;
}
if (property is SourcePropertySymbol sourceProperty)
{
return getPropertyAnnotations(sourceProperty);
}
return FlowAnalysisAnnotations.None;
}

static FlowAnalysisAnnotations getPropertyAnnotations(SourcePropertySymbol property)
{
var annotations = FlowAnalysisAnnotations.None;
if (property.HasAllowNull)
{
annotations |= FlowAnalysisAnnotations.AllowNull;
}
if (property.HasDisallowNull)
{
annotations |= FlowAnalysisAnnotations.DisallowNull;
}
return annotations;
}
}

private static FlowAnalysisAnnotations ToInwardAnnotations(FlowAnalysisAnnotations outwardAnnotations)
Expand Down
Loading