Browse Source

Attempt that makes all the implicit conversions work

However, it introduces a problem with one of the explicit conversion test cases.
newNRvisualizers
Daniel Grunwald 13 years ago
parent
commit
7eeb0348fb
  1. 79
      ICSharpCode.NRefactory.CSharp/Resolver/CSharpConversions.cs
  2. 2
      ICSharpCode.NRefactory.CSharp/Resolver/Log.cs
  3. 100
      ICSharpCode.NRefactory.Tests/CSharp/Resolver/ConversionsTest.cs
  4. 8
      ICSharpCode.NRefactory.Tests/CSharp/Resolver/ExplicitConversionsTest.cs
  5. 12
      ICSharpCode.NRefactory.Tests/CSharp/Resolver/OverloadResolutionTests.cs
  6. 2
      ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
  7. 2
      ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj

79
ICSharpCode.NRefactory.CSharp/Resolver/CSharpConversions.cs

@ -772,31 +772,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -772,31 +772,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
OperatorInfo SelectOperator(IType mostSpecificSource, IType mostSpecificTarget, IList<OperatorInfo> operators)
{
var selected = operators.Where(op => op.SourceType.Equals(mostSpecificSource) && op.TargetType.Equals(mostSpecificTarget)).ToList();
//TODO: This is fishy. I can't find the justification for this in the spec, but when performing algorithm in the spec for the conversion DateTime -> DateTimeOffset? (test UserDefinedImplicitNullableConversion)
// we get (according to the algorithm in §6.4.4, if my understanding is correct):
// S0 = DateTime, T0 = DateTimeOffset
// U = { DateTime -> DateTimeOffset (user-defined); DateTime? -> DateTimeOffset? (lifted) }
// Sx = DateTime
// Tx = DateTimeOffset?
// Now the problem is in the next step:
// "* If U contains exactly one user-defined conversion operator that converts from Sx to Tx, then this is the most specific conversion operator". Nope, no conversion DateTime -> DateTimeOffset?
// "* Otherwise, if U contains exactly one lifted conversion operator that converts from Sx to Tx, then this is the most specific conversion operator". Nope, no lifted conversion DateTime -> DateTimeOffset?
// "* Otherwise, the conversion is ambiguous and a compile-time error occurs". This is what happens!
if (selected.Count == 0 && NullableType.IsNullable(mostSpecificTarget))
selected = operators.Where(op => op.SourceType.Equals(mostSpecificSource) && op.TargetType.Equals(NullableType.GetUnderlyingType(mostSpecificTarget))).ToList();
if (selected.Count == 1)
return selected[0];
int nNonLifted = selected.Count(s => !s.IsLifted);
if (nNonLifted == 1)
return selected.First(s => !s.IsLifted);
else if (nNonLifted > 1)
return null; // Ambiguous
int nLifted = selected.Count(s => s.IsLifted);
if (nLifted == 1)
return selected.First(s => s.IsLifted);
else
return null; // Ambiguous or none applicable.
return null; // Ambiguous or none available
// If there was no non-lifted operator, all of them must have been lifted; so the
// "selected.Count == 1" check above should have found the unique lifted operator.
}
Conversion UserDefinedImplicitConversion(IType fromType, IType toType)
@ -805,10 +790,13 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -805,10 +790,13 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
var operators = GetApplicableConversionOperators(fromType, toType, false);
if (operators.Count > 0) {
var mostSpecificSource = operators.Any(op => op.SourceType.Equals(fromType)) ? fromType : FindMostEncompassedType(operators.Select(op => op.SourceType));
IType S0 = NullableType.GetUnderlyingType(fromType);
IType T0 = NullableType.GetUnderlyingType(toType);
var mostSpecificSource = operators.Any(op => op.SourceType.Equals(S0)) ? S0 : FindMostEncompassedType(operators.Select(op => op.SourceType));
if (mostSpecificSource == null)
return Conversion.None;
var mostSpecificTarget = operators.Any(op => op.TargetType.Equals(toType)) ? toType : FindMostEncompassingType(operators.Select(op => op.TargetType));
var mostSpecificTarget = operators.Any(op => op.TargetType.Equals(T0)) ? T0 : FindMostEncompassingType(operators.Select(op => op.TargetType));
if (mostSpecificTarget == null)
return Conversion.None;
@ -825,21 +813,24 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -825,21 +813,24 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
// C# 4.0 spec §6.4.5 User-defined implicit conversions
var operators = GetApplicableConversionOperators(fromType, toType, true);
if (operators.Count > 0) {
IType S0 = NullableType.GetUnderlyingType(fromType);
IType T0 = NullableType.GetUnderlyingType(toType);
IType mostSpecificSource;
if (operators.Any(op => op.SourceType.Equals(fromType)))
mostSpecificSource = fromType;
else if (operators.Any(op => IsEncompassedBy(fromType, op.SourceType)))
mostSpecificSource = FindMostEncompassedType(operators.Where(op => IsEncompassedBy(fromType, op.SourceType)).Select(op => op.SourceType));
if (operators.Any(op => op.SourceType.Equals(S0)))
mostSpecificSource = S0;
else if (operators.Any(op => IsEncompassedBy(S0, op.SourceType)))
mostSpecificSource = FindMostEncompassedType(operators.Where(op => IsEncompassedBy(S0, op.SourceType)).Select(op => op.SourceType));
else
mostSpecificSource = FindMostEncompassingType(operators.Select(op => op.SourceType));
if (mostSpecificSource == null)
return Conversion.None;
IType mostSpecificTarget;
if (operators.Any(op => op.TargetType.Equals(toType)))
mostSpecificTarget = toType;
else if (operators.Any(op => IsEncompassedBy(op.TargetType, toType)))
mostSpecificTarget = FindMostEncompassingType(operators.Where(op => IsEncompassedBy(op.TargetType, toType)).Select(op => op.TargetType));
if (operators.Any(op => op.TargetType.Equals(T0)))
mostSpecificTarget = T0;
else if (operators.Any(op => IsEncompassedBy(op.TargetType, T0)))
mostSpecificTarget = FindMostEncompassingType(operators.Where(op => IsEncompassedBy(op.TargetType, T0)).Select(op => op.TargetType));
else
mostSpecificTarget = FindMostEncompassedType(operators.Select(op => op.TargetType));
if (mostSpecificTarget == null)
@ -863,8 +854,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -863,8 +854,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
public OperatorInfo(IMethod method, IType sourceType, IType targetType, bool isLifted)
{
this.Method = method;
this.SourceType = sourceType;
this.TargetType = targetType;
this.SourceType = NullableType.GetUnderlyingType(sourceType);
this.TargetType = NullableType.GetUnderlyingType(targetType);
this.IsLifted = isLifted;
}
}
@ -893,25 +884,23 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -893,25 +884,23 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
} else {
isApplicable = IsEncompassedBy(fromType, sourceType) && IsEncompassedBy(targetType, toType);
}
if (isApplicable) {
result.Add(new OperatorInfo(op, sourceType, targetType, false));
}
// Try if the operator is applicable in lifted form:
if (NullableType.IsNonNullableValueType(sourceType)
&& NullableType.IsNonNullableValueType(targetType))
{
bool isApplicableInLiftedForm = false;
if (!isApplicable && NullableType.IsNonNullableValueType(sourceType)) {
// We don't bother checking if the operator is applicable in lifted form if it is already
// applicable in normal form, because the normal form will always be preferred.
IType liftedSourceType = NullableType.Create(compilation, sourceType);
IType liftedTargetType = NullableType.Create(compilation, targetType);
IType liftedTargetType = NullableType.IsNonNullableValueType(targetType) ? NullableType.Create(compilation, targetType) : targetType;
if (isExplicit) {
isApplicable = IsEncompassingOrEncompassedBy(fromType, liftedSourceType)
isApplicableInLiftedForm = IsEncompassingOrEncompassedBy(fromType, liftedSourceType)
&& IsEncompassingOrEncompassedBy(liftedTargetType, toType);
} else {
isApplicable = IsEncompassedBy(fromType, liftedSourceType) && IsEncompassedBy(liftedTargetType, toType);
}
if (isApplicable) {
result.Add(new OperatorInfo(op, liftedSourceType, liftedTargetType, true));
isApplicableInLiftedForm = IsEncompassedBy(fromType, liftedSourceType) && IsEncompassedBy(liftedTargetType, toType);
}
}
if (isApplicable || isApplicableInLiftedForm) {
result.Add(new OperatorInfo(op, sourceType, targetType, isApplicableInLiftedForm));
}
}
return result;
}

