提交 266582d1 编写于 作者: J Julien Couvreur 提交者: Julien Couvreur

Implement [NotNullWhenTrue] and fix non-generic out arguments

上级 4d285574
......@@ -374,7 +374,7 @@ private BoundExpression SetInferredType(BoundExpression expression, TypeSymbol t
{
case BoundKind.Local:
var local = (BoundLocal)variable.Single;
if (local.IsDeclaration)
if (local.DeclarationKind != BoundLocalDeclarationKind.None)
{
((SourceLocalSymbol)local.LocalSymbol).SetValEscape(rhsValEscape);
}
......@@ -826,7 +826,7 @@ private static string ExtractDeconstructResultElementName(BoundExpression expres
if ((object)declType != null)
{
return new BoundLocal(syntax, localSymbol, isDeclaration: true, constantValueOpt: null, isNullableUnknown: false, type: declType.TypeSymbol, hasErrors: hasErrors);
return new BoundLocal(syntax, localSymbol, BoundLocalDeclarationKind.WithExplicitType, constantValueOpt: null, isNullableUnknown: false, type: declType.TypeSymbol, hasErrors: hasErrors);
}
return new DeconstructionVariablePendingInference(syntax, localSymbol, receiverOpt: null);
......
......@@ -1549,7 +1549,7 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Diag
var constantValueOpt = localSymbol.IsConst && !IsInsideNameof && !type.IsErrorType()
? localSymbol.GetConstantValue(node, this.LocalInProgress, diagnostics) : null;
return new BoundLocal(node, localSymbol, isDeclaration: false, constantValueOpt: constantValueOpt, isNullableUnknown: isNullableUnknown, type: type, hasErrors: isError);
return new BoundLocal(node, localSymbol, BoundLocalDeclarationKind.None, constantValueOpt: constantValueOpt, isNullableUnknown: isNullableUnknown, type: type, hasErrors: isError);
}
case SymbolKind.Parameter:
......@@ -2345,7 +2345,7 @@ private BoundExpression BindOutDeclarationArgument(DeclarationExpressionSyntax d
CheckRestrictedTypeInAsync(this.ContainingMemberOrLambda, declType.TypeSymbol, diagnostics, typeSyntax);
return new BoundLocal(declarationExpression, localSymbol, isDeclaration: true, constantValueOpt: null, isNullableUnknown: false, type: declType.TypeSymbol);
return new BoundLocal(declarationExpression, localSymbol, BoundLocalDeclarationKind.WithExplicitType, constantValueOpt: null, isNullableUnknown: false, type: declType.TypeSymbol);
}
// Is this a field?
......
......@@ -133,13 +133,13 @@ public override Symbol ExpressionSymbol
}
public BoundLocal(SyntaxNode syntax, LocalSymbol localSymbol, ConstantValue constantValueOpt, TypeSymbol type, bool hasErrors = false)
: this(syntax, localSymbol, false, constantValueOpt, false, type, hasErrors)
: this(syntax, localSymbol, BoundLocalDeclarationKind.None, constantValueOpt, false, type, hasErrors)
{
}
public BoundLocal Update(LocalSymbol localSymbol, ConstantValue constantValueOpt, TypeSymbol type)
{
return this.Update(localSymbol, this.IsDeclaration, constantValueOpt, this.IsNullableUnknown, type);
return this.Update(localSymbol, this.DeclarationKind, constantValueOpt, this.IsNullableUnknown, type);
}
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.CSharp
{
/// <summary>
/// Indicates whether a bound local is also a declaration, and if so was it a declaration with an explicit or an inferred type.
/// Ex:
/// - In `M(x)`, `x` has `LocalDeclarationKind.None`
/// - In `M(out int x)`, `x` has `LocalDeclarationKind.WithExplicitType`
/// - In `M(out var x)`, `x` has `LocalDeclarationKind.WithInferredType`
/// </summary>
internal enum BoundLocalDeclarationKind
{
None = 0,
WithExplicitType,
WithInferredType
}
}
......@@ -30,6 +30,7 @@
<ValueType Name="NoOpStatementFlavor"/>
<ValueType Name="RefKind"/>
<ValueType Name="BoundTypeOrValueData"/>
<ValueType Name="BoundLocalDeclarationKind"/>
<AbstractNode Name="BoundInitializer" Base="BoundNode"/>
......@@ -1050,7 +1051,7 @@
<!-- Non-null type is required for this node kind -->
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
<Field Name="LocalSymbol" Type="LocalSymbol"/>
<Field Name="IsDeclaration" Type="bool"/>
<Field Name="DeclarationKind" Type="BoundLocalDeclarationKind" Null="NotApplicable"/>
<Field Name="ConstantValueOpt" Type="ConstantValue" Null="allow"/>
<!-- True if LocalSymbol.Type.IsNullable could not be inferred. -->
<Field Name="IsNullableUnknown" Type="bool"/>
......@@ -1719,7 +1720,7 @@
<!-- Special node, used only during nullability flow analysis -->
<!-- PROTOTYPE(NullableReferenceTypes): Derive from BoundValuePlaceholderBase and give specific name. -->
<Node Name="BoundValuePlaceholder" Base="BoundExpression">
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
<Field Name="Type" Type="TypeSymbol" Override="true" Null="allow"/> <!-- We use null Type for placeholders representing out vars -->
<Field Name="IsNullable" Type="bool?" Null="allow"/>
</Node>
</Tree>
......@@ -63,7 +63,7 @@ internal BoundExpression SetInferredType(TypeSymbol type, Binder binderOpt, Diag
}
localSymbol.SetTypeSymbol(TypeSymbolWithAnnotations.Create(type));
return new BoundLocal(this.Syntax, localSymbol, isDeclaration: true, constantValueOpt: null, isNullableUnknown: false, type: type, hasErrors: this.HasErrors || inferenceFailed);
return new BoundLocal(this.Syntax, localSymbol, BoundLocalDeclarationKind.WithInferredType, constantValueOpt: null, isNullableUnknown: false, type: type, hasErrors: this.HasErrors || inferenceFailed);
case SymbolKind.Field:
var fieldSymbol = (GlobalExpressionVariable)this.VariableSymbol;
......
......@@ -51,7 +51,7 @@ public override bool Equals(object obj)
internal string GetDebuggerDisplay()
{
return $"ContainingSlot={ContainingSlot}, Symbol={Symbol}";
return $"ContainingSlot={ContainingSlot}, Symbol={Symbol.GetDebuggerDisplay()}";
}
}
}
......
......@@ -1588,13 +1588,8 @@ public override BoundNode VisitCall(BoundCall node)
ImmutableArray<RefKind> refKindsOpt = node.ArgumentRefKindsOpt;
ImmutableArray<BoundExpression> arguments = RemoveArgumentConversions(node.Arguments, refKindsOpt);
ImmutableArray<int> argsToParamsOpt = node.ArgsToParamsOpt;
ImmutableArray<Result> results = VisitArgumentsEvaluate(arguments, refKindsOpt, method.Parameters, argsToParamsOpt, node.Expanded);
if (method.IsGenericMethod && HasImplicitTypeArguments(node))
{
method = InferMethod(node, method, results.SelectAsArray(r => r.Type));
}
VisitArgumentsWarn(arguments, refKindsOpt, method.Parameters, argsToParamsOpt, node.Expanded, results);
method = VisitArguments(node, arguments, refKindsOpt, method.Parameters, argsToParamsOpt, node.Expanded, method);
}
UpdateStateForCall(node);
......@@ -1627,19 +1622,11 @@ public override BoundNode VisitCall(BoundCall node)
(ParameterSymbol parameter, _) = GetCorrespondingParameter(i, parameters, argsToParamsOpt, expanded);
AttributeAnnotations annotations = parameter?.FlowAnalysisAnnotations ?? AttributeAnnotations.None;
// We'll ignore NotNullWhenFalse that is misused in metadata
if ((annotations & AttributeAnnotations.NotNullWhenFalse) != 0 &&
parameter.ContainingSymbol.GetTypeOrReturnType().SpecialType != SpecialType.System_Boolean)
{
annotations &= ~AttributeAnnotations.NotNullWhenFalse;
}
// Ignore NotNullWhenTrue that is inapplicable
annotations = removeInapplicableNotNullWhenSense(parameter, annotations, sense: true);
// We'll ignore EnsuresNotNull that is misused in metadata
if ((annotations & AttributeAnnotations.EnsuresNotNull) != 0 &&
(parameter.Type?.IsValueType != false || parameter.IsParams))
{
annotations &= ~AttributeAnnotations.EnsuresNotNull;
}
// Ignore NotNullWhenFalse that is inapplicable
annotations = removeInapplicableNotNullWhenSense(parameter, annotations, sense: false);
if (annotations != AttributeAnnotations.None && builder == null)
{
......@@ -1654,12 +1641,40 @@ public override BoundNode VisitCall(BoundCall node)
}
return builder == null ? default : builder.ToImmutableAndFree();
AttributeAnnotations removeInapplicableNotNullWhenSense(ParameterSymbol parameter, AttributeAnnotations annotations, bool sense)
{
var whenSense = sense ? AttributeAnnotations.NotNullWhenTrue : AttributeAnnotations.NotNullWhenFalse;
var whenNotSense = sense ? AttributeAnnotations.NotNullWhenFalse : AttributeAnnotations.NotNullWhenTrue;
// NotNullWhenSense (without NotNullWhenNotSense) must be applied on a bool-returning member
if ((annotations & whenSense) != 0 &&
(annotations & whenNotSense) == 0 &&
parameter.ContainingSymbol.GetTypeOrReturnType().SpecialType != SpecialType.System_Boolean)
{
annotations &= ~whenSense;
}
// NotNullWhenSense must be applied to a reference or unconstrained generic type
if ((annotations & whenSense) != 0 && parameter.Type.IsValueType != false)
{
annotations &= ~whenSense;
}
// NotNullWhenSense is inapplicable when argument corresponds to params parameter and we're in expanded form
if ((annotations & whenSense) != 0 && expanded && ReferenceEquals(parameter, parameters.Last()))
{
annotations &= ~whenSense;
}
return annotations;
}
}
// PROTOTYPE(NullableReferenceTypes): Record in the node whether type
// arguments were implicit, to allow for cases where the syntax is not an
// invocation (such as a synthesized call from a query interpretation).
private static bool HasImplicitTypeArguments(BoundCall node)
private static bool HasImplicitTypeArguments(BoundExpression node)
{
var syntax = node.Syntax;
if (syntax.Kind() != SyntaxKind.InvocationExpression)
......@@ -1705,47 +1720,57 @@ protected override void VisitArguments(ImmutableArray<BoundExpression> arguments
VisitArguments(node, arguments, refKindsOpt, property is null ? default : property.Parameters, argsToParamsOpt, expanded);
}
private void VisitArguments(
/// <summary>
/// If you pass in a method symbol, its types will be re-inferred and the re-inferred method will be returned.
/// </summary>
private MethodSymbol VisitArguments(
BoundExpression node,
ImmutableArray<BoundExpression> arguments,
ImmutableArray<RefKind> refKindsOpt,
ImmutableArray<ParameterSymbol> parameters,
ImmutableArray<int> argsToParamsOpt,
bool expanded)
bool expanded,
MethodSymbol method = null)
{
Debug.Assert(!arguments.IsDefault);
ImmutableArray<Result> results = VisitArgumentsEvaluate(arguments, refKindsOpt, parameters, argsToParamsOpt, expanded);
var savedState = this.State.Clone();
// We do a first pass to work through the arguments without making any assumptions
ImmutableArray<Result> results = VisitArgumentsEvaluate(arguments, refKindsOpt);
if ((object)method != null && method.IsGenericMethod && HasImplicitTypeArguments(node))
{
method = InferMethod((BoundCall)node, method, results.SelectAsArray(r => r.Type));
parameters = method.Parameters;
}
// PROTOTYPE(NullableReferenceTypes): Can we handle some error cases?
// (Compare with CSharpOperationFactory.CreateBoundCallOperation.)
if (!node.HasErrors && !parameters.IsDefault)
{
VisitArgumentsWarn(arguments, refKindsOpt, parameters, argsToParamsOpt, expanded, results);
VisitArgumentConversions(arguments, refKindsOpt, parameters, argsToParamsOpt, expanded, results);
}
}
private ImmutableArray<Result> VisitArgumentsEvaluate(
ImmutableArray<BoundExpression> arguments,
ImmutableArray<RefKind> refKindsOpt,
ImmutableArray<ParameterSymbol> parameters,
ImmutableArray<int> argsToParamsOpt,
bool expanded)
{
var savedState = this.State.Clone();
// We do a first pass to work through the arguments without making any assumptions
var results = VisitArgumentsEvaluate(arguments, refKindsOpt);
// We do a second pass through the arguments, ignoring any diagnostics produced, but honoring the annotations,
// to get the proper result state.
ImmutableArray<AttributeAnnotations> annotations = GetAnnotations(arguments.Length,
expanded, parameters, argsToParamsOpt);
ImmutableArray<AttributeAnnotations> annotations = GetAnnotations(arguments.Length, expanded, parameters, argsToParamsOpt);
if (!annotations.IsDefault)
{
this.SetState(savedState);
bool saveDisableDiagnostics = _disableDiagnostics;
_disableDiagnostics = true;
if (!node.HasErrors && !parameters.IsDefault)
{
VisitArgumentConversions(arguments, refKindsOpt, parameters, argsToParamsOpt, expanded, results); // recompute out vars after state was reset
}
VisitArgumentsEvaluateHonoringAnnotations(arguments, refKindsOpt, annotations);
_disableDiagnostics = saveDisableDiagnostics;
}
return results;
return method;
}
private ImmutableArray<Result> VisitArgumentsEvaluate(
......@@ -1787,7 +1812,7 @@ private void VisitArgumentEvaluate(ImmutableArray<BoundExpression> arguments, Im
/// <summary>
/// Visit all the arguments for the purpose of computing the exit state of the method,
/// given the annotations, but disabling warnings.
/// given the annotations.
/// </summary>
private void VisitArgumentsEvaluateHonoringAnnotations(
ImmutableArray<BoundExpression> arguments,
......@@ -1795,17 +1820,14 @@ private void VisitArgumentEvaluate(ImmutableArray<BoundExpression> arguments, Im
ImmutableArray<AttributeAnnotations> annotations)
{
Debug.Assert(!IsConditionalState);
bool saveDisableDiagnostics = _disableDiagnostics;
_disableDiagnostics = true;
Debug.Assert(annotations.Length == arguments.Length);
Debug.Assert(_disableDiagnostics);
for (int i = 0; i < arguments.Length; i++)
{
if (this.IsConditionalState)
{
// We could be in a conditional state because of NotNullWhenFalse annotation
// We could be in a conditional state because of a conditional annotation (like NotNullWhenFalse)
// Then WhenTrue/False states correspond to the invocation returning true/false
// We'll assume that we're in the unconditional state where the method returns true,
......@@ -1844,38 +1866,26 @@ private void VisitArgumentEvaluate(ImmutableArray<BoundExpression> arguments, Im
}
AttributeAnnotations annotation = annotations[i];
bool notNullWhenTrue = (annotation & AttributeAnnotations.NotNullWhenTrue) != 0;
bool notNullWhenFalse = (annotation & AttributeAnnotations.NotNullWhenFalse) != 0;
bool ensuresNotNull = (annotation & AttributeAnnotations.EnsuresNotNull) != 0;
if (ensuresNotNull)
// The WhenTrue/False states correspond to the invocation returning true/false
bool wasPreviouslySplit = this.IsConditionalState;
Split();
if (notNullWhenTrue)
{
// The variable in this slot is not null
if (this.IsConditionalState)
{
this.StateWhenTrue[slot] = true;
this.StateWhenFalse[slot] = true;
}
else
{
this.State[slot] = true;
}
this.StateWhenTrue[slot] = true;
}
else if (notNullWhenFalse)
if (notNullWhenFalse)
{
// We'll use the WhenTrue/False states to represent whether the invocation returns true/false
// PROTOTYPE(NullableReferenceTypes): Consider splitting for the entire method, not just once the first annotated argument is encountered
Split();
// The variable in this slot is not null when the method returns false
this.StateWhenFalse[slot] = true;
if (notNullWhenTrue && !wasPreviouslySplit) Unsplit();
}
}
_result = _invalidType;
_disableDiagnostics = saveDisableDiagnostics;
}
private void VisitArgumentsWarn(
private void VisitArgumentConversions(
ImmutableArray<BoundExpression> arguments,
ImmutableArray<RefKind> refKindsOpt,
ImmutableArray<ParameterSymbol> parameters,
......@@ -1890,7 +1900,7 @@ private void VisitArgumentEvaluate(ImmutableArray<BoundExpression> arguments, Im
{
continue;
}
VisitArgumentWarn(
VisitArgumentConversion(
arguments[i],
GetRefKind(refKindsOpt, i),
parameter,
......@@ -1902,7 +1912,7 @@ private void VisitArgumentEvaluate(ImmutableArray<BoundExpression> arguments, Im
/// <summary>
/// Report warnings for an argument corresponding to a specific parameter.
/// </summary>
private void VisitArgumentWarn(
private void VisitArgumentConversion(
BoundExpression argument,
RefKind refKind,
ParameterSymbol parameter,
......@@ -1926,6 +1936,11 @@ private void VisitArgumentEvaluate(ImmutableArray<BoundExpression> arguments, Im
}
break;
case RefKind.Out:
if (argument is BoundLocal local && local.DeclarationKind == BoundLocalDeclarationKind.WithInferredType)
{
_variableTypes[local.LocalSymbol] = parameterType;
resultType = parameterType;
}
if (!ReportNullReferenceAssignmentIfNecessary(argument, resultType, parameterType, useLegacyWarnings: UseLegacyWarnings(argument)))
{
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
......@@ -2050,7 +2065,8 @@ private MethodSymbol InferMethod(BoundCall node, MethodSymbol method, ImmutableA
// By using a generic BoundValuePlaceholder, we're losing inference in those cases.
// PROTOTYPE(NullableReferenceTypes): Inference should be based on
// unconverted arguments. Consider cases such as `default`, lambdas, tuples.
ImmutableArray<BoundExpression> arguments = argumentTypes.ZipAsArray(node.Arguments, (t, a) => ((object)t == null) ? a : new BoundValuePlaceholder(a.Syntax, t.IsNullable, t.TypeSymbol));
ImmutableArray<BoundExpression> arguments = argumentTypes.ZipAsArray(node.Arguments, s_makePlaceholderForArgumentFunc);
var refKinds = ArrayBuilder<RefKind>.GetInstance();
if (node.ArgumentRefKindsOpt != null)
{
......@@ -2089,6 +2105,23 @@ private MethodSymbol InferMethod(BoundCall node, MethodSymbol method, ImmutableA
return method;
}
private static readonly Func<TypeSymbolWithAnnotations, BoundExpression, BoundExpression> s_makePlaceholderForArgumentFunc =
(TypeSymbolWithAnnotations argumentType, BoundExpression argument) =>
{
if (argumentType is null)
{
return argument;
}
if (argument is BoundLocal local && local.DeclarationKind == BoundLocalDeclarationKind.WithInferredType)
{
// 'out var' doesn't contribute to inference
return new BoundValuePlaceholder(argument.Syntax, isNullable: null, type: null);
}
return new BoundValuePlaceholder(argument.Syntax, argumentType.IsNullable, argumentType.TypeSymbol);
};
private void ReplayReadsAndWrites(LocalFunctionSymbol localFunc,
SyntaxNode syntax,
bool writes)
......@@ -2269,20 +2302,7 @@ public override BoundNode VisitConversion(BoundConversion node)
}
}
TypeSymbolWithAnnotations resultType;
if (operand.Kind == BoundKind.Literal && (object)operand.Type == null && operand.ConstantValue.IsNull)
{
resultType = TypeSymbolWithAnnotations.Create(targetType, true);
}
else if (node.ConversionKind == ConversionKind.Identity && !node.ExplicitCastInCode)
{
resultType = operandType;
}
else
{
resultType = InferResultNullability(operand, node.Conversion, targetType, operandType, fromConversionNode: true);
}
_result = resultType;
_result = InferResultNullability(operand, node.Conversion, targetType, operandType, fromConversionNode: true);
}
return null;
......@@ -3693,6 +3713,7 @@ private string GetDebuggerDisplay()
}
}
[DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
#if REFERENCE_STATE
internal class LocalState : AbstractLocalState
#else
......@@ -3750,7 +3771,7 @@ public bool Reachable
}
}
public override string ToString()
internal string GetDebuggerDisplay()
{
var pooledBuilder = PooledStringBuilder.GetInstance();
var builder = pooledBuilder.Builder;
......
......@@ -166,7 +166,7 @@ protected override void VisitLvalue(BoundLocal node)
public override BoundNode VisitLocal(BoundLocal node)
{
if (IsInside && node.IsDeclaration)
if (IsInside && node.DeclarationKind != BoundLocalDeclarationKind.None)
{
_variablesDeclared.Add(node.LocalSymbol);
}
......
......@@ -187,6 +187,7 @@ internal enum BoundKind: byte
internal abstract partial class BoundInitializer : BoundNode
{
protected BoundInitializer(BoundKind kind, SyntaxNode syntax, bool hasErrors)
......@@ -3761,7 +3762,7 @@ public BoundBaseReference Update(TypeSymbol type)
internal sealed partial class BoundLocal : BoundExpression
{
public BoundLocal(SyntaxNode syntax, LocalSymbol localSymbol, bool isDeclaration, ConstantValue constantValueOpt, bool isNullableUnknown, TypeSymbol type, bool hasErrors)
public BoundLocal(SyntaxNode syntax, LocalSymbol localSymbol, BoundLocalDeclarationKind declarationKind, ConstantValue constantValueOpt, bool isNullableUnknown, TypeSymbol type, bool hasErrors)
: base(BoundKind.Local, syntax, type, hasErrors)
{
......@@ -3769,12 +3770,12 @@ public BoundLocal(SyntaxNode syntax, LocalSymbol localSymbol, bool isDeclaration
Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)");
this.LocalSymbol = localSymbol;
this.IsDeclaration = isDeclaration;
this.DeclarationKind = declarationKind;
this.ConstantValueOpt = constantValueOpt;
this.IsNullableUnknown = isNullableUnknown;
}
public BoundLocal(SyntaxNode syntax, LocalSymbol localSymbol, bool isDeclaration, ConstantValue constantValueOpt, bool isNullableUnknown, TypeSymbol type)
public BoundLocal(SyntaxNode syntax, LocalSymbol localSymbol, BoundLocalDeclarationKind declarationKind, ConstantValue constantValueOpt, bool isNullableUnknown, TypeSymbol type)
: base(BoundKind.Local, syntax, type)
{
......@@ -3782,7 +3783,7 @@ public BoundLocal(SyntaxNode syntax, LocalSymbol localSymbol, bool isDeclaration
Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)");
this.LocalSymbol = localSymbol;
this.IsDeclaration = isDeclaration;
this.DeclarationKind = declarationKind;
this.ConstantValueOpt = constantValueOpt;
this.IsNullableUnknown = isNullableUnknown;
}
......@@ -3790,7 +3791,7 @@ public BoundLocal(SyntaxNode syntax, LocalSymbol localSymbol, bool isDeclaration
public LocalSymbol LocalSymbol { get; }
public bool IsDeclaration { get; }
public BoundLocalDeclarationKind DeclarationKind { get; }
public ConstantValue ConstantValueOpt { get; }
......@@ -3801,11 +3802,11 @@ public override BoundNode Accept(BoundTreeVisitor visitor)
return visitor.VisitLocal(this);
}
public BoundLocal Update(LocalSymbol localSymbol, bool isDeclaration, ConstantValue constantValueOpt, bool isNullableUnknown, TypeSymbol type)
public BoundLocal Update(LocalSymbol localSymbol, BoundLocalDeclarationKind declarationKind, ConstantValue constantValueOpt, bool isNullableUnknown, TypeSymbol type)
{
if (localSymbol != this.LocalSymbol || isDeclaration != this.IsDeclaration || constantValueOpt != this.ConstantValueOpt || isNullableUnknown != this.IsNullableUnknown || type != this.Type)
if (localSymbol != this.LocalSymbol || declarationKind != this.DeclarationKind || constantValueOpt != this.ConstantValueOpt || isNullableUnknown != this.IsNullableUnknown || type != this.Type)
{
var result = new BoundLocal(this.Syntax, localSymbol, isDeclaration, constantValueOpt, isNullableUnknown, type, this.HasErrors);
var result = new BoundLocal(this.Syntax, localSymbol, declarationKind, constantValueOpt, isNullableUnknown, type, this.HasErrors);
result.WasCompilerGenerated = this.WasCompilerGenerated;
return result;
}
......@@ -6380,18 +6381,12 @@ internal sealed partial class BoundValuePlaceholder : BoundExpression
public BoundValuePlaceholder(SyntaxNode syntax, bool? isNullable, TypeSymbol type, bool hasErrors)
: base(BoundKind.ValuePlaceholder, syntax, type, hasErrors)
{
Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)");
this.IsNullable = isNullable;
}
public BoundValuePlaceholder(SyntaxNode syntax, bool? isNullable, TypeSymbol type)
: base(BoundKind.ValuePlaceholder, syntax, type)
{
Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)");
this.IsNullable = isNullable;
}
......@@ -9341,7 +9336,7 @@ public override BoundNode VisitBaseReference(BoundBaseReference node)
public override BoundNode VisitLocal(BoundLocal node)
{
TypeSymbol type = this.VisitType(node.Type);
return node.Update(node.LocalSymbol, node.IsDeclaration, node.ConstantValueOpt, node.IsNullableUnknown, type);
return node.Update(node.LocalSymbol, node.DeclarationKind, node.ConstantValueOpt, node.IsNullableUnknown, type);
}
public override BoundNode VisitPseudoVariable(BoundPseudoVariable node)
{
......@@ -10669,7 +10664,7 @@ public override TreeDumperNode VisitLocal(BoundLocal node, object arg)
return new TreeDumperNode("local", null, new TreeDumperNode[]
{
new TreeDumperNode("localSymbol", node.LocalSymbol, null),
new TreeDumperNode("isDeclaration", node.IsDeclaration, null),
new TreeDumperNode("declarationKind", node.DeclarationKind, null),
new TreeDumperNode("constantValueOpt", node.ConstantValueOpt, null),
new TreeDumperNode("isNullableUnknown", node.IsNullableUnknown, null),
new TreeDumperNode("type", node.Type, null)
......
......@@ -371,7 +371,7 @@ private IOperation CreateBoundCallOperation(BoundCall boundCall)
private IOperation CreateBoundLocalOperation(BoundLocal boundLocal)
{
ILocalSymbol local = boundLocal.LocalSymbol;
bool isDeclaration = boundLocal.IsDeclaration;
bool isDeclaration = boundLocal.DeclarationKind != BoundLocalDeclarationKind.None;
SyntaxNode syntax = boundLocal.Syntax;
ITypeSymbol type = boundLocal.Type;
Optional<object> constantValue = ConvertToOptional(boundLocal.ConstantValue);
......
......@@ -15,8 +15,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols
internal enum AttributeAnnotations
{
None = 0,
NotNullWhenFalse,
EnsuresNotNull,
NotNullWhenTrue = 1 << 0,
NotNullWhenFalse = 1 << 1,
EnsuresNotNull = NotNullWhenTrue | NotNullWhenFalse,
}
// PROTOTYPE(NullableReferenceTypes): external annotations should be removed or fully designed/productized
......@@ -165,35 +166,40 @@ private static void Add(TypeSymbol type, StringBuilder builder)
/// <summary>
/// index 0 is used for return type
/// other parameters follow
/// If there are no annotations on the member (not just that parameter), then returns null. The purpose is to ensure
/// that if some annotations are present on the member, then annotations win over the attributes on the member in all positions.
/// That could mean removing an attribute.
/// </summary>
internal static (bool hasAny, AttributeAnnotations annotations) GetExtraAttributes(string key, int parameterIndex)
internal static AttributeAnnotations? TryGetExtraAttributes(string key, int parameterIndex)
{
if (key is null)
{
return (false, default);
return null;
}
if (!Attributes.TryGetValue(key, out var extraAttributes))
{
return (false, default);
return null;
}
return (true, extraAttributes[parameterIndex + 1]);
return extraAttributes[parameterIndex + 1];
}
}
internal static class ParameterAnnotationsExtensions
{
internal static AttributeAnnotations With(this AttributeAnnotations value, bool notNullWhenFalse, bool ensuresNotNull)
// For EnsuresNotNull, you should set NotNullWhenTrue and NotNullWhenFalse
internal static AttributeAnnotations With(this AttributeAnnotations value,
bool notNullWhenTrue, bool notNullWhenFalse)
{
if (notNullWhenFalse)
{
value |= NotNullWhenFalse;
}
if (ensuresNotNull)
if (notNullWhenTrue)
{
value |= EnsuresNotNull;
value |= NotNullWhenTrue;
}
return value;
......
......@@ -31,8 +31,8 @@ private enum WellKnownAttributeFlags
IsCallerFilePath = 0x1 << 5,
IsCallerLineNumber = 0x1 << 6,
IsCallerMemberName = 0x1 << 7,
NotNullWhenFalse = 0x1 << 8,
EnsuresNotNull = 0x1 << 9,
NotNullWhenTrue = 0x1 << 8,
NotNullWhenFalse = 0x1 << 9,
}
private struct PackedFlags
......@@ -651,37 +651,38 @@ internal override AttributeAnnotations FlowAnalysisAnnotations
{
get
{
const WellKnownAttributeFlags notNullWhenTrue = WellKnownAttributeFlags.NotNullWhenTrue;
const WellKnownAttributeFlags notNullWhenFalse = WellKnownAttributeFlags.NotNullWhenFalse;
const WellKnownAttributeFlags ensuresNotNull = WellKnownAttributeFlags.EnsuresNotNull;
// PROTOTYPE(NullableReferenceTypes): the flags could be packed more
if (!_packedFlags.TryGetWellKnownAttribute(notNullWhenFalse, out bool hasNotNullWhenFalse) ||
!_packedFlags.TryGetWellKnownAttribute(ensuresNotNull, out bool hasEnsuresNotNull))
if (!_packedFlags.TryGetWellKnownAttribute(notNullWhenTrue, out bool hasNotNullWhenTrue) ||
!_packedFlags.TryGetWellKnownAttribute(notNullWhenFalse, out bool hasNotNullWhenFalse))
{
(bool memberHasAny, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations();
AttributeAnnotations? annotations = TryGetExtraAttributeAnnotations();
if (memberHasAny)
if (annotations.HasValue)
{
// External annotations win, if any is present on the member
hasNotNullWhenTrue = (annotations & AttributeAnnotations.NotNullWhenTrue) != 0;
hasNotNullWhenFalse = (annotations & AttributeAnnotations.NotNullWhenFalse) != 0;
hasEnsuresNotNull = (annotations & AttributeAnnotations.EnsuresNotNull) != 0;
}
else
{
hasNotNullWhenFalse = _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.NotNullWhenFalseAttribute);
hasEnsuresNotNull = _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.EnsuresNotNullAttribute);
bool hasEnsuresNotNull = _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.EnsuresNotNullAttribute);
hasNotNullWhenTrue = hasEnsuresNotNull || _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.NotNullWhenTrueAttribute);
hasNotNullWhenFalse = hasEnsuresNotNull || _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.NotNullWhenFalseAttribute);
}
_packedFlags.SetWellKnownAttribute(notNullWhenTrue, hasNotNullWhenTrue);
_packedFlags.SetWellKnownAttribute(notNullWhenFalse, hasNotNullWhenFalse);
_packedFlags.SetWellKnownAttribute(ensuresNotNull, hasEnsuresNotNull);
if (memberHasAny)
if (annotations.HasValue)
{
return annotations;
return annotations.Value;
}
}
return AttributeAnnotations.None.With(notNullWhenFalse: hasNotNullWhenFalse, ensuresNotNull: hasEnsuresNotNull);
return AttributeAnnotations.None.With(notNullWhenTrue: hasNotNullWhenTrue, notNullWhenFalse: hasNotNullWhenFalse);
}
}
......
......@@ -106,7 +106,9 @@ internal bool IsMarshalAsObject
public abstract int Ordinal { get; }
/// <summary>
/// Returns true if the parameter was declared as a parameter array.
/// Returns true if the parameter was declared as a parameter array.
/// Note: it is possible for any parameter to have the [ParamArray] attribute (for instance, in IL),
/// even if it is not the last parameter. So check for that.
/// </summary>
public abstract bool IsParams { get; }
......@@ -388,22 +390,22 @@ internal sealed override ObsoleteAttributeData ObsoleteAttributeData
internal abstract AttributeAnnotations FlowAnalysisAnnotations { get; }
/// <summary>
/// If there are any annotations on the member (not just that parameter), then memberHasExtra is true. The purpose is to ensure
/// If there are no annotations on the member (not just that parameter), then returns null. The purpose is to ensure
/// that if some annotations are present on the member, then annotations win over the attributes on the member in all positions.
/// That could mean removing an attribute.
/// </summary>
protected (bool memberHasExtra, AttributeAnnotations annotations) TryGetExtraAttributeAnnotations()
protected AttributeAnnotations? TryGetExtraAttributeAnnotations()
{
ParameterSymbol originalParameter = this.OriginalDefinition;
var containingMethod = originalParameter.ContainingSymbol as MethodSymbol;
if (containingMethod is null)
{
return (false, AttributeAnnotations.None);
return null;
}
string key = ExtraAnnotations.MakeMethodKey(containingMethod);
return ExtraAnnotations.GetExtraAttributes(key, this.Ordinal);
return ExtraAnnotations.TryGetExtraAttributes(key, this.Ordinal);
}
protected sealed override int HighestPriorityUseSiteError
......
......@@ -131,18 +131,19 @@ internal override AttributeAnnotations FlowAnalysisAnnotations
{
get
{
(bool memberHasAny, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations();
if (memberHasAny)
AttributeAnnotations? annotations = TryGetExtraAttributeAnnotations();
if (annotations.HasValue)
{
// PROTOTYPE(NullableReferenceTypes): Make sure this is covered by test
return annotations;
return annotations.Value;
}
CommonParameterWellKnownAttributeData attributeData = GetDecodedWellKnownAttributeData();
bool hasEnsuresNotNull = attributeData?.HasEnsuresNotNullAttribute == true;
return AttributeAnnotations.None
.With(notNullWhenFalse: attributeData?.HasNotNullWhenFalseAttribute == true,
ensuresNotNull: attributeData?.HasEnsuresNotNullAttribute == true);
.With(notNullWhenTrue: hasEnsuresNotNull || attributeData?.HasNotNullWhenTrueAttribute == true,
notNullWhenFalse: hasEnsuresNotNull || attributeData?.HasNotNullWhenFalseAttribute == true);
}
}
......@@ -624,6 +625,10 @@ internal override void DecodeWellKnownAttribute(ref DecodeWellKnownAttributeArgu
// NullableAttribute should not be set explicitly.
arguments.Diagnostics.Add(ErrorCode.ERR_ExplicitNullableAttribute, arguments.AttributeSyntaxOpt.Location);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.NotNullWhenTrueAttribute))
{
arguments.GetOrCreateData<CommonParameterWellKnownAttributeData>().HasNotNullWhenTrueAttribute = true;
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.NotNullWhenFalseAttribute))
{
arguments.GetOrCreateData<CommonParameterWellKnownAttributeData>().HasNotNullWhenFalseAttribute = true;
......
......@@ -84,11 +84,7 @@ internal override bool IsCallerMemberName
internal override AttributeAnnotations FlowAnalysisAnnotations
{
get
{
(_, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations();
return annotations;
}
get { return TryGetExtraAttributeAnnotations() ?? AttributeAnnotations.None; }
}
internal override MarshalPseudoCustomAttributeData MarshallingInformation
......
......@@ -258,6 +258,7 @@ static AttributeDescription()
private static readonly byte[][] s_signaturesOfOutAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfIsReadOnlyAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfIsUnmanagedAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfNotNullWhenTrueAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfNotNullWhenFalseAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfEnsuresNotNullAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfFixedBufferAttribute = { s_signature_HasThis_Void_Type_Int32 };
......@@ -456,6 +457,7 @@ static AttributeDescription()
internal static readonly AttributeDescription StructLayoutAttribute = new AttributeDescription("System.Runtime.InteropServices", "StructLayoutAttribute", s_signaturesOfStructLayoutAttribute);
internal static readonly AttributeDescription FieldOffsetAttribute = new AttributeDescription("System.Runtime.InteropServices", "FieldOffsetAttribute", s_signaturesOfFieldOffsetAttribute);
internal static readonly AttributeDescription FixedBufferAttribute = new AttributeDescription("System.Runtime.CompilerServices", "FixedBufferAttribute", s_signaturesOfFixedBufferAttribute);
internal static readonly AttributeDescription NotNullWhenTrueAttribute = new AttributeDescription("System.Runtime.CompilerServices", "NotNullWhenTrueAttribute", s_signaturesOfNotNullWhenTrueAttribute);
internal static readonly AttributeDescription NotNullWhenFalseAttribute = new AttributeDescription("System.Runtime.CompilerServices", "NotNullWhenFalseAttribute", s_signaturesOfNotNullWhenFalseAttribute);
internal static readonly AttributeDescription EnsuresNotNullAttribute = new AttributeDescription("System.Runtime.CompilerServices", "EnsuresNotNullAttribute", s_signaturesOfEnsuresNotNullAttribute);
internal static readonly AttributeDescription MarshalAsAttribute = new AttributeDescription("System.Runtime.InteropServices", "MarshalAsAttribute", s_signaturesOfMarshalAsAttribute);
......
......@@ -110,7 +110,23 @@ public bool HasIUnknownConstantAttribute
}
#endregion
// PROTOTYPE(NullableReferenceTypes): Consider moving the attribute for nullability to C#-specific type
// PROTOTYPE(NullableReferenceTypes): Consider moving the attributes for nullability to C#-specific type
private bool _hasNotNullWhenTrueAttribute;
public bool HasNotNullWhenTrueAttribute
{
get
{
VerifySealed(expected: true);
return _hasNotNullWhenTrueAttribute;
}
set
{
VerifySealed(expected: false);
_hasNotNullWhenTrueAttribute = value;
SetDataStored();
}
}
private bool _hasNotNullWhenFalseAttribute;
public bool HasNotNullWhenFalseAttribute
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册