Browse Source

Report more types of invalid equality comparisons as errors.

Mark an anonymous function conversion as invalid if there are compiler errors in the implicitly typed lambda.
newNRvisualizers
Daniel Grunwald 14 years ago
parent
commit
77ea4dae30
  1. 25
      ICSharpCode.NRefactory.CSharp/Ast/AstNodeCollection.cs
  2. 2
      ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs
  3. 59
      ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs
  4. 25
      ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs
  5. 57
      ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs
  6. 54
      ICSharpCode.NRefactory.Tests/CSharp/Resolver/BinaryOperatorTests.cs
  7. 53
      ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs
  8. 4
      ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs

25
ICSharpCode.NRefactory.CSharp/Ast/AstNodeCollection.cs

@ -128,6 +128,31 @@ namespace ICSharpCode.NRefactory.CSharp @@ -128,6 +128,31 @@ namespace ICSharpCode.NRefactory.CSharp
item.Remove();
}
/// <summary>
/// Returns the first element for which the predicate returns true,
/// or the null node (AstNode with IsNull=true) if no such object is found.
/// </summary>
public T FirstOrNullObject(Func<T, bool> predicate = null)
{
foreach (T item in this)
if (predicate == null || predicate(item))
return item;
return role.NullObject;
}
/// <summary>
/// Returns the last element for which the predicate returns true,
/// or the null node (AstNode with IsNull=true) if no such object is found.
/// </summary>
public T LastOrNullObject(Func<T, bool> predicate = null)
{
T result = role.NullObject;
foreach (T item in this)
if (predicate == null || predicate(item))
result = item;
return result;
}
bool ICollection<T>.IsReadOnly {
get { return false; }
}

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

@ -38,6 +38,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -38,6 +38,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
/// <summary>
/// Creates a new C# AST resolver.
/// Use this overload if you are resolving within a complete C# file.
/// </summary>
/// <param name="compilation">The current compilation.</param>
/// <param name="parsedFile">
@ -60,6 +61,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -60,6 +61,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
/// <summary>
/// Creates a new C# AST resolver.
/// Use this overload if you are resolving code snippets (not necessarily complete files).
/// </summary>
/// <param name="resolver">The resolver state at the root node (to be more precise: outside the root node).</param>
/// <param name="rootNode">The root node of the resolved tree.</param>

59
ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs

@ -755,42 +755,75 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -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)
));
}
}

25
ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs

@ -703,21 +703,25 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -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 @@ -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);

57
ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs

@ -286,21 +286,28 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -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 @@ -1761,7 +1768,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
IList<ResolveResult> 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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.
/// </summary>
sealed class LambdaTypeHypothesis
sealed class LambdaTypeHypothesis : IResolveVisitorNavigator
{
readonly ImplicitlyTypedLambda lambda;
internal readonly IParameter[] lambdaParameters;
@ -2116,6 +2117,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -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 @@ -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 @@ -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 @@ -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<Expression> returnExpressions, out IList<ResolveResult> returnValues)
void AnalyzeLambda(AstNode body, bool isAsync, out bool isValidAsVoidMethod, out bool isEndpointUnreachable, out IType inferredReturnType, out IList<Expression> returnExpressions, out IList<ResolveResult> returnValues)
{
isEndpointUnreachable = false;
Expression expr = body as Expression;
@ -2329,9 +2345,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -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)

54
ICSharpCode.NRefactory.Tests/CSharp/Resolver/BinaryOperatorTests.cs

@ -299,6 +299,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -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 { @@ -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<OperatorResolveResult>(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 { @@ -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 x) {
var c = $x == null$;
}
}";
var irr = Resolve<OperatorResolveResult>(program);
Assert.IsFalse(irr.IsError);
Assert.AreEqual(compilation.FindType(KnownTypeCode.Boolean), irr.Type);
}
[Test]
public void LiftedEqualityOperator()
{

53
ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs

@ -368,7 +368,7 @@ class TestClass { @@ -368,7 +368,7 @@ class TestClass {
string program = @"using System;
class TestClass {
static void Method<T>(System.Collections.Generic.List<T> list) {
$list.ConvertAll(x => (int)x)$;
$list.ConvertAll(x => (int)(object)x)$;
}
}";
var rr = Resolve<CSharpInvocationResolveResult>(program);
@ -500,18 +500,51 @@ class Test { @@ -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<Action> y) { Console.WriteLine(1); }
static void Foo(object x, Func<Func<int>, int> y) { Console.WriteLine(2); }
static void Main()
{
Foo(null, x => x()); // Prints 1
Foo(null, x => (x())); // Prints 2
}
}
*/
{ ";
var rr = ResolveAtLocation<CSharpInvocationResolveResult>(program + "$Foo(null, x => x()); // Prints 1\n}}");
Assert.IsFalse(rr.IsError);
Assert.AreEqual("System.String", rr.Member.Parameters[0].Type.ReflectionName);
rr = ResolveAtLocation<CSharpInvocationResolveResult>(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<int, bool> f) {}
static void Foo(Func<string, bool> f) {}
static void Main() { $Foo(x => x == ""text"")$; } }";
var rr = Resolve<CSharpInvocationResolveResult>(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<int, bool> f) {}
static void Foo(Func<string, bool> f) {}
static void Main() { $Foo(x => x == 42)$; } }";
var rr = Resolve<CSharpInvocationResolveResult>(program);
Assert.IsFalse(rr.IsError);
var invoke = rr.Member.Parameters.Single().Type.GetDelegateInvokeMethod();
Assert.AreEqual("System.Int32", invoke.Parameters.Single().Type.ReflectionName);
}
}
}

4
ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs

@ -59,6 +59,10 @@ namespace ICSharpCode.NRefactory.Semantics @@ -59,6 +59,10 @@ namespace ICSharpCode.NRefactory.Semantics
get { return targetResult; }
}
/// <summary>
/// Gets the member.
/// This property never returns null.
/// </summary>
public IMember Member {
get { return member; }
}

Loading…
Cancel
Save