Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IMethodSymbol.IsReadOnly to public API #34514

Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
196 changes: 196 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,202 @@ 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 {} }
}
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved

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 {} }
}

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 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; }

RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
/// <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