From 4c1dbb9adc45df9089681d9e27cd9c0ca6243798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20K=C3=A4ll=C3=A9n?= Date: Mon, 28 Jan 2013 22:17:48 +0100 Subject: [PATCH] Added information about built-in conversions before and after a user-defined conversion operator is applied --- .../Resolver/CSharpConversions.cs | 20 +++++----- .../Resolver/CSharpResolver.cs | 4 +- .../CSharp/Resolver/ConversionsTest.cs | 39 ++++++++++++++++++ .../Resolver/ExplicitConversionsTest.cs | 39 ++++++++++++++++++ .../Semantics/Conversion.cs | 40 +++++++++++++++---- 5 files changed, 123 insertions(+), 19 deletions(-) diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpConversions.cs index cd20d63cbd..23bc27e181 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpConversions.cs @@ -793,22 +793,22 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return best; } - Conversion SelectOperator(IType mostSpecificSource, IType mostSpecificTarget, IList operators, bool isImplicit) + Conversion SelectOperator(IType mostSpecificSource, IType mostSpecificTarget, IList operators, bool isImplicit, IType source, IType target) { var selected = operators.Where(op => op.SourceType.Equals(mostSpecificSource) && op.TargetType.Equals(mostSpecificTarget)).ToList(); if (selected.Count == 0) return Conversion.None; if (selected.Count == 1) - return Conversion.UserDefinedConversion(selected[0].Method, isLifted: selected[0].IsLifted, isImplicit: isImplicit); + return Conversion.UserDefinedConversion(selected[0].Method, isLifted: selected[0].IsLifted, isImplicit: isImplicit, conversionBeforeUserDefinedOperator: ExplicitConversion(source, mostSpecificSource), conversionAfterUserDefinedOperator: ExplicitConversion(mostSpecificTarget, target)); int nNonLifted = selected.Count(s => !s.IsLifted); if (nNonLifted == 1) { var op = selected.First(s => !s.IsLifted); - return Conversion.UserDefinedConversion(op.Method, isLifted: op.IsLifted, isImplicit: isImplicit); + return Conversion.UserDefinedConversion(op.Method, isLifted: op.IsLifted, isImplicit: isImplicit, conversionBeforeUserDefinedOperator: ExplicitConversion(source, mostSpecificSource), conversionAfterUserDefinedOperator: ExplicitConversion(mostSpecificTarget, target)); } - return Conversion.UserDefinedConversion(selected[0].Method, isLifted: selected[0].IsLifted, isImplicit: isImplicit, isAmbiguous: true); + return Conversion.UserDefinedConversion(selected[0].Method, isLifted: selected[0].IsLifted, isImplicit: isImplicit, isAmbiguous: true, conversionBeforeUserDefinedOperator: ExplicitConversion(source, mostSpecificSource), conversionAfterUserDefinedOperator: ExplicitConversion(mostSpecificTarget, target)); } Conversion UserDefinedImplicitConversion(ResolveResult fromResult, IType fromType, IType toType) @@ -819,16 +819,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (operators.Count > 0) { var mostSpecificSource = operators.Any(op => op.SourceType.Equals(fromType)) ? fromType : FindMostEncompassedType(operators.Select(op => op.SourceType)); if (mostSpecificSource == null) - return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: true, isLifted: operators[0].IsLifted, isAmbiguous: true); + return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: true, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); var mostSpecificTarget = operators.Any(op => op.TargetType.Equals(toType)) ? toType : FindMostEncompassingType(operators.Select(op => op.TargetType)); if (mostSpecificTarget == null) { if (NullableType.IsNullable(toType)) return UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType)); else - return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: true, isLifted: operators[0].IsLifted, isAmbiguous: true); + return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: true, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); } - var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators, true); + var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators, true, fromType, toType); if (selected != Conversion.None) { if (selected.IsLifted && NullableType.IsNullable(toType)) { // Prefer A -> B -> B? over A -> A? -> B? @@ -864,7 +864,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver mostSpecificSource = FindMostEncompassingType(operators.Select(op => op.SourceType)); } if (mostSpecificSource == null) - return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: false, isLifted: operators[0].IsLifted, isAmbiguous: true); + return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: false, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); IType mostSpecificTarget; if (operators.Any(op => op.TargetType.Equals(toType))) @@ -877,10 +877,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (NullableType.IsNullable(toType)) return UserDefinedExplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType)); else - return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: false, isLifted: operators[0].IsLifted, isAmbiguous: true); + return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: false, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); } - var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators, false); + var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators, false, fromType, toType); if (selected != Conversion.None) { if (selected.IsLifted && NullableType.IsNullable(toType)) { // Prefer A -> B -> B? over A -> A? -> B? diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs index 58c2b2ece1..20e3b0b8e2 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs @@ -2311,7 +2311,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (!c.IsValid) { var opTrue = input.Type.GetMethods(m => m.IsOperator && m.Name == "op_True").FirstOrDefault(); if (opTrue != null) { - c = Conversion.UserDefinedConversion(opTrue, isImplicit: true); + c = Conversion.UserDefinedConversion(opTrue, isImplicit: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); } } return Convert(input, boolean, c); @@ -2331,7 +2331,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (!c.IsValid) { var opFalse = input.Type.GetMethods(m => m.IsOperator && m.Name == "op_False").FirstOrDefault(); if (opFalse != null) { - c = Conversion.UserDefinedConversion(opFalse, isImplicit: true); + c = Conversion.UserDefinedConversion(opFalse, isImplicit: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); return Convert(input, boolean, c); } } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ConversionsTest.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ConversionsTest.cs index 4f32000037..84680e32fa 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ConversionsTest.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ConversionsTest.cs @@ -1251,5 +1251,44 @@ class Test { Assert.IsTrue(c.IsUserDefined); Assert.IsFalse(c.IsValid); } + + [Test] + public void UserDefinedImplicitConversion_ConversionBeforeUserDefinedOperatorIsCorrect() { + string program = @"using System; +class Convertible { + public static implicit operator Convertible(long l) {return new Convertible(); } +} +class Test { + public void M() { + int i = 33; + Convertible a = $i$; + } +}"; + var c = GetConversion(program); + Assert.IsTrue(c.IsValid); + Assert.IsTrue(c.ConversionBeforeUserDefinedOperator.IsImplicit); + Assert.IsTrue(c.ConversionBeforeUserDefinedOperator.IsNumericConversion); + Assert.IsTrue(c.ConversionBeforeUserDefinedOperator.IsValid); + Assert.IsTrue(c.ConversionAfterUserDefinedOperator.IsIdentityConversion); + } + + [Test] + public void UserDefinedImplicitConversion_ConversionAfterUserDefinedOperatorIsCorrect() { + string program = @"using System; +class Convertible { + public static implicit operator int(Convertible i) {return 0; } +} +class Test { + public void M() { + long a = $new Convertible()$; + } +}"; + var c = GetConversion(program); + Assert.IsTrue(c.IsValid); + Assert.IsTrue(c.ConversionBeforeUserDefinedOperator.IsIdentityConversion); + Assert.IsTrue(c.ConversionAfterUserDefinedOperator.IsImplicit); + Assert.IsTrue(c.ConversionAfterUserDefinedOperator.IsNumericConversion); + Assert.IsTrue(c.ConversionAfterUserDefinedOperator.IsValid); + } } } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ExplicitConversionsTest.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ExplicitConversionsTest.cs index 85ce4bca1f..1c2ae2962b 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ExplicitConversionsTest.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ExplicitConversionsTest.cs @@ -870,5 +870,44 @@ class Test { Assert.IsTrue(rr.Conversion.IsUserDefined); Assert.AreEqual("ci", rr.Conversion.Method.Parameters[0].Name); } + + [Test] + public void UserDefinedExplicitConversion_ConversionBeforeUserDefinedOperatorIsCorrect() { + string program = @"using System; +class Convertible { + public static implicit operator Convertible(int l) {return new Convertible(); } +} +class Test { + public void M() { + long i = 33; + Convertible a = $(Convertible)i$; + } +}"; + var rr = Resolve(program); + Assert.IsTrue(rr.Conversion.IsValid); + Assert.IsTrue(rr.Conversion.ConversionBeforeUserDefinedOperator.IsValid); + Assert.IsTrue(rr.Conversion.ConversionBeforeUserDefinedOperator.IsExplicit); + Assert.IsTrue(rr.Conversion.ConversionBeforeUserDefinedOperator.IsNumericConversion); + Assert.IsTrue(rr.Conversion.ConversionAfterUserDefinedOperator.IsIdentityConversion); + } + + [Test] + public void UserDefinedExplicitConversion_ConversionAfterUserDefinedOperatorIsCorrect() { + string program = @"using System; +class Convertible { + public static implicit operator long(Convertible i) {return 0; } +} +class Test { + public void M() { + int a = $(int)new Convertible()$; + } +}"; + var rr = Resolve(program); + Assert.IsTrue(rr.Conversion.IsValid); + Assert.IsTrue(rr.Conversion.ConversionBeforeUserDefinedOperator.IsIdentityConversion); + Assert.IsTrue(rr.Conversion.ConversionAfterUserDefinedOperator.IsValid); + Assert.IsTrue(rr.Conversion.ConversionAfterUserDefinedOperator.IsExplicit); + Assert.IsTrue(rr.Conversion.ConversionAfterUserDefinedOperator.IsNumericConversion); + } } } diff --git a/ICSharpCode.NRefactory/Semantics/Conversion.cs b/ICSharpCode.NRefactory/Semantics/Conversion.cs index 08bce4f863..c550a27a5a 100644 --- a/ICSharpCode.NRefactory/Semantics/Conversion.cs +++ b/ICSharpCode.NRefactory/Semantics/Conversion.cs @@ -75,26 +75,26 @@ namespace ICSharpCode.NRefactory.Semantics public static readonly Conversion TryCast = new BuiltinConversion(false, 9); [Obsolete("Use UserDefinedConversion() instead")] - public static Conversion UserDefinedImplicitConversion(IMethod operatorMethod, bool isLifted) + public static Conversion UserDefinedImplicitConversion(IMethod operatorMethod, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted) { if (operatorMethod == null) throw new ArgumentNullException("operatorMethod"); - return new UserDefinedConv(true, operatorMethod, isLifted, false); + return new UserDefinedConv(true, operatorMethod, conversionBeforeUserDefinedOperator, conversionAfterUserDefinedOperator, isLifted, false); } [Obsolete("Use UserDefinedConversion() instead")] - public static Conversion UserDefinedExplicitConversion(IMethod operatorMethod, bool isLifted) + public static Conversion UserDefinedExplicitConversion(IMethod operatorMethod, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted) { if (operatorMethod == null) throw new ArgumentNullException("operatorMethod"); - return new UserDefinedConv(false, operatorMethod, isLifted, false); + return new UserDefinedConv(false, operatorMethod, conversionBeforeUserDefinedOperator, conversionAfterUserDefinedOperator, isLifted, false); } - public static Conversion UserDefinedConversion(IMethod operatorMethod, bool isImplicit, bool isLifted = false, bool isAmbiguous = false) + public static Conversion UserDefinedConversion(IMethod operatorMethod, bool isImplicit, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted = false, bool isAmbiguous = false) { if (operatorMethod == null) throw new ArgumentNullException("operatorMethod"); - return new UserDefinedConv(isImplicit, operatorMethod, isLifted, isAmbiguous); + return new UserDefinedConv(isImplicit, operatorMethod, conversionBeforeUserDefinedOperator, conversionAfterUserDefinedOperator, isLifted, isAmbiguous); } public static Conversion MethodGroupConversion(IMethod chosenMethod, bool isVirtualMethodLookup) @@ -275,13 +275,17 @@ namespace ICSharpCode.NRefactory.Semantics { readonly IMethod method; readonly bool isLifted; + readonly Conversion conversionBeforeUserDefinedOperator; + readonly Conversion conversionAfterUserDefinedOperator; readonly bool isImplicit; readonly bool isValid; - public UserDefinedConv(bool isImplicit, IMethod method, bool isLifted, bool isAmbiguous) + public UserDefinedConv(bool isImplicit, IMethod method, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted, bool isAmbiguous) { this.method = method; this.isLifted = isLifted; + this.conversionBeforeUserDefinedOperator = conversionBeforeUserDefinedOperator; + this.conversionAfterUserDefinedOperator = conversionAfterUserDefinedOperator; this.isImplicit = isImplicit; this.isValid = !isAmbiguous; } @@ -306,6 +310,14 @@ namespace ICSharpCode.NRefactory.Semantics get { return true; } } + public override Conversion ConversionBeforeUserDefinedOperator { + get { return conversionBeforeUserDefinedOperator; } + } + + public override Conversion ConversionAfterUserDefinedOperator { + get { return conversionAfterUserDefinedOperator; } + } + public override IMethod Method { get { return method; } } @@ -456,7 +468,21 @@ namespace ICSharpCode.NRefactory.Semantics public virtual bool IsUserDefined { get { return false; } } + + /// + /// The conversion that is applied to the input before the user-defined conversion operator is invoked. + /// + public virtual Conversion ConversionBeforeUserDefinedOperator { + get { return null; } + } + /// + /// The conversion that is applied to the result of the user-defined conversion operator. + /// + public virtual Conversion ConversionAfterUserDefinedOperator { + get { return null; } + } + /// /// Gets whether this conversion is a boxing conversion. ///