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; } }