|
// 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.IO;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.Editor.UnitTests.Semantics;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Semantics;
public class SpeculationAnalyzerTests : SpeculationAnalyzerTestsBase
{
[Fact]
public void SpeculationAnalyzerDifferentOverloads()
{
Test("""
class Program
{
void Vain(int arg = 3) { }
void Vain(string arg) { }
void Main()
{
[|Vain(5)|];
}
}
""", "Vain(string.Empty)", true);
}
[Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/672396")]
public void SpeculationAnalyzerExtensionMethodExplicitInvocation()
{
// We consider a change here to be a change in semantics as an instance call became a static call. In
// practice this is fine as the only thing that makes this change is complexification, and we don't test for
// semantics changed after that as the purpose of complexification is to put us in a safe place to make
// changes that won't break semantics.
Test("""
static class Program
{
public static void Vain(this int arg) { }
static void Main()
{
[|5.Vain()|];
}
}
""", "Vain(5)", semanticChanges: true);
}
[Fact]
public void SpeculationAnalyzerImplicitBaseClassConversion()
{
Test("""
using System;
class Program
{
void Main()
{
Exception ex = [|(Exception)new InvalidOperationException()|];
}
}
""", "new InvalidOperationException()", false);
}
[Fact]
public void SpeculationAnalyzerImplicitNumericConversion()
{
Test("""
class Program
{
void Main()
{
long i = [|(long)5|];
}
}
""", "5", false);
}
[Fact]
public void SpeculationAnalyzerImplicitUserConversion()
{
Test("""
class From
{
public static implicit operator To(From from) { return new To(); }
}
class To { }
class Program
{
void Main()
{
To to = [|(To)new From()|];
}
}
""", "new From()", true);
}
[Fact]
public void SpeculationAnalyzerExplicitConversion()
{
Test("""
using System;
class Program
{
void Main()
{
Exception ex1 = new InvalidOperationException();
var ex2 = [|(InvalidOperationException)ex1|];
}
}
""", "ex1", true);
}
[Fact]
public void SpeculationAnalyzerArrayImplementingNonGenericInterface()
{
Test("""
using System.Collections;
class Program
{
void Main()
{
var a = new[] { 1, 2, 3 };
[|((IEnumerable)a).GetEnumerator()|];
}
}
""", "a.GetEnumerator()", false);
}
[Fact]
public void SpeculationAnalyzerVirtualMethodWithBaseConversion()
{
Test("""
using System;
using System.IO;
class Program
{
void Main()
{
var s = new MemoryStream();
[|((Stream)s).Flush()|];
}
}
""", "s.Flush()", false);
}
[Fact]
public void SpeculationAnalyzerNonVirtualMethodImplementingInterface()
{
Test("""
using System;
class Class : IComparable
{
public int CompareTo(object other) { return 1; }
}
class Program
{
static void Main()
{
var c = new Class();
var d = new Class();
[|((IComparable)c).CompareTo(d)|];
}
}
""", "c.CompareTo(d)", true);
}
[Fact]
public void SpeculationAnalyzerSealedClassImplementingInterface()
{
Test("""
using System;
sealed class Class : IComparable
{
public int CompareTo(object other) { return 1; }
}
class Program
{
static void Main()
{
var c = new Class();
var d = new Class();
[|((IComparable)c).CompareTo(d)|];
}
}
""", "((IComparable)c).CompareTo(d)", semanticChanges: false);
}
[Fact]
public void SpeculationAnalyzerValueTypeImplementingInterface()
{
Test("""
using System;
class Program
{
void Main()
{
decimal d = 5;
[|((IComparable<decimal>)d).CompareTo(6)|];
}
}
""", "d.CompareTo(6)", false);
}
[Fact]
public void SpeculationAnalyzerBinaryExpressionIntVsLong()
{
Test("""
class Program
{
void Main()
{
var r = [|1+1L|];
}
}
""", "1+1", true);
}
[Fact]
public void SpeculationAnalyzerQueryExpressionSelectType()
{
Test("""
using System.Linq;
class Program
{
static void Main(string[] args)
{
var items = [|from i in Enumerable.Range(0, 3) select (long)i|];
}
}
""", "from i in Enumerable.Range(0, 3) select i", true);
}
[Fact]
public void SpeculationAnalyzerQueryExpressionFromType()
{
Test("""
using System.Linq;
class Program
{
static void Main(string[] args)
{
var items = [|from i in new long[0] select i|];
}
}
""", "from i in new int[0] select i", true);
}
[Fact]
public void SpeculationAnalyzerQueryExpressionGroupByType()
{
Test("""
using System.Linq;
class Program
{
static void Main(string[] args)
{
var items = [|from i in Enumerable.Range(0, 3) group (long)i by i|];
}
}
""", "from i in Enumerable.Range(0, 3) group i by i", true);
}
[Fact]
public void SpeculationAnalyzerQueryExpressionOrderByType()
{
Test("""
using System.Linq;
class Program
{
static void Main(string[] args)
{
var items = from i in Enumerable.Range(0, 3) orderby [|(long)i|] select i;
}
}
""", "i", true);
}
[Fact]
public void SpeculationAnalyzerDifferentAttributeConstructors()
{
Test("""
using System;
class AnAttribute : Attribute
{
public AnAttribute(string a, long b) { }
public AnAttribute(int a, int b) { }
}
class Program
{
[An([|"5"|], 6)]
static void Main() { }
}
""", "5", false, "6");
// Note: the answer should have been that the replacement does change semantics (true),
// however to have enough context one must analyze AttributeSyntax instead of separate ExpressionSyntaxes it contains,
// which is not supported in SpeculationAnalyzer, but possible with GetSpeculativeSemanticModel API
}
[Fact]
public void SpeculationAnalyzerCollectionInitializers()
{
Test("""
using System.Collections;
class Collection : IEnumerable
{
public IEnumerator GetEnumerator() { throw new System.NotImplementedException(); }
public void Add(string s) { }
public void Add(int i) { }
void Main()
{
var c = new Collection { [|"5"|] };
}
}
""", "5", true);
}
[Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1088815")]
public void SpeculationAnalyzerBrokenCode()
{
Test("""
public interface IRogueAction
{
public string Name { get; private set; }
protected IRogueAction(string name)
{
[|this.Name|] = name;
}
}
""", "Name", semanticChanges: false, isBrokenCode: true);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/8111")]
public void SpeculationAnalyzerAnonymousObjectMemberDeclaredWithNeededCast()
{
Test("""
class Program
{
static void Main(string[] args)
{
object thing = new { shouldBeAnInt = [|(int)Directions.South|] };
}
public enum Directions { North, East, South, West }
}
""", "Directions.South", semanticChanges: true);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/8111")]
public void SpeculationAnalyzerAnonymousObjectMemberDeclaredWithUnneededCast()
{
Test("""
class Program
{
static void Main(string[] args)
{
object thing = new { shouldBeAnInt = [|(Directions)Directions.South|] };
}
public enum Directions { North, East, South, West }
}
""", "Directions.South", semanticChanges: false);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/19987")]
public void SpeculationAnalyzerSwitchCaseWithRedundantCast()
{
Test("""
class Program
{
static void Main(string[] arts)
{
var x = 1f;
switch (x)
{
case [|(float) 1|]:
System.Console.WriteLine("one");
break;
default:
System.Console.WriteLine("not one");
break;
}
}
}
""", "1", semanticChanges: false);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/19987")]
public void SpeculationAnalyzerSwitchCaseWithRequiredCast()
{
Test("""
class Program
{
static void Main(string[] arts)
{
object x = 1f;
switch (x)
{
case [|(float) 1|]: // without the case, object x does not match int 1
System.Console.WriteLine("one");
break;
default:
System.Console.WriteLine("not one");
break;
}
}
}
""", "1", semanticChanges: true);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/28412")]
public void SpeculationAnalyzerIndexerPropertyWithRedundantCast()
{
Test(code: """
class Indexer
{
public int this[int x] { get { return x; } }
}
class A
{
public Indexer Foo { get; } = new Indexer();
}
class B : A
{
}
class Program
{
static void Main(string[] args)
{
var b = new B();
var y = ([|(A)b|]).Foo[1];
}
}
""", replacementExpression: "b", semanticChanges: false);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/28412")]
public void SpeculationAnalyzerIndexerPropertyWithRequiredCast()
{
Test(code: """
class Indexer
{
public int this[int x] { get { return x; } }
}
class A
{
public Indexer Foo { get; } = new Indexer();
}
class B : A
{
public new Indexer Foo { get; } = new Indexer();
}
class Program
{
static void Main(string[] args)
{
var b = new B();
var y = ([|(A)b|]).Foo[1];
}
}
""", replacementExpression: "b", semanticChanges: true);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/28412")]
public void SpeculationAnalyzerDelegatePropertyWithRedundantCast()
{
Test(code: """
public delegate void MyDelegate();
class A
{
public MyDelegate Foo { get; }
}
class B : A
{
}
class Program
{
static void Main(string[] args)
{
var b = new B();
([|(A)b|]).Foo();
}
}
""", replacementExpression: "b", semanticChanges: false);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/28412")]
public void SpeculationAnalyzerDelegatePropertyWithRequiredCast()
{
Test(code: """
public delegate void MyDelegate();
class A
{
public MyDelegate Foo { get; }
}
class B : A
{
public new MyDelegate Foo { get; }
}
class Program
{
static void Main(string[] args)
{
var b = new B();
([|(A)b|]).Foo();
}
}
""", replacementExpression: "b", semanticChanges: true);
}
protected override SyntaxTree Parse(string text)
=> SyntaxFactory.ParseSyntaxTree(text);
protected override bool IsExpressionNode(SyntaxNode node)
=> node is ExpressionSyntax;
protected override Compilation CreateCompilation(SyntaxTree tree)
{
return CSharpCompilation.Create(
CompilationName,
[tree],
References,
TestOptions.ReleaseDll.WithSpecificDiagnosticOptions([KeyValuePairUtil.Create("CS0219", ReportDiagnostic.Suppress)]));
}
protected override bool CompilationSucceeded(Compilation compilation, Stream temporaryStream)
{
var langCompilation = compilation;
static bool isProblem(Diagnostic d) => d.Severity >= DiagnosticSeverity.Warning;
return !langCompilation.GetDiagnostics().Any(isProblem) &&
!langCompilation.Emit(temporaryStream).Diagnostics.Any(isProblem);
}
protected override bool ReplacementChangesSemantics(SyntaxNode initialNode, SyntaxNode replacementNode, SemanticModel initialModel)
=> new SpeculationAnalyzer((ExpressionSyntax)initialNode, (ExpressionSyntax)replacementNode, initialModel, CancellationToken.None).ReplacementChangesSemantics();
}
|