2
ICSharpCode.NRefactory.CSharp/Resolver/Log.cs

@ -30,7 +30,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -30,7 +30,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
/// </summary>
static class Log
{
const bool logEnabled = false;
const bool logEnabled = true;
#if __MonoCS__
[Conditional("DEBUG")]
#else

100
ICSharpCode.NRefactory.Tests/CSharp/Resolver/ConversionsTest.cs

@ -832,6 +832,66 @@ class Test { @@ -832,6 +832,66 @@ class Test {
Assert.AreEqual("System.Object", c.Method.Parameters.Single().Type.FullName);
}
[Test, Ignore("expression-based user-defined conversions not implemented")]
public void UserDefined_IntLiteral_ViaUInt_ToCustomStruct()
{
string program = @"using System;
struct T {
public static implicit operator T(uint a) { return new T(); }
}
class Test {
static void M() {
T t = $1$;
}
}";
var c = GetConversion(program);
Assert.IsTrue(c.IsValid);
Assert.IsTrue(c.IsUserDefined);
}
[Test]
public void UserDefined_NullLiteral_ViaString_ToCustomStruct()
{
string program = @"using System;
struct T {
public static implicit operator T(string a) { return new T(); }
}
class Test {
static void M() {
T t = $null$;
}
}";
var c = GetConversion(program);
Assert.IsTrue(c.IsValid);
Assert.IsTrue(c.IsUserDefined);
}
[Test]
public void UserDefined_CanUseLiftedEvenIfReturnTypeAlreadyNullable()
{
string program = @"using System;
struct S {
public static implicit operator short?(S s) { return 0; }
}
class Test {
static void M(S? s) {
int? i = $s$;
}
}";
var c = GetConversion(program);
Assert.IsTrue(c.IsValid);
Assert.IsTrue(c.IsUserDefined);
Assert.IsTrue(c.IsLifted);
}
[Test]
public void UserDefinedImplicitConversion_PicksExactSourceTypeIfPossible() {
string program = @"using System;
@ -990,5 +1050,45 @@ class Test { @@ -990,5 +1050,45 @@ class Test {
Assert.IsTrue(c.IsUserDefined);
Assert.AreEqual("ui", c.Method.Parameters[0].Name);
}
[Test]
public void UserDefinedImplicitConversion_UseShortResult_BecauseNullableCannotBeUnpacked()
{
string program = @"using System;
class Test {
public static implicit operator int?(Test i) { return 0; }
public static implicit operator short(Test s) { return 0; }
}
class Program {
public static void Main(string[] args)
{
int x = $new Test()$;
}
}";
var c = GetConversion(program);
Assert.IsTrue(c.IsValid);
Assert.IsTrue(c.IsUserDefined);
Assert.AreEqual("System.Int16", c.Method.ReturnType.FullName);
}
[Test]
public void UserDefinedImplicitConversion_UseShortResult_X()
{
string program = @"using System;
class Test {
public static implicit operator short(Test d) { return 0; }
public static implicit operator byte?(Test d) { return 0; }
}
class Program {
public static void Main(string[] args)
{
int? x = $new Test()$;
}
}";
var c = GetConversion(program);
Assert.IsTrue(c.IsValid);
Assert.IsTrue(c.IsUserDefined);
Assert.AreEqual("System.Int16", c.Method.ReturnType.FullName);
}
}
}

8
ICSharpCode.NRefactory.Tests/CSharp/Resolver/ExplicitConversionsTest.cs

@ -686,7 +686,7 @@ class Test { @@ -686,7 +686,7 @@ class Test {
Assert.IsFalse(rr.Conversion.IsValid);
}
[Test]
[Test, Ignore("We detect an ambiguity where csc and mcs can compile the code; but given that csc and mcs compile the code differently, the ambiguity may be an acceptable choice...")]
public void UserDefinedExplicitConversion_DefinedNullableTakesPrecedenceOverLifted() {
string program = @"using System;
struct Convertible {
@ -698,6 +698,12 @@ class Test { @@ -698,6 +698,12 @@ class Test {
a = $(Convertible?)(int?)33$;
}
}";
// There are three applicable conversions in this test:
// 1) int? -> Convertible? via lifted form of the first user-defined operator
// 2) int? -> Convertible? via second user-defined operator
// 3) int? -> int -> Convertible -> Convertible? (explicit nullable, first user defined, implicit nullable)
// csc picks option 2; mcs picks option 1.
// NRefactory currently fails with an ambiguity between 2 and 3.
var rr = Resolve<ConversionResolveResult>(program);
Assert.IsTrue(rr.Conversion.IsValid);
Assert.IsTrue(rr.Conversion.IsUserDefined);

12
ICSharpCode.NRefactory.Tests/CSharp/Resolver/OverloadResolutionTests.cs

@ -89,6 +89,18 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -89,6 +89,18 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
Assert.AreSame(c1, r.BestCandidate);
}
[Test]
public void PreferUIntOverLong_FromIntLiteral()
{
ResolveResult[] args = { new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), 1) };
OverloadResolution r = new OverloadResolution(compilation, args);
var c1 = MakeMethod(typeof(uint));
Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(c1));
Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(MakeMethod(typeof(long))));
Assert.IsFalse(r.IsAmbiguous);
Assert.AreSame(c1, r.BestCandidate);
}
[Test]
public void NullableIntAndNullableUIntIsAmbiguous()
{

2
ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj

@ -370,7 +370,7 @@ @@ -370,7 +370,7 @@
<Compile Include="CSharp\CodeCompletion\ImportCompletionTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Mono.Cecil\Mono.Cecil.csproj">
<ProjectReference Include="..\..\cecil\Mono.Cecil.csproj">
<Project>{D68133BD-1E63-496E-9EDE-4FBDBF77B486}</Project>
<Name>Mono.Cecil</Name>
<Private>False</Private>

2
ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj

@ -272,7 +272,7 @@ @@ -272,7 +272,7 @@
<Folder Include="Utils\CompositeFormatStringParser\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Mono.Cecil\Mono.Cecil.csproj">
<ProjectReference Include="..\..\cecil\Mono.Cecil.csproj">
<Project>{D68133BD-1E63-496E-9EDE-4FBDBF77B486}</Project>
<Name>Mono.Cecil</Name>
</ProjectReference>

Loading…
Cancel
Save