Lazily realize IOperation.ConstantValue

Today, we realize ConstantValues of nodes as soon as an IOperation is created. This means that for things like constant string addition, we immediately use O(n^2) memory when an IOperation tree is fully realized. Internally the compiler uses an approach to only realize the final string when necessary, and this ports IOperation to use that representation under the hood for strings. When an IOperation.ConstantValue that is a string is realized, we take a WeakReference to that string. If a subsequent request comes in and the reference has not yet expired, we return that same string to avoid duplication, but avoid rooting the object if one user requests the constant value and then immediately gets rid of it. Fixes https://github.com/dotnet/roslyn/issues/43019.
上级 212fc8f2
......@@ -62,6 +62,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpErrorFactsGenerator",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpSyntaxGenerator", "src\Tools\Source\CompilerGeneratorTools\Source\CSharpSyntaxGenerator\CSharpSyntaxGenerator.csproj", "{288089C5-8721-458E-BE3E-78990DAB5E2D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IOperationGenerator", "src\Tools\Source\CompilerGeneratorTools\Source\IOperationGenerator\CompilersIOperationGenerator.csproj", "{D0A79850-B32A-45E5-9FD5-D43CB345867A}"
EndProject
Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VisualBasicSyntaxGenerator", "src\Tools\Source\CompilerGeneratorTools\Source\VisualBasicSyntaxGenerator\VisualBasicSyntaxGenerator.vbproj", "{6AA96934-D6B7-4CC8-990D-DB6B9DD56E34}"
EndProject
Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VisualBasicErrorFactsGenerator", "src\Tools\Source\CompilerGeneratorTools\Source\VisualBasicErrorFactsGenerator\VisualBasicErrorFactsGenerator.vbproj", "{909B656F-6095-4AC2-A5AB-C3F032315C45}"
......@@ -301,6 +303,10 @@ Global
{288089C5-8721-458E-BE3E-78990DAB5E2D}.Debug|Any CPU.Build.0 = Debug|x64
{288089C5-8721-458E-BE3E-78990DAB5E2D}.Release|Any CPU.ActiveCfg = Release|x64
{288089C5-8721-458E-BE3E-78990DAB5E2D}.Release|Any CPU.Build.0 = Release|x64
{D0A79850-B32A-45E5-9FD5-D43CB345867A}.Debug|Any CPU.ActiveCfg = Debug|x64
{D0A79850-B32A-45E5-9FD5-D43CB345867A}.Debug|Any CPU.Build.0 = Debug|x64
{D0A79850-B32A-45E5-9FD5-D43CB345867A}.Release|Any CPU.ActiveCfg = Release|x64
{D0A79850-B32A-45E5-9FD5-D43CB345867A}.Release|Any CPU.Build.0 = Release|x64
{6AA96934-D6B7-4CC8-990D-DB6B9DD56E34}.Debug|Any CPU.ActiveCfg = Debug|x64
{6AA96934-D6B7-4CC8-990D-DB6B9DD56E34}.Debug|Any CPU.Build.0 = Debug|x64
{6AA96934-D6B7-4CC8-990D-DB6B9DD56E34}.Release|Any CPU.ActiveCfg = Release|x64
......@@ -464,6 +470,7 @@ Global
{02459936-CD2C-4F61-B671-5C518F2A3DDC} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC}
{288089C5-8721-458E-BE3E-78990DAB5E2E} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC}
{288089C5-8721-458E-BE3E-78990DAB5E2D} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC}
{D0A79850-B32A-45E5-9FD5-D43CB345867A} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC}
{6AA96934-D6B7-4CC8-990D-DB6B9DD56E34} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC}
{909B656F-6095-4AC2-A5AB-C3F032315C45} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC}
{D0BC9BE7-24F6-40CA-8DC6-FCB93BD44B34} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9}
......
......@@ -1968,9 +1968,9 @@ public override CommonConversion ClassifyCommonConversion(ITypeSymbol source, IT
return ClassifyConversion(source, destination).ToCommonConversion();
}
internal override IConvertibleConversion ClassifyConvertibleConversion(IOperation source, ITypeSymbol? destination, out Optional<object> constantValue)
internal override IConvertibleConversion ClassifyConvertibleConversion(IOperation source, ITypeSymbol? destination, out OperationConstantValue constantValue)
{
constantValue = default;
constantValue = OperationConstantValue.None;
if (destination is null)
{
......@@ -1983,7 +1983,7 @@ internal override IConvertibleConversion ClassifyConvertibleConversion(IOperatio
{
if (source.ConstantValue.HasValue && source.ConstantValue.Value is null && destination.IsReferenceType)
{
constantValue = source.ConstantValue;
constantValue = source.GetConstantValue();
return Conversion.NullLiteral;
}
......@@ -1994,7 +1994,7 @@ internal override IConvertibleConversion ClassifyConvertibleConversion(IOperatio
if (result.IsReference && source.ConstantValue.HasValue && source.ConstantValue.Value is null)
{
constantValue = source.ConstantValue;
constantValue = source.GetConstantValue();
}
return result;
......
......@@ -16,9 +16,9 @@ internal sealed partial class CSharpOperationFactory
{
private static readonly IConvertibleConversion s_boxedIdentityConversion = Conversion.Identity;
internal static Optional<object> ConvertToOptional(ConstantValue value)
internal static OperationConstantValue ConvertToConstantValue(ConstantValue value)
{
return value != null && !value.IsBad ? new Optional<object>(value.Value) : default(Optional<object>);
return OperationConstantValue.Create(value);
}
internal ImmutableArray<BoundStatement> ToStatements(BoundStatement statement)
......@@ -37,7 +37,7 @@ internal ImmutableArray<BoundStatement> ToStatements(BoundStatement statement)
}
private IInstanceReferenceOperation CreateImplicitReceiver(SyntaxNode syntax, TypeSymbol type) =>
new InstanceReferenceOperation(InstanceReferenceKind.ImplicitReceiver, _semanticModel, syntax, type.GetPublicSymbol(), constantValue: default, isImplicit: true);
new InstanceReferenceOperation(InstanceReferenceKind.ImplicitReceiver, _semanticModel, syntax, type.GetPublicSymbol(), constantValue: OperationConstantValue.None, isImplicit: true);
internal IArgumentOperation CreateArgumentOperation(ArgumentKind kind, IParameterSymbol parameter, BoundExpression expression)
{
......@@ -95,7 +95,7 @@ internal IVariableInitializerOperation CreateVariableDeclaratorInitializer(Bound
initializerIsImplicit = true;
}
return new CSharpLazyVariableInitializerOperation(this, boundLocalDeclaration.InitializerOpt, _semanticModel, initializerSyntax, type: null, constantValue: default, initializerIsImplicit);
return new CSharpLazyVariableInitializerOperation(this, boundLocalDeclaration.InitializerOpt, _semanticModel, initializerSyntax, type: null, constantValue: OperationConstantValue.None, initializerIsImplicit);
}
return null;
......@@ -106,7 +106,7 @@ private IVariableDeclaratorOperation CreateVariableDeclaratorInternal(BoundLocal
ILocalSymbol symbol = boundLocalDeclaration.LocalSymbol.GetPublicSymbol();
SyntaxNode syntaxNode = boundLocalDeclaration.Syntax;
ITypeSymbol type = null;
Optional<object> constantValue = default;
var constantValue = OperationConstantValue.None;
bool isImplicit = false;
return new CSharpLazyVariableDeclaratorOperation(this, boundLocalDeclaration, symbol, _semanticModel, syntax, type, constantValue, isImplicit);
......@@ -114,7 +114,7 @@ private IVariableDeclaratorOperation CreateVariableDeclaratorInternal(BoundLocal
internal IVariableDeclaratorOperation CreateVariableDeclarator(BoundLocal boundLocal)
{
return boundLocal == null ? null : new VariableDeclaratorOperation(boundLocal.LocalSymbol.GetPublicSymbol(), initializer: null, ignoredArguments: ImmutableArray<IOperation>.Empty, semanticModel: _semanticModel, syntax: boundLocal.Syntax, type: null, constantValue: default, isImplicit: false);
return boundLocal == null ? null : new VariableDeclaratorOperation(boundLocal.LocalSymbol.GetPublicSymbol(), initializer: null, ignoredArguments: ImmutableArray<IOperation>.Empty, semanticModel: _semanticModel, syntax: boundLocal.Syntax, type: null, constantValue: OperationConstantValue.None, isImplicit: false);
}
internal IOperation CreateReceiverOperation(BoundNode instance, Symbol symbol)
......@@ -156,7 +156,7 @@ internal IEventReferenceOperation CreateBoundEventAccessOperation(BoundEventAssi
SyntaxNode eventAccessSyntax = ((AssignmentExpressionSyntax)syntax).Left;
bool isImplicit = boundEventAssignmentOperator.WasCompilerGenerated;
return new CSharpLazyEventReferenceOperation(this, instance, @event, _semanticModel, eventAccessSyntax, @event.Type, ConvertToOptional(null), isImplicit);
return new CSharpLazyEventReferenceOperation(this, instance, @event, _semanticModel, eventAccessSyntax, @event.Type, ConvertToConstantValue(null), isImplicit);
}
internal IOperation CreateDelegateTargetOperation(BoundNode delegateNode)
......@@ -393,7 +393,7 @@ internal static ImmutableArray<BoundNode> CreateInvalidChildrenFromArgumentsExpr
semanticModel: _semanticModel,
syntax: syntax,
type: type,
constantValue: default,
constantValue: OperationConstantValue.None,
isImplicit: true);
// Find matching declaration for the current argument.
......@@ -409,7 +409,7 @@ internal static ImmutableArray<BoundNode> CreateInvalidChildrenFromArgumentsExpr
semanticModel: _semanticModel,
syntax: value.Syntax,
type: property.Type.GetPublicSymbol(),
constantValue: default,
constantValue: OperationConstantValue.None,
isImplicit: true);
isImplicitAssignment = true;
}
......@@ -421,16 +421,15 @@ internal static ImmutableArray<BoundNode> CreateInvalidChildrenFromArgumentsExpr
_semanticModel,
anonymousProperty.Syntax,
anonymousProperty.Type.GetPublicSymbol(),
ConvertToOptional(anonymousProperty.ConstantValue),
ConvertToConstantValue(anonymousProperty.ConstantValue),
anonymousProperty.WasCompilerGenerated);
isImplicitAssignment = isImplicit;
}
var assignmentSyntax = value.Syntax?.Parent ?? syntax;
ITypeSymbol assignmentType = target.Type;
Optional<object> constantValue = value.ConstantValue;
bool isRef = false;
var assignment = new SimpleAssignmentOperation(isRef, target, value, _semanticModel, assignmentSyntax, assignmentType, constantValue, isImplicitAssignment);
var assignment = new SimpleAssignmentOperation(isRef, target, value, _semanticModel, assignmentSyntax, assignmentType, value.GetConstantValue(), isImplicitAssignment);
builder.Add(assignment);
}
......
......@@ -3434,7 +3434,9 @@ static void F(IEnumerable<C> c)
);
}
[Fact]
// Attempting to call `ConstantValue` on every constiuent string component times out the IOperation runner.
// Instead, we manually validate just the top level
[ConditionalFact(typeof(NoIOperationValidation)), WorkItem(43019, "https://github.com/dotnet/roslyn/issues/43019")]
public void TestLargeStringConcatenation()
{
// When the compiler folds string concatenations using an O(n^2) algorithm, this program cannot be
......@@ -3455,12 +3457,29 @@ static void Main()
";
StringBuilder source = new StringBuilder();
source.Append(source0);
for (int i = 0; i < 5000; i++)
const int NumIterations = 5000;
for (int i = 0; i < NumIterations; i++)
{
source.Append(@"""Lorem ipsum dolor sit amet"" + "", consectetur adipiscing elit, sed"" + "" do eiusmod tempor incididunt"" + "" ut labore et dolore magna aliqua. "" +" + "\n");
}
source.Append(source1);
CompileAndVerify(source.ToString(), expectedOutput: "58430604");
var comp = CreateCompilation(source.ToString(), options: TestOptions.ReleaseExe);
CompileAndVerify(comp, expectedOutput: "58430604");
var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree);
var initializer = tree.GetRoot().DescendantNodes().OfType<VariableDeclaratorSyntax>().Single().Initializer.Value;
var literalOperation = model.GetOperation(initializer);
var stringTextBuilder = new StringBuilder();
stringTextBuilder.Append("BEGIN ");
for (int i = 0; i < NumIterations; i++)
{
stringTextBuilder.Append("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ");
}
stringTextBuilder.Append("END");
Assert.Equal(stringTextBuilder.ToString(), literalOperation.ConstantValue);
}
}
......
......@@ -10957,7 +10957,10 @@ public class C {
Assert.True(type.IsErrorType());
}
[Fact, WorkItem(529600, "DevDiv"), WorkItem(7398, "https://github.com/dotnet/roslyn/issues/7398")]
// Attempting to call `ConstantValue` on every constituent string component realizes every string, effectively
// replicating the original O(n^2) bug that this test is demonstrating is fixed.
[ConditionalFact(typeof(NoIOperationValidation))]
[WorkItem(43019, "https://github.com/dotnet/roslyn/issues/43019"), WorkItem(529600, "DevDiv"), WorkItem(7398, "https://github.com/dotnet/roslyn/issues/7398")]
public void Bug529600()
{
// History of this bug: When constant folding a long sequence of string concatentations, there is
......@@ -11013,11 +11016,42 @@ static void Main()
C1;
}}
";
CreateCompilation(source).VerifyDiagnostics(
var comp = CreateCompilation(source);
comp.VerifyDiagnostics(
// (28,68): error CS8095: Length of String constant resulting from concatenation exceeds System.Int32.MaxValue. Try splitting the string into multiple constants.
// C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 + C1 +
Diagnostic(ErrorCode.ERR_ConstantStringTooLong, "C1").WithLocation(28, 68)
);
// If we realize every string constant value when each IOperation is created, then attempting to enumerate all
// IOperations will consume O(n^2) memory. This demonstrates that these values are not eagerly created, and the
// test runs quickly and without issue
var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree);
var fieldInitializerOperations = tree.GetRoot().DescendantNodes().OfType<VariableDeclaratorSyntax>()
.Select(v => v.Initializer.Value)
.Select(i => model.GetOperation(i));
int numChildren = 0;
foreach (var initializerOp in fieldInitializerOperations)
{
enumerateChildren(initializerOp);
}
Assert.Equal(1203, numChildren);
void enumerateChildren(IOperation iop)
{
numChildren++;
Assert.NotNull(iop);
foreach (var child in iop.Children)
{
enumerateChildren(child);
}
}
}
[Fact, WorkItem(39975, "https://github.com/dotnet/roslyn/issues/39975")]
......
......@@ -75,7 +75,7 @@ public void IDynamicInvocationExpression_PublicExtensionMethodTests()
Assert.Throws<ArgumentNullException>(() => nullDynamicExpression.GetArgumentRefKind(0));
Func<ImmutableArray<IOperation>, ImmutableArray<string>, ImmutableArray<RefKind>, HasDynamicArgumentsExpression> createDynamicExpression =
(arguments, argumentNames, argumentRefKinds) => new DynamicInvocationOperation(null, arguments, argumentNames, argumentRefKinds, null, null, null, null, false);
(arguments, argumentNames, argumentRefKinds) => new DynamicInvocationOperation(null, arguments, argumentNames, argumentRefKinds, null, null, null, OperationConstantValue.None, false);
TestCore(createDynamicExpression);
}
......@@ -90,7 +90,7 @@ public void IDynamicIndexerAccessExpression_PublicExtensionMethodTests()
Assert.Throws<ArgumentNullException>(() => nullDynamicExpression.GetArgumentRefKind(0));
Func<ImmutableArray<IOperation>, ImmutableArray<string>, ImmutableArray<RefKind>, HasDynamicArgumentsExpression> createDynamicExpression =
(arguments, argumentNames, argumentRefKinds) => new DynamicIndexerAccessOperation(null, arguments, argumentNames, argumentRefKinds, null, null, null, null, false);
(arguments, argumentNames, argumentRefKinds) => new DynamicIndexerAccessOperation(null, arguments, argumentNames, argumentRefKinds, null, null, null, OperationConstantValue.None, false);
TestCore(createDynamicExpression);
}
......@@ -105,7 +105,7 @@ public void IDynamicObjectCreationExpression_PublicExtensionMethodTests()
Assert.Throws<ArgumentNullException>(() => nullDynamicExpression.GetArgumentRefKind(0));
Func<ImmutableArray<IOperation>, ImmutableArray<string>, ImmutableArray<RefKind>, HasDynamicArgumentsExpression> createDynamicExpression =
(arguments, argumentNames, argumentRefKinds) => new DynamicObjectCreationOperation(arguments, argumentNames, argumentRefKinds, null, null, null, null, null, false);
(arguments, argumentNames, argumentRefKinds) => new DynamicObjectCreationOperation(arguments, argumentNames, argumentRefKinds, null, null, null, null, OperationConstantValue.None, false);
TestCore(createDynamicExpression);
}
......@@ -127,7 +127,7 @@ public void TestGetFlowGraphNullArgument()
public void TestGetFlowGraphInvalidArgumentWithNonNullParent()
{
IOperation parent = new BlockOperation(ImmutableArray<IOperation>.Empty, ImmutableArray<ILocalSymbol>.Empty,
semanticModel: null, syntax: null, type: null, constantValue: default, isImplicit: false);
semanticModel: null, syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: false);
TestGetFlowGraphInvalidArgumentCore(argumentExceptionMessage: CodeAnalysisResources.NotARootOperation, parent);
}
......@@ -146,7 +146,7 @@ private void TestGetFlowGraphInvalidArgumentCore(string argumentExceptionMessage
{
IBlockOperation block = new BlockOperation(
ImmutableArray<IOperation>.Empty, ImmutableArray<ILocalSymbol>.Empty,
semanticModel: null, syntax: null, type: null, constantValue: default, isImplicit: false);
semanticModel: null, syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: false);
block = Operation.SetParentOperation(block, parent);
_ = ControlFlowGraph.Create(block);
}
......@@ -164,7 +164,7 @@ private void TestGetFlowGraphInvalidArgumentCore(string argumentExceptionMessage
IFieldInitializerOperation initializer = new FieldInitializerOperation(
ImmutableArray<IFieldSymbol>.Empty, ImmutableArray<ILocalSymbol>.Empty,
value: null, semanticModel: null,
syntax: null, type: null, constantValue: default, isImplicit: false);
syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: false);
initializer = Operation.SetParentOperation(initializer, parent);
_ = ControlFlowGraph.Create(initializer);
}
......@@ -182,7 +182,7 @@ private void TestGetFlowGraphInvalidArgumentCore(string argumentExceptionMessage
IPropertyInitializerOperation initializer = new PropertyInitializerOperation(
ImmutableArray<IPropertySymbol>.Empty, ImmutableArray<ILocalSymbol>.Empty,
value: null, semanticModel: null,
syntax: null, type: null, constantValue: default, isImplicit: false);
syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: false);
initializer = Operation.SetParentOperation(initializer, parent);
_ = ControlFlowGraph.Create(initializer);
}
......@@ -200,7 +200,7 @@ private void TestGetFlowGraphInvalidArgumentCore(string argumentExceptionMessage
IParameterInitializerOperation initializer = new ParameterInitializerOperation(
parameter: null, locals: ImmutableArray<ILocalSymbol>.Empty,
value: null, semanticModel: null,
syntax: null, type: null, constantValue: default, isImplicit: false);
syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: false);
initializer = Operation.SetParentOperation(initializer, parent);
_ = ControlFlowGraph.Create(initializer);
}
......
......@@ -1428,7 +1428,7 @@ bool isContainingAssemblyInReferences(ISymbol s)
ISymbol within,
ITypeSymbol? throughType);
internal abstract IConvertibleConversion ClassifyConvertibleConversion(IOperation source, ITypeSymbol destination, out Optional<object> constantValue);
internal abstract IConvertibleConversion ClassifyConvertibleConversion(IOperation source, ITypeSymbol destination, out OperationConstantValue constantValue);
#endregion
......
因为 它太大了无法显示 source diff 。你可以改为 查看blob
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
......@@ -19,31 +20,33 @@ namespace Microsoft.CodeAnalysis
internal abstract class Operation : IOperation
{
protected static readonly IOperation s_unset = new EmptyOperation(
semanticModel: null, syntax: null, type: null, constantValue: default, isImplicit: true);
semanticModel: null, syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: true);
protected static readonly IBlockOperation s_unsetBlock = new BlockOperation(
operations: ImmutableArray<IOperation>.Empty, locals: default, semanticModel: null, syntax: null, type: null, constantValue: default, isImplicit: true);
operations: ImmutableArray<IOperation>.Empty, locals: default, semanticModel: null, syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: true);
protected static readonly IArrayInitializerOperation s_unsetArrayInitializer = new ArrayInitializerOperation(
elementValues: ImmutableArray<IOperation>.Empty, semanticModel: null, syntax: null, type: null, constantValue: default, isImplicit: true);
elementValues: ImmutableArray<IOperation>.Empty, semanticModel: null, syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: true);
protected static readonly IEventReferenceOperation s_unsetEventReference = new EventReferenceOperation(
@event: null, instance: null, semanticModel: null, syntax: null, type: null, constantValue: default, isImplicit: true);
@event: null, instance: null, semanticModel: null, syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: true);
protected static readonly IObjectOrCollectionInitializerOperation s_unsetObjectOrCollectionInitializer = new ObjectOrCollectionInitializerOperation(
initializers: ImmutableArray<IOperation>.Empty, semanticModel: null, syntax: null, type: null, constantValue: default, isImplicit: true);
initializers: ImmutableArray<IOperation>.Empty, semanticModel: null, syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: true);
protected static readonly IPatternOperation s_unsetPattern = new ConstantPatternOperation(
value: null, inputType: null, semanticModel: null, syntax: null, type: null, constantValue: default, isImplicit: true);
value: null, inputType: null, semanticModel: null, syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: true);
protected static readonly IVariableDeclarationGroupOperation s_unsetVariableDeclarationGroup = new VariableDeclarationGroupOperation(
declarations: ImmutableArray<IVariableDeclarationOperation>.Empty, semanticModel: null, syntax: null, type: null, constantValue: default, isImplicit: true);
declarations: ImmutableArray<IVariableDeclarationOperation>.Empty, semanticModel: null, syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: true);
protected static readonly IVariableInitializerOperation s_unsetVariableInitializer = new VariableInitializerOperation(
locals: ImmutableArray<ILocalSymbol>.Empty, value: null, semanticModel: null, syntax: null, type: null, constantValue: default, isImplicit: false);
private readonly SemanticModel _owningSemanticModelOpt;
locals: ImmutableArray<ILocalSymbol>.Empty, value: null, semanticModel: null, syntax: null, type: null, constantValue: OperationConstantValue.None, isImplicit: false);
private readonly SemanticModel? _owningSemanticModelOpt;
// this will be lazily initialized. this will be initialized only once
// but once initialized, will never change
private IOperation _parentDoNotAccessDirectly;
private IOperation? _parentDoNotAccessDirectly;
protected Operation(OperationKind kind, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional<object> constantValue, bool isImplicit)
protected Operation(OperationKind kind, SemanticModel? semanticModel, SyntaxNode syntax, ITypeSymbol type, OperationConstantValue constantValue, bool isImplicit)
{
// Constant value must at least be ConstantValue.None.
Debug.Assert(constantValue is object);
// Constant value cannot be "null" for non-nullable value type operations.
Debug.Assert(type?.IsValueType != true || ITypeSymbolHelpers.IsNullableType(type) || !constantValue.HasValue || constantValue.Value != null);
Debug.Assert(type?.IsValueType != true || ITypeSymbolHelpers.IsNullableType(type) || !constantValue.Value.HasValue || constantValue.Value.Value != null);
#if DEBUG
if (semanticModel != null)
......@@ -65,7 +68,7 @@ protected Operation(OperationKind kind, SemanticModel semanticModel, SyntaxNode
Kind = kind;
Syntax = syntax;
Type = type;
ConstantValue = constantValue;
OperationConstantValue = constantValue;
IsImplicit = isImplicit;
_parentDoNotAccessDirectly = s_unset;
......@@ -74,7 +77,7 @@ protected Operation(OperationKind kind, SemanticModel semanticModel, SyntaxNode
/// <summary>
/// IOperation that has this operation as a child
/// </summary>
public IOperation Parent
public IOperation? Parent
{
get
{
......@@ -105,7 +108,7 @@ public IOperation Parent
/// <summary>
/// Result type of the operation, or null if the operation does not produce a result.
/// </summary>
public ITypeSymbol Type { get; }
public ITypeSymbol? Type { get; }
/// <summary>
/// The source language of the IOperation. Possible values are <see cref="LanguageNames.CSharp"/> and <see cref="LanguageNames.VisualBasic"/>.
......@@ -119,14 +122,15 @@ public string Language
get => Syntax.Language;
}
internal OperationConstantValue OperationConstantValue { get; }
/// <summary>
/// If the operation is an expression that evaluates to a constant value, <see cref="Optional{Object}.HasValue"/> is true and <see cref="Optional{Object}.Value"/> is the value of the expression. Otherwise, <see cref="Optional{Object}.HasValue"/> is false.
/// </summary>
public Optional<object> ConstantValue { get; }
public Optional<object?> ConstantValue => OperationConstantValue.Value;
public abstract IEnumerable<IOperation> Children { get; }
SemanticModel IOperation.SemanticModel => _owningSemanticModelOpt?.ContainingModelOrSelf;
SemanticModel? IOperation.SemanticModel => _owningSemanticModelOpt?.ContainingModelOrSelf;
/// <summary>
/// Gets the owning semantic model for this operation node.
......@@ -134,13 +138,13 @@ public string Language
/// is the semantic model on which <see cref="SemanticModel.GetOperation(SyntaxNode, CancellationToken)"/> was invoked
/// to create this node.
/// </summary>
internal SemanticModel OwningSemanticModel => _owningSemanticModelOpt;
internal SemanticModel? OwningSemanticModel => _owningSemanticModelOpt;
public abstract void Accept(OperationVisitor visitor);
public abstract TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, TResult> visitor, TArgument argument);
protected void SetParentOperation(IOperation parent)
protected void SetParentOperation(IOperation? parent)
{
var result = Interlocked.CompareExchange(ref _parentDoNotAccessDirectly, parent, s_unset);
......@@ -212,7 +216,7 @@ internal static void VerifyParentOperation(IOperation parent, IOperation child)
private static readonly ObjectPool<Queue<IOperation>> s_queuePool =
new ObjectPool<Queue<IOperation>>(() => new Queue<IOperation>(), 10);
private IOperation WalkDownOperationToFindParent(HashSet<IOperation> operationAlreadyProcessed, IOperation root)
private IOperation? WalkDownOperationToFindParent(HashSet<IOperation> operationAlreadyProcessed, IOperation root)
{
void EnqueueChildOperations(Queue<IOperation> queue, IOperation parent)
{
......@@ -267,9 +271,10 @@ void EnqueueChildOperations(Queue<IOperation> queue, IOperation parent)
}
// internal for testing
internal IOperation SearchParentOperation()
internal IOperation? SearchParentOperation()
{
var operationAlreadyProcessed = PooledHashSet<IOperation>.GetInstance();
Debug.Assert(OwningSemanticModel is object);
if (OwningSemanticModel.Root == Syntax)
{
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Diagnostics;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Root type for operation constant values.
/// </summary>
internal abstract class OperationConstantValue
{
internal static OperationConstantValue None => NoConstantValue.Instance;
internal static OperationConstantValue Create(ConstantValue? originalConstant)
=> originalConstant switch
{
null => NoConstantValue.Instance,
{ IsBad: true } => NoConstantValue.Instance,
{ IsString: true } => new StringConstantValue(originalConstant),
{ IsBoolean: true } => originalConstant.BooleanValue ? NonStringConstantValue.True : NonStringConstantValue.False,
{ IsNull: true } => NonStringConstantValue.Null,
_ => new NonStringConstantValue(originalConstant.Value)
};
internal static OperationConstantValue FromBoolean(bool value)
=> Create(value ? ConstantValue.True : ConstantValue.False);
protected OperationConstantValue() { }
internal abstract Optional<object?> Value { get; }
private sealed class NoConstantValue : OperationConstantValue
{
internal static readonly NoConstantValue Instance = new NoConstantValue();
private NoConstantValue() { }
internal override Optional<object?> Value => default;
}
private sealed class NonStringConstantValue : OperationConstantValue
{
internal static readonly NonStringConstantValue True = new NonStringConstantValue(true);
internal static readonly NonStringConstantValue False = new NonStringConstantValue(false);
internal static readonly NonStringConstantValue Null = new NonStringConstantValue(null);
internal NonStringConstantValue(object? constantValue)
{
Debug.Assert(!(constantValue is string));
Value = new Optional<object?>(constantValue);
}
internal override Optional<object?> Value { get; }
}
private sealed class StringConstantValue : OperationConstantValue
{
/// <summary>
/// Some string constant values can have large costs to realize. To compensate, we realize
/// constant values lazily, and hold onto a weak reference. If the next time a user asks for the contant
/// value the previous one still exists, we can avoid rerealizing it. But we don't want to root the constant
/// value if no users are still using it.
/// </summary>
private WeakReference<string>? _constantValueReference;
private readonly ConstantValue _originalConstantValue;
internal StringConstantValue(ConstantValue originalConstantValue)
{
Debug.Assert(originalConstantValue.IsString);
_originalConstantValue = originalConstantValue;
}
internal override Optional<object?> Value
{
get
{
string? constantValue = null;
if (_constantValueReference?.TryGetTarget(out constantValue) != true)
{
// Note: we could end up realizing the constant value multiple times if there's
// a race here. Currently, this isn't believed to be an issue, as the assignment
// to _constantValueReference is atomic so the worst that will happen is we return
// different instances of a string constant to users.
constantValue = _originalConstantValue.StringValue;
Debug.Assert(constantValue != null);
_constantValueReference = new WeakReference<string>(constantValue);
}
return new Optional<object?>(constantValue);
}
}
}
}
}
......@@ -376,5 +376,10 @@ public static IOperation GetCorrespondingOperation(this IBranchOperation operati
return null;
}
internal static OperationConstantValue GetConstantValue(this IOperation operation)
{
return ((Operation)operation).OperationConstantValue;
}
}
}
......@@ -11,7 +11,7 @@ internal static class OperationFactory
{
public static IInvalidOperation CreateInvalidOperation(SemanticModel semanticModel, SyntaxNode syntax, ImmutableArray<IOperation> children, bool isImplicit)
{
return new InvalidOperation(children, semanticModel, syntax, type: null, constantValue: default(Optional<object>), isImplicit: isImplicit);
return new InvalidOperation(children, semanticModel, syntax, type: null, constantValue: OperationConstantValue.None, isImplicit: isImplicit);
}
}
}
......@@ -1807,8 +1807,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return ClassifyConversion(source, destination).ToCommonConversion()
End Function
Friend Overrides Function ClassifyConvertibleConversion(source As IOperation, destination As ITypeSymbol, ByRef constantValue As [Optional](Of Object)) As IConvertibleConversion
constantValue = Nothing
Friend Overrides Function ClassifyConvertibleConversion(source As IOperation, destination As ITypeSymbol, ByRef constantValue As OperationConstantValue) As IConvertibleConversion
constantValue = OperationConstantValue.None
If destination Is Nothing Then
Return New Conversion(Nothing) ' No conversion
......@@ -1818,7 +1818,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
If sourceType Is Nothing Then
If source.ConstantValue.HasValue AndAlso source.ConstantValue.Value Is Nothing AndAlso destination.IsReferenceType Then
constantValue = source.ConstantValue
constantValue = source.GetConstantValue()
Return New Conversion(New KeyValuePair(Of ConversionKind, MethodSymbol)(ConversionKind.WideningNothingLiteral, Nothing))
End If
......@@ -1828,7 +1828,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Dim result As Conversion = ClassifyConversion(sourceType, destination)
If result.IsReference AndAlso source.ConstantValue.HasValue AndAlso source.ConstantValue.Value Is Nothing Then
constantValue = source.ConstantValue
constantValue = source.GetConstantValue()
End If
Return result
......
......@@ -11,8 +11,8 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.Operations
Partial Friend NotInheritable Class VisualBasicOperationFactory
Private Shared Function ConvertToOptional(value As ConstantValue) As [Optional](Of Object)
Return If(value Is Nothing OrElse value.IsBad, New [Optional](Of Object)(), New [Optional](Of Object)(value.Value))
Private Shared Function ConvertToOptional(value As ConstantValue) As OperationConstantValue
Return OperationConstantValue.Create(value)
End Function
Private Shared Function IsMidStatement(node As BoundNode) As Boolean
......@@ -92,7 +92,7 @@ Namespace Microsoft.CodeAnalysis.Operations
Dim leftOperand As Lazy(Of IOperation) = New Lazy(Of IOperation)(Function() Create(boundAssignment.Left))
Dim syntax As SyntaxNode = boundAssignment.Syntax
Dim type As ITypeSymbol = boundAssignment.Type
Dim constantValue As [Optional](Of Object) = ConvertToOptional(boundAssignment.ConstantValueOpt)
Dim constantValue As OperationConstantValue = ConvertToOptional(boundAssignment.ConstantValueOpt)
Dim isImplicit As Boolean = boundAssignment.WasCompilerGenerated
Return New VisualBasicLazyCompoundAssignmentOperation(Me, boundAssignment, inConversion, outConversion, operatorInfo.OperatorKind,
......@@ -358,7 +358,7 @@ Namespace Microsoft.CodeAnalysis.Operations
_semanticModel,
value.Syntax,
[property].Type,
constantValue:=Nothing,
constantValue:=OperationConstantValue.None,
isImplicit:=True)
isImplicitAssignment = True
Else
......@@ -371,7 +371,7 @@ Namespace Microsoft.CodeAnalysis.Operations
Dim isRef As Boolean = False
Dim syntax As SyntaxNode = If(value.Syntax?.Parent, expression.Syntax)
Dim type As ITypeSymbol = target.Type
Dim constantValue As [Optional](Of Object) = value.ConstantValue
Dim constantValue As OperationConstantValue = value.GetConstantValue()
Dim assignment = New SimpleAssignmentOperation(isRef, target, value, _semanticModel, syntax, type, constantValue, isImplicitAssignment)
builder.Add(assignment)
Next i
......@@ -451,14 +451,14 @@ Namespace Microsoft.CodeAnalysis.Operations
initializerSyntax = last.InitializerOpt.Syntax
isImplicit = True
End If
initializer = New VisualBasicLazyVariableInitializerOperation(Me, last.InitializerOpt, _semanticModel, initializerSyntax, type:=Nothing, constantValue:=Nothing, isImplicit)
initializer = New VisualBasicLazyVariableInitializerOperation(Me, last.InitializerOpt, _semanticModel, initializerSyntax, type:=Nothing, constantValue:=OperationConstantValue.None, isImplicit)
End If
Else
Dim asNewDeclarations = DirectCast(first, BoundAsNewLocalDeclarations)
declarators = asNewDeclarations.LocalDeclarations.SelectAsArray(AddressOf GetVariableDeclarator)
Dim initializerSyntax As AsClauseSyntax = DirectCast(asNewDeclarations.Syntax, VariableDeclaratorSyntax).AsClause
Dim initializerValue As IOperation = Create(asNewDeclarations.Initializer)
initializer = New VisualBasicLazyVariableInitializerOperation(Me, asNewDeclarations.Initializer, _semanticModel, initializerSyntax, type:=Nothing, constantValue:=Nothing, isImplicit:=False)
initializer = New VisualBasicLazyVariableInitializerOperation(Me, asNewDeclarations.Initializer, _semanticModel, initializerSyntax, type:=Nothing, constantValue:=OperationConstantValue.None, isImplicit:=False)
End If
builder.Add(New VariableDeclarationOperation(declarators,
......@@ -467,7 +467,7 @@ Namespace Microsoft.CodeAnalysis.Operations
_semanticModel,
declarationGroup.Key,
type:=Nothing,
constantValue:=Nothing,
constantValue:=OperationConstantValue.None,
isImplicit:=False))
Next
......@@ -479,12 +479,12 @@ Namespace Microsoft.CodeAnalysis.Operations
If boundLocalDeclaration.IdentifierInitializerOpt IsNot Nothing Then
Dim syntax = boundLocalDeclaration.Syntax
Dim initializerValue As BoundNode = boundLocalDeclaration.IdentifierInitializerOpt
initializer = New VisualBasicLazyVariableInitializerOperation(Me, initializerValue, _semanticModel, syntax, type:=Nothing, constantValue:=Nothing, isImplicit:=True)
initializer = New VisualBasicLazyVariableInitializerOperation(Me, initializerValue, _semanticModel, syntax, type:=Nothing, constantValue:=OperationConstantValue.None, isImplicit:=True)
End If
Dim ignoredArguments = ImmutableArray(Of IOperation).Empty
Return New VariableDeclaratorOperation(boundLocalDeclaration.LocalSymbol, initializer, ignoredArguments, _semanticModel, boundLocalDeclaration.Syntax, type:=Nothing, constantValue:=Nothing, isImplicit:=boundLocalDeclaration.WasCompilerGenerated)
Return New VariableDeclaratorOperation(boundLocalDeclaration.LocalSymbol, initializer, ignoredArguments, _semanticModel, boundLocalDeclaration.Syntax, type:=Nothing, constantValue:=OperationConstantValue.None, isImplicit:=boundLocalDeclaration.WasCompilerGenerated)
End Function
Private Function GetUsingStatementDeclaration(resourceList As ImmutableArray(Of BoundLocalDeclarationBase), syntax As SyntaxNode) As IVariableDeclarationGroupOperation
......@@ -493,14 +493,14 @@ Namespace Microsoft.CodeAnalysis.Operations
_semanticModel,
syntax,
type:=Nothing,
constantValue:=Nothing,
constantValue:=OperationConstantValue.None,
isImplicit:=False) ' Declaration is always explicit
End Function
Friend Function GetAddRemoveHandlerStatementExpression(statement As BoundAddRemoveHandlerStatement) As IOperation
Dim adds = statement.Kind = BoundKind.AddHandlerStatement
Return New VisualBasicLazyEventAssignmentOperation(
Me, statement, adds:=adds, semanticModel:=_semanticModel, syntax:=statement.Syntax, type:=Nothing, constantValue:=Nothing, isImplicit:=True)
Me, statement, adds:=adds, semanticModel:=_semanticModel, syntax:=statement.Syntax, type:=Nothing, constantValue:=OperationConstantValue.None, isImplicit:=True)
End Function
#Region "Conversions"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册