|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
[CompilerTrait(CompilerFeature.ReadOnlyReferences)]
public class CodeGenReadOnlyStructTests : CompilingTestBase
{
[Fact]
[CompilerTrait(CompilerFeature.PEVerifyCompat)]
public void InvokeOnReadOnlyStaticField()
{
var text = @"
class Program
{
static readonly S1 sf;
static void Main()
{
System.Console.Write(sf.M1());
System.Console.Write(sf.ToString());
}
readonly struct S1
{
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
}
";
var comp = CompileAndVerify(text, parseOptions: TestOptions.Regular, verify: Verification.Fails, expectedOutput: @"12");
comp.VerifyIL("Program.Main", @"
{
// Code size 37 (0x25)
.maxstack 1
IL_0000: ldsflda ""Program.S1 Program.sf""
IL_0005: call ""string Program.S1.M1()""
IL_000a: call ""void System.Console.Write(string)""
IL_000f: ldsflda ""Program.S1 Program.sf""
IL_0014: constrained. ""Program.S1""
IL_001a: callvirt ""string object.ToString()""
IL_001f: call ""void System.Console.Write(string)""
IL_0024: ret
}");
comp = CompileAndVerify(text, parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature(), verify: Verification.Passes, expectedOutput: @"12");
comp.VerifyIL("Program.Main", @"
{
// Code size 43 (0x2b)
.maxstack 1
.locals init (Program.S1 V_0)
IL_0000: ldsfld ""Program.S1 Program.sf""
IL_0005: stloc.0
IL_0006: ldloca.s V_0
IL_0008: call ""string Program.S1.M1()""
IL_000d: call ""void System.Console.Write(string)""
IL_0012: ldsfld ""Program.S1 Program.sf""
IL_0017: stloc.0
IL_0018: ldloca.s V_0
IL_001a: constrained. ""Program.S1""
IL_0020: callvirt ""string object.ToString()""
IL_0025: call ""void System.Console.Write(string)""
IL_002a: ret
}");
}
[Fact]
[CompilerTrait(CompilerFeature.PEVerifyCompat)]
public void InvokeOnReadOnlyStaticFieldMetadata()
{
var text1 = @"
public readonly struct S1
{
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
";
var comp1 = CreateCompilation(text1, assemblyName: "A");
var ref1 = comp1.EmitToImageReference();
var text = @"
class Program
{
static readonly S1 sf;
static void Main()
{
System.Console.Write(sf.M1());
System.Console.Write(sf.ToString());
}
}
";
var comp = CompileAndVerify(text, new[] { ref1 }, parseOptions: TestOptions.Regular, verify: Verification.Fails, expectedOutput: @"12");
comp.VerifyIL("Program.Main", @"
{
// Code size 37 (0x25)
.maxstack 1
IL_0000: ldsflda ""S1 Program.sf""
IL_0005: call ""string S1.M1()""
IL_000a: call ""void System.Console.Write(string)""
IL_000f: ldsflda ""S1 Program.sf""
IL_0014: constrained. ""S1""
IL_001a: callvirt ""string object.ToString()""
IL_001f: call ""void System.Console.Write(string)""
IL_0024: ret
}");
comp = CompileAndVerify(text, new[] { ref1 }, parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature(), verify: Verification.Passes, expectedOutput: @"12");
comp.VerifyIL("Program.Main", @"
{
// Code size 43 (0x2b)
.maxstack 1
.locals init (S1 V_0)
IL_0000: ldsfld ""S1 Program.sf""
IL_0005: stloc.0
IL_0006: ldloca.s V_0
IL_0008: call ""string S1.M1()""
IL_000d: call ""void System.Console.Write(string)""
IL_0012: ldsfld ""S1 Program.sf""
IL_0017: stloc.0
IL_0018: ldloca.s V_0
IL_001a: constrained. ""S1""
IL_0020: callvirt ""string object.ToString()""
IL_0025: call ""void System.Console.Write(string)""
IL_002a: ret
}");
}
[Fact]
[CompilerTrait(CompilerFeature.PEVerifyCompat)]
public void InvokeOnReadOnlyInstanceField()
{
var text = @"
class Program
{
readonly S1 f;
static void Main()
{
var p = new Program();
System.Console.Write(p.f.M1());
System.Console.Write(p.f.ToString());
}
readonly struct S1
{
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
}
";
var comp = CompileAndVerify(text, parseOptions: TestOptions.Regular, verify: Verification.FailsPEVerify, expectedOutput: @"12");
comp.VerifyIL("Program.Main", @"
{
// Code size 43 (0x2b)
.maxstack 2
IL_0000: newobj ""Program..ctor()""
IL_0005: dup
IL_0006: ldflda ""Program.S1 Program.f""
IL_000b: call ""string Program.S1.M1()""
IL_0010: call ""void System.Console.Write(string)""
IL_0015: ldflda ""Program.S1 Program.f""
IL_001a: constrained. ""Program.S1""
IL_0020: callvirt ""string object.ToString()""
IL_0025: call ""void System.Console.Write(string)""
IL_002a: ret
}");
comp = CompileAndVerify(text, parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature(), verify: Verification.Passes, expectedOutput: @"12");
comp.VerifyIL("Program.Main", @"
{
// Code size 49 (0x31)
.maxstack 2
.locals init (Program.S1 V_0)
IL_0000: newobj ""Program..ctor()""
IL_0005: dup
IL_0006: ldfld ""Program.S1 Program.f""
IL_000b: stloc.0
IL_000c: ldloca.s V_0
IL_000e: call ""string Program.S1.M1()""
IL_0013: call ""void System.Console.Write(string)""
IL_0018: ldfld ""Program.S1 Program.f""
IL_001d: stloc.0
IL_001e: ldloca.s V_0
IL_0020: constrained. ""Program.S1""
IL_0026: callvirt ""string object.ToString()""
IL_002b: call ""void System.Console.Write(string)""
IL_0030: ret
}");
}
[Fact]
[CompilerTrait(CompilerFeature.PEVerifyCompat)]
public void InvokeOnReadOnlyInstanceFieldGeneric()
{
var text = @"
class Program
{
readonly S1<string> f;
static void Main()
{
var p = new Program();
System.Console.Write(p.f.M1(""hello""));
System.Console.Write(p.f.ToString());
}
readonly struct S1<T>
{
public T M1(T arg)
{
return arg;
}
public override string ToString()
{
return ""2"";
}
}
}
";
// PEVerify: Cannot change initonly field outside its .ctor.
var comp = CompileAndVerify(text, parseOptions: TestOptions.Regular, verify: Verification.FailsPEVerify, expectedOutput: @"hello2");
comp.VerifyIL("Program.Main", @"
{
// Code size 48 (0x30)
.maxstack 3
IL_0000: newobj ""Program..ctor()""
IL_0005: dup
IL_0006: ldflda ""Program.S1<string> Program.f""
IL_000b: ldstr ""hello""
IL_0010: call ""string Program.S1<string>.M1(string)""
IL_0015: call ""void System.Console.Write(string)""
IL_001a: ldflda ""Program.S1<string> Program.f""
IL_001f: constrained. ""Program.S1<string>""
IL_0025: callvirt ""string object.ToString()""
IL_002a: call ""void System.Console.Write(string)""
IL_002f: ret
}");
comp = CompileAndVerify(text, parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature(), verify: Verification.Passes, expectedOutput: @"hello2");
comp.VerifyIL("Program.Main", @"
{
// Code size 54 (0x36)
.maxstack 3
.locals init (Program.S1<string> V_0)
IL_0000: newobj ""Program..ctor()""
IL_0005: dup
IL_0006: ldfld ""Program.S1<string> Program.f""
IL_000b: stloc.0
IL_000c: ldloca.s V_0
IL_000e: ldstr ""hello""
IL_0013: call ""string Program.S1<string>.M1(string)""
IL_0018: call ""void System.Console.Write(string)""
IL_001d: ldfld ""Program.S1<string> Program.f""
IL_0022: stloc.0
IL_0023: ldloca.s V_0
IL_0025: constrained. ""Program.S1<string>""
IL_002b: callvirt ""string object.ToString()""
IL_0030: call ""void System.Console.Write(string)""
IL_0035: ret
}");
}
[Fact]
[CompilerTrait(CompilerFeature.PEVerifyCompat)]
public void InvokeOnReadOnlyInstanceFieldGenericMetadata()
{
var text1 = @"
readonly public struct S1<T>
{
public T M1(T arg)
{
return arg;
}
public override string ToString()
{
return ""2"";
}
}
";
var comp1 = CreateCompilation(text1, assemblyName: "A");
var ref1 = comp1.EmitToImageReference();
var text = @"
class Program
{
readonly S1<string> f;
static void Main()
{
var p = new Program();
System.Console.Write(p.f.M1(""hello""));
System.Console.Write(p.f.ToString());
}
}
";
// PEVerify: Cannot change initonly field outside its .ctor.
var comp = CompileAndVerify(text, new[] { ref1 }, parseOptions: TestOptions.Regular, verify: Verification.FailsPEVerify, expectedOutput: @"hello2");
comp.VerifyIL("Program.Main", @"
{
// Code size 48 (0x30)
.maxstack 3
IL_0000: newobj ""Program..ctor()""
IL_0005: dup
IL_0006: ldflda ""S1<string> Program.f""
IL_000b: ldstr ""hello""
IL_0010: call ""string S1<string>.M1(string)""
IL_0015: call ""void System.Console.Write(string)""
IL_001a: ldflda ""S1<string> Program.f""
IL_001f: constrained. ""S1<string>""
IL_0025: callvirt ""string object.ToString()""
IL_002a: call ""void System.Console.Write(string)""
IL_002f: ret
}");
comp = CompileAndVerify(text, new[] { ref1 }, parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature(), verify: Verification.Passes, expectedOutput: @"hello2");
comp.VerifyIL("Program.Main", @"
{
// Code size 54 (0x36)
.maxstack 3
.locals init (S1<string> V_0)
IL_0000: newobj ""Program..ctor()""
IL_0005: dup
IL_0006: ldfld ""S1<string> Program.f""
IL_000b: stloc.0
IL_000c: ldloca.s V_0
IL_000e: ldstr ""hello""
IL_0013: call ""string S1<string>.M1(string)""
IL_0018: call ""void System.Console.Write(string)""
IL_001d: ldfld ""S1<string> Program.f""
IL_0022: stloc.0
IL_0023: ldloca.s V_0
IL_0025: constrained. ""S1<string>""
IL_002b: callvirt ""string object.ToString()""
IL_0030: call ""void System.Console.Write(string)""
IL_0035: ret
}");
}
[Fact]
public void InvokeOnReadOnlyThis()
{
var text = @"
class Program
{
static void Main()
{
Test(default(S1));
}
static void Test(in S1 arg)
{
System.Console.Write(arg.M1());
System.Console.Write(arg.ToString());
}
readonly struct S1
{
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
}
";
var comp = CompileAndVerify(text, parseOptions: TestOptions.Regular, verify: Verification.Passes, expectedOutput: @"12");
comp.VerifyIL("Program.Test", @"
{
// Code size 29 (0x1d)
.maxstack 1
IL_0000: ldarg.0
IL_0001: call ""string Program.S1.M1()""
IL_0006: call ""void System.Console.Write(string)""
IL_000b: ldarg.0
IL_000c: constrained. ""Program.S1""
IL_0012: callvirt ""string object.ToString()""
IL_0017: call ""void System.Console.Write(string)""
IL_001c: ret
}");
}
[Fact]
public void InvokeOnThis()
{
var text = @"
class Program
{
static void Main()
{
default(S1).Test();
}
readonly struct S1
{
public void Test()
{
System.Console.Write(this.M1());
System.Console.Write(ToString());
}
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
}
";
var comp = CompileAndVerify(text, parseOptions: TestOptions.Regular, verify: Verification.Passes, expectedOutput: @"12");
comp.VerifyIL("Program.S1.Test()", @"
{
// Code size 29 (0x1d)
.maxstack 1
IL_0000: ldarg.0
IL_0001: call ""string Program.S1.M1()""
IL_0006: call ""void System.Console.Write(string)""
IL_000b: ldarg.0
IL_000c: constrained. ""Program.S1""
IL_0012: callvirt ""string object.ToString()""
IL_0017: call ""void System.Console.Write(string)""
IL_001c: ret
}");
comp.VerifyIL("Program.Main()", @"
{
// Code size 15 (0xf)
.maxstack 2
.locals init (Program.S1 V_0)
IL_0000: ldloca.s V_0
IL_0002: dup
IL_0003: initobj ""Program.S1""
IL_0009: call ""void Program.S1.Test()""
IL_000e: ret
}");
}
[Fact, WorkItem(66365, "https://github.com/dotnet/roslyn/issues/66365")]
public void InvokeOnThisBaseMethods()
{
var text = @"
class Program
{
static void Main()
{
default(S1).Test();
}
readonly struct S1
{
public void Test()
{
System.Console.Write(this.GetType());
System.Console.Write(ToString());
}
}
}
";
var comp = CompileAndVerify(text, parseOptions: TestOptions.Regular, verify: Verification.Passes, expectedOutput: @"Program+S1Program+S1");
// We may be able to optimize this case (ie. avoid defensive copy) since the struct and the caller are in the same module
// Tracked by https://github.com/dotnet/roslyn/issues/66365
comp.VerifyIL("Program.S1.Test()", @"
{
// Code size 47 (0x2f)
.maxstack 1
.locals init (Program.S1 V_0)
IL_0000: ldarg.0
IL_0001: ldobj ""Program.S1""
IL_0006: box ""Program.S1""
IL_000b: call ""System.Type object.GetType()""
IL_0010: call ""void System.Console.Write(object)""
IL_0015: ldarg.0
IL_0016: ldobj ""Program.S1""
IL_001b: stloc.0
IL_001c: ldloca.s V_0
IL_001e: constrained. ""Program.S1""
IL_0024: callvirt ""string object.ToString()""
IL_0029: call ""void System.Console.Write(string)""
IL_002e: ret
}");
}
[Fact]
public void AssignThis()
{
var text = @"
class Program
{
static void Main()
{
S1 v = new S1(42);
System.Console.Write(v.x);
S1 v2 = new S1(v);
System.Console.Write(v2.x);
}
readonly struct S1
{
public readonly int x;
public S1(int i)
{
x = i; // OK
}
public S1(S1 arg)
{
this = arg; // OK
}
}
}
";
var comp = CompileAndVerify(text, parseOptions: TestOptions.Regular, verify: Verification.Passes, expectedOutput: @"4242");
comp.VerifyIL("Program.S1..ctor(int)", @"
{
// Code size 8 (0x8)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: stfld ""int Program.S1.x""
IL_0007: ret
}");
comp.VerifyIL("Program.S1..ctor(Program.S1)", @"
{
// Code size 8 (0x8)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: stobj ""Program.S1""
IL_0007: ret
}");
}
[Fact]
public void AssignThisErr()
{
var text = @"
class Program
{
static void Main()
{
}
static void TakesRef(ref S1 arg){}
static void TakesRef(ref int arg){}
readonly struct S1
{
readonly int x;
public S1(int i)
{
x = i; // OK
}
public S1(S1 arg)
{
this = arg; // OK
}
public void Test1()
{
this = default; // error
}
public void Test2()
{
TakesRef(ref this); // error
}
public void Test3()
{
TakesRef(ref this.x); // error
}
}
}
";
var comp = CreateCompilation(text);
comp.VerifyDiagnostics(
// (27,13): error CS1604: Cannot assign to 'this' because it is read-only
// this = default; // error
Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "this").WithArguments("this").WithLocation(27, 13),
// (32,26): error CS1605: Cannot use 'this' as a ref or out value because it is read-only
// TakesRef(ref this); // error
Diagnostic(ErrorCode.ERR_RefReadonlyLocal, "this").WithArguments("this").WithLocation(32, 26),
// (37,26): error CS0192: A readonly field cannot be used as a ref or out value (except in a constructor)
// TakesRef(ref this.x); // error
Diagnostic(ErrorCode.ERR_RefReadonly, "this.x").WithLocation(37, 26)
);
}
[Fact]
public void AssignThisNestedMethods()
{
var text = @"
using System;
class Program
{
static void Main()
{
}
static void TakesRef(ref S1 arg){}
static void TakesRef(ref int arg){}
readonly struct S1
{
readonly int x;
public S1(int i)
{
void F() { x = i;} // Error
Action a = () => { x = i;}; // Error
F();
}
public S1(S1 arg)
{
void F() { this = arg;} // Error
Action a = () => { this = arg;}; // Error
F();
}
public void Test1()
{
void F() { this = default;} // Error
Action a = () => { this = default;}; // Error
F();
}
public void Test2()
{
void F() { TakesRef(ref this);} // Error
Action a = () => { TakesRef(ref this);}; // Error
F();
}
public void Test3()
{
void F() { TakesRef(ref this.x);} // Error
Action a = () => { TakesRef(ref this.x);}; // Error
F();
}
}
}
";
var comp = CreateCompilation(text);
comp.VerifyDiagnostics(
// (19,24): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead.
// void F() { x = i;} // Error
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "x").WithLocation(19, 24),
// (20,32): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead.
// Action a = () => { x = i;}; // Error
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "x").WithLocation(20, 32),
// (26,24): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead.
// void F() { this = arg;} // Error
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "this").WithLocation(26, 24),
// (27,32): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead.
// Action a = () => { this = arg;}; // Error
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "this").WithLocation(27, 32),
// (33,24): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead.
// void F() { this = default;} // Error
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "this").WithLocation(33, 24),
// (34,32): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead.
// Action a = () => { this = default;}; // Error
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "this").WithLocation(34, 32),
// (40,37): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead.
// void F() { TakesRef(ref this);} // Error
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "this").WithLocation(40, 37),
// (41,45): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead.
// Action a = () => { TakesRef(ref this);}; // Error
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "this").WithLocation(41, 45),
// (47,37): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead.
// void F() { TakesRef(ref this.x);} // Error
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "this").WithLocation(47, 37),
// (48,45): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead.
// Action a = () => { TakesRef(ref this.x);}; // Error
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "this").WithLocation(48, 45)
);
}
[Fact]
public void ReadOnlyStructApi()
{
var text = @"
class Program
{
readonly struct S1
{
public S1(int dummy)
{
}
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
readonly struct S1<T>
{
public S1(int dummy)
{
}
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
struct S2
{
public S2(int dummy)
{
}
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
class C1
{
public C1(int dummy)
{
}
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
delegate int D1();
}
";
var comp = CreateCompilation(text, parseOptions: TestOptions.Regular);
// S1
NamedTypeSymbol namedType = comp.GetTypeByMetadataName("Program+S1");
Assert.True(namedType.IsReadOnly);
Assert.Equal(RefKind.Out, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("ToString").ThisParameter.RefKind);
void validate(ModuleSymbol module)
{
var test = module.ContainingAssembly.GetTypeByMetadataName("Program+S1");
var peModule = (PEModuleSymbol)module;
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PENamedTypeSymbol)test).Handle));
AssertDeclaresType(peModule, WellKnownType.System_Runtime_CompilerServices_IsReadOnlyAttribute, Accessibility.Internal);
}
CompileAndVerify(comp, symbolValidator: validate);
// S1<T>
namedType = comp.GetTypeByMetadataName("Program+S1`1");
Assert.True(namedType.IsReadOnly);
Assert.Equal(RefKind.Out, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("ToString").ThisParameter.RefKind);
// T
TypeSymbol type = namedType.TypeParameters[0];
Assert.True(namedType.IsReadOnly);
Assert.Equal(RefKind.Out, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("ToString").ThisParameter.RefKind);
// S1<object>
namedType = namedType.Construct(comp.ObjectType);
Assert.True(namedType.IsReadOnly);
Assert.Equal(RefKind.Out, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("ToString").ThisParameter.RefKind);
// S2
namedType = comp.GetTypeByMetadataName("Program+S2");
Assert.False(namedType.IsReadOnly);
Assert.Equal(RefKind.Out, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.Ref, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.Ref, namedType.GetMethod("ToString").ThisParameter.RefKind);
// C1
namedType = comp.GetTypeByMetadataName("Program+C1");
Assert.False(namedType.IsReadOnly);
Assert.Equal(RefKind.None, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.None, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.None, namedType.GetMethod("ToString").ThisParameter.RefKind);
// D1
namedType = comp.GetTypeByMetadataName("Program+D1");
Assert.False(namedType.IsReadOnly);
Assert.Equal(RefKind.None, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.None, namedType.GetMethod("Invoke").ThisParameter.RefKind);
// object[]
type = comp.CreateArrayTypeSymbol(comp.ObjectType);
Assert.False(type.IsReadOnly);
// dynamic
type = comp.DynamicType;
Assert.False(type.IsReadOnly);
// object
type = comp.ObjectType;
Assert.False(type.IsReadOnly);
// anonymous type
INamedTypeSymbol iNamedType = comp.CreateAnonymousTypeSymbol(ImmutableArray.Create<ITypeSymbol>(comp.ObjectType.GetPublicSymbol()), ImmutableArray.Create("qq"));
Assert.False(iNamedType.IsReadOnly);
// pointer type
type = (TypeSymbol)comp.CreatePointerTypeSymbol(comp.ObjectType);
Assert.False(type.IsReadOnly);
// tuple type
iNamedType = comp.CreateTupleTypeSymbol(ImmutableArray.Create<ITypeSymbol>(comp.ObjectType.GetPublicSymbol(), comp.ObjectType.GetPublicSymbol()));
Assert.False(iNamedType.IsReadOnly);
// S1 from image
var clientComp = CreateCompilation("", references: new[] { comp.EmitToImageReference() });
NamedTypeSymbol s1 = clientComp.GetTypeByMetadataName("Program+S1");
Assert.True(s1.IsReadOnly);
Assert.Empty(s1.GetAttributes());
Assert.Equal(RefKind.Out, s1.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.In, s1.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.In, s1.GetMethod("ToString").ThisParameter.RefKind);
}
[Fact]
public void ReadOnlyStructApiMetadata()
{
var text1 = @"
class Program
{
readonly struct S1
{
public S1(int dummy)
{
}
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
readonly struct S1<T>
{
public S1(int dummy)
{
}
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
struct S2
{
public S2(int dummy)
{
}
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
class C1
{
public C1(int dummy)
{
}
public string M1()
{
return ""1"";
}
public override string ToString()
{
return ""2"";
}
}
delegate int D1();
}
";
var comp1 = CreateCompilation(text1, assemblyName: "A");
var ref1 = comp1.EmitToImageReference();
var comp = CreateCompilation("//NO CODE HERE", new[] { ref1 }, parseOptions: TestOptions.Regular);
// S1
NamedTypeSymbol namedType = comp.GetTypeByMetadataName("Program+S1");
Assert.True(namedType.IsReadOnly);
Assert.Equal(RefKind.Out, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("ToString").ThisParameter.RefKind);
// S1<T>
namedType = comp.GetTypeByMetadataName("Program+S1`1");
Assert.True(namedType.IsReadOnly);
Assert.Equal(RefKind.Out, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("ToString").ThisParameter.RefKind);
// T
TypeSymbol type = namedType.TypeParameters[0];
Assert.True(namedType.IsReadOnly);
Assert.Equal(RefKind.Out, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("ToString").ThisParameter.RefKind);
// S1<object>
namedType = namedType.Construct(comp.ObjectType);
Assert.True(namedType.IsReadOnly);
Assert.Equal(RefKind.Out, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.In, namedType.GetMethod("ToString").ThisParameter.RefKind);
// S2
namedType = comp.GetTypeByMetadataName("Program+S2");
Assert.False(namedType.IsReadOnly);
Assert.Equal(RefKind.Out, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.Ref, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.Ref, namedType.GetMethod("ToString").ThisParameter.RefKind);
// C1
namedType = comp.GetTypeByMetadataName("Program+C1");
Assert.False(namedType.IsReadOnly);
Assert.Equal(RefKind.None, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.None, namedType.GetMethod("M1").ThisParameter.RefKind);
Assert.Equal(RefKind.None, namedType.GetMethod("ToString").ThisParameter.RefKind);
// D1
namedType = comp.GetTypeByMetadataName("Program+D1");
Assert.False(namedType.IsReadOnly);
Assert.Equal(RefKind.None, namedType.Constructors[0].ThisParameter.RefKind);
Assert.Equal(RefKind.None, namedType.GetMethod("Invoke").ThisParameter.RefKind);
// object[]
type = comp.CreateArrayTypeSymbol(comp.ObjectType);
Assert.False(type.IsReadOnly);
// dynamic
type = comp.DynamicType;
Assert.False(type.IsReadOnly);
// object
type = comp.ObjectType;
Assert.False(type.IsReadOnly);
// anonymous type
INamedTypeSymbol iNamedType = comp.CreateAnonymousTypeSymbol(ImmutableArray.Create<ITypeSymbol>(comp.ObjectType.GetPublicSymbol()), ImmutableArray.Create("qq"));
Assert.False(iNamedType.IsReadOnly);
// pointer type
type = (TypeSymbol)comp.CreatePointerTypeSymbol(comp.ObjectType);
Assert.False(type.IsReadOnly);
// tuple type
iNamedType = comp.CreateTupleTypeSymbol(ImmutableArray.Create<ITypeSymbol>(comp.ObjectType.GetPublicSymbol(), comp.ObjectType.GetPublicSymbol()));
Assert.False(iNamedType.IsReadOnly);
}
[Fact]
public void CorrectOverloadOfStackAllocSpanChosen()
{
var comp = CreateCompilationWithMscorlibAndSpan(@"
using System;
class Test
{
unsafe public static void Main()
{
bool condition = false;
var span1 = condition ? stackalloc int[1] : new Span<int>(null, 2);
Console.Write(span1.Length);
var span2 = condition ? stackalloc int[1] : stackalloc int[4];
Console.Write(span2.Length);
}
}", TestOptions.UnsafeReleaseExe);
CompileAndVerify(comp, expectedOutput: "24", verify: Verification.Fails);
}
[Fact]
public void StackAllocExpressionIL()
{
var comp = CreateCompilationWithMscorlibAndSpan(@"
using System;
class Test
{
public static void Main()
{
Span<int> x = stackalloc int[10];
Console.WriteLine(x.Length);
}
}", TestOptions.ReleaseExe);
CompileAndVerify(comp, expectedOutput: "10", verify: Verification.Fails).VerifyIL("Test.Main", @"
{
// Code size 26 (0x1a)
.maxstack 2
.locals init (System.Span<int> V_0) //x
IL_0000: ldc.i4.s 40
IL_0002: conv.u
IL_0003: localloc
IL_0005: ldc.i4.s 10
IL_0007: newobj ""System.Span<int>..ctor(void*, int)""
IL_000c: stloc.0
IL_000d: ldloca.s V_0
IL_000f: call ""int System.Span<int>.Length.get""
IL_0014: call ""void System.Console.WriteLine(int)""
IL_0019: ret
}");
}
[Fact]
public void StackAllocSpanLengthNotEvaluatedTwice()
{
var comp = CreateCompilationWithMscorlibAndSpan(@"
using System;
class Test
{
private static int length = 0;
private static int GetLength()
{
return ++length;
}
public static void Main()
{
for (int i = 0; i < 5; i++)
{
Span<int> x = stackalloc int[GetLength()];
Console.Write(x.Length);
}
}
}", TestOptions.ReleaseExe);
CompileAndVerify(comp, expectedOutput: "12345", verify: Verification.Fails).VerifyIL("Test.Main", @"
{
// Code size 44 (0x2c)
.maxstack 2
.locals init (int V_0, //i
System.Span<int> V_1, //x
int V_2)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: br.s IL_0027
IL_0004: call ""int Test.GetLength()""
IL_0009: stloc.2
IL_000a: ldloc.2
IL_000b: conv.u
IL_000c: ldc.i4.4
IL_000d: mul.ovf.un
IL_000e: localloc
IL_0010: ldloc.2
IL_0011: newobj ""System.Span<int>..ctor(void*, int)""
IL_0016: stloc.1
IL_0017: ldloca.s V_1
IL_0019: call ""int System.Span<int>.Length.get""
IL_001e: call ""void System.Console.Write(int)""
IL_0023: ldloc.0
IL_0024: ldc.i4.1
IL_0025: add
IL_0026: stloc.0
IL_0027: ldloc.0
IL_0028: ldc.i4.5
IL_0029: blt.s IL_0004
IL_002b: ret
}");
}
[Fact]
public void StackAllocSpanLengthConstantFolding()
{
var comp = CreateCompilationWithMscorlibAndSpan(@"
using System;
class Test
{
public static void Main()
{
const int a = 5, b = 6;
Span<int> x = stackalloc int[a * b];
Console.Write(x.Length);
}
}", TestOptions.ReleaseExe);
CompileAndVerify(comp, expectedOutput: "30", verify: Verification.Fails).VerifyIL("Test.Main", @"
{
// Code size 26 (0x1a)
.maxstack 2
.locals init (System.Span<int> V_0) //x
IL_0000: ldc.i4.s 120
IL_0002: conv.u
IL_0003: localloc
IL_0005: ldc.i4.s 30
IL_0007: newobj ""System.Span<int>..ctor(void*, int)""
IL_000c: stloc.0
IL_000d: ldloca.s V_0
IL_000f: call ""int System.Span<int>.Length.get""
IL_0014: call ""void System.Console.Write(int)""
IL_0019: ret
}");
}
[Fact]
public void StackAllocSpanLengthOverflow()
{
var comp = CreateCompilationWithMscorlibAndSpan(@"
using System;
class Test
{
static void M()
{
Span<int> x = stackalloc int[int.MaxValue];
}
public static void Main()
{
try
{
M();
}
catch (OverflowException)
{
Console.WriteLine(""overflow"");
}
}
}", TestOptions.ReleaseExe);
var expectedIL = @"
{
// Code size 22 (0x16)
.maxstack 2
IL_0000: ldc.i4 0x7fffffff
IL_0005: conv.u
IL_0006: ldc.i4.4
IL_0007: mul.ovf.un
IL_0008: localloc
IL_000a: ldc.i4 0x7fffffff
IL_000f: newobj ""System.Span<int>..ctor(void*, int)""
IL_0014: pop
IL_0015: ret
}";
var isx86 = (IntPtr.Size == 4);
if (isx86)
{
CompileAndVerify(comp, expectedOutput: "overflow", verify: Verification.Fails).VerifyIL("Test.M", expectedIL);
}
else
{
// On 64bit the native int does not overflow, so we get StackOverflow instead
// therefore we will just check the IL
CompileAndVerify(comp, verify: Verification.Fails).VerifyIL("Test.M", expectedIL);
}
}
[Fact]
public void ImplicitCastOperatorOnStackAllocIsLoweredCorrectly()
{
var comp = CreateCompilationWithMscorlibAndSpan(@"
using System;
unsafe class Test
{
public static void Main()
{
Test obj1 = stackalloc int[10];
Console.Write(""|"");
Test obj2 = stackalloc double[10];
}
public static implicit operator Test(Span<int> value)
{
Console.Write(""SpanOpCalled"");
return default(Test);
}
public static implicit operator Test(double* value)
{
Console.Write(""PointerOpCalled"");
return default(Test);
}
}", TestOptions.UnsafeReleaseExe);
CompileAndVerify(comp, expectedOutput: "SpanOpCalled|PointerOpCalled", verify: Verification.Fails);
}
[Fact]
public void ExplicitCastOperatorOnStackAllocIsLoweredCorrectly()
{
var comp = CreateCompilationWithMscorlibAndSpan(@"
using System;
unsafe class Test
{
public static void Main()
{
Test obj1 = (Test)stackalloc int[10];
}
public static explicit operator Test(Span<int> value)
{
Console.Write(""SpanOpCalled"");
return default(Test);
}
}", TestOptions.UnsafeReleaseExe);
CompileAndVerify(comp, expectedOutput: "SpanOpCalled", verify: Verification.Fails);
}
[Fact]
public void ReadOnlyMembers_Metadata()
{
var csharp = @"
public struct S
{
public void M1() {}
public readonly void M2() {}
public int P1 { get; set; }
public readonly int P2 => 42;
public int P3 { readonly get => 123; set {} }
public int P4 { get => 123; readonly set {} }
public static int P5 { get; set; }
}
";
CompileAndVerify(csharp, symbolValidator: validate);
void validate(ModuleSymbol module)
{
var type = module.ContainingAssembly.GetTypeByMetadataName("S");
var m1 = type.GetMethod("M1");
var m2 = type.GetMethod("M2");
var p1 = type.GetProperty("P1");
var p2 = type.GetProperty("P2");
var p3 = type.GetProperty("P3");
var p4 = type.GetProperty("P4");
var p5 = type.GetProperty("P5");
var peModule = (PEModuleSymbol)module;
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m1).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m1).Signature.ReturnParam.Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m2).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m2).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p1).Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p1.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p1.GetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p1.SetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p1.SetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p2).Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p2.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p2.GetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p3).Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p3.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p3.GetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p3.SetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p3.SetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p4).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p4.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p4.GetMethod).Signature.ReturnParam.Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p4.SetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p4.SetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p5).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p5.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p5.GetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p5.SetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p5.SetMethod).Signature.ReturnParam.Handle));
AssertDeclaresType(peModule, WellKnownType.System_Runtime_CompilerServices_IsReadOnlyAttribute, Accessibility.Internal);
}
}
[Fact]
public void ReadOnlyMembers_RefReturn_Metadata()
{
var csharp = @"
public struct S
{
static int i;
public ref int M1() => ref i;
public readonly ref int M2() => ref i;
public ref readonly int M3() => ref i;
public readonly ref readonly int M4() => ref i;
public ref int P1 { get => ref i; }
public readonly ref int P2 { get => ref i; }
public ref readonly int P3 { get => ref i; }
public readonly ref readonly int P4 { get => ref i; }
}
";
CompileAndVerify(csharp, symbolValidator: validate);
void validate(ModuleSymbol module)
{
var type = module.ContainingAssembly.GetTypeByMetadataName("S");
var m1 = type.GetMethod("M1");
var m2 = type.GetMethod("M2");
var m3 = type.GetMethod("M3");
var m4 = type.GetMethod("M4");
var p1 = type.GetProperty("P1");
var p2 = type.GetProperty("P2");
var p3 = type.GetProperty("P3");
var p4 = type.GetProperty("P4");
var peModule = (PEModuleSymbol)module;
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m1).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m1).Signature.ReturnParam.Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m2).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m2).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m3).Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m3).Signature.ReturnParam.Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m4).Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m4).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p1).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p1.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p1.GetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p2).Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p2.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p2.GetMethod).Signature.ReturnParam.Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p3).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p3.GetMethod).Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p3.GetMethod).Signature.ReturnParam.Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p4).Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p4.GetMethod).Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p4.GetMethod).Signature.ReturnParam.Handle));
AssertDeclaresType(peModule, WellKnownType.System_Runtime_CompilerServices_IsReadOnlyAttribute, Accessibility.Internal);
}
}
[Fact]
public void ReadOnlyStruct_ReadOnlyMembers_Metadata()
{
var csharp = @"
// note that both the type and member declarations are marked 'readonly'
public readonly struct S
{
public void M1() {}
public readonly void M2() {}
public int P1 { get; }
public readonly int P2 => 42;
public int P3 { readonly get => 123; set {} }
public int P4 { get => 123; readonly set {} }
public static int P5 { get; set; }
}
";
CompileAndVerify(csharp, symbolValidator: validate);
void validate(ModuleSymbol module)
{
var type = module.ContainingAssembly.GetTypeByMetadataName("S");
var m1 = type.GetMethod("M1");
var m2 = type.GetMethod("M2");
var p1 = type.GetProperty("P1");
var p2 = type.GetProperty("P2");
var p3 = type.GetProperty("P3");
var p4 = type.GetProperty("P4");
var p5 = type.GetProperty("P5");
var peModule = (PEModuleSymbol)module;
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m1).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m1).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m2).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)m2).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p1).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p1.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p1.GetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p2).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p2.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p2.GetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p3).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p3.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p3.GetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p3.SetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p3.SetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p4).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p4.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p4.GetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p4.SetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p4.SetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEPropertySymbol)p5).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p5.GetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p5.GetMethod).Signature.ReturnParam.Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p5.SetMethod).Handle));
Assert.False(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)p5.SetMethod).Signature.ReturnParam.Handle));
AssertDeclaresType(peModule, WellKnownType.System_Runtime_CompilerServices_IsReadOnlyAttribute, Accessibility.Internal);
}
}
[Fact]
public void ReadOnlyMembers_MetadataRoundTrip()
{
var external = @"
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; set {} }
public int P4 { get => 123; readonly set {} }
public static int P5 { get; set; }
public readonly event Action<EventArgs> E { add {} remove {} }
}
public readonly struct S2
{
public void M1() {}
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 {} }
}
";
var externalComp = CreateCompilation(external);
externalComp.VerifyDiagnostics();
verify(externalComp);
var comp = CreateCompilation("", references: new[] { externalComp.EmitToImageReference() });
verify(comp);
var comp2 = CreateCompilation("", references: new[] { externalComp.ToMetadataReference() });
verify(comp2);
void verify(CSharpCompilation comp)
{
var s1 = comp.GetMember<NamedTypeSymbol>("S1");
verifyReadOnly(s1.GetMethod("M1"), false, RefKind.Ref);
verifyReadOnly(s1.GetMethod("M2"), true, RefKind.RefReadOnly);
verifyReadOnly(s1.GetProperty("P1").GetMethod, true, RefKind.RefReadOnly);
verifyReadOnly(s1.GetProperty("P1").SetMethod, false, RefKind.Ref);
verifyReadOnly(s1.GetProperty("P2").GetMethod, true, RefKind.RefReadOnly);
verifyReadOnly(s1.GetProperty("P3").GetMethod, true, RefKind.RefReadOnly);
verifyReadOnly(s1.GetProperty("P3").SetMethod, false, RefKind.Ref);
verifyReadOnly(s1.GetProperty("P4").GetMethod, false, RefKind.Ref);
verifyReadOnly(s1.GetProperty("P4").SetMethod, true, RefKind.RefReadOnly);
verifyReadOnly(s1.GetProperty("P5").GetMethod, false, null);
verifyReadOnly(s1.GetProperty("P5").SetMethod, false, null);
verifyReadOnly(s1.GetEvent("E").AddMethod, true, RefKind.RefReadOnly);
verifyReadOnly(s1.GetEvent("E").RemoveMethod, true, RefKind.RefReadOnly);
var s2 = comp.GetMember<NamedTypeSymbol>("S2");
verifyReadOnly(s2.GetMethod("M1"), true, RefKind.RefReadOnly);
verifyReadOnly(s2.GetProperty("P1").GetMethod, true, RefKind.RefReadOnly);
verifyReadOnly(s2.GetProperty("P2").GetMethod, true, RefKind.RefReadOnly);
verifyReadOnly(s2.GetProperty("P3").SetMethod, true, RefKind.RefReadOnly);
verifyReadOnly(s2.GetProperty("P4").GetMethod, false, null);
verifyReadOnly(s2.GetProperty("P4").SetMethod, false, null);
verifyReadOnly(s2.GetEvent("E").AddMethod, true, RefKind.RefReadOnly);
verifyReadOnly(s2.GetEvent("E").RemoveMethod, true, RefKind.RefReadOnly);
void verifyReadOnly(MethodSymbol method, bool isReadOnly, RefKind? refKind)
{
Assert.Equal(isReadOnly, method.IsEffectivelyReadOnly);
Assert.Equal(refKind, method.ThisParameter?.RefKind);
}
}
}
[Fact]
public void StaticReadOnlyMethod_FromMetadata()
{
var il = @"
.class private auto ansi '<Module>'
{
} // end of class <Module>
.class public auto ansi beforefieldinit System.Runtime.CompilerServices.IsReadOnlyAttribute
extends [mscorlib]System.Attribute
{
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2050
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Attribute::.ctor()
IL_0006: ret
} // end of method IsReadOnlyAttribute::.ctor
} // end of class System.Runtime.CompilerServices.IsReadOnlyAttribute
.class public sequential ansi sealed beforefieldinit S
extends [mscorlib]System.ValueType
{
.pack 0
.size 1
// Methods
.method public hidebysig
instance void M1 () cil managed
{
.custom instance void System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x2058
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // end of method S::M1
// Methods
.method public hidebysig static
void M2 () cil managed
{
.custom instance void System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x2058
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // end of method S::M2
} // end of class S
";
var ilRef = CompileIL(il);
var comp = CreateCompilation("", references: new[] { ilRef });
var s = comp.GetMember<NamedTypeSymbol>("S");
var m1 = s.GetMethod("M1");
Assert.True(m1.IsDeclaredReadOnly);
Assert.True(m1.IsEffectivelyReadOnly);
// even though the IsReadOnlyAttribute is in metadata,
// we ruled out the possibility of the method being readonly because it's static
var m2 = s.GetMethod("M2");
Assert.False(m2.IsDeclaredReadOnly);
Assert.False(m2.IsEffectivelyReadOnly);
}
[Fact]
public void ReadOnlyMethod_CallNormalMethod()
{
var csharp = @"
public struct S
{
public int i;
public readonly void M1()
{
// should create local copy
M2();
System.Console.Write(i);
// explicit local copy, no warning
var copy = this;
copy.M2();
System.Console.Write(copy.i);
}
void M2()
{
i = 23;
}
static void Main()
{
var s = new S { i = 1 };
s.M1();
}
}
";
var verifier = CompileAndVerify(csharp, expectedOutput: "123");
verifier.VerifyDiagnostics(
// (9,9): warning CS8655: Call to non-readonly member 'S.M2()' from a 'readonly' member results in an implicit copy of 'this'.
// M2();
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "M2").WithArguments("S.M2()", "this").WithLocation(9, 9));
verifier.VerifyIL("S.M1", @"
{
// Code size 51 (0x33)
.maxstack 1
.locals init (S V_0, //copy
S V_1)
IL_0000: ldarg.0
IL_0001: ldobj ""S""
IL_0006: stloc.1
IL_0007: ldloca.s V_1
IL_0009: call ""void S.M2()""
IL_000e: ldarg.0
IL_000f: ldfld ""int S.i""
IL_0014: call ""void System.Console.Write(int)""
IL_0019: ldarg.0
IL_001a: ldobj ""S""
IL_001f: stloc.0
IL_0020: ldloca.s V_0
IL_0022: call ""void S.M2()""
IL_0027: ldloc.0
IL_0028: ldfld ""int S.i""
IL_002d: call ""void System.Console.Write(int)""
IL_0032: ret
}");
}
[Fact]
public void InMethod_CallNormalMethod()
{
var csharp = @"
public struct S
{
public int i;
public static void M1(in S s)
{
// should create local copy
s.M2();
System.Console.Write(s.i);
// explicit local copy, no warning
var copy = s;
copy.M2();
System.Console.Write(copy.i);
}
void M2()
{
i = 23;
}
static void Main()
{
var s = new S { i = 1 };
M1(in s);
}
}
";
var verifier = CompileAndVerify(csharp, expectedOutput: "123");
// should warn about calling s.M2 in warning wave (see https://github.com/dotnet/roslyn/issues/33968)
verifier.VerifyDiagnostics();
verifier.VerifyIL("S.M1", @"
{
// Code size 51 (0x33)
.maxstack 1
.locals init (S V_0, //copy
S V_1)
IL_0000: ldarg.0
IL_0001: ldobj ""S""
IL_0006: stloc.1
IL_0007: ldloca.s V_1
IL_0009: call ""void S.M2()""
IL_000e: ldarg.0
IL_000f: ldfld ""int S.i""
IL_0014: call ""void System.Console.Write(int)""
IL_0019: ldarg.0
IL_001a: ldobj ""S""
IL_001f: stloc.0
IL_0020: ldloca.s V_0
IL_0022: call ""void S.M2()""
IL_0027: ldloc.0
IL_0028: ldfld ""int S.i""
IL_002d: call ""void System.Console.Write(int)""
IL_0032: ret
}");
}
[Fact]
public void InMethod_CallMethodFromMetadata()
{
var external = @"
public struct S
{
public int i;
public readonly void M1() {}
public void M2()
{
i = 23;
}
}
";
var image = CreateCompilation(external).EmitToImageReference();
var csharp = @"
public static class C
{
public static void M1(in S s)
{
// should not copy, no warning
s.M1();
System.Console.Write(s.i);
// should create local copy, warn in warning wave
s.M2();
System.Console.Write(s.i);
// explicit local copy, no warning
var copy = s;
copy.M2();
System.Console.Write(copy.i);
}
static void Main()
{
var s = new S { i = 1 };
M1(in s);
}
}
";
var verifier = CompileAndVerify(csharp, references: new[] { image }, expectedOutput: "1123");
// should warn about calling s.M2 in warning wave (see https://github.com/dotnet/roslyn/issues/33968)
verifier.VerifyDiagnostics();
verifier.VerifyIL("C.M1", @"
{
// Code size 68 (0x44)
.maxstack 1
.locals init (S V_0, //copy
S V_1)
IL_0000: ldarg.0
IL_0001: call ""readonly void S.M1()""
IL_0006: ldarg.0
IL_0007: ldfld ""int S.i""
IL_000c: call ""void System.Console.Write(int)""
IL_0011: ldarg.0
IL_0012: ldobj ""S""
IL_0017: stloc.1
IL_0018: ldloca.s V_1
IL_001a: call ""void S.M2()""
IL_001f: ldarg.0
IL_0020: ldfld ""int S.i""
IL_0025: call ""void System.Console.Write(int)""
IL_002a: ldarg.0
IL_002b: ldobj ""S""
IL_0030: stloc.0
IL_0031: ldloca.s V_0
IL_0033: call ""void S.M2()""
IL_0038: ldloc.0
IL_0039: ldfld ""int S.i""
IL_003e: call ""void System.Console.Write(int)""
IL_0043: ret
}");
}
[Fact]
public void InMethod_CallGetAccessorFromMetadata()
{
var external = @"
public struct S
{
public int i;
public readonly int P1 => 42;
public int P2 => i = 23;
}
";
var image = CreateCompilation(external).EmitToImageReference();
var csharp = @"
public static class C
{
public static void M1(in S s)
{
// should not copy, no warning
_ = s.P1;
System.Console.Write(s.i);
// should create local copy, warn in warning wave
_ = s.P2;
System.Console.Write(s.i);
// explicit local copy, no warning
var copy = s;
_ = copy.P2;
System.Console.Write(copy.i);
}
static void Main()
{
var s = new S { i = 1 };
M1(in s);
}
}
";
var verifier = CompileAndVerify(csharp, references: new[] { image }, expectedOutput: "1123");
// should warn about calling s.M2 in warning wave (see https://github.com/dotnet/roslyn/issues/33968)
verifier.VerifyDiagnostics();
verifier.VerifyIL("C.M1", @"
{
// Code size 71 (0x47)
.maxstack 1
.locals init (S V_0, //copy
S V_1)
IL_0000: ldarg.0
IL_0001: call ""readonly int S.P1.get""
IL_0006: pop
IL_0007: ldarg.0
IL_0008: ldfld ""int S.i""
IL_000d: call ""void System.Console.Write(int)""
IL_0012: ldarg.0
IL_0013: ldobj ""S""
IL_0018: stloc.1
IL_0019: ldloca.s V_1
IL_001b: call ""int S.P2.get""
IL_0020: pop
IL_0021: ldarg.0
IL_0022: ldfld ""int S.i""
IL_0027: call ""void System.Console.Write(int)""
IL_002c: ldarg.0
IL_002d: ldobj ""S""
IL_0032: stloc.0
IL_0033: ldloca.s V_0
IL_0035: call ""int S.P2.get""
IL_003a: pop
IL_003b: ldloc.0
IL_003c: ldfld ""int S.i""
IL_0041: call ""void System.Console.Write(int)""
IL_0046: ret
}");
}
[Fact]
public void ReadOnlyMethod_CallReadOnlyMethod()
{
var csharp = @"
public struct S
{
public int i;
public readonly int M1() => M2() + 1;
public readonly int M2() => i;
}
";
var comp = CompileAndVerify(csharp);
comp.VerifyIL("S.M1", @"
{
// Code size 9 (0x9)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""readonly int S.M2()""
IL_0006: ldc.i4.1
IL_0007: add
IL_0008: ret
}
");
comp.VerifyDiagnostics();
}
[Fact]
public void ReadOnlyGetAccessor_CallReadOnlyGetAccessor()
{
var csharp = @"
public struct S
{
public int i;
public readonly int P1 { get => P2 + 1; }
public readonly int P2 { get => i; }
}
";
var comp = CompileAndVerify(csharp);
comp.VerifyIL("S.P1.get", @"
{
// Code size 9 (0x9)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""readonly int S.P2.get""
IL_0006: ldc.i4.1
IL_0007: add
IL_0008: ret
}
");
comp.VerifyDiagnostics();
}
[Fact]
public void ReadOnlyGetAccessor_CallAutoGetAccessor()
{
var csharp = @"
public struct S
{
public readonly int P1 { get => P2 + 1; }
public int P2 { get; }
}
";
var comp = CompileAndVerify(csharp);
comp.VerifyIL("S.P1.get", @"
{
// Code size 9 (0x9)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""readonly int S.P2.get""
IL_0006: ldc.i4.1
IL_0007: add
IL_0008: ret
}
");
comp.VerifyDiagnostics();
}
[Fact]
public void InParam_CallReadOnlyMethod()
{
var csharp = @"
public struct S
{
public int i;
public static int M1(in S s) => s.M2() + 1;
public readonly int M2() => i;
}
";
var comp = CompileAndVerify(csharp);
comp.VerifyIL("S.M1", @"
{
// Code size 9 (0x9)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""readonly int S.M2()""
IL_0006: ldc.i4.1
IL_0007: add
IL_0008: ret
}
");
comp.VerifyDiagnostics();
}
[Fact]
public void ReadOnlyMethod_CallReadOnlyMethodOnField()
{
var csharp = @"
public struct S1
{
public readonly void M1() {}
}
public struct S2
{
S1 s1;
public readonly void M2()
{
s1.M1();
}
}
";
var comp = CompileAndVerify(csharp);
comp.VerifyIL("S2.M2", @"
{
// Code size 12 (0xc)
.maxstack 1
IL_0000: ldarg.0
IL_0001: ldflda ""S1 S2.s1""
IL_0006: call ""readonly void S1.M1()""
IL_000b: ret
}");
comp.VerifyDiagnostics(
// (9,8): warning CS0649: Field 'S2.s1' is never assigned to, and will always have its default value
// S1 s1;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "s1").WithArguments("S2.s1", "").WithLocation(9, 8)
);
}
[Fact]
public void ReadOnlyMethod_CallNormalMethodOnField()
{
var csharp = @"
public struct S1
{
public void M1() {}
}
public struct S2
{
S1 s1;
public readonly void M2()
{
s1.M1();
}
}
";
var comp = CompileAndVerify(csharp);
comp.VerifyIL("S2.M2", @"
{
// Code size 15 (0xf)
.maxstack 1
.locals init (S1 V_0)
IL_0000: ldarg.0
IL_0001: ldfld ""S1 S2.s1""
IL_0006: stloc.0
IL_0007: ldloca.s V_0
IL_0009: call ""void S1.M1()""
IL_000e: ret
}");
// should warn about calling s2.M2 in warning wave (see https://github.com/dotnet/roslyn/issues/33968)
comp.VerifyDiagnostics();
}
[Fact]
public void ReadOnlyMethod_CallBaseMethod()
{
var csharp = @"
public struct S
{
readonly void M()
{
// no warnings for calls to base members
GetType();
ToString();
GetHashCode();
Equals(null);
}
}
";
var comp = CompileAndVerify(csharp);
comp.VerifyDiagnostics();
// GetType should box 'this'.
comp.VerifyIL("S.M", @"
{
// Code size 82 (0x52)
.maxstack 2
.locals init (S V_0)
IL_0000: ldarg.0
IL_0001: ldobj ""S""
IL_0006: box ""S""
IL_000b: call ""System.Type object.GetType()""
IL_0010: pop
IL_0011: ldarg.0
IL_0012: ldobj ""S""
IL_0017: stloc.0
IL_0018: ldloca.s V_0
IL_001a: constrained. ""S""
IL_0020: callvirt ""string object.ToString()""
IL_0025: pop
IL_0026: ldarg.0
IL_0027: ldobj ""S""
IL_002c: stloc.0
IL_002d: ldloca.s V_0
IL_002f: constrained. ""S""
IL_0035: callvirt ""int object.GetHashCode()""
IL_003a: pop
IL_003b: ldarg.0
IL_003c: ldobj ""S""
IL_0041: stloc.0
IL_0042: ldloca.s V_0
IL_0044: ldnull
IL_0045: constrained. ""S""
IL_004b: callvirt ""bool object.Equals(object)""
IL_0050: pop
IL_0051: ret
}");
}
[Fact]
public void ReadOnlyMethod_OverrideBaseMethod()
{
var csharp = @"
public struct S
{
// note: GetType can't be overridden
public override string ToString() => throw null;
public override int GetHashCode() => throw null;
public override bool Equals(object o) => throw null;
readonly void M()
{
// should warn--non-readonly invocation
ToString();
GetHashCode();
Equals(null);
// ok
base.ToString();
base.GetHashCode();
base.Equals(null);
}
}
";
var verifier = CompileAndVerify(csharp);
verifier.VerifyDiagnostics(
// (12,9): warning CS8655: Call to non-readonly member 'S.ToString()' from a 'readonly' member results in an implicit copy of 'this'.
// ToString();
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "ToString").WithArguments("S.ToString()", "this").WithLocation(12, 9),
// (13,9): warning CS8655: Call to non-readonly member 'S.GetHashCode()' from a 'readonly' member results in an implicit copy of 'this'.
// GetHashCode();
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "GetHashCode").WithArguments("S.GetHashCode()", "this").WithLocation(13, 9),
// (14,9): warning CS8655: Call to non-readonly member 'S.Equals(object)' from a 'readonly' member results in an implicit copy of 'this'.
// Equals(null);
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "Equals").WithArguments("S.Equals(object)", "this").WithLocation(14, 9));
// Verify that calls to non-readonly overrides pass the address of a temp, not the address of 'this'
verifier.VerifyIL("S.M", @"
{
// Code size 117 (0x75)
.maxstack 2
.locals init (S V_0)
IL_0000: ldarg.0
IL_0001: ldobj ""S""
IL_0006: stloc.0
IL_0007: ldloca.s V_0
IL_0009: constrained. ""S""
IL_000f: callvirt ""string object.ToString()""
IL_0014: pop
IL_0015: ldarg.0
IL_0016: ldobj ""S""
IL_001b: stloc.0
IL_001c: ldloca.s V_0
IL_001e: constrained. ""S""
IL_0024: callvirt ""int object.GetHashCode()""
IL_0029: pop
IL_002a: ldarg.0
IL_002b: ldobj ""S""
IL_0030: stloc.0
IL_0031: ldloca.s V_0
IL_0033: ldnull
IL_0034: constrained. ""S""
IL_003a: callvirt ""bool object.Equals(object)""
IL_003f: pop
IL_0040: ldarg.0
IL_0041: ldobj ""S""
IL_0046: box ""S""
IL_004b: call ""string System.ValueType.ToString()""
IL_0050: pop
IL_0051: ldarg.0
IL_0052: ldobj ""S""
IL_0057: box ""S""
IL_005c: call ""int System.ValueType.GetHashCode()""
IL_0061: pop
IL_0062: ldarg.0
IL_0063: ldobj ""S""
IL_0068: box ""S""
IL_006d: ldnull
IL_006e: call ""bool System.ValueType.Equals(object)""
IL_0073: pop
IL_0074: ret
}");
}
[Fact]
public void ReadOnlyMethod_ReadOnlyOverrideBaseMethod()
{
var csharp = @"
public struct S
{
// note: GetType can't be overridden
public readonly override string ToString() => throw null;
public readonly override int GetHashCode() => throw null;
public readonly override bool Equals(object o) => throw null;
readonly void M()
{
// no warnings
ToString();
GetHashCode();
Equals(null);
base.ToString();
base.GetHashCode();
base.Equals(null);
}
}
";
var verifier = CompileAndVerify(csharp);
verifier.VerifyDiagnostics();
// Verify that calls to readonly override members pass the address of 'this' (not a temp)
verifier.VerifyIL("S.M", @"
{
// Code size 93 (0x5d)
.maxstack 2
IL_0000: ldarg.0
IL_0001: constrained. ""S""
IL_0007: callvirt ""string object.ToString()""
IL_000c: pop
IL_000d: ldarg.0
IL_000e: constrained. ""S""
IL_0014: callvirt ""int object.GetHashCode()""
IL_0019: pop
IL_001a: ldarg.0
IL_001b: ldnull
IL_001c: constrained. ""S""
IL_0022: callvirt ""bool object.Equals(object)""
IL_0027: pop
IL_0028: ldarg.0
IL_0029: ldobj ""S""
IL_002e: box ""S""
IL_0033: call ""string System.ValueType.ToString()""
IL_0038: pop
IL_0039: ldarg.0
IL_003a: ldobj ""S""
IL_003f: box ""S""
IL_0044: call ""int System.ValueType.GetHashCode()""
IL_0049: pop
IL_004a: ldarg.0
IL_004b: ldobj ""S""
IL_0050: box ""S""
IL_0055: ldnull
IL_0056: call ""bool System.ValueType.Equals(object)""
IL_005b: pop
IL_005c: ret
}");
}
[Fact]
public void ReadOnlyMethod_NewBaseMethod()
{
var csharp = @"
public struct S
{
public new System.Type GetType() => throw null;
public new string ToString() => throw null;
public new int GetHashCode() => throw null;
public new bool Equals(object o) => throw null;
readonly void M()
{
// should warn--non-readonly invocation
GetType();
ToString();
GetHashCode();
Equals(null);
// ok
base.GetType();
base.ToString();
base.GetHashCode();
base.Equals(null);
}
}
";
var verifier = CompileAndVerify(csharp);
verifier.VerifyDiagnostics(
// (12,9): warning CS8655: Call to non-readonly member 'S.GetType()' from a 'readonly' member results in an implicit copy of 'this'.
// GetType();
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "GetType").WithArguments("S.GetType()", "this").WithLocation(12, 9),
// (13,9): warning CS8655: Call to non-readonly member 'S.ToString()' from a 'readonly' member results in an implicit copy of 'this'.
// ToString();
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "ToString").WithArguments("S.ToString()", "this").WithLocation(13, 9),
// (14,9): warning CS8655: Call to non-readonly member 'S.GetHashCode()' from a 'readonly' member results in an implicit copy of 'this'.
// GetHashCode();
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "GetHashCode").WithArguments("S.GetHashCode()", "this").WithLocation(14, 9),
// (15,9): warning CS8655: Call to non-readonly member 'S.Equals(object)' from a 'readonly' member results in an implicit copy of 'this'.
// Equals(null);
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "Equals").WithArguments("S.Equals(object)", "this").WithLocation(15, 9));
// Verify that calls to new non-readonly members pass an address to a temp and that calls to base members use a box.
verifier.VerifyIL("S.M", @"
{
// Code size 131 (0x83)
.maxstack 2
.locals init (S V_0)
IL_0000: ldarg.0
IL_0001: ldobj ""S""
IL_0006: stloc.0
IL_0007: ldloca.s V_0
IL_0009: call ""System.Type S.GetType()""
IL_000e: pop
IL_000f: ldarg.0
IL_0010: ldobj ""S""
IL_0015: stloc.0
IL_0016: ldloca.s V_0
IL_0018: call ""string S.ToString()""
IL_001d: pop
IL_001e: ldarg.0
IL_001f: ldobj ""S""
IL_0024: stloc.0
IL_0025: ldloca.s V_0
IL_0027: call ""int S.GetHashCode()""
IL_002c: pop
IL_002d: ldarg.0
IL_002e: ldobj ""S""
IL_0033: stloc.0
IL_0034: ldloca.s V_0
IL_0036: ldnull
IL_0037: call ""bool S.Equals(object)""
IL_003c: pop
IL_003d: ldarg.0
IL_003e: ldobj ""S""
IL_0043: box ""S""
IL_0048: call ""System.Type object.GetType()""
IL_004d: pop
IL_004e: ldarg.0
IL_004f: ldobj ""S""
IL_0054: box ""S""
IL_0059: call ""string System.ValueType.ToString()""
IL_005e: pop
IL_005f: ldarg.0
IL_0060: ldobj ""S""
IL_0065: box ""S""
IL_006a: call ""int System.ValueType.GetHashCode()""
IL_006f: pop
IL_0070: ldarg.0
IL_0071: ldobj ""S""
IL_0076: box ""S""
IL_007b: ldnull
IL_007c: call ""bool System.ValueType.Equals(object)""
IL_0081: pop
IL_0082: ret
}");
}
[Fact]
public void ReadOnlyMethod_ReadOnlyNewBaseMethod()
{
var csharp = @"
public struct S
{
public readonly new System.Type GetType() => throw null;
public readonly new string ToString() => throw null;
public readonly new int GetHashCode() => throw null;
public readonly new bool Equals(object o) => throw null;
readonly void M()
{
// no warnings
GetType();
ToString();
GetHashCode();
Equals(null);
base.GetType();
base.ToString();
base.GetHashCode();
base.Equals(null);
}
}
";
var verifier = CompileAndVerify(csharp);
verifier.VerifyDiagnostics();
// Verify that calls to readonly new members pass the address of 'this' (not a temp) and that calls to base members use a box.
verifier.VerifyIL("S.M", @"
{
// Code size 99 (0x63)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""readonly System.Type S.GetType()""
IL_0006: pop
IL_0007: ldarg.0
IL_0008: call ""readonly string S.ToString()""
IL_000d: pop
IL_000e: ldarg.0
IL_000f: call ""readonly int S.GetHashCode()""
IL_0014: pop
IL_0015: ldarg.0
IL_0016: ldnull
IL_0017: call ""readonly bool S.Equals(object)""
IL_001c: pop
IL_001d: ldarg.0
IL_001e: ldobj ""S""
IL_0023: box ""S""
IL_0028: call ""System.Type object.GetType()""
IL_002d: pop
IL_002e: ldarg.0
IL_002f: ldobj ""S""
IL_0034: box ""S""
IL_0039: call ""string System.ValueType.ToString()""
IL_003e: pop
IL_003f: ldarg.0
IL_0040: ldobj ""S""
IL_0045: box ""S""
IL_004a: call ""int System.ValueType.GetHashCode()""
IL_004f: pop
IL_0050: ldarg.0
IL_0051: ldobj ""S""
IL_0056: box ""S""
IL_005b: ldnull
IL_005c: call ""bool System.ValueType.Equals(object)""
IL_0061: pop
IL_0062: ret
}");
}
[Fact]
public void ReadOnlyMethod_FixedThis()
{
var csharp = @"
struct S
{
int i;
readonly unsafe void M()
{
fixed (S* sp = &this)
{
sp->i = 42;
}
}
static void Main()
{
var s = new S();
s.M();
System.Console.Write(s.i);
}
}
";
CompileAndVerify(csharp, options: TestOptions.UnsafeReleaseExe, verify: Verification.Fails, expectedOutput: "42");
}
public static TheoryData<bool, CSharpParseOptions, Verification> ReadOnlyGetter_LangVersion_Data() =>
new TheoryData<bool, CSharpParseOptions, Verification>
{
{ false, TestOptions.Regular7_3, Verification.Passes },
{ true, null, Verification.Fails }
};
[Theory]
[MemberData(nameof(ReadOnlyGetter_LangVersion_Data))]
public void ReadOnlyGetter_LangVersion(bool isReadOnly, CSharpParseOptions parseOptions, Verification verify)
{
var csharp = @"
struct S
{
public int P { get; }
static readonly S Field = default;
static void M()
{
_ = Field.P;
}
}
";
var verifier = CompileAndVerify(csharp, parseOptions: parseOptions, verify: verify);
var type = ((CSharpCompilation)verifier.Compilation).GetMember<NamedTypeSymbol>("S");
Assert.Equal(isReadOnly, type.GetProperty("P").GetMethod.IsDeclaredReadOnly);
Assert.Equal(isReadOnly, type.GetProperty("P").GetMethod.IsEffectivelyReadOnly);
}
[Fact]
public void ReadOnlyEvent_Emit()
{
var csharp = @"
public struct S
{
public readonly event System.Action E { add { } remove { } }
}
";
CompileAndVerify(csharp, symbolValidator: validate).VerifyDiagnostics();
void validate(ModuleSymbol module)
{
var testStruct = module.ContainingAssembly.GetTypeByMetadataName("S");
var peModule = (PEModuleSymbol)module;
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)testStruct.GetEvent("E").AddMethod).Handle));
Assert.True(peModule.Module.HasIsReadOnlyAttribute(((PEMethodSymbol)testStruct.GetEvent("E").RemoveMethod).Handle));
AssertDeclaresType(peModule, WellKnownType.System_Runtime_CompilerServices_IsReadOnlyAttribute, Accessibility.Internal);
}
}
}
}
|