diff --git a/ICSharpCode.NRefactory.CSharp/Ast/AstNodeCollection.cs b/ICSharpCode.NRefactory.CSharp/Ast/AstNodeCollection.cs
index b34519e8da..4e28849be4 100644
--- a/ICSharpCode.NRefactory.CSharp/Ast/AstNodeCollection.cs
+++ b/ICSharpCode.NRefactory.CSharp/Ast/AstNodeCollection.cs
@@ -128,6 +128,31 @@ namespace ICSharpCode.NRefactory.CSharp
item.Remove();
}
+ ///
+ /// Returns the first element for which the predicate returns true,
+ /// or the null node (AstNode with IsNull=true) if no such object is found.
+ ///
+ public T FirstOrNullObject(Func predicate = null)
+ {
+ foreach (T item in this)
+ if (predicate == null || predicate(item))
+ return item;
+ return role.NullObject;
+ }
+
+ ///
+ /// Returns the last element for which the predicate returns true,
+ /// or the null node (AstNode with IsNull=true) if no such object is found.
+ ///
+ public T LastOrNullObject(Func predicate = null)
+ {
+ T result = role.NullObject;
+ foreach (T item in this)
+ if (predicate == null || predicate(item))
+ result = item;
+ return result;
+ }
+
bool ICollection.IsReadOnly {
get { return false; }
}
diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs
index 756d22ba6a..83e2a051ee 100644
--- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs
+++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs
@@ -38,6 +38,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
///
/// Creates a new C# AST resolver.
+ /// Use this overload if you are resolving within a complete C# file.
///
/// The current compilation.
///
@@ -60,6 +61,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
///
/// Creates a new C# AST resolver.
+ /// Use this overload if you are resolving code snippets (not necessarily complete files).
///
/// The resolver state at the root node (to be more precise: outside the root node).
/// The root node of the resolved tree.
diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs
index 543c70aa5a..3dee9a3de3 100644
--- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs
+++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs
@@ -755,42 +755,75 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
}
// C# 4.0 spec: §7.10 Relational and type-testing operators
- static readonly TypeCode[] equalityOperatorsFor = {
+ static readonly TypeCode[] valueEqualityOperatorsFor = {
TypeCode.Int32, TypeCode.UInt32,
TypeCode.Int64, TypeCode.UInt64,
TypeCode.Single, TypeCode.Double,
TypeCode.Decimal,
- TypeCode.Boolean,
- TypeCode.String, TypeCode.Object
+ TypeCode.Boolean
};
- OperatorMethod[] equalityOperators;
+ OperatorMethod[] valueEqualityOperators;
- public OperatorMethod[] EqualityOperators {
+ public OperatorMethod[] ValueEqualityOperators {
get {
- OperatorMethod[] ops = equalityOperators;
+ OperatorMethod[] ops = valueEqualityOperators;
if (ops != null) {
LazyInit.ReadBarrier();
return ops;
} else {
- return LazyInit.GetOrSet(ref equalityOperators, Lift(
- equalityOperatorsFor.Select(c => new EqualityOperatorMethod(this, c, false)).ToArray()
+ return LazyInit.GetOrSet(ref valueEqualityOperators, Lift(
+ valueEqualityOperatorsFor.Select(c => new EqualityOperatorMethod(this, c, false)).ToArray()
));
}
}
}
- OperatorMethod[] inequalityOperators;
+ OperatorMethod[] valueInequalityOperators;
- public OperatorMethod[] InequalityOperators {
+ public OperatorMethod[] ValueInequalityOperators {
get {
- OperatorMethod[] ops = inequalityOperators;
+ OperatorMethod[] ops = valueInequalityOperators;
if (ops != null) {
LazyInit.ReadBarrier();
return ops;
} else {
- return LazyInit.GetOrSet(ref inequalityOperators, Lift(
- equalityOperatorsFor.Select(c => new EqualityOperatorMethod(this, c, true)).ToArray()
+ return LazyInit.GetOrSet(ref valueInequalityOperators, Lift(
+ valueEqualityOperatorsFor.Select(c => new EqualityOperatorMethod(this, c, true)).ToArray()
+ ));
+ }
+ }
+ }
+
+ OperatorMethod[] referenceEqualityOperators;
+
+ public OperatorMethod[] ReferenceEqualityOperators {
+ get {
+ OperatorMethod[] ops = referenceEqualityOperators;
+ if (ops != null) {
+ LazyInit.ReadBarrier();
+ return ops;
+ } else {
+ return LazyInit.GetOrSet(ref referenceEqualityOperators, Lift(
+ new EqualityOperatorMethod(this, TypeCode.Object, false),
+ new EqualityOperatorMethod(this, TypeCode.String, false)
+ ));
+ }
+ }
+ }
+
+ OperatorMethod[] referenceInequalityOperators;
+
+ public OperatorMethod[] ReferenceInequalityOperators {
+ get {
+ OperatorMethod[] ops = referenceInequalityOperators;
+ if (ops != null) {
+ LazyInit.ReadBarrier();
+ return ops;
+ } else {
+ return LazyInit.GetOrSet(ref referenceInequalityOperators, Lift(
+ new EqualityOperatorMethod(this, TypeCode.Object, true),
+ new EqualityOperatorMethod(this, TypeCode.String, true)
));
}
}
diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs
index bede38b5cc..b524045a11 100644
--- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs
+++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs
@@ -703,21 +703,25 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs);
}
if (op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality) {
- if (lhsType.Kind == TypeKind.Null && NullableType.IsNullable(rhs.Type)
- || rhsType.Kind == TypeKind.Null && NullableType.IsNullable(lhs.Type))
- {
- // §7.10.9 Equality operators and null
- // "x == null", "null == x", "x != null" and "null != x" are valid
- // even if the struct does not define operator ==.
+ if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true) {
+ // If it's a reference comparison
+ if (op == BinaryOperatorType.Equality)
+ methodGroup = operators.ReferenceEqualityOperators;
+ else
+ methodGroup = operators.ReferenceInequalityOperators;
+ break;
+ } else if (lhsType.Kind == TypeKind.Null && IsNullableTypeOrNonValueType(rhs.Type)
+ || IsNullableTypeOrNonValueType(lhs.Type) && rhsType.Kind == TypeKind.Null) {
+ // compare type parameter or nullable type with the null literal
return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs);
}
}
switch (op) {
case BinaryOperatorType.Equality:
- methodGroup = operators.EqualityOperators;
+ methodGroup = operators.ValueEqualityOperators;
break;
case BinaryOperatorType.InEquality:
- methodGroup = operators.InequalityOperators;
+ methodGroup = operators.ValueInequalityOperators;
break;
case BinaryOperatorType.LessThan:
methodGroup = operators.LessThanOperators;
@@ -800,6 +804,11 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
}
}
+ bool IsNullableTypeOrNonValueType(IType type)
+ {
+ return NullableType.IsNullable(type) || type.IsReferenceType != false;
+ }
+
ResolveResult BinaryOperatorResolveResult(IType resultType, ResolveResult lhs, BinaryOperatorType op, ResolveResult rhs)
{
return new OperatorResolveResult(resultType, BinaryOperatorExpression.GetLinqNodeType(op, this.CheckForOverflow), lhs, rhs);
diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs b/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs
index e469cd4e69..a8802127bc 100644
--- a/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs
+++ b/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs
@@ -286,21 +286,28 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
public readonly IType ReturnType;
public readonly ExplicitlyTypedLambda ExplicitlyTypedLambda;
public readonly LambdaTypeHypothesis Hypothesis;
+ readonly bool isValid;
- public AnonymousFunctionConversion(IType returnType, LambdaTypeHypothesis hypothesis)
+ public AnonymousFunctionConversion(IType returnType, LambdaTypeHypothesis hypothesis, bool isValid)
{
if (returnType == null)
throw new ArgumentNullException("returnType");
this.ReturnType = returnType;
this.Hypothesis = hypothesis;
+ this.isValid = isValid;
}
- public AnonymousFunctionConversion(IType returnType, ExplicitlyTypedLambda explicitlyTypedLambda)
+ public AnonymousFunctionConversion(IType returnType, ExplicitlyTypedLambda explicitlyTypedLambda, bool isValid)
{
if (returnType == null)
throw new ArgumentNullException("returnType");
this.ReturnType = returnType;
this.ExplicitlyTypedLambda = explicitlyTypedLambda;
+ this.isValid = isValid;
+ }
+
+ public override bool IsValid {
+ get { return isValid; }
}
public override bool IsImplicit {
@@ -1761,7 +1768,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
IList returnValues;
bool isValidAsVoidMethod;
bool isEndpointUnreachable;
- bool success;
// The actual return type is set when the lambda is applied by the conversion.
IType actualReturnType;
@@ -1822,7 +1828,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
delegate {
var oldNavigator = visitor.navigator;
visitor.navigator = new ConstantModeResolveVisitorNavigator(ResolveVisitorNavigationMode.Resolve, oldNavigator);
- visitor.AnalyzeLambda(body, isAsync, out success, out isValidAsVoidMethod, out isEndpointUnreachable, out inferredReturnType, out returnExpressions, out returnValues);
+ visitor.AnalyzeLambda(body, isAsync, out isValidAsVoidMethod, out isEndpointUnreachable, out inferredReturnType, out returnExpressions, out returnValues);
visitor.navigator = oldNavigator;
});
Log.Unindent();
@@ -1831,7 +1837,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
if (inferredReturnType == null)
throw new InvalidOperationException("AnalyzeLambda() didn't set inferredReturnType");
}
- return success;
+ return true;
}
public override Conversion IsValid(IType[] parameterTypes, IType returnType, Conversions conversions)
@@ -1841,11 +1847,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
bool valid = Analyze() && IsValidLambda(isValidAsVoidMethod, isEndpointUnreachable, isAsync, returnValues, returnType, conversions);
Log.Unindent();
Log.WriteLine("{0} is {1} for return-type {2}", this, valid ? "valid" : "invalid", returnType);
- if (valid) {
- return new AnonymousFunctionConversion(returnType, this);
- } else {
- return Conversion.None;
- }
+ return new AnonymousFunctionConversion(returnType, this, valid);
}
public override IType GetInferredReturnType(IType[] parameterTypes)
@@ -2021,7 +2023,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
return h;
}
ResolveVisitor visitor = new ResolveVisitor(storedContext, parsedFile);
- visitor.SetNavigator(new ConstantModeResolveVisitorNavigator(ResolveVisitorNavigationMode.Resolve, null));
var newHypothesis = new LambdaTypeHypothesis(this, parameterTypes, visitor, lambda != null ? lambda.Parameters : null);
hypotheses.Add(newHypothesis);
return newHypothesis;
@@ -2094,7 +2095,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
/// with the parent ResolveVisitor.
/// This is done when the AnonymousFunctionConversion is applied on the parent visitor.
///
- sealed class LambdaTypeHypothesis
+ sealed class LambdaTypeHypothesis : IResolveVisitorNavigator
{
readonly ImplicitlyTypedLambda lambda;
internal readonly IParameter[] lambdaParameters;
@@ -2116,6 +2117,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
this.lambda = lambda;
this.parameterTypes = parameterTypes;
this.visitor = visitor;
+ visitor.SetNavigator(this);
Log.WriteLine("Analyzing " + ToString() + "...");
Log.Indent();
@@ -2138,12 +2140,29 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
}
}
- visitor.AnalyzeLambda(lambda.BodyExpression, lambda.IsAsync, out success, out isValidAsVoidMethod, out isEndpointUnreachable, out inferredReturnType, out returnExpressions, out returnValues);
+ success = true;
+ visitor.AnalyzeLambda(lambda.BodyExpression, lambda.IsAsync, out isValidAsVoidMethod, out isEndpointUnreachable, out inferredReturnType, out returnExpressions, out returnValues);
visitor.resolver = oldResolver;
Log.Unindent();
Log.WriteLine("Finished analyzing " + ToString());
}
+ ResolveVisitorNavigationMode IResolveVisitorNavigator.Scan(AstNode node)
+ {
+ return ResolveVisitorNavigationMode.Resolve;
+ }
+
+ void IResolveVisitorNavigator.Resolved(AstNode node, ResolveResult result)
+ {
+ if (result.IsError)
+ success = false;
+ }
+
+ void IResolveVisitorNavigator.ProcessConversion(Expression expression, ResolveResult result, Conversion conversion, IType targetType)
+ {
+ success &= conversion.IsValid;
+ }
+
internal int CountUnknownParameters()
{
int c = 0;
@@ -2156,11 +2175,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
public Conversion IsValid(IType returnType, Conversions conversions)
{
- if (success && IsValidLambda(isValidAsVoidMethod, isEndpointUnreachable, lambda.IsAsync, returnValues, returnType, conversions)) {
- return new AnonymousFunctionConversion(returnType, this);
- } else {
- return Conversion.None;
- }
+ bool valid = success && IsValidLambda(isValidAsVoidMethod, isEndpointUnreachable, lambda.IsAsync, returnValues, returnType, conversions);
+ return new AnonymousFunctionConversion(returnType, this, valid);
}
public void MergeInto(ResolveVisitor parentVisitor, IType returnType)
@@ -2288,7 +2304,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
return SpecialType.UnknownType;
}
- void AnalyzeLambda(AstNode body, bool isAsync, out bool success, out bool isValidAsVoidMethod, out bool isEndpointUnreachable, out IType inferredReturnType, out IList returnExpressions, out IList returnValues)
+ void AnalyzeLambda(AstNode body, bool isAsync, out bool isValidAsVoidMethod, out bool isEndpointUnreachable, out IType inferredReturnType, out IList returnExpressions, out IList returnValues)
{
isEndpointUnreachable = false;
Expression expr = body as Expression;
@@ -2329,9 +2345,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
if (isAsync)
inferredReturnType = GetTaskType(inferredReturnType);
Log.WriteLine("Lambda return type was inferred to: " + inferredReturnType);
- // TODO: check for compiler errors within the lambda body
-
- success = true;
}
static bool ExpressionPermittedAsStatement(Expression expr)
diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/BinaryOperatorTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/BinaryOperatorTests.cs
index d0536247f2..4e8d5d23a6 100644
--- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/BinaryOperatorTests.cs
+++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/BinaryOperatorTests.cs
@@ -299,6 +299,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
TestOperator(MakeResult(typeof(int)), BinaryOperatorType.Equality, MakeResult(typeof(float)),
Conversion.ImplicitNumericConversion, Conversion.IdentityConversion, typeof(bool));
+
+ AssertType(typeof(bool), resolver.ResolveBinaryOperator(
+ BinaryOperatorType.Equality, MakeResult(typeof(int)), MakeConstant(null)));
+
+ AssertError(typeof(bool), resolver.ResolveBinaryOperator(
+ BinaryOperatorType.Equality, MakeResult(typeof(int)), MakeResult(typeof(string))));
+
+ AssertError(typeof(bool), resolver.ResolveBinaryOperator(
+ BinaryOperatorType.Equality, MakeResult(typeof(int)), MakeResult(typeof(object))));
}
[Test]
@@ -586,6 +595,37 @@ class Test {
Assert.AreEqual("System.Byte", irr.Type.ReflectionName);
}
+ [Test]
+ public void CompareDateTimeWithNullLiteral()
+ {
+ string program = @"using System;
+class Test {
+ static void Inc(DateTime x) {
+ var c = $x == null$;
+ }
+}";
+ var irr = Resolve(program);
+ Assert.IsFalse(irr.IsError);
+ Assert.IsTrue(irr.IsLiftedOperator);
+ Assert.IsNotNull(irr.UserDefinedOperatorMethod);
+ Assert.AreEqual(compilation.FindType(KnownTypeCode.Boolean), irr.Type);
+ }
+
+ [Test]
+ public void CompareStructWithNullLiteral()
+ {
+ string program = @"
+struct X { }
+class Test {
+ static void Inc(X x) {
+ var c = $x == null$;
+ }
+}";
+ var irr = Resolve(program);
+ Assert.IsTrue(irr.IsError);
+ Assert.AreEqual(compilation.FindType(KnownTypeCode.Boolean), irr.Type);
+ }
+
[Test]
public void CompareNullableStructWithNullLiteral()
{
@@ -601,6 +641,20 @@ class Test {
Assert.AreEqual(compilation.FindType(KnownTypeCode.Boolean), irr.Type);
}
+ [Test]
+ public void CompareUnrestrictedTypeParameterWithNullLiteral()
+ {
+ string program = @"
+class Test {
+ static void Inc(X x) {
+ var c = $x == null$;
+ }
+}";
+ var irr = Resolve(program);
+ Assert.IsFalse(irr.IsError);
+ Assert.AreEqual(compilation.FindType(KnownTypeCode.Boolean), irr.Type);
+ }
+
[Test]
public void LiftedEqualityOperator()
{
diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs
index aa41c7ca76..b66ce93d1f 100644
--- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs
+++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs
@@ -368,7 +368,7 @@ class TestClass {
string program = @"using System;
class TestClass {
static void Method(System.Collections.Generic.List list) {
- $list.ConvertAll(x => (int)x)$;
+ $list.ConvertAll(x => (int)(object)x)$;
}
}";
var rr = Resolve(program);
@@ -500,18 +500,51 @@ class Test {
Assert.AreEqual("propertyExpression", rr.Member.Parameters.Single().Name);
}
- /* TODO write test for this
-class A
-{
+ [Test]
+ public void ParenthesizedExpressionIsNotValidExpressionStatement()
+ {
+ string program = @"using System;
+class A {
static void Foo(string x, Action y) { Console.WriteLine(1); }
static void Foo(object x, Func, int> y) { Console.WriteLine(2); }
static void Main()
- {
- Foo(null, x => x()); // Prints 1
- Foo(null, x => (x())); // Prints 2
- }
-}
- */
+ { ";
+ var rr = ResolveAtLocation(program + "$Foo(null, x => x()); // Prints 1\n}}");
+ Assert.IsFalse(rr.IsError);
+ Assert.AreEqual("System.String", rr.Member.Parameters[0].Type.ReflectionName);
+
+ rr = ResolveAtLocation(program + "$Foo(null, x => (x())); // Prints 2\n}}");
+ Assert.IsFalse(rr.IsError);
+ Assert.AreEqual("System.Object", rr.Member.Parameters[0].Type.ReflectionName);
+ }
+
+ [Test]
+ public void LambdaWithComparisonToString()
+ {
+ string program = @"using System;
+class Test {
+ static void Foo(Func f) {}
+ static void Foo(Func f) {}
+ static void Main() { $Foo(x => x == ""text"")$; } }";
+ var rr = Resolve(program);
+ Assert.IsFalse(rr.IsError);
+ var invoke = rr.Member.Parameters.Single().Type.GetDelegateInvokeMethod();
+ Assert.AreEqual("System.String", invoke.Parameters.Single().Type.ReflectionName);
+ }
+
+ [Test]
+ public void LambdaWithComparisonToInt()
+ {
+ string program = @"using System;
+class Test {
+ static void Foo(Func f) {}
+ static void Foo(Func f) {}
+ static void Main() { $Foo(x => x == 42)$; } }";
+ var rr = Resolve(program);
+ Assert.IsFalse(rr.IsError);
+ var invoke = rr.Member.Parameters.Single().Type.GetDelegateInvokeMethod();
+ Assert.AreEqual("System.Int32", invoke.Parameters.Single().Type.ReflectionName);
+ }
}
}
diff --git a/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs b/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs
index 27e4f5e6eb..ca742b2cb4 100644
--- a/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs
+++ b/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs
@@ -59,6 +59,10 @@ namespace ICSharpCode.NRefactory.Semantics
get { return targetResult; }
}
+ ///
+ /// Gets the member.
+ /// This property never returns null.
+ ///
public IMember Member {
get { return member; }
}