Browse Source

A new user-defined operator try

newNRvisualizers
Erik Källén 13 years ago
parent
commit
a1337bc954
  1. 82
      ICSharpCode.NRefactory.CSharp/Resolver/CSharpConversions.cs
  2. 20
      ICSharpCode.NRefactory.Tests/CSharp/Resolver/ConversionsTest.cs
  3. 63
      ICSharpCode.NRefactory.Tests/CSharp/Resolver/ExplicitConversionsTest.cs

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

@ -779,17 +779,22 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -779,17 +779,22 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
return best;
}
OperatorInfo SelectOperator(IType mostSpecificSource, IType mostSpecificTarget, IList<OperatorInfo> operators)
Conversion SelectOperator(IType mostSpecificSource, IType mostSpecificTarget, IList<OperatorInfo> operators, bool isExplicit)
{
Func<OperatorInfo, Conversion> create = op => isExplicit ? Conversion.UserDefinedExplicitConversion(op.Method, op.IsLifted) : Conversion.UserDefinedImplicitConversion(op.Method, op.IsLifted);
var selected = operators.Where(op => op.SourceType.Equals(mostSpecificSource) && op.TargetType.Equals(mostSpecificTarget)).ToList();
if (selected.Count == 0)
return null;
if (selected.Count == 1)
return selected[0];
return create(selected[0]);
int nNonLifted = selected.Count(s => !s.IsLifted);
if (nNonLifted == 1)
return selected.First(s => !s.IsLifted);
return create(selected.First(s => !s.IsLifted));
return null; // Ambiguous or none available
return Conversion.None; // Ambiguous
// 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.
}
@ -800,18 +805,27 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -800,18 +805,27 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
var operators = GetApplicableConversionOperators(fromResult, fromType, toType, false);
if (operators.Count > 0) {
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));
var mostSpecificSource = operators.Any(op => op.SourceType.Equals(fromType)) ? fromType : FindMostEncompassedType(operators.Select(op => op.SourceType));
if (mostSpecificSource == null)
return Conversion.None;
var mostSpecificTarget = operators.Any(op => op.TargetType.Equals(T0)) ? T0 : FindMostEncompassingType(operators.Select(op => op.TargetType));
var mostSpecificTarget = operators.Any(op => op.TargetType.Equals(toType)) ? toType : FindMostEncompassingType(operators.Select(op => op.TargetType));
if (mostSpecificTarget == null)
return Conversion.None;
return NullableType.IsNullable(toType) ? UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType)) : Conversion.None;
var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators);
return selected != null ? Conversion.UserDefinedImplicitConversion(selected.Method, selected.IsLifted) : Conversion.None;
var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators, false);
if (selected != null) {
if (selected.IsLifted && NullableType.IsNullable(toType)) {
// Prefer A -> B -> B? over A -> A? -> B?
var other = UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType));
if (other.IsValid)
return other;
}
return selected;
}
else if (NullableType.IsNullable(toType))
return UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType));
else
return Conversion.None;
}
else {
return Conversion.None;
@ -823,14 +837,11 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -823,14 +837,11 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
// C# 4.0 spec §6.4.5 User-defined implicit conversions
var operators = GetApplicableConversionOperators(fromResult, 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(S0))) {
mostSpecificSource = S0;
if (operators.Any(op => op.SourceType.Equals(fromType))) {
mostSpecificSource = fromType;
} else {
var operatorsWithSourceEncompassingFromType = operators.Where(op => IsEncompassedBy(S0, op.SourceType) || ImplicitConstantExpressionConversion(fromResult, op.SourceType));
var operatorsWithSourceEncompassingFromType = operators.Where(op => IsEncompassedBy(fromType, op.SourceType) || ImplicitConstantExpressionConversion(fromResult, NullableType.GetUnderlyingType(op.SourceType))).ToList();
if (operatorsWithSourceEncompassingFromType.Any())
mostSpecificSource = FindMostEncompassedType(operatorsWithSourceEncompassingFromType.Select(op => op.SourceType));
else
@ -840,17 +851,29 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -840,17 +851,29 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
return Conversion.None;
IType mostSpecificTarget;
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));
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));
else
mostSpecificTarget = FindMostEncompassedType(operators.Select(op => op.TargetType));
if (mostSpecificTarget == null)
return Conversion.None;
return NullableType.IsNullable(toType) ? UserDefinedExplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType)) : Conversion.None;
var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators);
return selected != null ? Conversion.UserDefinedExplicitConversion(selected.Method, selected.IsLifted) : Conversion.None;
var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators, true);
if (selected != null) {
if (selected.IsLifted && NullableType.IsNullable(toType)) {
// Prefer A -> B -> B? over A -> A? -> B?
var other = UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType));
if (other.IsValid)
return other;
}
return selected;
}
else if (NullableType.IsNullable(toType))
return UserDefinedExplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType));
else
return Conversion.None;
}
else {
return Conversion.None;
@ -867,8 +890,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -867,8 +890,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
public OperatorInfo(IMethod method, IType sourceType, IType targetType, bool isLifted)
{
this.Method = method;
this.SourceType = NullableType.GetUnderlyingType(sourceType);
this.TargetType = NullableType.GetUnderlyingType(targetType);
this.SourceType = sourceType;
this.TargetType = targetType;
this.IsLifted = isLifted;
}
}
@ -900,9 +923,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -900,9 +923,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
}
// Try if the operator is applicable in lifted form:
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.
if (NullableType.IsNonNullableValueType(sourceType)) {
// An operator can be applicable in both lifted and non-lifted form in case of explicit conversions
IType liftedSourceType = NullableType.Create(compilation, sourceType);
IType liftedTargetType = NullableType.IsNonNullableValueType(targetType) ? NullableType.Create(compilation, targetType) : targetType;
if (isExplicit) {

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

@ -1045,7 +1045,25 @@ class Test { @@ -1045,7 +1045,25 @@ class Test {
Assert.IsTrue(c.IsUserDefined);
Assert.AreEqual("ui", c.Method.Parameters[0].Name);
}
[Test]
public void UserDefinedImplicitConversion_NullableUIntConstant() {
string program = @"using System;
class Convertible {
public static implicit operator Convertible(long? l) {return new Convertible(); }
public static implicit operator Convertible(uint? ui) {return new Convertible(); }
}
class Test {
public void M() {
Convertible a = $33$;
}
}";
var c = GetConversion(program);
Assert.IsTrue(c.IsValid);
Assert.IsTrue(c.IsUserDefined);
Assert.AreEqual("ui", c.Method.Parameters[0].Name);
}
[Test]
public void UserDefinedImplicitConversion_UseShortResult_BecauseNullableCannotBeUnpacked()
{

63
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, 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...")]
[Test]
public void UserDefinedExplicitConversion_DefinedNullableTakesPrecedenceOverLifted() {
string program = @"using System;
struct Convertible {
@ -698,12 +698,6 @@ class Test { @@ -698,12 +698,6 @@ 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);
@ -728,5 +722,60 @@ class Test { @@ -728,5 +722,60 @@ class Test {
Assert.IsTrue(rr.Conversion.IsUserDefined);
Assert.AreEqual("ui", rr.Conversion.Method.Parameters[0].Name);
}
[Test]
public void UserDefinedExplicitConversion_NullableUIntConstant() {
string program = @"using System;
class Convertible {
public static explicit operator Convertible(long? l) {return new Convertible(); }
public static explicit operator Convertible(uint? ui) {return new Convertible(); }
}
class Test {
public void M() {
Convertible a = $(Convertible)33$;
}
}";
var rr = Resolve<ConversionResolveResult>(program);
Assert.IsTrue(rr.Conversion.IsValid);
Assert.IsTrue(rr.Conversion.IsUserDefined);
Assert.AreEqual("ui", rr.Conversion.Method.Parameters[0].Name);
}
[Test]
public void UseDefinedExplicitConversion_Lifted() {
string program = @"
struct Convertible {
public static explicit operator Convertible(int i) { return new Convertible(); }
}
class Test {
public void M(int? i) {
a = $(Convertible?)i$;
}
}";
var rr = Resolve<ConversionResolveResult>(program);
Assert.IsTrue(rr.Conversion.IsValid);
Assert.IsTrue(rr.Conversion.IsUserDefined);
Assert.IsTrue(rr.Conversion.IsLifted);
Assert.IsTrue(rr.Input is LocalResolveResult);
}
[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);
}
}
}

Loading…
Cancel
Save