diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index 6f900ad67a150..3a3156c984eda 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -302,20 +302,18 @@ internal virtual bool IsExplicitInterfaceImplementation get { return ExplicitInterfaceImplementations.Any(); } } - // PROTOTYPE: this should become public API /// /// Indicates whether the method is declared readonly, i.e. - /// whether 'this' is readonly in the scope of the method. + /// whether the 'this' receiver parameter is 'ref readonly'. /// See also /// internal abstract bool IsDeclaredReadOnly { get; } - // PROTOTYPE: this should become public API /// /// Indicates whether the method is effectively readonly, /// by either the method or the containing type being marked readonly. /// - internal bool IsEffectivelyReadOnly => (IsDeclaredReadOnly || ContainingType?.IsReadOnly == true) && IsValidReadOnlyTarget; + internal virtual bool IsEffectivelyReadOnly => (IsDeclaredReadOnly || ContainingType?.IsReadOnly == true) && IsValidReadOnlyTarget; protected bool IsValidReadOnlyTarget => !IsStatic && ContainingType.IsStructType() && MethodKind != MethodKind.Constructor; @@ -1063,6 +1061,14 @@ IMethodSymbol IMethodSymbol.ConstructedFrom } } + bool IMethodSymbol.IsReadOnly + { + get + { + return this.IsEffectivelyReadOnly; + } + } + IMethodSymbol IMethodSymbol.OriginalDefinition { get diff --git a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs index b53733ef2482e..a734e643634d8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs @@ -373,9 +373,10 @@ internal override bool IsExplicitInterfaceImplementation get { return false; } } - // extension methods are static and therefore not readonly internal override bool IsDeclaredReadOnly => false; + internal override bool IsEffectivelyReadOnly => _reducedFrom.Parameters[0].RefKind == RefKind.In; + public override ImmutableArray ExplicitInterfaceImplementations { get { return ImmutableArray.Empty; } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ReadOnlyStructsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ReadOnlyStructsTests.cs index 6c1c461694ec6..a76581d75a53e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ReadOnlyStructsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ReadOnlyStructsTests.cs @@ -1000,6 +1000,179 @@ public readonly int M() Assert.True(method.IsEffectivelyReadOnly); } + [Fact] + public void ReadOnlyMembers_SemanticModel() + { + var csharp = @" +using System; + +public struct S1 +{ + public void M1() {} + public readonly void M2() {} + + public int P1 { get; set; } + public readonly int P2 => 42; + public int P3 { readonly get => 123; } + public int P4 { readonly set {} } + public static int P5 { get; set; } + public readonly event Action E { add {} remove {} } +} + +public readonly struct S2 +{ + public void M1() {} + public static void M2() {} + + public int P1 { get; } + public int P2 => 42; + public int P3 { set {} } + public static int P4 { get; set; } + public event Action E { add {} remove {} } +} +"; + Compilation comp = CreateCompilation(csharp); + var s1 = (INamedTypeSymbol)comp.GetSymbolsWithName("S1").Single(); + + Assert.False(getMethod(s1, "M1").IsReadOnly); + + Assert.True(getMethod(s1, "M2").IsReadOnly); + + Assert.True(getProperty(s1, "P1").GetMethod.IsReadOnly); + Assert.False(getProperty(s1, "P1").SetMethod.IsReadOnly); + + Assert.True(getProperty(s1, "P2").GetMethod.IsReadOnly); + + Assert.True(getProperty(s1, "P3").GetMethod.IsReadOnly); + + Assert.True(getProperty(s1, "P4").SetMethod.IsReadOnly); + + Assert.False(getProperty(s1, "P5").GetMethod.IsReadOnly); + Assert.False(getProperty(s1, "P5").SetMethod.IsReadOnly); + + Assert.True(getEvent(s1, "E").AddMethod.IsReadOnly); + Assert.True(getEvent(s1, "E").RemoveMethod.IsReadOnly); + + var s2 = comp.GetMember("S2"); + Assert.True(getMethod(s2, "M1").IsReadOnly); + Assert.False(getMethod(s2, "M2").IsReadOnly); + + Assert.True(getProperty(s2, "P1").GetMethod.IsReadOnly); + + Assert.True(getProperty(s2, "P2").GetMethod.IsReadOnly); + + Assert.True(getProperty(s2, "P3").SetMethod.IsReadOnly); + + Assert.False(getProperty(s2, "P4").GetMethod.IsReadOnly); + Assert.False(getProperty(s2, "P4").SetMethod.IsReadOnly); + + Assert.True(getEvent(s2, "E").AddMethod.IsReadOnly); + Assert.True(getEvent(s2, "E").RemoveMethod.IsReadOnly); + + static IMethodSymbol getMethod(INamedTypeSymbol symbol, string name) => (IMethodSymbol)symbol.GetMembers(name).Single(); + static IPropertySymbol getProperty(INamedTypeSymbol symbol, string name) => (IPropertySymbol)symbol.GetMembers(name).Single(); + static IEventSymbol getEvent(INamedTypeSymbol symbol, string name) => (IEventSymbol)symbol.GetMembers(name).Single(); + } + + [Fact] + public void ReadOnlyMembers_ExtensionMethods_SemanticModel() + { + var csharp = @" +public struct S1 {} +public readonly struct S2 {} + +public static class C +{ + static void M1(this S1 s1) {} + static void M2(this ref S1 s1) {} + static void M3(this in S1 s1) {} + static void M4(this S2 s2) {} + static void M5(this ref S2 s2) {} + static void M6(this in S2 s2) {} + + static void Test() + { + var s1 = new S1(); + s1.M1(); + s1.M2(); + s1.M3(); + + var s2 = new S2(); + s2.M4(); + s2.M5(); + s2.M6(); + } +} +"; + Compilation comp = CreateCompilation(csharp); + + var c = comp.GetMember("C.Test"); + var testMethodSyntax = (MethodDeclarationSyntax)c.DeclaringSyntaxReferences.Single().GetSyntax(); + + var semanticModel = comp.GetSemanticModel(testMethodSyntax.SyntaxTree); + var statements = testMethodSyntax.Body.Statements; + + testStatement(statements[1], false); + testStatement(statements[2], false); + testStatement(statements[3], true); + + testStatement(statements[5], false); + testStatement(statements[6], false); + testStatement(statements[7], true); + + void testStatement(StatementSyntax statementSyntax, bool isEffectivelyReadOnly) + { + var expressionStatement = (ExpressionStatementSyntax)statementSyntax; + var invocationExpression = (InvocationExpressionSyntax)expressionStatement.Expression; + + var symbol = (MethodSymbol)semanticModel.GetSymbolInfo(invocationExpression.Expression).Symbol; + var reducedFrom = symbol.ReducedFrom; + + Assert.Equal(isEffectivelyReadOnly, symbol.IsEffectivelyReadOnly); + Assert.Equal(isEffectivelyReadOnly, ((IMethodSymbol)symbol).IsReadOnly); + + Assert.False(symbol.IsDeclaredReadOnly); + Assert.False(reducedFrom.IsDeclaredReadOnly); + Assert.False(reducedFrom.IsEffectivelyReadOnly); + Assert.False(((IMethodSymbol)reducedFrom).IsReadOnly); + } + } + + [Fact] + public void ReadOnlyMembers_RefReturningProperty() + { + var csharp = @" +public struct S1 +{ + private static int i = 0; + + public ref int P1 => ref i; + public readonly ref int P2 => ref i; + public ref readonly int P3 => ref i; + public readonly ref readonly int P4 => ref i; +} +"; + var comp = CreateCompilation(csharp); + + var s1 = comp.GetMember("S1"); + + check(s1.GetProperty("P1"), true, false, false); + check(s1.GetProperty("P2"), true, false, true); + check(s1.GetProperty("P3"), false, true, false); + check(s1.GetProperty("P4"), false, true, true); + + static void check(PropertySymbol property, bool returnsByRef, bool returnsByRefReadonly, bool isReadOnly) + { + Assert.Equal(returnsByRef, property.ReturnsByRef); + Assert.Equal(returnsByRefReadonly, property.ReturnsByRefReadonly); + + Assert.True(property.IsReadOnly); + Assert.Equal(isReadOnly, property.GetMethod.IsDeclaredReadOnly); + Assert.Equal(isReadOnly, property.GetMethod.IsEffectivelyReadOnly); + Assert.Equal(isReadOnly, ((IMethodSymbol)property.GetMethod).IsReadOnly); + } + } + [Fact] public void ReadOnlyClass() { diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 5ac7d7cdc85a5..4f003152c24f7 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,5 +1,6 @@ *REMOVED*Microsoft.CodeAnalysis.Operations.IEventAssignmentOperation.EventReference.get -> Microsoft.CodeAnalysis.Operations.IEventReferenceOperation *REMOVED*Microsoft.CodeAnalysis.SpecialType.Count = 43 -> Microsoft.CodeAnalysis.SpecialType +Microsoft.CodeAnalysis.IMethodSymbol.IsReadOnly.get -> bool Microsoft.CodeAnalysis.IFieldSymbol.IsFixedSizeBuffer.get -> bool Microsoft.CodeAnalysis.ITypeSymbol.IsRefLikeType.get -> bool Microsoft.CodeAnalysis.ITypeSymbol.IsUnmanagedType.get -> bool diff --git a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs index 85043194a20cb..fb28b65a1b379 100644 --- a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs @@ -117,6 +117,14 @@ public interface IMethodSymbol : ISymbol /// IMethodSymbol ConstructedFrom { get; } + /// + /// Indicates whether the method is readonly, i.e. + /// i.e. whether the 'this' receiver parameter is 'ref readonly'. + /// Returns true for readonly instance methods and accessors + /// and for reduced extension methods with a 'this in' parameter. + /// + bool IsReadOnly { get; } + /// /// Get the original definition of this symbol. If this symbol is derived from another /// symbol by (say) type substitution, this gets the original symbol, as it was defined in diff --git a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb index d27c72c44f527..25b868e5491f7 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb @@ -100,6 +100,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Get End Property + ''' + ''' Always returns false because the 'readonly members' feature is not available in VB. + ''' + Private ReadOnly Property IMethodSymbol_IsReadOnly As Boolean Implements IMethodSymbol.IsReadOnly + Get + Return False + End Get + End Property + ''' ''' Returns true if this method has no return type; i.e., is a Sub instead of a Function. ''' diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs index 13e54207aff59..85fe2f63c5007 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs @@ -25,6 +25,8 @@ public WrappedMethodSymbol(IMethodSymbol methodSymbol, bool canImplementImplicit public IMethodSymbol ConstructedFrom => _symbol.ConstructedFrom; + public bool IsReadOnly => _symbol.IsReadOnly; + public ImmutableArray ExplicitInterfaceImplementations { get diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs index c3936eece6ae6..c440086bb2949 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Editing; @@ -39,6 +38,7 @@ protected CodeGenerationAbstractMethodSymbol( public abstract ImmutableArray TypeParameters { get; } public abstract ImmutableArray Parameters { get; } public abstract IMethodSymbol ConstructedFrom { get; } + public abstract bool IsReadOnly { get; } public abstract IMethodSymbol OverriddenMethod { get; } public abstract IMethodSymbol ReducedFrom { get; } public abstract ITypeSymbol GetTypeInferredDuringReduction(ITypeParameterSymbol reducedFromTypeParameter); diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConstructedMethodSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConstructedMethodSymbol.cs index 28e10e143a3d9..8c3504d2d68a6 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConstructedMethodSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConstructedMethodSymbol.cs @@ -70,6 +70,8 @@ public override ImmutableArray Parameters public override IMethodSymbol ConstructedFrom => _constructedFrom; + public override bool IsReadOnly => _constructedFrom.IsReadOnly; + public override IMethodSymbol OverriddenMethod => // TODO(cyrusn): Construct this. _constructedFrom.OverriddenMethod; diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs index 06fc76867d504..3ac4c6d2ba05c 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Editing; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeGeneration { @@ -85,6 +86,8 @@ public override ImmutableArray TypeArguments public override IMethodSymbol ConstructedFrom => this; + public override bool IsReadOnly => throw ExceptionUtilities.Unreachable; + public override IMethodSymbol OverriddenMethod => null; public override IMethodSymbol ReducedFrom => null;