-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Enforce unconditional attributes within method bodies #40789
Changes from 6 commits
3e6559d
c688ccb
ac90c31
db6d529
8f58732
efad142
9e6927f
c1bbd61
9cc632d
2e3a1d9
501855b
342f856
8cf4b46
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1057,7 +1057,6 @@ private void ReportNullableAssignmentIfNecessary( | |
} | ||
|
||
if (value == null || | ||
value.WasCompilerGenerated || | ||
!targetType.HasType || | ||
targetType.Type.IsValueType) | ||
{ | ||
|
@@ -1420,9 +1419,17 @@ private void EnterParameter(ParameterSymbol parameter, TypeWithAnnotations param | |
|
||
private static TypeWithState GetParameterState(TypeWithAnnotations parameterType, FlowAnalysisAnnotations parameterAnnotations) | ||
{ | ||
return ((parameterAnnotations & FlowAnalysisAnnotations.AllowNull) != 0) ? | ||
TypeWithState.Create(parameterType.Type, NullableFlowState.MaybeDefault) : | ||
parameterType.ToTypeWithState(); | ||
if ((parameterAnnotations & FlowAnalysisAnnotations.AllowNull) != 0) | ||
{ | ||
return TypeWithState.Create(parameterType.Type, NullableFlowState.MaybeDefault); | ||
} | ||
|
||
if ((parameterAnnotations & FlowAnalysisAnnotations.DisallowNull) != 0) | ||
{ | ||
return TypeWithState.Create(parameterType.Type, NullableFlowState.NotNull); | ||
} | ||
|
||
return parameterType.ToTypeWithState(); | ||
} | ||
|
||
protected override BoundNode VisitReturnStatementNoAdjust(BoundReturnStatement node) | ||
|
@@ -1437,17 +1444,21 @@ protected override BoundNode VisitReturnStatementNoAdjust(BoundReturnStatement n | |
|
||
// Should not convert to method return type when inferring return type (when _returnTypesOpt != null). | ||
if (_returnTypesOpt == null && | ||
TryGetReturnType(out TypeWithAnnotations returnType)) | ||
TryGetReturnType(out TypeWithAnnotations returnType, out FlowAnalysisAnnotations returnAnnotations)) | ||
{ | ||
TypeWithState returnState; | ||
if (node.RefKind == RefKind.None) | ||
{ | ||
VisitOptionalImplicitConversion(expr, returnType, useLegacyWarnings: false, trackMembers: false, AssignmentKind.Return); | ||
returnState = VisitOptionalImplicitConversion(expr, returnType, useLegacyWarnings: false, trackMembers: false, AssignmentKind.Return); | ||
} | ||
else | ||
{ | ||
// return ref expr; | ||
VisitRefExpression(expr, returnType); | ||
returnState = VisitRefExpression(expr, returnType); | ||
} | ||
|
||
// If the return has annotations, we perform an additional check for nullable value types | ||
CheckDisallowedNullAssignment(returnState, returnAnnotations, node.Syntax.Location, forReturn: true); | ||
} | ||
else | ||
{ | ||
|
@@ -1483,12 +1494,13 @@ private TypeWithState VisitRefExpression(BoundExpression expr, TypeWithAnnotatio | |
return resultType; | ||
} | ||
|
||
private bool TryGetReturnType(out TypeWithAnnotations type) | ||
private bool TryGetReturnType(out TypeWithAnnotations type, out FlowAnalysisAnnotations annotations) | ||
{ | ||
var method = _symbol as MethodSymbol; | ||
if (method is null) | ||
{ | ||
type = default; | ||
annotations = FlowAnalysisAnnotations.None; | ||
return false; | ||
} | ||
|
||
|
@@ -1499,22 +1511,26 @@ private bool TryGetReturnType(out TypeWithAnnotations type) | |
if (returnType.IsVoidType()) | ||
{ | ||
type = default; | ||
annotations = FlowAnalysisAnnotations.None; | ||
return false; | ||
} | ||
|
||
if (!method.IsAsync) | ||
{ | ||
type = ApplyUnconditionalAnnotations(returnType, delegateOrMethod.ReturnTypeFlowAnalysisAnnotations); | ||
annotations = delegateOrMethod.ReturnTypeFlowAnalysisAnnotations; | ||
type = ApplyUnconditionalAnnotations(returnType, annotations); | ||
return true; | ||
} | ||
|
||
if (returnType.Type.IsGenericTaskType(compilation)) | ||
{ | ||
type = ((NamedTypeSymbol)returnType.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single(); | ||
annotations = FlowAnalysisAnnotations.None; | ||
return true; | ||
} | ||
|
||
type = default; | ||
annotations = FlowAnalysisAnnotations.None; | ||
return false; | ||
} | ||
|
||
|
@@ -3224,21 +3240,11 @@ private void ReinferMethodAndVisitArguments(BoundCall node, TypeWithState receiv | |
{ | ||
returnState = returnState.WithNotNullState(); | ||
} | ||
ReportMaybeNullFromTypeParameterValueIfNeeded(node, returnState, GetRValueAnnotations(method)); | ||
|
||
SetResult(node, returnState, method.ReturnTypeWithAnnotations); | ||
SetUpdatedSymbol(node, node.Method, method); | ||
} | ||
|
||
/// <summary> | ||
/// Members that return [MaybeNull]T values (for an unconstrained type parameter) produce a warning upon usage, | ||
/// just like default(T). | ||
/// </summary> | ||
private void ReportMaybeNullFromTypeParameterValueIfNeeded(BoundExpression expr, TypeWithState typeWithState, FlowAnalysisAnnotations annotations) | ||
{ | ||
// Not implemented. See https://github.com/dotnet/roslyn/issues/38638 for an alternative. | ||
} | ||
|
||
private void LearnFromEqualsMethod(MethodSymbol method, BoundCall node, TypeWithState receiverType, ImmutableArray<VisitArgumentResult> results) | ||
{ | ||
// easy out | ||
|
@@ -3254,7 +3260,6 @@ private void LearnFromEqualsMethod(MethodSymbol method, BoundCall node, TypeWith | |
return; | ||
} | ||
|
||
|
||
var isStaticEqualsMethod = method.Equals(compilation.GetSpecialTypeMember(SpecialMember.System_Object__EqualsObjectObject)) | ||
|| method.Equals(compilation.GetSpecialTypeMember(SpecialMember.System_Object__ReferenceEquals)); | ||
if (isStaticEqualsMethod || | ||
|
@@ -3498,6 +3503,11 @@ private static TypeWithAnnotations ApplyUnconditionalAnnotations(TypeWithAnnotat | |
return declaredType.AsAnnotated(); | ||
} | ||
|
||
if ((annotations & FlowAnalysisAnnotations.NotNull) == FlowAnalysisAnnotations.NotNull) | ||
{ | ||
return declaredType.AsNotAnnotated(); | ||
} | ||
|
||
return declaredType; | ||
} | ||
|
||
|
@@ -3883,12 +3893,39 @@ private void VisitArgumentConversionAndInboundAssignmentsAndPreConditions( | |
Debug.Assert(!this.IsConditionalState); | ||
} | ||
|
||
private void CheckDisallowedNullAssignment(TypeWithState state, FlowAnalysisAnnotations annotations, Location location) | ||
private void CheckDisallowedNullAssignment(TypeWithState state, FlowAnalysisAnnotations annotations, Location location, bool forReturn = false) | ||
{ | ||
if (((annotations & FlowAnalysisAnnotations.DisallowNull) != 0) && state.Type.IsNullableTypeOrTypeParameter() && state.MayBeNull) | ||
if (forReturn) | ||
{ | ||
annotations = ToInwardAnnotations(annotations); | ||
} | ||
|
||
// We do this extra check for types whose non-nullable version cannot be represented | ||
if (((annotations & FlowAnalysisAnnotations.DisallowNull) != 0) && hasNoNonNullableCounterpart(state.Type) && state.MayBeNull) | ||
{ | ||
ReportDiagnostic(ErrorCode.WRN_DisallowNullAttributeForbidsMaybeNullAssignment, location); | ||
} | ||
|
||
static bool hasNoNonNullableCounterpart(TypeSymbol type) | ||
{ | ||
if (type is null) | ||
{ | ||
return false; | ||
} | ||
|
||
// Some types that could receive a maybe-null value have a NotNull counterpart: | ||
// [NotNull]string? -> string | ||
// [NotNull]string -> string | ||
// [NotNull]TClass -> TClass | ||
// [NotNull]TClass? -> TClass | ||
// | ||
// While others don't: | ||
// [NotNull]int? -> X | ||
// [NotNull]TNullable -> X | ||
// [NotNull]TStruct? -> X | ||
// [NotNull]TOpen -> X | ||
return (type.Kind == SymbolKind.TypeParameter && !type.IsReferenceType) || type.IsNullableTypeOrTypeParameter(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
|
@@ -3930,8 +3967,6 @@ private void VisitArgumentOutboundAssignmentsAndPostConditions( | |
{ | ||
ReportNullableAssignmentIfNecessary(parameterValue, lValueType, applyPostConditionsUnconditionally(parameterWithState, parameterAnnotations), UseLegacyWarnings(argument, result.LValueType)); | ||
} | ||
|
||
ReportMaybeNullFromTypeParameterValueIfNeeded(argument, parameterWithState, parameterAnnotations); | ||
} | ||
break; | ||
case RefKind.Out: | ||
|
@@ -3977,8 +4012,6 @@ private void VisitArgumentOutboundAssignmentsAndPostConditions( | |
ReportNullabilityMismatchInArgument(argument.Syntax, lValueType.Type, parameter, parameterType.Type, forOutput: true); | ||
} | ||
} | ||
|
||
ReportMaybeNullFromTypeParameterValueIfNeeded(argument, parameterWithState, parameterAnnotations); | ||
} | ||
break; | ||
default: | ||
|
@@ -6058,6 +6091,7 @@ private FlowAnalysisAnnotations GetLValueAnnotations(BoundExpression expr) | |
BoundFieldAccess field => getFieldAnnotations(field.FieldSymbol), | ||
BoundObjectInitializerMember { MemberSymbol: PropertySymbol prop } => getSetterAnnotations(prop), | ||
BoundObjectInitializerMember { MemberSymbol: FieldSymbol field } => getFieldAnnotations(field), | ||
BoundParameter { ParameterSymbol: ParameterSymbol parameter } => ToInwardAnnotations(GetParameterAnnotations(parameter)), | ||
_ => FlowAnalysisAnnotations.None | ||
}; | ||
|
||
|
@@ -6099,6 +6133,21 @@ static FlowAnalysisAnnotations getPropertyAnnotations(SourcePropertySymbol prope | |
} | ||
} | ||
|
||
private static FlowAnalysisAnnotations ToInwardAnnotations(FlowAnalysisAnnotations outwardAnnotations) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Consider naming ToInboundAnnotations to resemble similar terminology used in VisitArguments |
||
{ | ||
var annotations = FlowAnalysisAnnotations.None; | ||
if ((outwardAnnotations & FlowAnalysisAnnotations.MaybeNull) != 0) | ||
{ | ||
// MaybeNull and MaybeNullWhen count as MaybeNull | ||
annotations |= FlowAnalysisAnnotations.AllowNull; | ||
} | ||
if ((outwardAnnotations & FlowAnalysisAnnotations.NotNull) == FlowAnalysisAnnotations.NotNull) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Style between this and the above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's on purpose. MaybeNull is two bits, either of which should count (see comment above). In reply to: 368161275 [](ancestors = 368161275) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
{ | ||
annotations |= FlowAnalysisAnnotations.DisallowNull; | ||
} | ||
return annotations; | ||
} | ||
|
||
private static bool UseLegacyWarnings(BoundExpression expr, TypeWithAnnotations exprType) | ||
{ | ||
switch (expr.Kind) | ||
|
@@ -6521,6 +6570,7 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) | |
public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node) | ||
{ | ||
var left = node.Left; | ||
var right = node.Right; | ||
Visit(left); | ||
TypeWithAnnotations declaredType = LvalueResultType; | ||
TypeWithAnnotations leftLValueType = declaredType; | ||
|
@@ -6553,13 +6603,13 @@ public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmen | |
} | ||
|
||
TypeWithState resultType; | ||
TypeWithState rightType = VisitRvalueWithState(node.Right); | ||
TypeWithState rightType = VisitRvalueWithState(right); | ||
if ((object)node.Operator.ReturnType != null) | ||
{ | ||
if (node.Operator.Kind.IsUserDefined() && (object)node.Operator.Method != null && node.Operator.Method.ParameterCount == 2) | ||
{ | ||
MethodSymbol method = node.Operator.Method; | ||
VisitArguments(node, ImmutableArray.Create(node.Left, node.Right), method.ParameterRefKinds, method.Parameters, argsToParamsOpt: default, | ||
VisitArguments(node, ImmutableArray.Create<BoundExpression>(node.Left, right), method.ParameterRefKinds, method.Parameters, argsToParamsOpt: default, | ||
expanded: true, invokedAsExtensionMethod: false, method); | ||
} | ||
|
||
|
@@ -6782,11 +6832,6 @@ private Symbol VisitMemberAccess(BoundExpression node, BoundExpression receiverO | |
} | ||
} | ||
|
||
if (_expressionIsRead) | ||
{ | ||
ReportMaybeNullFromTypeParameterValueIfNeeded(node, resultType, memberAnnotations); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 removed empty method #Resolved |
||
} | ||
|
||
SetResult(node, resultType, type); | ||
return member; | ||
} | ||
|
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.
📝 not sure if we can hit this. Just included for safety. #Resolved