From e17ba7462a8a32bce1b7cd5a22e47fbf588a7d67 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 7 Aug 2011 14:12:36 +0200 Subject: [PATCH] Add unit tests for TypeSystemAstBuilder. --- ICSharpCode.NRefactory.Demo/CSDemo.cs | 7 +- .../Parser/TypeSystemConvertVisitorTests.cs | 4 +- .../Refactoring/TypeSystemAstBuilderTests.cs | 185 ++++++++++++++++++ .../Documentation/IDStringTests.cs | 7 +- .../ICSharpCode.NRefactory.Tests.csproj | 2 + .../TypeSystem/GetAllBaseTypesTest.cs | 11 ++ .../CSharp/Parser/TypeSystemConvertVisitor.cs | 10 +- .../Refactoring/TypeSystemAstBuilder.cs | 78 +++++--- .../TypeSystem/ExtensionMethods.cs | 3 +- .../SubstitutionTypeReference.cs | 10 + 10 files changed, 276 insertions(+), 41 deletions(-) create mode 100644 ICSharpCode.NRefactory.Tests/CSharp/Refactoring/TypeSystemAstBuilderTests.cs diff --git a/ICSharpCode.NRefactory.Demo/CSDemo.cs b/ICSharpCode.NRefactory.Demo/CSDemo.cs index 8c0f7237e4..8f8a4f5bca 100644 --- a/ICSharpCode.NRefactory.Demo/CSDemo.cs +++ b/ICSharpCode.NRefactory.Demo/CSDemo.cs @@ -182,9 +182,8 @@ namespace ICSharpCode.NRefactory.Demo void ResolveButtonClick(object sender, EventArgs e) { SimpleProjectContent project = new SimpleProjectContent(); - TypeSystemConvertVisitor convertVisitor = new TypeSystemConvertVisitor(project, "dummy.cs"); - compilationUnit.AcceptVisitor(convertVisitor, null); - project.UpdateProjectContent(null, convertVisitor.ParsedFile); + var parsedFile = new TypeSystemConvertVisitor(project, "dummy.cs").Convert(compilationUnit); + project.UpdateProjectContent(null, parsedFile); List projects = new List(); projects.Add(project); @@ -197,7 +196,7 @@ namespace ICSharpCode.NRefactory.Demo if (csharpTreeView.SelectedNode != null) { navigator = new NodeListResolveVisitorNavigator(new[] { (AstNode)csharpTreeView.SelectedNode.Tag }); } - ResolveVisitor visitor = new ResolveVisitor(resolver, convertVisitor.ParsedFile, navigator); + ResolveVisitor visitor = new ResolveVisitor(resolver, parsedFile, navigator); visitor.Scan(compilationUnit); csharpTreeView.BeginUpdate(); ShowResolveResultsInTree(csharpTreeView.Nodes, visitor); diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs index bbd29ef103..8a39a1af39 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs @@ -27,9 +27,7 @@ namespace ICSharpCode.NRefactory.CSharp.Parser } testCasePC = new SimpleProjectContent(); - TypeSystemConvertVisitor visitor = new TypeSystemConvertVisitor(testCasePC, fileName); - cu.AcceptVisitor(visitor, null); - ParsedFile parsedFile = visitor.ParsedFile; + ParsedFile parsedFile = new TypeSystemConvertVisitor(testCasePC, fileName).Convert(cu); parsedFile.Freeze(); testCasePC.UpdateProjectContent(null, parsedFile); } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Refactoring/TypeSystemAstBuilderTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Refactoring/TypeSystemAstBuilderTests.cs new file mode 100644 index 0000000000..ab8e56d281 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/Refactoring/TypeSystemAstBuilderTests.cs @@ -0,0 +1,185 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT license (for details please see \doc\license.txt) + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem.Implementation; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + [TestFixture] + public class TypeSystemAstBuilderTests + { + const string program = @" +using System; +using System.Collections.Generic; +class Base { + public class Nested { } +} +class Derived : Base { } + +namespace NS { + using R = global::System.Reflection; + using L = List; + + class System { } +} +"; + + SimpleProjectContent pc; + ITypeResolveContext ctx; + ITypeDefinition baseClass, derivedClass, nestedClass, systemClass; + ParsedFile parsedFile; + + [SetUp] + public void SetUp() + { + pc = new SimpleProjectContent(); + var cu = new CSharpParser().Parse(new StringReader(program)); + parsedFile = new TypeSystemConvertVisitor(pc, "program.cs").Convert(cu); + pc.UpdateProjectContent(null, parsedFile); + + ctx = new CompositeTypeResolveContext(new[] { pc, CecilLoaderTests.Mscorlib }); + + baseClass = pc.GetTypeDefinition(string.Empty, "Base", 1, StringComparer.Ordinal); + nestedClass = baseClass.NestedTypes.Single(); + derivedClass = pc.GetTypeDefinition(string.Empty, "Derived", 2, StringComparer.Ordinal); + systemClass = pc.GetTypeDefinition("NS", "System", 0, StringComparer.Ordinal); + } + + TypeSystemAstBuilder CreateBuilder(ITypeDefinition currentTypeDef = null) + { + return new TypeSystemAstBuilder( + new CSharpResolver(ctx) { + UsingScope = currentTypeDef != null ? parsedFile.GetUsingScope(currentTypeDef.Region.Begin) : parsedFile.RootUsingScope, + CurrentTypeDefinition = currentTypeDef + }); + } + + string TypeToString(ITypeReference type, ITypeDefinition currentTypeDef = null) + { + var builder = CreateBuilder(currentTypeDef); + IType resolvedType = type.Resolve(ctx); + AstType node = builder.ConvertType(resolvedType); + return node.ToString(); + } + + [Test] + public void PrimitiveVoid() + { + Assert.AreEqual("void", TypeToString(KnownTypeReference.Void)); + } + + [Test] + public void PrimitiveInt() + { + Assert.AreEqual("int", TypeToString(KnownTypeReference.Int32)); + } + + [Test] + public void PrimitiveDecimal() + { + Assert.AreEqual("decimal", TypeToString(KnownTypeReference.Decimal)); + } + + [Test] + public void SystemType() + { + Assert.AreEqual("Type", TypeToString(KnownTypeReference.Type)); + } + + [Test] + public void ListOfNSSystem() + { + var type = new ParameterizedType(ctx.GetTypeDefinition(typeof(List<>)), new[] { systemClass }); + Assert.AreEqual("List", TypeToString(type)); + Assert.AreEqual("List", TypeToString(type, systemClass)); + } + + [Test] + public void NonGenericIEnumerable() + { + Assert.AreEqual("System.Collections.IEnumerable", TypeToString(typeof(IEnumerable).ToTypeReference())); + } + + [Test] + public void NonGenericIEnumerableWithSystemNamespaceCollision() + { + Assert.AreEqual("global::System.Collections.IEnumerable", TypeToString(typeof(IEnumerable).ToTypeReference(), systemClass)); + } + + [Test] + public void AliasedNamespace() + { + var type = typeof(System.Reflection.Assembly).ToTypeReference(); + Assert.AreEqual("R.Assembly", TypeToString(type, systemClass)); + } + + [Test] + public void AliasedType() + { + var type = new ParameterizedTypeReference(ctx.GetTypeDefinition(typeof(List<>)), new[] { KnownTypeReference.Char }); + Assert.AreEqual("List", TypeToString(type)); + Assert.AreEqual("L", TypeToString(type, systemClass)); + } + + [Test] + public void UnboundType() + { + Assert.AreEqual("Base<>", TypeToString(baseClass)); + Assert.AreEqual("Base<>.Nested<>", TypeToString(nestedClass)); + } + + [Test] + public void NestedType() + { + var type = new ParameterizedTypeReference(nestedClass, new[] { KnownTypeReference.Char, KnownTypeReference.String }); + Assert.AreEqual("Base.Nested", TypeToString(type)); + Assert.AreEqual("Base.Nested", TypeToString(type, baseClass)); + Assert.AreEqual("Base.Nested", TypeToString(type, nestedClass)); + Assert.AreEqual("Base.Nested", TypeToString(type, derivedClass)); + } + + [Test] + public void NestedTypeInCurrentClass() + { + var type = new ParameterizedTypeReference(nestedClass, new[] { baseClass.TypeParameters[0], KnownTypeReference.String }); + Assert.AreEqual("Nested", TypeToString(type, baseClass)); + Assert.AreEqual("Nested", TypeToString(type, nestedClass)); + } + + [Test] + public void NestedTypeInDerivedClass() + { + var type1 = new ParameterizedTypeReference(nestedClass, new[] { derivedClass.TypeParameters[0], KnownTypeReference.String }); + Assert.AreEqual("Base.Nested", TypeToString(type1, derivedClass)); + + var type2 = new ParameterizedTypeReference(nestedClass, new[] { derivedClass.TypeParameters[1], KnownTypeReference.String }); + Assert.AreEqual("Nested", TypeToString(type2, derivedClass)); + } + + [Test] + public void MultidimensionalArray() + { + Assert.AreEqual("byte[][,]", TypeToString(typeof(byte[][,]).ToTypeReference())); + } + + [Test] + public void Pointer() + { + Assert.AreEqual("long*", TypeToString(typeof(long*).ToTypeReference())); + } + + [Test] + public void NullableType() + { + Assert.AreEqual("ulong?", TypeToString(typeof(ulong?).ToTypeReference())); + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/Documentation/IDStringTests.cs b/ICSharpCode.NRefactory.Tests/Documentation/IDStringTests.cs index dba7c8e7ef..0cabfdc68c 100644 --- a/ICSharpCode.NRefactory.Tests/Documentation/IDStringTests.cs +++ b/ICSharpCode.NRefactory.Tests/Documentation/IDStringTests.cs @@ -30,11 +30,10 @@ namespace ICSharpCode.NRefactory.Documentation void Init(string program) { pc = new IDStringTestProjectContent(); - CSharpParser parser = new CSharpParser(); - TypeSystemConvertVisitor cv = new TypeSystemConvertVisitor(pc, "program.cs"); - parser.Parse(new StringReader(program)).AcceptVisitor(cv, null); - pc.UpdateProjectContent(null, cv.ParsedFile); + var cu = new CSharpParser().Parse(new StringReader(program)); + var parsedFile = new TypeSystemConvertVisitor(pc, "program.cs").Convert(cu); + pc.UpdateProjectContent(null, parsedFile); } ITypeDefinition GetTypeDefinition(string nameSpace, string name, int typeParameterCount = 0) diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 17716c60e9..3308cc5c08 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -117,6 +117,7 @@ + @@ -165,6 +166,7 @@ + diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/GetAllBaseTypesTest.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/GetAllBaseTypesTest.cs index 37bd3d1fc3..771d3bb49d 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/GetAllBaseTypesTest.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/GetAllBaseTypesTest.cs @@ -166,5 +166,16 @@ namespace ICSharpCode.NRefactory.TypeSystem typeof(IEnumerable<>), typeof(ICollection<>), typeof(IList<>)), typeof(List).ToTypeReference().Resolve(context).GetAllBaseTypeDefinitions(context).OrderBy(t => t.ReflectionName).ToArray()); } + + [Test] + public void BaseTypeDefinitionsOfStringArray() + { + Assert.AreEqual( + GetTypes(typeof(Array), typeof(object), + typeof(ICloneable), typeof(IStructuralComparable), typeof(IStructuralEquatable), + typeof(IList), typeof(ICollection), typeof(IEnumerable), + typeof(IEnumerable<>), typeof(ICollection<>), typeof(IList<>)), + typeof(string[]).ToTypeReference().Resolve(context).GetAllBaseTypeDefinitions(context).OrderBy(t => t.ReflectionName).ToArray()); + } } } diff --git a/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs b/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs index 82ac415300..d385518361 100644 --- a/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs @@ -54,6 +54,12 @@ namespace ICSharpCode.NRefactory.CSharp this.currentTypeDefinition = currentTypeDefinition; } + public ParsedFile Convert(AstNode node) + { + node.AcceptVisitor(this, null); + return parsedFile; + } + public ParsedFile ParsedFile { get { return parsedFile; } } @@ -720,8 +726,8 @@ namespace ICSharpCode.NRefactory.CSharp typeArguments.Add(ConvertType(ta, parentTypeDefinition, parentMethodDefinition, parentUsingScope, lookupMode)); } if (typeArguments.Count == 0 && parentMethodDefinition != null) { - // SimpleTypeOrNamespaceReference doesn't support method type parameters, - // so we directly handle them here. + // SimpleTypeOrNamespaceReference doesn't have a 'current method' context + // so we have to handle method type parameters here. foreach (ITypeParameter tp in parentMethodDefinition.TypeParameters) { if (tp.Name == s.Identifier) return tp; diff --git a/ICSharpCode.NRefactory/CSharp/Refactoring/TypeSystemAstBuilder.cs b/ICSharpCode.NRefactory/CSharp/Refactoring/TypeSystemAstBuilder.cs index 6bd0c98309..f9d9ff18e9 100644 --- a/ICSharpCode.NRefactory/CSharp/Refactoring/TypeSystemAstBuilder.cs +++ b/ICSharpCode.NRefactory/CSharp/Refactoring/TypeSystemAstBuilder.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; - +using System.Linq; using ICSharpCode.NRefactory.CSharp.Resolver; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; @@ -54,10 +54,10 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring ITypeDefinition typeDef = type as ITypeDefinition; if (typeDef != null) { if (typeDef.TypeParameterCount > 0) { - // Create an unbound type + // Unbound type IType[] typeArguments = new IType[typeDef.TypeParameterCount]; for (int i = 0; i < typeArguments.Length; i++) { - typeArguments[i] = SharedTypes.UnknownType; + typeArguments[i] = SharedTypes.UnboundTypeArgument; } return ConvertTypeDefinition(typeDef, typeArguments); } else { @@ -103,14 +103,40 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring return new PrimitiveType("string"); } // There is no type code for System.Void - if (typeDef != null && typeDef.Namespace == "System" && typeDef.Name == "Void" && typeDef.TypeParameterCount == 0) + if (typeDef.Kind == TypeKind.Void) return new PrimitiveType("void"); - if (resolver != null && TypeArgumentsTrivial(typeArguments, OuterTypeParameterCount(typeDef))) { - TypeResolveResult trr = resolver.ResolveSimpleName(typeDef.Name, typeArguments) as TypeResolveResult; - if (trr != null && !trr.IsError && trr.Type.GetDefinition() == typeDef) { + + // The number of type parameters belonging to outer classes + int outerTypeParameterCount; + if (typeDef.DeclaringType != null) + outerTypeParameterCount = typeDef.DeclaringType.TypeParameterCount; + else + outerTypeParameterCount = 0; + + if (resolver != null) { + // Look if there's an alias to the target type + for (UsingScope usingScope = resolver.UsingScope; usingScope != null; usingScope = usingScope.Parent) { + foreach (var pair in usingScope.UsingAliases) { + IType type = pair.Value.Resolve(resolver.Context); + if (TypeMatches(type, typeDef, typeArguments)) + return new SimpleType(pair.Key); + } + } + + IList localTypeArguments; + if (typeDef.TypeParameterCount > outerTypeParameterCount) { + localTypeArguments = new IType[typeDef.TypeParameterCount - outerTypeParameterCount]; + for (int i = 0; i < localTypeArguments.Count; i++) { + localTypeArguments[i] = typeArguments[outerTypeParameterCount + i]; + } + } else { + localTypeArguments = EmptyList.Instance; + } + TypeResolveResult trr = resolver.ResolveSimpleName(typeDef.Name, localTypeArguments) as TypeResolveResult; + if (trr != null && !trr.IsError && TypeMatches(trr.Type, typeDef, typeArguments)) { // We can use the short type name SimpleType shortResult = new SimpleType(typeDef.Name); - AddTypeArguments(shortResult, typeArguments, OuterTypeParameterCount(typeDef), typeDef.TypeParameterCount); + AddTypeArguments(shortResult, typeArguments, outerTypeParameterCount, typeDef.TypeParameterCount); return shortResult; } } @@ -129,33 +155,31 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring } } result.MemberName = typeDef.Name; - AddTypeArguments(result, typeArguments, OuterTypeParameterCount(typeDef), typeDef.TypeParameterCount); + AddTypeArguments(result, typeArguments, outerTypeParameterCount, typeDef.TypeParameterCount); return result; } /// - /// Gets the number of type parameters belonging to outer classes. - /// - int OuterTypeParameterCount(ITypeDefinition typeDef) - { - if (typeDef.DeclaringType != null) - return typeDef.DeclaringType.TypeParameterCount; - else - return 0; - } - - /// - /// Gets whether the first type arguments are trivial, - /// that is, they point to a type parameter with the same index. + /// Gets whether 'type' is the same as 'typeDef' parameterized with the given type arguments. /// - bool TypeArgumentsTrivial(IList typeArguments, int num) + bool TypeMatches(IType type, ITypeDefinition typeDef, IList typeArguments) { - for (int i = 0; i < num; i++) { - ITypeParameter tp = typeArguments[i] as ITypeParameter; - if (!(tp != null && tp.OwnerType == EntityType.TypeDefinition && tp.Index == i)) + if (typeDef.TypeParameterCount == 0) { + return typeDef.Equals(type); + } else { + if (!typeDef.Equals(type.GetDefinition())) return false; + ParameterizedType pt = type as ParameterizedType; + if (pt == null) { + return typeArguments.All(t => t.Kind == TypeKind.UnboundTypeArgument); + } + var ta = pt.TypeArguments; + for (int i = 0; i < ta.Count; i++) { + if (!ta[i].Equals(typeArguments[i])) + return false; + } + return true; } - return true; } /// diff --git a/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs b/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs index 4c5abd66e5..7361166970 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs @@ -170,6 +170,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// Gets whether the type is an delegate type. /// /// This method returns false for System.Delegate itself + [Obsolete("Use type.Kind == TypeKind.Delegate instead")] public static bool IsDelegate(this IType type) { if (type == null) @@ -196,7 +197,7 @@ namespace ICSharpCode.NRefactory.TypeSystem SpecializedMethod m = new SpecializedMethod(method); m.SetDeclaringType(pt); var substitution = pt.GetSubstitution(); - m.SubstituteTypes(t => new SubstitutionTypeReference(t, substitution)); + m.SubstituteTypes(t => SubstitutionTypeReference.Create(t, substitution)); return m; } return method; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/SubstitutionTypeReference.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/SubstitutionTypeReference.cs index aaf38718c9..390f0495bb 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/SubstitutionTypeReference.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/SubstitutionTypeReference.cs @@ -23,6 +23,16 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation this.substitution = substitution; } + public static ITypeReference Create(ITypeReference baseTypeReference, TypeVisitor substitution) + { + IType baseType = baseTypeReference as IType; + if (baseType != null && substitution != null) { + return baseType.AcceptVisitor(substitution); + } else { + return new SubstitutionTypeReference(baseTypeReference, substitution); + } + } + public IType Resolve(ITypeResolveContext context) { return baseTypeReference.Resolve(context).AcceptVisitor(substitution);