diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs index 1d260edb4b..3efd3abe88 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs @@ -139,7 +139,12 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// public IType GetExpectedType(Expression expr, CancellationToken cancellationToken = default(CancellationToken)) { - throw new NotImplementedException(); + if (expr == null || expr.IsNull) + throw new ArgumentNullException("expr"); + InitResolver(expr); + lock (resolveVisitor) { + return resolveVisitor.GetConversionWithTargetType(expr).TargetType; + } } /// @@ -147,7 +152,12 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// public Conversion GetConversion(Expression expr, CancellationToken cancellationToken = default(CancellationToken)) { - throw new NotImplementedException(); + if (expr == null || expr.IsNull) + throw new ArgumentNullException("expr"); + InitResolver(expr); + lock (resolveVisitor) { + return resolveVisitor.GetConversionWithTargetType(expr).Conversion; + } } /// diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs index 6107d5136c..543c70aa5a 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpOperators.cs @@ -825,7 +825,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver public override OperatorMethod Lift(CSharpOperators operators) { - return new LiftedBinaryOperatorMethod(operators, this); + var lifted = new LiftedBinaryOperatorMethod(operators, this); + lifted.ReturnType = this.ReturnType; // don't lift the return type for relational operators + return lifted; } } diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs index c0797147e0..a3a3ff2a2a 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs @@ -692,6 +692,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } else if (lhsType is PointerType && rhsType is PointerType) { 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 ==. + return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs); + } + } switch (op) { case BinaryOperatorType.Equality: methodGroup = operators.EqualityOperators; @@ -900,10 +910,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver TypeCode lhsCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(lhs.Type)); TypeCode rhsCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(rhs.Type)); // if one of the inputs is the null literal, promote that to the type of the other operand - if (isNullable && SpecialType.NullType.Equals(lhs.Type)) { + if (isNullable && SpecialType.NullType.Equals(lhs.Type) && rhsCode >= TypeCode.Boolean && rhsCode <= TypeCode.Decimal) { lhs = CastTo(rhsCode, isNullable, lhs, allowNullableConstants); lhsCode = rhsCode; - } else if (isNullable && SpecialType.NullType.Equals(rhs.Type)) { + } else if (isNullable && SpecialType.NullType.Equals(rhs.Type) && lhsCode >= TypeCode.Boolean && lhsCode <= TypeCode.Decimal) { rhs = CastTo(lhsCode, isNullable, rhs, allowNullableConstants); rhsCode = lhsCode; } @@ -1075,9 +1085,13 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver LiftedUserDefinedOperator LiftUserDefinedOperator(IMethod m) { - IType returnType = m.ReturnType; - if (!NullableType.IsNonNullableValueType(returnType)) - return null; // cannot lift this operator + if (IsComparisonOperator(m)) { + if (!m.ReturnType.Equals(compilation.FindType(KnownTypeCode.Boolean))) + return null; // cannot lift this operator + } else { + if (!NullableType.IsNonNullableValueType(m.ReturnType)) + return null; // cannot lift this operator + } for (int i = 0; i < m.Parameters.Count; i++) { if (!NullableType.IsNonNullableValueType(m.Parameters[i].Type)) return null; // cannot lift this operator @@ -1085,6 +1099,23 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return new LiftedUserDefinedOperator(m); } + static bool IsComparisonOperator(IMethod m) + { + var type = OperatorDeclaration.GetOperatorType(m.Name); + if (type.HasValue) { + switch (type.Value) { + case OperatorType.Equality: + case OperatorType.Inequality: + case OperatorType.GreaterThan: + case OperatorType.LessThan: + case OperatorType.GreaterThanOrEqual: + case OperatorType.LessThanOrEqual: + return true; + } + } + return false; + } + sealed class LiftedUserDefinedOperator : SpecializedMethod, OverloadResolution.ILiftedOperator { internal readonly IParameterizedMember nonLiftedOperator; @@ -1094,6 +1125,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver EmptyList.Instance, new MakeNullableVisitor(nonLiftedMethod.Compilation)) { this.nonLiftedOperator = nonLiftedMethod; + // Comparison operators keep the 'bool' return type even when lifted. + if (IsComparisonOperator(nonLiftedMethod)) + this.ReturnType = nonLiftedMethod.ReturnType; } public IList NonLiftedParameters { @@ -1420,12 +1454,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver foreach (var importedNamespace in u.Usings) { ITypeDefinition def = importedNamespace.GetTypeDefinition(identifier, k); if (def != null) { - if (firstResult == null) { - if (parameterizeResultType && k > 0) - firstResult = new ParameterizedType(def, typeArguments); - else - firstResult = def; - } else { + IType resultType; + if (parameterizeResultType && k > 0) + resultType = new ParameterizedType(def, typeArguments); + else + resultType = def; + + if (firstResult == null || !TopLevelTypeDefinitionIsAccessible(firstResult.GetDefinition())) { + firstResult = resultType; + } else if (TopLevelTypeDefinitionIsAccessible(def)) { return new AmbiguousTypeResolveResult(firstResult); } } @@ -1438,6 +1475,14 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return null; } + bool TopLevelTypeDefinitionIsAccessible(ITypeDefinition typeDef) + { + if (typeDef.IsInternal) { + return typeDef.ParentAssembly.InternalsVisibleTo(compilation.MainAssembly); + } + return true; + } + /// /// Looks up an alias (identifier in front of :: operator) /// diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs b/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs index 4676191db3..67864dbde1 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs @@ -58,7 +58,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver // The ResolveVisitor is also responsible for handling lambda expressions. static readonly ResolveResult errorResult = ErrorResolveResult.UnknownError; - static readonly ResolveResult transparentIdentifierResolveResult = new ResolveResult(SpecialType.UnboundTypeArgument); readonly ResolveResult voidResult; CSharpResolver resolver; @@ -68,6 +67,19 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver readonly CSharpParsedFile parsedFile; readonly Dictionary resolveResultCache = new Dictionary(); readonly Dictionary resolverBeforeDict = new Dictionary(); + readonly Dictionary conversionDict = new Dictionary(); + + internal struct ConversionWithTargetType + { + public readonly Conversion Conversion; + public readonly IType TargetType; + + public ConversionWithTargetType(Conversion conversion, IType targetType) + { + this.Conversion = conversion; + this.TargetType = targetType; + } + } IResolveVisitorNavigator navigator; bool resolverEnabled; @@ -294,8 +306,18 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver afc.ExplicitlyTypedLambda.ApplyReturnType(this, afc.ReturnType); Log.Unindent(); } - if (conversion != Conversion.IdentityConversion) + if (conversion != Conversion.IdentityConversion) { navigator.ProcessConversion(expr, rr, conversion, targetType); + conversionDict[expr] = new ConversionWithTargetType(conversion, targetType); + } + } + + void ImportConversions(ResolveVisitor childVisitor) + { + foreach (var pair in childVisitor.conversionDict) { + conversionDict.Add(pair.Key, pair.Value); + navigator.ProcessConversion(pair.Key, resolveResultCache[pair.Key], pair.Value.Conversion, pair.Value.TargetType); + } } /// @@ -462,6 +484,19 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } return null; } + + public ConversionWithTargetType GetConversionWithTargetType(Expression expr) + { + MergeUndecidedLambdas(); + ResolveParentForConversion(expr); + ConversionWithTargetType result; + if (conversionDict.TryGetValue(expr, out result)) { + return result; + } else { + ResolveResult rr = GetResolveResultIfResolved(expr); + return new ConversionWithTargetType(Conversion.IdentityConversion, rr != null ? rr.Type : SpecialType.UnknownType); + } + } #endregion #region Track UsingScope @@ -912,7 +947,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver // 7.6.10.6 Anonymous object creation expressions List properties = new List(); foreach (var expr in anonymousTypeCreateExpression.Initializers) { - Scan(expr); Expression resolveExpr; var name = GetAnonymousTypePropertyName(expr, out resolveExpr); if (!string.IsNullOrEmpty(name)) { @@ -930,7 +964,11 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver properties.Add(property); } } - return new ResolveResult(new AnonymousType(resolver.Compilation, properties)); + IType anonymousType = new AnonymousType(resolver.Compilation, properties); + resolver = resolver.PushInitializerType(anonymousType); + ScanChildren(anonymousTypeCreateExpression); + resolver = resolver.PopInitializerType(); + return new ResolveResult(anonymousType); } sealed class VarTypeReference : ITypeReference @@ -1881,8 +1919,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver Log.WriteLine("Applying return type {0} to explicitly-typed lambda {1}", returnType, this.LambdaExpression); if (isAsync) returnType = parentVisitor.UnpackTask(returnType); - for (int i = 0; i < returnExpressions.Count; i++) { - visitor.ProcessConversion(returnExpressions[i], returnValues[i], returnType); + if (returnType.Kind != TypeKind.Void) { + for (int i = 0; i < returnExpressions.Count; i++) { + visitor.ProcessConversion(returnExpressions[i], returnValues[i], returnType); + } } } @@ -2167,8 +2207,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver Log.WriteLine("Applying return type {0} to implicitly-typed lambda {1}", returnType, lambda.LambdaExpression); if (lambda.IsAsync) returnType = parentVisitor.UnpackTask(returnType); - for (int i = 0; i < returnExpressions.Count; i++) { - visitor.ProcessConversion(returnExpressions[i], returnValues[i], returnType); + if (returnType.Kind != TypeKind.Void) { + for (int i = 0; i < returnExpressions.Count; i++) { + visitor.ProcessConversion(returnExpressions[i], returnValues[i], returnType); + } } visitor.MergeUndecidedLambdas(); @@ -2179,6 +2221,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver foreach (var pair in visitor.resolveResultCache) { parentVisitor.StoreResult(pair.Key, pair.Value); } + parentVisitor.ImportConversions(visitor); parentVisitor.undecidedLambdas.Remove(lambda); } @@ -2218,21 +2261,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver Log.Indent(); while (undecidedLambdas.Count > 0) { LambdaBase lambda = undecidedLambdas[0]; - AstNode parent = lambda.LambdaExpression.Parent; - // Continue going upwards until we find a node that can be resolved and provides - // an expected type. - while (ActsAsParenthesizedExpression(parent) || parent is NamedArgumentExpression || parent is ArrayInitializerExpression) { - parent = parent.Parent; - } - CSharpResolver storedResolver; - if (parent != null && resolverBeforeDict.TryGetValue(parent, out storedResolver)) { - Log.WriteLine("Trying to resolve '" + parent + "' in order to merge the lambda..."); - Log.Indent(); - ResetContext(storedResolver, delegate { Resolve(parent); }); - Log.Unindent(); - } else { - Log.WriteLine("Could not find a suitable parent for '" + lambda); - } + ResolveParentForConversion(lambda.LambdaExpression); if (lambda.IsUndecided) { // Lambda wasn't merged by resolving its parent -> enforce merging Log.WriteLine("Lambda wasn't merged by conversion - enforce merging"); @@ -2243,6 +2272,25 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver Log.WriteLine("MergeUndecidedLambdas() finished."); } + void ResolveParentForConversion(AstNode expression) + { + AstNode parent = expression.Parent; + // Continue going upwards until we find a node that can be resolved and provides + // an expected type. + while (ActsAsParenthesizedExpression(parent) || parent is NamedArgumentExpression || parent is ArrayInitializerExpression) { + parent = parent.Parent; + } + CSharpResolver storedResolver; + if (parent != null && resolverBeforeDict.TryGetValue(parent, out storedResolver)) { + Log.WriteLine("Trying to resolve '" + parent + "' in order to find the conversion applied to '" + expression + "'..."); + Log.Indent(); + ResetContext(storedResolver, delegate { Resolve(parent); }); + Log.Unindent(); + } else { + Log.WriteLine("Could not find a suitable parent for '" + expression); + } + } + internal static bool ActsAsParenthesizedExpression(AstNode expression) { return expression is ParenthesizedExpression || expression is CheckedExpression || expression is UncheckedExpression; @@ -3192,6 +3240,11 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return GetElementTypeFromIEnumerable(type, resolver.Compilation, false); } + ResolveResult MakeTransparentIdentifierResolveResult() + { + return new ResolveResult(new AnonymousType(resolver.Compilation, EmptyList.Instance)); + } + sealed class QueryExpressionLambdaConversion : Conversion { internal readonly IType[] ParameterTypes; @@ -3319,7 +3372,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver selectResult = Resolve(selectClause.Expression); } else { // from .. from ... ... - introduce a transparent identifier - selectResult = transparentIdentifierResolveResult; + selectResult = MakeTransparentIdentifierResolveResult(); } ResolveResult methodGroup = resolver.ResolveMemberAccess(currentQueryResult, "SelectMany", EmptyList.Instance, true); ResolveResult[] arguments = { @@ -3352,7 +3405,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (resolverEnabled && currentQueryResult != null) { // resolve the .Select() call ResolveResult methodGroup = resolver.ResolveMemberAccess(currentQueryResult, "Select", EmptyList.Instance, true); - ResolveResult[] arguments = { new QueryExpressionLambda(1, transparentIdentifierResolveResult) }; + ResolveResult[] arguments = { new QueryExpressionLambda(1, MakeTransparentIdentifierResolveResult()) }; return resolver.ResolveInvocation(methodGroup, arguments); } else { return null; @@ -3405,7 +3458,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver selectResult = Resolve(selectClause.Expression); } else { // from .. join ... ... - introduce a transparent identifier - selectResult = transparentIdentifierResolveResult; + selectResult = MakeTransparentIdentifierResolveResult(); } var methodGroup = resolver.ResolveMemberAccess(currentQueryResult, "Join", EmptyList.Instance); @@ -3444,7 +3497,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver groupJoinLambda = new ImplicitlyTypedLambda(selectClause, selectLambdaParameters, this); } else { // from .. join ... ... - introduce a transparent identifier - groupJoinLambda = new QueryExpressionLambda(2, transparentIdentifierResolveResult); + groupJoinLambda = new QueryExpressionLambda(2, MakeTransparentIdentifierResolveResult()); } ResolveResult[] arguments = { diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs b/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs index 9401d6f136..df04b3c99c 100644 --- a/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs @@ -50,8 +50,8 @@ namespace ICSharpCode.NRefactory.ConsistencyCheck solution.AllFiles.Count(), solution.Projects.Count); - //RunTestOnAllFiles("Roundtripping test", RoundtripTest.RunTest); - RunTestOnAllFiles("Resolver test", ResolverTest.RunTest); + RunTestOnAllFiles("Roundtripping test", RoundtripTest.RunTest); + //RunTestOnAllFiles("Resolver test", ResolverTest.RunTest); Console.Write("Press any key to continue . . . "); Console.ReadKey(true); diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/ResolverTest.cs b/ICSharpCode.NRefactory.ConsistencyCheck/ResolverTest.cs index 3ff1429ef4..a6bfa1570f 100644 --- a/ICSharpCode.NRefactory.ConsistencyCheck/ResolverTest.cs +++ b/ICSharpCode.NRefactory.ConsistencyCheck/ResolverTest.cs @@ -63,7 +63,7 @@ namespace ICSharpCode.NRefactory.ConsistencyCheck { if (!nodesWithConversions.Add(expression)) throw new InvalidOperationException("Duplicate ProcessConversion() call"); - if (conversion == Conversion.None) { + if (!conversion.IsValid) { Console.WriteLine("Compiler error at " + fileName + ":" + expression.StartLocation + ": Cannot convert from " + result + " to " + targetType); } } diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/RoundtripTest.cs b/ICSharpCode.NRefactory.ConsistencyCheck/RoundtripTest.cs index 68befc6a84..e0e61263d2 100644 --- a/ICSharpCode.NRefactory.ConsistencyCheck/RoundtripTest.cs +++ b/ICSharpCode.NRefactory.ConsistencyCheck/RoundtripTest.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Diagnostics; using System.IO; using System.Text; using ICSharpCode.NRefactory.CSharp; @@ -33,8 +34,8 @@ namespace ICSharpCode.NRefactory.ConsistencyCheck { public static void RunTest(CSharpFile file) { - // TODO: also try Windows-style newlines once the parser bug with integer literals followed by \r is fixed string code = file.Content.Text.Replace("\r\n", "\n"); + Debug.Assert(code.IndexOf('\r') < 0); if (code.Contains("#pragma")) return; // skip code with preprocessor directives if (code.Contains("enum VarianceModifier") || file.FileName.EndsWith("ecore.cs") || file.FileName.EndsWith("method.cs")) @@ -43,8 +44,6 @@ namespace ICSharpCode.NRefactory.ConsistencyCheck return; // skip due to optional , at end of array initializer (see ArrayCreateExpressionTests.ArrayInitializerWithCommaAtEnd) if (file.FileName.EndsWith("cs-parser.cs")) return; // skip due to completely messed up comment locations - if (file.FileName.EndsWith("PrimitiveExpressionTests.cs")) - return; // skip due to PrimitiveExpressionTests.*WithLeadingDot if (file.FileName.Contains("FormattingTests") || file.FileName.Contains("ContextAction") || file.FileName.Contains("CodeCompletion")) return; // skip due to AttributeSectionTests.AttributeWithEmptyParenthesis if (file.FileName.EndsWith("TypeSystemTests.TestCase.cs")) @@ -53,8 +52,12 @@ namespace ICSharpCode.NRefactory.ConsistencyCheck return; // skip due to PreprocessorDirectiveTests.NestedInactiveIf if (file.FileName.EndsWith("property.cs")) return; // skip due to PreprocessorDirectiveTests.CommentOnEndOfIfDirective + if (file.FileName.EndsWith("DefaultResolvedTypeDefinition.cs")) + return; // skip due to MethodDeclarationTests.GenericMethodWithMultipleConstraints Roundtrip(file.Project.CreateParser(), file.FileName, code); + // After trying unix-style newlines, also try windows-style newlines: + Roundtrip(file.Project.CreateParser(), file.FileName, code.Replace("\n", "\r\n")); } public static void Roundtrip(CSharpParser parser, string fileName, string code) @@ -86,6 +89,10 @@ namespace ICSharpCode.NRefactory.ConsistencyCheck if (pos2 != generatedCode.Length) throw new InvalidOperationException("Mismatch at end of file " + fileName); + // 3b - validate that there are no lone \r + if (generatedCode.Replace(w.NewLine, "\n").IndexOf('\r') >= 0) + throw new InvalidOperationException(@"Got lone \r in " + fileName); + // 4. Parse generated output CompilationUnit generatedCU; try { diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeMembers/MethodDeclarationTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeMembers/MethodDeclarationTests.cs index b4c36c12e1..c48b7be3b3 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeMembers/MethodDeclarationTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeMembers/MethodDeclarationTests.cs @@ -281,6 +281,30 @@ namespace ICSharpCode.NRefactory.CSharp.Parser.TypeMembers }); } + [Test, Ignore("Parser bug: constraints added in wrong order")] + public void GenericMethodWithMultipleConstraints() + { + ParseUtilCSharp.AssertTypeMember( + "void MyMethod() where A : IA where B : IB {} ", + new MethodDeclaration { + ReturnType = new PrimitiveType("void"), + Name = "MyMethod", + TypeParameters = { + new TypeParameterDeclaration { Name = "A" }, + new TypeParameterDeclaration { Name = "B" } + }, + Constraints = { + new Constraint { + TypeParameter = new SimpleType("A"), + BaseTypes = { new SimpleType("IA") } + }, + new Constraint { + TypeParameter = new SimpleType("B"), + BaseTypes = { new SimpleType("IB") } + } + }}); + } + [Test] public void IncompleteConstraintsTest() { diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs index 5e0f847c08..38567372d5 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs @@ -21,6 +21,7 @@ using System.IO; using System.Linq; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; +using ICSharpCode.NRefactory.TypeSystem.TestCase; using ICSharpCode.NRefactory.Utils; using NUnit.Framework; @@ -60,6 +61,25 @@ namespace ICSharpCode.NRefactory.CSharp.Parser Assert.IsTrue(method.IsExplicitInterfaceImplementation); Assert.AreEqual("System.IDisposable.Dispose", method.InterfaceImplementations.Single().FullName); } + + [Test] + public void ExplicitGenericInterfaceImplementation() + { + ITypeDefinition impl = GetTypeDefinition(typeof(NRefactory.TypeSystem.TestCase.ExplicitGenericInterfaceImplementation)); + IType genericInterfaceOfString = compilation.FindType(typeof(IGenericInterface)); + IMethod implMethod1 = impl.Methods.Single(m => m.Name == "Test" && !m.Parameters[1].IsRef); + IMethod implMethod2 = impl.Methods.Single(m => m.Name == "Test" && m.Parameters[1].IsRef); + Assert.IsTrue(implMethod1.IsExplicitInterfaceImplementation); + Assert.IsTrue(implMethod2.IsExplicitInterfaceImplementation); + + IMethod interfaceMethod1 = (IMethod)implMethod1.InterfaceImplementations.Single(); + Assert.AreEqual(genericInterfaceOfString, interfaceMethod1.DeclaringType); + Assert.IsTrue(!interfaceMethod1.Parameters[1].IsRef); + + IMethod interfaceMethod2 = (IMethod)implMethod2.InterfaceImplementations.Single(); + Assert.AreEqual(genericInterfaceOfString, interfaceMethod2.DeclaringType); + Assert.IsTrue(interfaceMethod2.Parameters[1].IsRef); + } } [TestFixture] diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/AnonymousTypeTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/AnonymousTypeTests.cs new file mode 100644 index 0000000000..4990c0c88e --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/AnonymousTypeTests.cs @@ -0,0 +1,64 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.CSharp.Resolver +{ + [TestFixture] + public class AnonymousTypeTests : ResolverTestBase + { + const string programStart = @"using System; +using System.Collections.Generic; +using System.Linq; +class Test { + void M(IEnumerable list1, IEnumerable list2) { + "; + const string programEnd = " } }"; + + [Test] + public void Zip() + { + string program = programStart + "$var$ q = list1.Zip(list2, (a,b) => new { a, b });" + programEnd; + var rr = Resolve(program); + Assert.AreEqual("System.Collections.Generic.IEnumerable", rr.Type.FullName); + var type = (AnonymousType)((ParameterizedType)rr.Type).TypeArguments.Single(); + Assert.AreEqual(TypeKind.Anonymous, type.Kind); + Assert.AreEqual(2, type.Properties.Count); + Assert.AreEqual("a", type.Properties[0].Name); + Assert.AreEqual("b", type.Properties[1].Name); + Assert.AreEqual("System.String", type.Properties[0].ReturnType.ReflectionName); + Assert.AreEqual("System.Int32", type.Properties[1].ReturnType.ReflectionName); + } + + [Test] + public void ZipItem1() + { + string program = programStart + "var q = list1.Zip(list2, (a,b) => new { $Item1 = a$, Item2 = b });" + programEnd; + var rr = Resolve(program); + Assert.AreEqual(TypeKind.Anonymous, rr.Member.DeclaringType.Kind); + Assert.AreEqual("Item1", rr.Member.Name); + Assert.AreEqual(EntityType.Property, rr.Member.EntityType); + Assert.AreEqual("System.String", rr.Member.ReturnType.FullName); + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/BinaryOperatorTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/BinaryOperatorTests.cs index ec952f48a8..d0536247f2 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/BinaryOperatorTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/BinaryOperatorTests.cs @@ -342,6 +342,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver AssertType(typeof(bool), resolver.ResolveBinaryOperator( BinaryOperatorType.InEquality, MakeResult(typeof(int*)), MakeResult(typeof(uint*)))); + + AssertType(typeof(bool), resolver.ResolveBinaryOperator( + BinaryOperatorType.InEquality, MakeResult(typeof(bool?)), MakeConstant(null))); } [Test] @@ -365,6 +368,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver AssertType(typeof(bool), resolver.ResolveBinaryOperator( BinaryOperatorType.LessThan, MakeResult(typeof(int*)), MakeResult(typeof(uint*)))); + + TestOperator(MakeResult(typeof(int?)), BinaryOperatorType.LessThan, MakeResult(typeof(int)), + Conversion.IdentityConversion, Conversion.ImplicitNullableConversion, typeof(bool)); } [Test] @@ -579,5 +585,37 @@ class Test { Assert.IsNull(irr.UserDefinedOperatorMethod); Assert.AreEqual("System.Byte", irr.Type.ReflectionName); } + + [Test] + public void CompareNullableStructWithNullLiteral() + { + string program = @" +struct X { } +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() + { + string program = @" +struct X { + public static bool operator ==(X a, X b) {} +} +class Test { + static void Inc(X? x) { + var c = $x == x$; + } +}"; + var irr = Resolve(program); + Assert.IsFalse(irr.IsError); + Assert.AreEqual(compilation.FindType(KnownTypeCode.Boolean), irr.Type); + } } } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs index 6bdab5b599..7e54b8c730 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs @@ -392,6 +392,34 @@ class TestClass { Assert.IsFalse(rr.HasParameterList); } + [Test] + public void NonVoidMethodInActionLambdaIsValidConversion() + { + string program = @"using System; +class TestClass { + void Run(Action a) { } + int M() { + Run(() => $M()$); + } +}"; + var c = GetConversion(program); + Assert.IsTrue(c.IsValid); + } + + [Test] + public void NonVoidMethodInImplicitlyTypedActionLambdaIsValidConversion() + { + string program = @"using System; +class TestClass { + void Run(Action a) { } + int M() { + Run(x => $M()$); + } +}"; + var c = GetConversion(program); + Assert.IsTrue(c.IsValid); + } + /* TODO write test for this class A { diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LinqTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LinqTests.cs index f25973fc89..2ef6cc1bcd 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LinqTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LinqTests.cs @@ -17,8 +17,10 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Linq; using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem.Implementation; using NUnit.Framework; namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -333,5 +335,49 @@ class TestClass var rr = Resolve(program); Assert.AreEqual("System.String", rr.Type.FullName); } + + [Test] + public void SelectManyInvocation() + { + string program = @"using System; using System.Linq; +class TestClass +{ + static void M(string[] args) + { + var query = from w in args $from c in w$ select c - '0'; + } +}"; + var rr = Resolve(program); + Assert.IsFalse(rr.IsError); + Assert.AreEqual("SelectMany", rr.Member.Name); + Assert.AreEqual(3, rr.Member.Parameters.Count); + var typeArguments = ((SpecializedMethod)rr.Member).TypeArguments; + Assert.AreEqual(3, typeArguments.Count); + Assert.AreEqual("System.String", typeArguments[0].ReflectionName, "TSource"); + Assert.AreEqual("System.Char", typeArguments[1].ReflectionName, "TCollection"); + Assert.AreEqual("System.Int32", typeArguments[2].ReflectionName, "TResult"); + } + + [Test] + public void SelectManyInvocationWithTransparentIdentifier() + { + string program = @"using System; using System.Linq; +class TestClass +{ + static void M(string[] args) + { + var query = from w in args $from c in w$ orderby c select c - '0'; + } +}"; + var rr = Resolve(program); + Assert.IsFalse(rr.IsError); + Assert.AreEqual("SelectMany", rr.Member.Name); + Assert.AreEqual(3, rr.Member.Parameters.Count); + var typeArguments = ((SpecializedMethod)rr.Member).TypeArguments; + Assert.AreEqual(3, typeArguments.Count); + Assert.AreEqual("System.String", typeArguments[0].ReflectionName, "TSource"); + Assert.AreEqual("System.Char", typeArguments[1].ReflectionName, "TCollection"); + Assert.AreEqual(TypeKind.Anonymous, typeArguments[2].Kind, "TResult"); + } } } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs index 7cba4c32a7..d44cc4f161 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs @@ -159,7 +159,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } } - protected ResolveResult Resolve(string code) + protected Tuple PrepareResolver(string code) { CompilationUnit cu = new CSharpParser().Parse(new StringReader(code.Replace("$", "")), "code.cs"); @@ -176,16 +176,34 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver cu.AcceptVisitor(fnv, null); Assert.IsNotNull(fnv.ResultNode, "Did not find DOM node at the specified location"); + CSharpAstResolver resolver = new CSharpAstResolver(compilation, cu, parsedFile); + return Tuple.Create(resolver, fnv.ResultNode); + } + + protected ResolveResult Resolve(string code) + { + var prep = PrepareResolver(code); Debug.WriteLine(new string('=', 70)); - Debug.WriteLine("Starting new resolver for " + fnv.ResultNode); + Debug.WriteLine("Starting new resolver for " + prep.Item2); - CSharpAstResolver resolver = new CSharpAstResolver(compilation, cu, parsedFile); - ResolveResult rr = resolver.Resolve(fnv.ResultNode); + ResolveResult rr = prep.Item1.Resolve(prep.Item2); Assert.IsNotNull(rr, "ResolveResult is null - did something go wrong while navigating to the target node?"); Debug.WriteLine("ResolveResult is " + rr); return rr; } + protected Conversion GetConversion(string code) + { + var prep = PrepareResolver(code); + return prep.Item1.GetConversion((Expression)prep.Item2); + } + + protected IType GetExpectedType(string code) + { + var prep = PrepareResolver(code); + return prep.Item1.GetExpectedType((Expression)prep.Item2); + } + protected T Resolve(string code) where T : ResolveResult { ResolveResult rr = Resolve(code); diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 4fa3d983ce..dbc2da4f08 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -1,4 +1,4 @@ - + {63D3B27A-D966-4902-90B3-30290E1692F1} @@ -146,6 +146,7 @@ + diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.TestCase.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.TestCase.cs index 74a5fb7718..f9314df9c6 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.TestCase.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.TestCase.cs @@ -166,4 +166,16 @@ namespace ICSharpCode.NRefactory.TypeSystem.TestCase { void IDisposable.Dispose() {} } + + public interface IGenericInterface + { + void Test(T a, S b) where S : T; + void Test(T a, ref S b); + } + + public class ExplicitGenericInterfaceImplementation : IGenericInterface + { + void IGenericInterface.Test(string a, T b) {} + void IGenericInterface.Test(string a, ref T b) {} + } } diff --git a/ICSharpCode.NRefactory/Role.cs b/ICSharpCode.NRefactory/Role.cs index 012dd2bb1a..185a7e505a 100644 --- a/ICSharpCode.NRefactory/Role.cs +++ b/ICSharpCode.NRefactory/Role.cs @@ -48,7 +48,7 @@ namespace ICSharpCode.NRefactory /// /// /// Roles used for non-collections should always have a null object, so that no AST property returns null. - /// However, roles used for collections only may leave out the null object. + /// However, if a role used for collections only, it may leave out the null object. /// public T NullObject { get { return nullObject; } @@ -66,7 +66,7 @@ namespace ICSharpCode.NRefactory this.name = name; } - public Role(string name, T nullObject = null) + public Role(string name, T nullObject) { if (name == null) throw new ArgumentNullException("name"); diff --git a/ICSharpCode.NRefactory/TypeSystem/AnonymousType.cs b/ICSharpCode.NRefactory/TypeSystem/AnonymousType.cs index 1109eea0f9..23fb367a52 100644 --- a/ICSharpCode.NRefactory/TypeSystem/AnonymousType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/AnonymousType.cs @@ -42,7 +42,22 @@ namespace ICSharpCode.NRefactory.TypeSystem this.compilation = compilation; this.unresolvedProperties = properties.ToArray(); var context = new SimpleTypeResolveContext(compilation.MainAssembly); - this.resolvedProperties = new ProjectedList(context, unresolvedProperties, (c, p) => (IProperty)p.CreateResolved(c)); + this.resolvedProperties = new ProjectedList(context, unresolvedProperties, (c, p) => new AnonymousTypeProperty(p, c, this)); + } + + sealed class AnonymousTypeProperty : DefaultResolvedProperty, IEntity + { + readonly AnonymousType declaringType; + + public AnonymousTypeProperty(IUnresolvedProperty unresolved, ITypeResolveContext parentContext, AnonymousType declaringType) + : base(unresolved, parentContext) + { + this.declaringType = declaringType; + } + + IType IEntity.DeclaringType { + get { return declaringType; } + } } public override ITypeReference ToTypeReference() diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultMemberReference.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultMemberReference.cs index e31b64d644..522f716425 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultMemberReference.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultMemberReference.cs @@ -70,7 +70,17 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation if (parameterizedMember == null || parameterizedMember.Parameters.Count == 0) return member; } else if (parameterTypes.Count == parameterizedMember.Parameters.Count) { - + bool signatureMatches = true; + for (int i = 0; i < parameterTypes.Count; i++) { + IType type1 = ParameterListComparer.Instance.NormalizeMethodTypeParameters(resolvedParameterTypes[i]); + IType type2 = ParameterListComparer.Instance.NormalizeMethodTypeParameters(parameterizedMember.Parameters[i].Type); + if (!type1.Equals(type2)) { + signatureMatches = false; + break; + } + } + if (signatureMatches) + return member; } } return null; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMember.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMember.cs index fb81ef7524..94af4875a5 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMember.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMember.cs @@ -92,6 +92,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation public IType ReturnType { get { return returnType; } + protected set { returnType = value; } } public bool IsVirtual {