|
// 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.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Completion.Providers;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
using CompletionItem = Microsoft.CodeAnalysis.Completion.CompletionItem;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders;
[Trait(Traits.Feature, Traits.Features.Completion)]
public class ConversionCompletionProviderTests : AbstractCSharpCompletionProviderTests
{
internal override Type GetCompletionProviderType()
=> typeof(UnnamedSymbolCompletionProvider);
private static string FormatExplicitConversionDescription(string fromType, string toType)
=> string.Format(WorkspacesResources.Predefined_conversion_from_0_to_1, fromType, toType);
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task OperatorIndexerCompletionItemsShouldBePlacedLastInCompletionList()
{
var castCompletionItem = (await GetCompletionItemsAsync("""
public class C
{
public static explicit operator float(C c) => 0;
}
public class Program
{
public static void Main()
{
var c = new C();
c.$$
}
}
""", SourceCodeKind.Regular)).Single();
var completionList = new[] {
CompletionItem.Create("SomeText0"),
castCompletionItem,
CompletionItem.Create("SomeText1"),
CompletionItem.Create("\uffdcStartWith_FFDC_Identifier"), // http://www.fileformat.info/info/unicode/char/ffdc/index.htm
CompletionItem.Create("SomeText2"),
CompletionItem.Create("\uD884\uDF4AStartWith_3134A_Identifier"), // http://www.fileformat.info/info/unicode/char/3134a/index.htm
CompletionItem.Create("SomeText3"),
};
Array.Sort(completionList);
Assert.Collection(completionList,
c => Assert.Equal("SomeText0", c.DisplayText),
c => Assert.Equal("SomeText1", c.DisplayText),
c => Assert.Equal("SomeText2", c.DisplayText),
c => Assert.Equal("SomeText3", c.DisplayText),
c => Assert.Equal("\uD884\uDF4AStartWith_3134A_Identifier", c.DisplayText),
c => Assert.Equal("\uffdcStartWith_FFDC_Identifier", c.DisplayText),
c =>
{
Assert.Same(c, castCompletionItem);
Assert.Equal("float", c.DisplayText);
Assert.Equal("\uFFFD001_float", c.SortText);
Assert.Equal("float", c.FilterText);
});
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitUserDefinedConversionIsNotOfferedAfterNumberLiteral()
{
// User may want to type a floating point literal.
await VerifyNoItemsExistAsync("""
public class C
{
public static explicit operator float(C c) => 0;
}
public class Program
{
public static void Main()
{
1.$$
}
}
""", SourceCodeKind.Regular);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitUserDefinedConversionIsSuggestedAfterDot()
{
await VerifyItemExistsAsync("""
public class C
{
public static explicit operator float(C c) => 0;
}
public class Program
{
public static void Main()
{
var c = new C();
c.$$
}
}
""", "float", displayTextPrefix: "(", displayTextSuffix: ")", glyph: (int)Glyph.Operator, matchingFilters: [FilterSet.OperatorFilter]);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitUserDefinedConversionIsSuggestedIfMemberNameIsPartiallyWritten()
{
await VerifyItemExistsAsync("""
public class C
{
public void fly() { }
public static explicit operator float(C c) => 0;
}
public class Program
{
public static void Main()
{
var c = new C();
c.fl$$
}
}
""", "float", displayTextPrefix: "(", displayTextSuffix: ")", glyph: (int)Glyph.Operator, matchingFilters: [FilterSet.OperatorFilter]);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
[InlineData("c.$$", true)]
[InlineData("c.fl$$", true)]
[InlineData("c. $$", true)]
[InlineData("c.fl $$", false)]
[InlineData("c.($$", false)]
[InlineData("c$$", false)]
[InlineData("""
"c.$$
""", false)]
[InlineData("c?.$$", true)]
[InlineData("((C)c).$$", true)]
[InlineData("(true ? c : c).$$", true)]
[InlineData("c.$$ var x=0;", true)]
public async Task ExplicitUserDefinedConversionDifferentExpressions(string expression, bool shouldSuggestConversion)
{
Func<string, string, Task> verifyFunc = shouldSuggestConversion
? (markup, expectedItem) => VerifyItemExistsAsync(markup, expectedItem, displayTextPrefix: "(", displayTextSuffix: ")")
: (markup, expectedItem) => VerifyItemIsAbsentAsync(markup, expectedItem, displayTextPrefix: "(", displayTextSuffix: ")");
await verifyFunc(@$"
public class C
{{
public static explicit operator float(C c) => 0;
}}
public class Program
{{
public static void Main()
{{
var c = new C();
{expression}
}}
}}
", "float");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitUserDefinedConversionIsNotSuggestedOnStaticAccess()
{
await VerifyNoItemsExistAsync("""
public class C
{
public static explicit operator float(C c) => 0;
}
public class Program
{
public static void Main()
{
C.$$
}
}
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitUserDefinedConversionIsNotSuggestedInNameofContext()
{
await VerifyNoItemsExistAsync("""
public class C
{
public static explicit operator float(C c) => 0;
}
public class Program
{
public static void Main()
{
var c = new C();
var name = nameof(c.$$
}
}
""");
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
[InlineData("", "Nested1.C", "Nested2.C")]
[InlineData("using N1.Nested1;", "C", "Nested2.C")]
[InlineData("using N1.Nested2;", "C", "Nested1.C")]
[InlineData("using N1.Nested1;using N1.Nested2;", "Nested1.C", "Nested2.C")]
public async Task ExplicitUserDefinedConversionTypeDisplayStringIsMinimal(string usingDirective, string displayText1, string displayText2)
{
var items = await GetCompletionItemsAsync(@$"
namespace N1.Nested1
{{
public class C
{{
}}
}}
namespace N1.Nested2
{{
public class C
{{
}}
}}
namespace N2
{{
public class Conversion
{{
public static explicit operator N1.Nested1.C(Conversion _) => new N1.Nested1.C();
public static explicit operator N1.Nested2.C(Conversion _) => new N1.Nested2.C();
}}
}}
namespace N1
{{
{usingDirective}
public class Test
{{
public void M()
{{
var conversion = new N2.Conversion();
conversion.$$
}}
}}
}}
", SourceCodeKind.Regular);
Assert.Collection(items,
i => Assert.Equal(displayText1, i.DisplayText),
i => Assert.Equal(displayText2, i.DisplayText));
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitUserDefinedConversionIsSuggestedForAllExplicitConversionsToOtherTypesAndNotForImplicitConversions()
{
var items = await GetCompletionItemsAsync("""
public class C
{
public static explicit operator float(C c) => 0;
public static explicit operator int(C c) => 0;
public static explicit operator C(float f) => new C();
public static implicit operator C(string s) => new C();
public static implicit operator string(C c) => ";
}
public class Program
{
public static void Main()
{
var c = new C();
c.$$
}
}
""", SourceCodeKind.Regular);
Assert.Collection(items,
i => Assert.Equal("float", i.DisplayText),
i => Assert.Equal("int", i.DisplayText));
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitUserDefinedConversionIgnoresConversionLikeMethods()
{
await VerifyNoItemsExistAsync("""
public class C
{
public static bool op_Explicit(C c) => false;
}
public class Program
{
public static void Main()
{
var c = new C();
c.$$
}
}
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitUserDefinedConversionIgnoreMalformedOperators()
{
await VerifyNoItemsExistAsync("""
public class C
{
public static explicit operator int() => 0;
}
public class Program
{
public static void Main()
{
var c = new C();
c.$$
}
}
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitUserDefinedConversionFromOtherTypeToTargetIsNotSuggested()
{
await VerifyNoItemsExistAsync("""
public class C
{
public static explicit operator C(D d) => null;
}
public class D
{
}
public class Program
{
public static void Main()
{
var d = new D();
d.$$
}
}
""");
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitConversionOfNullableStructToNullableStructIsOffered()
{
// Lifted conversion https://docs.microsoft.com/hu-hu/dotnet/csharp/language-reference/language-specification/conversions#lifted-conversion-operators
await VerifyItemExistsAsync("""
public struct S {
public static explicit operator int(S _) => 0;
}
public class Program
{
public static void Main()
{
S? s = null;
s.$$
}
}
""", "int?", displayTextPrefix: "(", displayTextSuffix: ")", glyph: (int)Glyph.Operator, matchingFilters: [FilterSet.OperatorFilter]);
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitConversionDescriptionIsIsGiven()
{
const string Markup = """
public struct S {
/// <summary>
/// Explicit conversion of <see cref="S"/> to <see cref="int"/>.
/// </summary>
/// <param name="value">The <see cref="S"/> to convert</param>
public static explicit operator int(S value) => 0;
}
public class Program
{
public static void Main()
{
var s = new S();
s.$$
}
}
""";
await VerifyItemExistsAsync(Markup, "int", displayTextPrefix: "(", displayTextSuffix: ")",
glyph: (int)Glyph.Operator,
matchingFilters: [FilterSet.OperatorFilter],
expectedDescriptionOrNull:
"""
S.explicit operator int(S value)
Explicit conversion of S to int.
""");
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitConversionDescriptionIsIsGivenLifted()
{
const string Markup = """
public struct S {
/// <summary>
/// Explicit conversion of <see cref="S"/> to <see cref="int"/>.
/// </summary>
/// <param name="value">The <see cref="S"/> to convert</param>
public static explicit operator int(S value) => 0;
}
public class Program
{
public static void Main()
{
S? s = new S();
s.$$
}
}
""";
await VerifyItemExistsAsync(Markup, "int?", displayTextPrefix: "(", displayTextSuffix: ")",
glyph: (int)Glyph.Operator,
matchingFilters: [FilterSet.OperatorFilter],
expectedDescriptionOrNull:
"""
S.explicit operator int?(S? value)
Explicit conversion of S to int.
""");
}
[WpfTheory, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
[InlineData("sbyte", "byte", "char", "uint", "ulong", "ushort")]
[InlineData("byte", "char", "sbyte")]
[InlineData("short", "byte", "char", "sbyte", "uint", "ulong", "ushort")]
[InlineData("ushort", "byte", "char", "sbyte", "short")]
[InlineData("int", "byte", "char", "sbyte", "short", "uint", "ulong", "ushort")]
[InlineData("uint", "byte", "char", "int", "sbyte", "short", "ushort")]
[InlineData("long", "byte", "char", "int", "sbyte", "short", "uint", "ulong", "ushort")]
[InlineData("ulong", "byte", "char", "int", "long", "sbyte", "short", "uint", "ushort")]
[InlineData("char", "byte", "sbyte", "short")]
[InlineData("float", "byte", "char", "decimal", "int", "long", "sbyte", "short", "uint", "ulong", "ushort")]
[InlineData("double", "byte", "char", "decimal", "float", "int", "long", "sbyte", "short", "uint", "ulong", "ushort")]
[InlineData("decimal", "byte", "char", "double", "float", "int", "long", "sbyte", "short", "uint", "ulong", "ushort")]
public async Task ExplicitBuiltInNumericConversionsAreOfferedAcordingToSpec(string fromType, params string[] toTypes)
{
// built-in numeric conversions:
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/numeric-conversions
var items = await GetCompletionItemsAsync(@$"
public class Program
{{
public static void Main()
{{
{fromType} i = default({fromType});
i.$$
}}
}}
", SourceCodeKind.Regular);
AssertEx.SetEqual(items.Select(i => i.DisplayText), toTypes);
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitBuiltInNumericConversionDescriptionIsLikeAConversionOperatorDescription()
{
const string Markup = """
public class Program
{
public static void Main()
{
int i = 0;
i.$$
}
}
""";
await VerifyItemExistsAsync(Markup, "byte", displayTextPrefix: "(", displayTextSuffix: ")",
glyph: (int)Glyph.Operator,
matchingFilters: [FilterSet.OperatorFilter],
expectedDescriptionOrNull:
$@"int.explicit operator byte(int value)
{(FormatExplicitConversionDescription(fromType: "int", toType: "byte"))}");
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitBuiltInNumericConversionDescriptionIsLikeAConversionOperatorDescriptionLifted()
{
const string Markup = """
public class Program
{
public static void Main()
{
int? i = 0;
i.$$
}
}
""";
await VerifyItemExistsAsync(Markup, "byte?", displayTextPrefix: "(", displayTextSuffix: ")",
glyph: (int)Glyph.Operator,
matchingFilters: [FilterSet.OperatorFilter],
expectedDescriptionOrNull:
$@"int.explicit operator byte?(int? value)
{(FormatExplicitConversionDescription(fromType: "int", toType: "byte"))}");
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitBuiltInEnumConversionsAreSortedAndComplete()
{
// built-in enum conversions:
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#explicit-enumeration-conversions
var items = await GetCompletionItemsAsync("""
public enum E { One }
public class Program
{
public static void Main()
{
var e = E.One;
e.$$
}
}
""", SourceCodeKind.Regular);
var expected = new[] { "byte", "char", "decimal", "double", "float", "int", "long", "sbyte", "short", "uint", "ulong", "ushort" };
AssertEx.SetEqual(items.Select(i => i.DisplayText), expected);
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitBuiltInEnumConversionDescriptionIsLikeAConversionOperatorDescription()
{
const string Markup = """
public enum E { One }
public class Program
{
public static void Main()
{
var e = E.One;
e.$$
}
}
""";
await VerifyItemExistsAsync(Markup, "int", displayTextPrefix: "(", displayTextSuffix: ")",
glyph: (int)Glyph.Operator,
matchingFilters: [FilterSet.OperatorFilter],
expectedDescriptionOrNull:
$@"E.explicit operator int(E value)
{FormatExplicitConversionDescription("E", "int")}");
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitBuiltInEnumConversionDescriptionIsLikeAConversionOperatorDescriptionLifted()
{
const string Markup = """
public enum E { One }
public class Program
{
public static void Main()
{
E? e = E.One;
e.$$
}
}
""";
await VerifyItemExistsAsync(Markup, "int?", displayTextPrefix: "(", displayTextSuffix: ")",
glyph: (int)Glyph.Operator,
matchingFilters: [FilterSet.OperatorFilter],
expectedDescriptionOrNull:
$@"E.explicit operator int?(E? value)
{(FormatExplicitConversionDescription(fromType: "E", toType: "int"))}");
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitBuiltInEnumConversionDescriptionIsLikeAConversionOperatorDescriptionUnimportedNamespaceMinimalName()
{
const string Markup = """
namespace A.B
{
public enum E { One }
}
namespace A.C
{
public class Program
{
public static void Main()
{
var e = B.E.One;
e.$$
}
}
}
""";
await VerifyItemExistsAsync(Markup, "int", displayTextPrefix: "(", displayTextSuffix: ")",
glyph: (int)Glyph.Operator,
matchingFilters: [FilterSet.OperatorFilter],
expectedDescriptionOrNull:
@$"B.E.explicit operator int(B.E value)
{FormatExplicitConversionDescription("B.E", "int")}");
}
[WpfTheory, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
[InlineData("e.$$", true)]
[InlineData("e. $$", true)]
[InlineData("e.in$$", true)]
[InlineData("E.$$", false)] // Don't infer with enum member suggestion
[InlineData("E.One.$$", true)]
public async Task ExplicitBuiltInEnumConversionToIntAreOffered(string expression, bool conversionIsOffered)
{
Func<string, Task> verifyFunc = conversionIsOffered
? markup => VerifyItemExistsAsync(markup, "int", displayTextPrefix: "(", displayTextSuffix: ")")
: markup => VerifyNoItemsExistAsync(markup);
await verifyFunc(@$"
public enum E {{ One }}
public class Program
{{
public static void Main()
{{
var e = E.One;
{expression}
}}
}}
");
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitUserDefinedConversionInheritedConversions()
{
// Base class lookup rule: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#processing-of-user-defined-explicit-conversions
await VerifyItemExistsAsync("""
public class Base {
public static explicit operator int(Base b) => 0;
}
public class Derived: Base
{
}
public class Program
{
public static void Main()
{
var d = new Derived();
var i = d.$$
}
}
""", "int", displayTextPrefix: "(", displayTextSuffix: ")", glyph: (int)Glyph.Operator, matchingFilters: [FilterSet.OperatorFilter]);
}
[WpfTheory, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
[InlineData("C", "byte")]
[InlineData("byte", "C")]
public async Task ExplicitBuiltinConversionWithAlias(string fromType, string expected)
{
await VerifyItemExistsAsync(@$"
using C = System.Char;
public class Program
{{
public static void Main()
{{
var test = new {fromType}();
var i = test.$$
}}
}}
", expected, displayTextPrefix: "(", displayTextSuffix: ")", glyph: (int)Glyph.Operator, matchingFilters: [FilterSet.OperatorFilter]);
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task TestEditorBrowsableOnConversionIsRespected_EditorBrowsableStateNever()
{
var markup = """
namespace N
{
public class Program
{
public static void Main()
{
var c = new C();
c.$$
}
}
}
""";
var referencedCode = """
using System.ComponentModel;
namespace N
{
public class C
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static explicit operator int(C _) => 0;
}
}
""";
await VerifyItemInEditorBrowsableContextsAsync(
markup: markup,
referencedCode: referencedCode,
item: "int",
expectedSymbolsSameSolution: 1,
expectedSymbolsMetadataReference: 0,
sourceLanguage: LanguageNames.CSharp,
referencedLanguage: LanguageNames.CSharp);
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task TestEditorBrowsableOnConversionIsRespected_EditorBrowsableStateNever_InheritedConversion_1()
{
var markup = """
namespace N
{
public class Program
{
public static void Main()
{
var d = new Derived();
d.$$
}
}
}
""";
var referencedCode = """
using System.ComponentModel;
namespace N
{
public class Base {
[EditorBrowsable(EditorBrowsableState.Never)]
public static explicit operator int(Base b) => 0;
}
public class Derived: Base
{
}
}
""";
await VerifyItemInEditorBrowsableContextsAsync(
markup: markup,
referencedCode: referencedCode,
item: "int",
expectedSymbolsSameSolution: 1,
expectedSymbolsMetadataReference: 0,
sourceLanguage: LanguageNames.CSharp,
referencedLanguage: LanguageNames.CSharp);
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task TestEditorBrowsableOnConversionIsRespected_EditorBrowsableStateNever_InheritedConversion_2()
{
var markup = """
namespace N
{
public class Derived: Base
{
}
public class Program
{
public static void Main()
{
var d = new Derived();
d.$$
}
}
}
""";
var referencedCode = """
using System.ComponentModel;
namespace N
{
public class Base {
[EditorBrowsable(EditorBrowsableState.Never)]
public static explicit operator int(Base b) => 0;
}
}
""";
await VerifyItemInEditorBrowsableContextsAsync(
markup: markup,
referencedCode: referencedCode,
item: "int",
expectedSymbolsSameSolution: 1,
expectedSymbolsMetadataReference: 0,
sourceLanguage: LanguageNames.CSharp,
referencedLanguage: LanguageNames.CSharp);
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task TestEditorBrowsableOnConversionIsRespected_EditorBrowsableStateAdvanced()
{
var markup = """
namespace N
{
public class Program
{
public static void Main()
{
var c = new C();
c.$$
}
}
}
""";
var referencedCode = """
using System.ComponentModel;
namespace N
{
public class C
{
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static explicit operator int(C _) => 0;
}
}
""";
HideAdvancedMembers = false;
await VerifyItemInEditorBrowsableContextsAsync(
markup: markup,
referencedCode: referencedCode,
item: "int",
expectedSymbolsSameSolution: 1,
expectedSymbolsMetadataReference: 1,
sourceLanguage: LanguageNames.CSharp,
referencedLanguage: LanguageNames.CSharp);
HideAdvancedMembers = true;
await VerifyItemInEditorBrowsableContextsAsync(
markup: markup,
referencedCode: referencedCode,
item: "int",
expectedSymbolsSameSolution: 1,
expectedSymbolsMetadataReference: 0,
sourceLanguage: LanguageNames.CSharp,
referencedLanguage: LanguageNames.CSharp);
}
[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47511")]
public async Task ExplicitUserDefinedConversionOfDerefenrencedPointerIsNotOffered()
{
await VerifyNoItemsExistAsync("""
public struct S {
public static explicit operator int(S s) => 0;
}
public class Program
{
public static void Main()
{
unsafe{
var s = new S();
S* p = &s;
var i = p->$$;
}
}
}
""");
}
}
|