Skip to content

Commit

Permalink
Add IMethodSymbol.IsReadOnly to public API (#34514)
Browse files Browse the repository at this point in the history
* Add IsDeclaredReadOnly and IsEffectivelyReadOnly to public API

* Fix build. Add simple SemanticModel test.

* Fix Roslyn users of IMethodSymbol. Expand semantic model test.

* Fix visibility of MethodSymbol.vb members

* Implement ReadOnly method API in CodeGenerationConstructedMethodSymbol

* Cleanup usings and change NotImplementedException to ExceptionUtilities.Unreachable

* Simplify down to IMethodSymbol.IsReadOnly

* Fix IsReadOnly API for reduced extension methods. Test ref readonly ref
properties.

* Ensure that non-reduced extension methods are never 'readonly'

* Update doc

* Reduced extension methods are not declared readonly, they're effectively readonly.

* Remove unused copy of extension methods test source
  • Loading branch information
RikkiGibson authored Apr 3, 2019
1 parent d1d5595 commit cfea028
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 6 deletions.
14 changes: 10 additions & 4 deletions src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,20 +302,18 @@ internal virtual bool IsExplicitInterfaceImplementation
get { return ExplicitInterfaceImplementations.Any(); }
}

// PROTOTYPE: this should become public API
/// <summary>
/// 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 <see cref="IsEffectivelyReadOnly"/>
/// </summary>
internal abstract bool IsDeclaredReadOnly { get; }

// PROTOTYPE: this should become public API
/// <summary>
/// Indicates whether the method is effectively readonly,
/// by either the method or the containing type being marked readonly.
/// </summary>
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;

Expand Down Expand Up @@ -1063,6 +1061,14 @@ IMethodSymbol IMethodSymbol.ConstructedFrom
}
}

bool IMethodSymbol.IsReadOnly
{
get
{
return this.IsEffectivelyReadOnly;
}
}

IMethodSymbol IMethodSymbol.OriginalDefinition
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MethodSymbol> ExplicitInterfaceImplementations
{
get { return ImmutableArray<MethodSymbol>.Empty; }
Expand Down
173 changes: 173 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/ReadOnlyStructsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<EventArgs> 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<EventArgs> 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<NamedTypeSymbol>("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<MethodSymbol>("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<NamedTypeSymbol>("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()
{
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ public interface IMethodSymbol : ISymbol
/// </summary>
IMethodSymbol ConstructedFrom { get; }

/// <summary>
/// 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.
/// </summary>
bool IsReadOnly { get; }

/// <summary>
/// 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
Expand Down
9 changes: 9 additions & 0 deletions src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
End Get
End Property

''' <summary>
''' Always returns false because the 'readonly members' feature is not available in VB.
''' </summary>
Private ReadOnly Property IMethodSymbol_IsReadOnly As Boolean Implements IMethodSymbol.IsReadOnly
Get
Return False
End Get
End Property

''' <summary>
''' Returns true if this method has no return type; i.e., is a Sub instead of a Function.
''' </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public WrappedMethodSymbol(IMethodSymbol methodSymbol, bool canImplementImplicit

public IMethodSymbol ConstructedFrom => _symbol.ConstructedFrom;

public bool IsReadOnly => _symbol.IsReadOnly;

public ImmutableArray<IMethodSymbol> ExplicitInterfaceImplementations
{
get
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -39,6 +38,7 @@ protected CodeGenerationAbstractMethodSymbol(
public abstract ImmutableArray<ITypeParameterSymbol> TypeParameters { get; }
public abstract ImmutableArray<IParameterSymbol> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public override ImmutableArray<IParameterSymbol> Parameters

public override IMethodSymbol ConstructedFrom => _constructedFrom;

public override bool IsReadOnly => _constructedFrom.IsReadOnly;

public override IMethodSymbol OverriddenMethod =>
// TODO(cyrusn): Construct this.
_constructedFrom.OverriddenMethod;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Editing;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CodeGeneration
{
Expand Down Expand Up @@ -85,6 +86,8 @@ public override ImmutableArray<ITypeSymbol> TypeArguments

public override IMethodSymbol ConstructedFrom => this;

public override bool IsReadOnly => throw ExceptionUtilities.Unreachable;

public override IMethodSymbol OverriddenMethod => null;

public override IMethodSymbol ReducedFrom => null;
Expand Down

0 comments on commit cfea028

Please sign in to comment.