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;