diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 8ee4e477c6460..de920105e4f0c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7547,6 +7547,11 @@ private BoundExpression BindLeftIdentifierOfPotentialColorColorMemberAccess(Iden Debug.Assert(!leftType.IsDynamic()); Debug.Assert(IsPotentialColorColorReceiver(left, leftType)); + if (leftSymbol is SourceFieldSymbolWithSyntaxReference fieldLeft) + { + ConstantFieldsInProgress.RemoveIfLastDependency(fieldLeft); + } + // NOTE: ReplaceTypeOrValueReceiver will call CheckValue explicitly. boundValue = BindToNaturalType(boundValue, valueDiagnostics); return new BoundTypeOrValueExpression(left, diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index c0256e804e944..d6a30139e1dd9 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -1989,6 +1989,16 @@ private BoundExpression ReplaceTypeOrValueReceiver(BoundExpression receiver, boo } else { + // If we resort to rebinding back to the field, we attempt to add the dependency back + if (typeOrValue.Data.ValueExpression.ExpressionSymbol is SourceFieldSymbolWithSyntaxReference sourceField + && sourceField.IsConst) + { + if (!ConstantFieldsInProgress.IsEmpty) + { + ConstantFieldsInProgress.AddDependency(sourceField); + } + } + diagnostics.AddRange(typeOrValue.Data.ValueDiagnostics); return CheckValue(typeOrValue.Data.ValueExpression, BindValueKind.RValue, diagnostics); } diff --git a/src/Compilers/CSharp/Portable/Binder/ConstantFieldsInProgress.cs b/src/Compilers/CSharp/Portable/Binder/ConstantFieldsInProgress.cs index 80b55d4252883..b6a970de740df 100644 --- a/src/Compilers/CSharp/Portable/Binder/ConstantFieldsInProgress.cs +++ b/src/Compilers/CSharp/Portable/Binder/ConstantFieldsInProgress.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { @@ -20,6 +19,13 @@ internal sealed class ConstantFieldsInProgress private readonly SourceFieldSymbol _fieldOpt; private readonly HashSet _dependencies; + /// + /// Stores the last dependency that was added to the set. This is used to check if + /// the dependency that was added is not after all a dependency after successful rebinding + /// in Color Color resolution, i.e. `public const Color Color = Color.Red;` + /// + private SourceFieldSymbolWithSyntaxReference _lastDependency; + internal static readonly ConstantFieldsInProgress Empty = new ConstantFieldsInProgress(null, null); internal ConstantFieldsInProgress( @@ -38,6 +44,16 @@ public bool IsEmpty internal void AddDependency(SourceFieldSymbolWithSyntaxReference field) { _dependencies.Add(field); + _lastDependency = field; + } + + internal void RemoveIfLastDependency(SourceFieldSymbolWithSyntaxReference field) + { + if (_lastDependency is not null && _lastDependency == (object)field) + { + _dependencies.Remove(_lastDependency); + _lastDependency = null; + } } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ColorColorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ColorColorTests.cs index 3b759fc18bcf9..46302b0c20387 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ColorColorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ColorColorTests.cs @@ -2368,6 +2368,108 @@ instance void .ctor () cil managed compilation.VerifyDiagnostics(); } + [WorkItem("https://github.com/dotnet/roslyn/issues/45739")] + [Fact] + public void ConstFieldEnum() + { + const string source = @" +using Color2 = Color; + +enum Color +{ + Red, + Green, + Blue, + Yellow, +} + +class M +{ + public const Color Color = Color.Red; + public const Color2 Color2 = Color2.Yellow; +} + +class Program +{ + static void Main() + { + System.Console.Write(M.Color == Color.Red); + System.Console.Write(M.Color2 == Color.Yellow); + } +} +"; + + CompileAndVerify(source, expectedOutput: "TrueTrue") + .VerifyDiagnostics(); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/45739")] + [Fact] + public void ConstFieldPrimitives() + { + const string source = @" +using System; +using SystemChar = System.Char; + +class M +{ + public const Int32 Int32 = Int32.MaxValue; + public const SystemChar SystemChar = SystemChar.MaxValue; +} + +class Program +{ + static void Main() + { + System.Console.Write(M.Int32 == Int32.MaxValue); + System.Console.Write(M.SystemChar == SystemChar.MaxValue); + } +} +"; + + CompileAndVerify(source, expectedOutput: "TrueTrue") + .VerifyDiagnostics(); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/45739")] + [Fact] + public void ConstFieldPrimitivesActualCircular() + { + string source = @" +using System; +using SystemChar = System.Char; + +class M +{ + public const Int32 Int32 = Int32.Increment(); + public const SystemChar SystemChar = SystemChar.Increment(); +} + +public static class Extensions +{ + public static int Increment(this int i) => i + 1; + public static char Increment(this ref char c) => ++c; +} +"; + + var compilation = CreateCompilation(source); + + compilation.VerifyDiagnostics( + // (7,24): error CS0110: The evaluation of the constant value for 'M.Int32' involves a circular definition + // public const Int32 Int32 = Int32.Increment(); + Diagnostic(ErrorCode.ERR_CircConstValue, "Int32").WithArguments("M.Int32").WithLocation(7, 24), + // (7,32): error CS0133: The expression being assigned to 'M.Int32' must be constant + // public const Int32 Int32 = Int32.Increment(); + Diagnostic(ErrorCode.ERR_NotConstantExpression, "Int32.Increment()").WithArguments("M.Int32").WithLocation(7, 32), + // (8,29): error CS0110: The evaluation of the constant value for 'M.SystemChar' involves a circular definition + // public const SystemChar SystemChar = SystemChar.Increment(); + Diagnostic(ErrorCode.ERR_CircConstValue, "SystemChar").WithArguments("M.SystemChar").WithLocation(8, 29), + // (8,42): error CS1510: A ref or out value must be an assignable variable + // public const SystemChar SystemChar = SystemChar.Increment(); + Diagnostic(ErrorCode.ERR_RefLvalueExpected, "SystemChar").WithLocation(8, 42) + ); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71039")] public void ConstInAttributes_NoCycle_01() {