From 9efef610b18befcc639b7f1347be1fb668e4b296 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 10 Oct 2010 18:15:29 +0200 Subject: [PATCH] Rename AggregateTypeResolveContext to CompositeTypeResolveContext. Rename DotNetName to ReflectionName. Added ReflectionName-parser to ReflectionHelper. Move the ReaderWriterLock synchronization from TypeStorage to SimpleProjectContent. Added some documentation to the README. --- .../TypeSystem/CecilLoaderTests.cs | 6 +- .../TypeSystem/GetAllBaseTypesTest.cs | 8 +- .../TypeSystem/ReflectionHelperTests.cs | 48 +++-- .../TypeSystem/TypeSystemTests.cs | 28 +-- .../CSharp/Resolver/CSharpResolver.cs | 2 +- .../CSharp/Resolver/Conversions.cs | 2 +- .../ICSharpCode.NRefactory.csproj | 3 +- .../TypeSystem/CecilLoader.cs | 2 +- .../TypeSystem/INamedElement.cs | 4 +- .../ISynchronizedTypeResolveContext.cs | 3 + ICSharpCode.NRefactory/TypeSystem/IType.cs | 2 +- .../TypeSystem/ITypeResolveContext.cs | 20 ++ .../Implementation/AbstractMember.cs | 6 +- .../TypeSystem/Implementation/AbstractType.cs | 4 +- .../AggregateTypeResolveContext.cs | 98 --------- .../CompositeTypeResolveContext.cs | 148 ++++++++++++++ .../Implementation/DefaultTypeDefinition.cs | 8 +- .../Implementation/DefaultTypeParameter.cs | 2 +- .../Implementation/GetClassTypeReference.cs | 12 ++ .../Implementation/ProxyTypeResolveContext.cs | 7 + .../Implementation/SimpleProjectContent.cs | 156 +++++++++++---- .../TypeSystem/Implementation/TypeStorage.cs | 125 +++++------- .../Implementation/TypeWithElementType.cs | 4 +- .../TypeSystem/ParameterizedType.cs | 8 +- .../TypeSystem/ReflectionHelper.cs | 187 +++++++++++++++++- .../ReflectionNameParseException.cs | 45 +++++ README | 98 +++++++++ 27 files changed, 758 insertions(+), 278 deletions(-) delete mode 100644 ICSharpCode.NRefactory/TypeSystem/Implementation/AggregateTypeResolveContext.cs create mode 100644 ICSharpCode.NRefactory/TypeSystem/Implementation/CompositeTypeResolveContext.cs create mode 100644 ICSharpCode.NRefactory/TypeSystem/ReflectionNameParseException.cs diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs index d2dbe37130..af2cc7c1a7 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs @@ -58,7 +58,7 @@ namespace ICSharpCode.NRefactory.TypeSystem { ITypeDefinition c = Mscorlib.GetClass(typeof(IntPtr)); IMethod toPointer = c.Methods.Single(p => p.Name == "ToPointer"); - Assert.AreEqual("System.Void*", toPointer.ReturnType.Resolve(ctx).DotNetName); + Assert.AreEqual("System.Void*", toPointer.ReturnType.Resolve(ctx).ReflectionName); Assert.IsInstanceOf(typeof(PointerType), toPointer.ReturnType.Resolve(ctx)); Assert.AreEqual("System.Void", ((PointerType)toPointer.ReturnType.Resolve(ctx)).ElementType.FullName); } @@ -107,7 +107,7 @@ namespace ICSharpCode.NRefactory.TypeSystem ITypeDefinition c = Mscorlib.GetClass(typeof(Environment.SpecialFolder)); Assert.IsNotNull(c, "c is null"); Assert.AreEqual("System.Environment.SpecialFolder", c.FullName); - Assert.AreEqual("System.Environment+SpecialFolder", c.DotNetName); + Assert.AreEqual("System.Environment+SpecialFolder", c.ReflectionName); } [Test] @@ -135,7 +135,7 @@ namespace ICSharpCode.NRefactory.TypeSystem var dictionaryRT = new ParameterizedType(dictionary, new[] { Mscorlib.GetClass(typeof(string)), Mscorlib.GetClass(typeof(int)) }); IProperty valueProperty = dictionaryRT.GetProperties(ctx).Single(p => p.Name == "Values"); IType parameterizedValueCollection = valueProperty.ReturnType.Resolve(ctx); - Assert.AreEqual("System.Collections.Generic.Dictionary`2+ValueCollection[[System.String],[System.Int32]]", parameterizedValueCollection.DotNetName); + Assert.AreEqual("System.Collections.Generic.Dictionary`2+ValueCollection[[System.String],[System.Int32]]", parameterizedValueCollection.ReflectionName); Assert.AreSame(valueCollection, parameterizedValueCollection.GetDefinition()); } diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/GetAllBaseTypesTest.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/GetAllBaseTypesTest.cs index 1709c9f145..16e8652be9 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/GetAllBaseTypesTest.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/GetAllBaseTypesTest.cs @@ -18,12 +18,12 @@ namespace ICSharpCode.NRefactory.TypeSystem IType[] GetAllBaseTypes(Type type) { - return type.ToTypeReference().Resolve(context).GetAllBaseTypes(context).OrderBy(t => t.DotNetName).ToArray(); + return type.ToTypeReference().Resolve(context).GetAllBaseTypes(context).OrderBy(t => t.ReflectionName).ToArray(); } IType[] GetTypes(params Type[] types) { - return types.Select(t => t.ToTypeReference().Resolve(context)).OrderBy(t => t.DotNetName).ToArray();; + return types.Select(t => t.ToTypeReference().Resolve(context)).OrderBy(t => t.ReflectionName).ToArray();; } [Test] @@ -85,7 +85,7 @@ namespace ICSharpCode.NRefactory.TypeSystem mscorlib.GetClass(typeof(object)) }; Assert.AreEqual(expected, - c.GetAllBaseTypes(context).OrderBy(t => t.DotNetName).ToArray()); + c.GetAllBaseTypes(context).OrderBy(t => t.ReflectionName).ToArray()); } [Test] @@ -103,7 +103,7 @@ namespace ICSharpCode.NRefactory.TypeSystem mscorlib.GetClass(typeof(ValueType)) }; Assert.AreEqual(expected, - s.GetAllBaseTypes(context).OrderBy(t => t.DotNetName).ToArray()); + s.GetAllBaseTypes(context).OrderBy(t => t.ReflectionName).ToArray()); } } } diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/ReflectionHelperTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/ReflectionHelperTests.cs index d1d6c3b170..f5cb6984b2 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/ReflectionHelperTests.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/ReflectionHelperTests.cs @@ -12,11 +12,13 @@ namespace ICSharpCode.NRefactory.TypeSystem [TestFixture] public class ReflectionHelperTests { + ITypeResolveContext context = CecilLoaderTests.Mscorlib; + void TestGetClass(Type type) { ITypeDefinition t = CecilLoaderTests.Mscorlib.GetClass(type); Assert.NotNull(t, type.FullName); - Assert.AreEqual(type.FullName, t.DotNetName); + Assert.AreEqual(type.FullName, t.ReflectionName); } [Test] @@ -53,25 +55,25 @@ namespace ICSharpCode.NRefactory.TypeSystem public void TestToTypeReferenceInnerClass() { Assert.AreEqual("System.Environment+SpecialFolder", - typeof(Environment.SpecialFolder).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(Environment.SpecialFolder).ToTypeReference().Resolve(context).ReflectionName); } [Test] public void TestToTypeReferenceUnboundGenericClass() { Assert.AreEqual("System.Action`1", - typeof(Action<>).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(Action<>).ToTypeReference().Resolve(context).ReflectionName); Assert.AreEqual("System.Action`2", - typeof(Action<,>).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(Action<,>).ToTypeReference().Resolve(context).ReflectionName); } [Test] public void TestToTypeReferenceBoundGenericClass() { Assert.AreEqual("System.Action`1[[System.String]]", - typeof(Action).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(Action).ToTypeReference().Resolve(context).ReflectionName); Assert.AreEqual("System.Action`2[[System.Int32],[System.Int16]]", - typeof(Action).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(Action).ToTypeReference().Resolve(context).ReflectionName); } @@ -79,56 +81,56 @@ namespace ICSharpCode.NRefactory.TypeSystem public void TestToTypeReferenceNullableType() { Assert.AreEqual("System.Nullable`1[[System.Int32]]", - typeof(int?).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(int?).ToTypeReference().Resolve(context).ReflectionName); } [Test] public void TestToTypeReferenceInnerClassInUnboundGenericType() { Assert.AreEqual("System.Collections.Generic.Dictionary`2+ValueCollection[[`0],[`1]]", - typeof(Dictionary<,>.ValueCollection).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(Dictionary<,>.ValueCollection).ToTypeReference().Resolve(context).ReflectionName); } [Test] public void TestToTypeReferenceInnerClassInBoundGenericType() { Assert.AreEqual("System.Collections.Generic.Dictionary`2+KeyCollection[[System.String],[System.Int32]]", - typeof(Dictionary.KeyCollection).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(Dictionary.KeyCollection).ToTypeReference().Resolve(context).ReflectionName); } [Test] public void TestToTypeReferenceArrayType() { Assert.AreEqual(typeof(int[]).FullName, - typeof(int[]).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(int[]).ToTypeReference().Resolve(context).ReflectionName); } [Test] public void TestToTypeReferenceMultidimensionalArrayType() { Assert.AreEqual(typeof(int[,]).FullName, - typeof(int[,]).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(int[,]).ToTypeReference().Resolve(context).ReflectionName); } [Test] public void TestToTypeReferenceJaggedMultidimensionalArrayType() { Assert.AreEqual(typeof(int[,][,,]).FullName, - typeof(int[,][,,]).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(int[,][,,]).ToTypeReference().Resolve(context).ReflectionName); } [Test] public void TestToTypeReferencePointerType() { Assert.AreEqual(typeof(int*).FullName, - typeof(int*).ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(int*).ToTypeReference().Resolve(context).ReflectionName); } [Test] public void TestToTypeReferenceByReferenceType() { Assert.AreEqual(typeof(int).MakeByRefType().FullName, - typeof(int).MakeByRefType().ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + typeof(int).MakeByRefType().ToTypeReference().Resolve(context).ReflectionName); } [Test] @@ -138,11 +140,23 @@ namespace ICSharpCode.NRefactory.TypeSystem Type parameterType = convertAllInfo.GetParameters()[0].ParameterType; // Converter[[`0],[``0]] // cannot resolve generic types without knowing the parent entity: Assert.AreEqual("System.Converter`2[[?],[?]]", - parameterType.ToTypeReference().Resolve(CecilLoaderTests.Mscorlib).DotNetName); + parameterType.ToTypeReference().Resolve(context).ReflectionName); // now try with parent entity: - IMethod convertAll = CecilLoaderTests.Mscorlib.GetClass(typeof(List<>)).Methods.Single(m => m.Name == "ConvertAll"); + IMethod convertAll = context.GetClass(typeof(List<>)).Methods.Single(m => m.Name == "ConvertAll"); Assert.AreEqual("System.Converter`2[[`0],[``0]]", - parameterType.ToTypeReference(entity: convertAll).Resolve(CecilLoaderTests.Mscorlib).DotNetName); + parameterType.ToTypeReference(entity: convertAll).Resolve(context).ReflectionName); + } + + [Test] + public void ParseReflectionNameSimpleTypes() + { + Assert.AreEqual("System.Int32", ReflectionHelper.ParseReflectionName("System.Int32").Resolve(context).ReflectionName); + Assert.AreEqual("System.Int32&", ReflectionHelper.ParseReflectionName("System.Int32&").Resolve(context).ReflectionName); + Assert.AreEqual("System.Int32*&", ReflectionHelper.ParseReflectionName("System.Int32*&").Resolve(context).ReflectionName); + Assert.AreEqual("System.Int32", ReflectionHelper.ParseReflectionName(typeof(int).AssemblyQualifiedName).Resolve(context).ReflectionName); + Assert.AreEqual("System.Action`1[[System.String]]", ReflectionHelper.ParseReflectionName("System.Action`1[[System.String]]").Resolve(context).ReflectionName); + Assert.AreEqual("System.Action`1[[System.String]]", ReflectionHelper.ParseReflectionName("System.Action`1[[System.String, mscorlib]]").Resolve(context).ReflectionName); + Assert.AreEqual("System.Int32[,,][,]", ReflectionHelper.ParseReflectionName(typeof(int[,][,,]).AssemblyQualifiedName).Resolve(context).ReflectionName); } } } diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs index 93b60b804e..f86449948b 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs @@ -22,7 +22,7 @@ namespace ICSharpCode.NRefactory.TypeSystem [SetUpAttribute] public void SetUp() { - ctx = AggregateTypeResolveContext.Combine(testCasePC, CecilLoaderTests.Mscorlib); + ctx = CompositeTypeResolveContext.Combine(testCasePC, CecilLoaderTests.Mscorlib); } ITypeDefinition GetClass(Type type) @@ -37,7 +37,7 @@ namespace ICSharpCode.NRefactory.TypeSystem Assert.AreEqual(typeof(SimplePublicClass).Name, c.Name); Assert.AreEqual(typeof(SimplePublicClass).FullName, c.FullName); Assert.AreEqual(typeof(SimplePublicClass).Namespace, c.Namespace); - Assert.AreEqual(typeof(SimplePublicClass).FullName, c.DotNetName); + Assert.AreEqual(typeof(SimplePublicClass).FullName, c.ReflectionName); Assert.AreEqual(Accessibility.Public, c.Accessibility); Assert.IsFalse(c.IsAbstract); @@ -77,26 +77,26 @@ namespace ICSharpCode.NRefactory.TypeSystem ITypeDefinition testClass = testCasePC.GetClass(typeof(DynamicTest)); IMethod m1 = testClass.Methods.Single(me => me.Name == "DynamicGenerics1"); - Assert.AreEqual("System.Collections.Generic.List`1[[dynamic]]", m1.ReturnType.Resolve(ctx).DotNetName); - Assert.AreEqual("System.Action`3[[System.Object],[dynamic[]],[System.Object]]", m1.Parameters[0].Type.Resolve(ctx).DotNetName); + Assert.AreEqual("System.Collections.Generic.List`1[[dynamic]]", m1.ReturnType.Resolve(ctx).ReflectionName); + Assert.AreEqual("System.Action`3[[System.Object],[dynamic[]],[System.Object]]", m1.Parameters[0].Type.Resolve(ctx).ReflectionName); IMethod m2 = testClass.Methods.Single(me => me.Name == "DynamicGenerics2"); - Assert.AreEqual("System.Action`3[[System.Object],[dynamic],[System.Object]]", m2.Parameters[0].Type.Resolve(ctx).DotNetName); + Assert.AreEqual("System.Action`3[[System.Object],[dynamic],[System.Object]]", m2.Parameters[0].Type.Resolve(ctx).ReflectionName); IMethod m3 = testClass.Methods.Single(me => me.Name == "DynamicGenerics3"); - Assert.AreEqual("System.Action`3[[System.Int32],[dynamic],[System.Object]]", m3.Parameters[0].Type.Resolve(ctx).DotNetName); + Assert.AreEqual("System.Action`3[[System.Int32],[dynamic],[System.Object]]", m3.Parameters[0].Type.Resolve(ctx).ReflectionName); IMethod m4 = testClass.Methods.Single(me => me.Name == "DynamicGenerics4"); - Assert.AreEqual("System.Action`3[[System.Int32[]],[dynamic],[System.Object]]", m4.Parameters[0].Type.Resolve(ctx).DotNetName); + Assert.AreEqual("System.Action`3[[System.Int32[]],[dynamic],[System.Object]]", m4.Parameters[0].Type.Resolve(ctx).ReflectionName); IMethod m5 = testClass.Methods.Single(me => me.Name == "DynamicGenerics5"); - Assert.AreEqual("System.Action`3[[System.Int32*[]],[dynamic],[System.Object]]", m5.Parameters[0].Type.Resolve(ctx).DotNetName); + Assert.AreEqual("System.Action`3[[System.Int32*[]],[dynamic],[System.Object]]", m5.Parameters[0].Type.Resolve(ctx).ReflectionName); IMethod m6 = testClass.Methods.Single(me => me.Name == "DynamicGenerics6"); - Assert.AreEqual("System.Action`3[[System.Object],[dynamic],[System.Object]]&", m6.Parameters[0].Type.Resolve(ctx).DotNetName); + Assert.AreEqual("System.Action`3[[System.Object],[dynamic],[System.Object]]&", m6.Parameters[0].Type.Resolve(ctx).ReflectionName); IMethod m7 = testClass.Methods.Single(me => me.Name == "DynamicGenerics7"); - Assert.AreEqual("System.Action`3[[System.Int32[][,]],[dynamic],[System.Object]]", m7.Parameters[0].Type.Resolve(ctx).DotNetName); + Assert.AreEqual("System.Action`3[[System.Int32[][,]],[dynamic],[System.Object]]", m7.Parameters[0].Type.Resolve(ctx).ReflectionName); } [Test] @@ -117,7 +117,7 @@ namespace ICSharpCode.NRefactory.TypeSystem Assert.AreEqual("System.Collections.Generic.IDictionary", crt.FullName); Assert.AreEqual("System.String", crt.TypeArguments[0].FullName); // ? for NUnit.TestAttribute (because that assembly isn't in ctx) - Assert.AreEqual("System.Collections.Generic.IList`1[[?]]", crt.TypeArguments[1].DotNetName); + Assert.AreEqual("System.Collections.Generic.IList`1[[?]]", crt.TypeArguments[1].ReflectionName); } [Test] @@ -140,7 +140,7 @@ namespace ICSharpCode.NRefactory.TypeSystem Assert.AreSame(m, m.TypeParameters[0].ParentMethod); Assert.AreSame(m, m.TypeParameters[1].ParentMethod); - Assert.AreEqual("System.IComparable`1[[``1]]", m.TypeParameters[0].Constraints[0].Resolve(ctx).DotNetName); + Assert.AreEqual("System.IComparable`1[[``1]]", m.TypeParameters[0].Constraints[0].Resolve(ctx).ReflectionName); Assert.AreSame(m.TypeParameters[0], m.TypeParameters[1].Constraints[0].Resolve(ctx)); } @@ -202,8 +202,8 @@ namespace ICSharpCode.NRefactory.TypeSystem var e = testCasePC.GetClass(typeof(MyEnum)); Assert.AreEqual(ClassType.Enum, e.ClassType); Assert.AreEqual(false, e.IsReferenceType); - Assert.AreEqual("System.Int16", e.BaseTypes[0].Resolve(ctx).DotNetName); - Assert.AreEqual(new[] { "System.Enum" }, e.GetBaseTypes(ctx).Select(t => t.DotNetName).ToArray()); + Assert.AreEqual("System.Int16", e.BaseTypes[0].Resolve(ctx).ReflectionName); + Assert.AreEqual(new[] { "System.Enum" }, e.GetBaseTypes(ctx).Select(t => t.ReflectionName).ToArray()); } [Test] diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs index 05c2c972a8..5a9eec6be3 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs @@ -178,7 +178,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver get { return string.Empty; } } - string INamedElement.DotNetName { + string INamedElement.ReflectionName { get { return "operator"; } } diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs b/ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs index 3a1f6a9509..0ef43cb060 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs @@ -21,7 +21,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (context == null) throw new ArgumentNullException("context"); this.context = context; - this.objectType = context.GetClass(typeof(object)) ?? SharedTypes.UnknownType; + this.objectType = TypeCode.Object.ToTypeReference().Resolve(context); this.dynamicErasure = new DynamicErasure(this); } diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index db619cb4e5..9b4549c688 100644 --- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -179,7 +179,7 @@ - + @@ -206,6 +206,7 @@ + diff --git a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs index 517b2b99df..0c06377818 100644 --- a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs +++ b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs @@ -48,7 +48,7 @@ namespace ICSharpCode.NRefactory.TypeSystem TypeStorage typeStorage = new TypeStorage(); CecilProjectContent pc = new CecilProjectContent(typeStorage, assemblyDefinition.Name.FullName, assemblyAttributes.AsReadOnly()); - this.EarlyBindContext = AggregateTypeResolveContext.Combine(pc, this.EarlyBindContext); + this.EarlyBindContext = CompositeTypeResolveContext.Combine(pc, this.EarlyBindContext); List types = new List(); foreach (ModuleDefinition module in assemblyDefinition.Modules) { foreach (TypeDefinition td in module.Types) { diff --git a/ICSharpCode.NRefactory/TypeSystem/INamedElement.cs b/ICSharpCode.NRefactory/TypeSystem/INamedElement.cs index 4ea13ced37..a76a4e6d44 100644 --- a/ICSharpCode.NRefactory/TypeSystem/INamedElement.cs +++ b/ICSharpCode.NRefactory/TypeSystem/INamedElement.cs @@ -54,7 +54,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// "System.Collections.Generic.List`1[[System.String]]" for List<string> /// "System.Environment+SpecialFolder" for Environment.SpecialFolder /// - string DotNetName { + string ReflectionName { get; } } @@ -83,7 +83,7 @@ namespace ICSharpCode.NRefactory.TypeSystem } } - string INamedElement.DotNetName { + string INamedElement.ReflectionName { get { Contract.Ensures(!string.IsNullOrEmpty(Contract.Result())); return null; diff --git a/ICSharpCode.NRefactory/TypeSystem/ISynchronizedTypeResolveContext.cs b/ICSharpCode.NRefactory/TypeSystem/ISynchronizedTypeResolveContext.cs index c0de30a2b5..085cb25cb8 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ISynchronizedTypeResolveContext.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ISynchronizedTypeResolveContext.cs @@ -17,6 +17,9 @@ namespace ICSharpCode.NRefactory.TypeSystem /// A simple implementation might enter a ReaderWriterLock when the synchronized context /// is created, and releases the lock when Dispose() is called. /// However, implementations based on immutable data structures are also possible. + /// + /// Calling Synchronize() on an already synchronized context is possible, but has no effect. + /// Only disposing the outermost ISynchronizedTypeResolveContext releases the lock. /// public interface ISynchronizedTypeResolveContext : ITypeResolveContext, IDisposable { diff --git a/ICSharpCode.NRefactory/TypeSystem/IType.cs b/ICSharpCode.NRefactory/TypeSystem/IType.cs index 5f47fcbc0a..23492bc57c 100644 --- a/ICSharpCode.NRefactory/TypeSystem/IType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/IType.cs @@ -178,7 +178,7 @@ namespace ICSharpCode.NRefactory.TypeSystem } } - string INamedElement.DotNetName { + string INamedElement.ReflectionName { get { Contract.Ensures(Contract.Result() != null); return null; diff --git a/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs b/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs index c63abf4503..c2a42172cf 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs @@ -66,6 +66,22 @@ namespace ICSharpCode.NRefactory.TypeSystem /// However, implementations based on immutable data structures are also possible. /// ISynchronizedTypeResolveContext Synchronize(); + + /// + /// Returns an object if caching information based on this resolve context is allowed, + /// or null if caching is not allowed. + /// Whenever the resolve context changes in some way, this property must return a new object. + /// + /// + /// This allows consumers to see whether their cache is still valid by comparing the current + /// CacheToken with the one they saw before. + /// + /// For ISynchronizedTypeResolveContext, this property could be implemented as return this;. + /// However, it is a bad idea to use an object is large or that references a large object graph + /// -- consumers may store a reference to the cache token indefinately, possible extending the + /// lifetime of the ITypeResolveContext. + /// + object CacheToken { get; } } [ContractClassFor(typeof(ITypeResolveContext))] @@ -104,5 +120,9 @@ namespace ICSharpCode.NRefactory.TypeSystem Contract.Ensures(Contract.Result>() != null); return null; } + + object ITypeResolveContext.CacheToken { + get { return null; } + } } } diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractMember.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractMember.cs index a4bb90f8a8..adc85ab49e 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractMember.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractMember.cs @@ -237,13 +237,13 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return declaringTypeDefinition.Namespace; } } - public virtual string DotNetName { - get { return this.DeclaringType.DotNetName + "." + this.Name; } + public virtual string ReflectionName { + get { return this.DeclaringType.ReflectionName + "." + this.Name; } } public override string ToString() { - return "[" + EntityType + " " + DotNetName + ":" + ReturnType + "]"; + return "[" + EntityType + " " + ReflectionName + ":" + ReturnType + "]"; } public virtual void PrepareForInterning(IInterningProvider provider) diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs index c6ee99d70c..e074d43b56 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs @@ -32,7 +32,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return string.Empty; } } - public virtual string DotNetName { + public virtual string ReflectionName { get { return this.FullName; } } @@ -101,7 +101,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation public override string ToString() { - return this.DotNetName; + return this.ReflectionName; } public virtual IType AcceptVisitor(TypeVisitor visitor) diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/AggregateTypeResolveContext.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/AggregateTypeResolveContext.cs deleted file mode 100644 index 6a4d8ca1d3..0000000000 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/AggregateTypeResolveContext.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2010 AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) -// This code is distributed under MIT X11 license (for details please see \doc\license.txt) - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace ICSharpCode.NRefactory.TypeSystem.Implementation -{ - /// - /// Represents multiple type resolve contexts. - /// - public class AggregateTypeResolveContext : ITypeResolveContext - { - /// - /// Creates a that combines the given resolve contexts. - /// If one of the input parameters is null, the other input parameter is returned directly. - /// If both input parameters are null, the function returns null. - /// - public static ITypeResolveContext Combine(ITypeResolveContext a, ITypeResolveContext b) - { - if (a == null) - return b; - if (b == null) - return a; - return new AggregateTypeResolveContext(new [] { a, b }); - } - - readonly ITypeResolveContext[] contexts; - - /// - /// Creates a new - /// - public AggregateTypeResolveContext(IEnumerable contexts) - { - if (contexts == null) - throw new ArgumentNullException("contexts"); - this.contexts = contexts.ToArray(); - } - - /// - public ITypeDefinition GetClass(string fullTypeName, int typeParameterCount, StringComparer nameComparer) - { - foreach (ITypeResolveContext context in contexts) { - ITypeDefinition d = context.GetClass(fullTypeName, typeParameterCount, nameComparer); - if (d != null) - return d; - } - return null; - } - - /// - public IEnumerable GetClasses() - { - return contexts.SelectMany(c => c.GetClasses()); - } - - /// - public IEnumerable GetClasses(string nameSpace, StringComparer nameComparer) - { - return contexts.SelectMany(c => c.GetClasses(nameSpace, nameComparer)); - } - - /// - public IEnumerable GetNamespaces() - { - return contexts.SelectMany(c => c.GetNamespaces()).Distinct(); - } - - /// - public ISynchronizedTypeResolveContext Synchronize() - { - ISynchronizedTypeResolveContext[] sync = new ISynchronizedTypeResolveContext[contexts.Length]; - for (int i = 0; i < sync.Length; i++) { - sync[i] = contexts[i].Synchronize(); - } - return new AggregateSynchronizedTypeResolveContext(sync); - } - - sealed class AggregateSynchronizedTypeResolveContext : AggregateTypeResolveContext, ISynchronizedTypeResolveContext - { - readonly ISynchronizedTypeResolveContext[] sync; - - public AggregateSynchronizedTypeResolveContext(ISynchronizedTypeResolveContext[] sync) - : base(sync) - { - this.sync = sync; - } - - public void Dispose() - { - foreach (var element in sync) { - element.Dispose(); - } - } - } - } -} diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/CompositeTypeResolveContext.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/CompositeTypeResolveContext.cs new file mode 100644 index 0000000000..25534bb86a --- /dev/null +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/CompositeTypeResolveContext.cs @@ -0,0 +1,148 @@ +// Copyright (c) 2010 AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace ICSharpCode.NRefactory.TypeSystem.Implementation +{ + /// + /// Represents multiple type resolve contexts. + /// + public class CompositeTypeResolveContext : ITypeResolveContext + { + /// + /// Creates a that combines the given resolve contexts. + /// If one of the input parameters is null, the other input parameter is returned directly. + /// If both input parameters are null, the function returns null. + /// + public static ITypeResolveContext Combine(ITypeResolveContext a, ITypeResolveContext b) + { + if (a == null) + return b; + if (b == null) + return a; + return new CompositeTypeResolveContext(new [] { a, b }); + } + + readonly ITypeResolveContext[] children; + + /// + /// Creates a new + /// + public CompositeTypeResolveContext(IEnumerable children) + { + if (children == null) + throw new ArgumentNullException("children"); + this.children = children.ToArray(); + foreach (ITypeResolveContext c in this.children) { + if (c == null) + throw new ArgumentException("children enumeration contains nulls"); + } + } + + private CompositeTypeResolveContext(ITypeResolveContext[] children) + { + Debug.Assert(children != null); + this.children = children; + } + + /// + public ITypeDefinition GetClass(string fullTypeName, int typeParameterCount, StringComparer nameComparer) + { + foreach (ITypeResolveContext context in children) { + ITypeDefinition d = context.GetClass(fullTypeName, typeParameterCount, nameComparer); + if (d != null) + return d; + } + return null; + } + + /// + public IEnumerable GetClasses() + { + return children.SelectMany(c => c.GetClasses()); + } + + /// + public IEnumerable GetClasses(string nameSpace, StringComparer nameComparer) + { + return children.SelectMany(c => c.GetClasses(nameSpace, nameComparer)); + } + + /// + public IEnumerable GetNamespaces() + { + return children.SelectMany(c => c.GetNamespaces()).Distinct(); + } + + /// + public virtual ISynchronizedTypeResolveContext Synchronize() + { + return Synchronize(new object()); + } + + ISynchronizedTypeResolveContext Synchronize(object cacheToken) + { + ISynchronizedTypeResolveContext[] sync = new ISynchronizedTypeResolveContext[children.Length]; + bool success = false; + try { + for (int i = 0; i < sync.Length; i++) { + sync[i] = children[i].Synchronize(); + if (sync[i] == null) + throw new InvalidOperationException(children[i] + ".ToString() returned null"); + } + ISynchronizedTypeResolveContext r = new CompositeSynchronizedTypeResolveContext(sync, cacheToken); + success = true; + return r; + } finally { + if (!success) { + // something went wrong, so immediately dispose the contexts we acquired + for (int i = 0; i < sync.Length; i++) { + if (sync[i] != null) + sync[i].Dispose(); + } + } + } + } + + public virtual object CacheToken { + // We don't know if our input contexts are mutable, so, to be on the safe side, + // we don't implement caching here. + get { return null; } + } + + sealed class CompositeSynchronizedTypeResolveContext : CompositeTypeResolveContext, ISynchronizedTypeResolveContext + { + readonly object cacheToken; + + public CompositeSynchronizedTypeResolveContext(ISynchronizedTypeResolveContext[] children, object cacheToken) + : base(children) + { + Debug.Assert(cacheToken != null); + this.cacheToken = cacheToken; + } + + public void Dispose() + { + foreach (ISynchronizedTypeResolveContext element in children) { + element.Dispose(); + } + } + + public override object CacheToken { + // I expect CompositeTypeResolveContext to be used for almost all resolver operations, + // so this is the only place where implementing cacheToken is really important. + get { return cacheToken; } + } + + public override ISynchronizedTypeResolveContext Synchronize() + { + // re-use the same cache token for nested synchronized contexts + return base.Synchronize(cacheToken); + } + } + } +} diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs index bce4411001..4aa8c80cb6 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs @@ -197,15 +197,15 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return this.ns; } } - public string DotNetName { + public string ReflectionName { get { if (declaringTypeDefinition != null) { int tpCount = this.TypeParameterCount - declaringTypeDefinition.TypeParameterCount; string combinedName; if (tpCount > 0) - combinedName = declaringTypeDefinition.DotNetName + "+" + this.Name + "`" + tpCount.ToString(CultureInfo.InvariantCulture); + combinedName = declaringTypeDefinition.ReflectionName + "+" + this.Name + "`" + tpCount.ToString(CultureInfo.InvariantCulture); else - combinedName = declaringTypeDefinition.DotNetName + "+" + this.Name; + combinedName = declaringTypeDefinition.ReflectionName + "+" + this.Name; return combinedName; } else { int tpCount = this.TypeParameterCount; @@ -523,7 +523,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation public override string ToString() { - return DotNetName; + return ReflectionName; } /// diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs index 57c54dec4a..9d098ebff9 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs @@ -71,7 +71,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return string.Empty; } } - public string DotNetName { + public string ReflectionName { get { if (parent is IMethod) return "``" + index.ToString(); diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/GetClassTypeReference.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/GetClassTypeReference.cs index f6cc715f5f..921a43e8fa 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/GetClassTypeReference.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/GetClassTypeReference.cs @@ -25,6 +25,18 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { if (context == null) throw new ArgumentNullException("context"); + // TODO: caching idea: if these lookups are a performance problem and the same GetClassTypeReference + // is asked to resolve lots of times in a row, try the following: + // Give ITypeResolveContext a property "object CacheToken { get; }" which is non-null if the + // context supports caching, and returns the same object only as long as the context is unchanged. + + // Then store a thread-local array KeyValuePair with the last 5 (?) resolved + // types, and a thread-local reference to the cache token. Subsequent calls with the same cache token + // do a quick (reference equality) lookup in the array first. + // This should be faster than any ServiceContainer-based caches. + // It is worth an idea to make CacheToken implement IServiceContainer, so that other (more expensive) + // caches can be registered there, but I think it's troublesome as one ITypeResolveContext should be usable + // on multiple threads. return context.GetClass(fullTypeName, typeParameterCount, StringComparer.Ordinal) ?? SharedTypes.UnknownType; } diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/ProxyTypeResolveContext.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/ProxyTypeResolveContext.cs index 755c9777b4..8ed811d13a 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/ProxyTypeResolveContext.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/ProxyTypeResolveContext.cs @@ -53,5 +53,12 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { return target.Synchronize(); } + + /// + public virtual object CacheToken { + // Don't forward this by default; we don't know what derived classes are doing; + // it might not be cache-safe. + get { return null; } + } } } diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleProjectContent.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleProjectContent.cs index 9d824128d3..5b1da950c2 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleProjectContent.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleProjectContent.cs @@ -5,60 +5,53 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Threading; namespace ICSharpCode.NRefactory.TypeSystem.Implementation { /// /// Simple implementation that stores the list of classes/namespaces. + /// Synchronization is implemented using a . /// /// - /// This is a decorator around that adds support for the IProjectContent interface - /// and for partial classes. + /// Compared with , this class adds support for the IProjectContent interface, + /// for partial classes, and for multi-threading. /// - public class SimpleProjectContent : ProxyTypeResolveContext, IProjectContent + public sealed class SimpleProjectContent : IProjectContent { - readonly TypeStorage types; + // This class is sealed by design: + // the synchronization story doesn't mix well with someone trying to extend this class. + // If you wanted to derive from this: use delegation, not inheritance. - #region Constructors - /// - /// Creates a new SimpleProjectContent. - /// - public SimpleProjectContent() - : this(new TypeStorage()) - { - } - - /// - /// Creates a new SimpleProjectContent that reuses the specified type storage. - /// - protected SimpleProjectContent(TypeStorage types) - : base(types) - { - this.types = types; - this.assemblyAttributes = new List(); - this.readOnlyAssemblyAttributes = assemblyAttributes.AsReadOnly(); - } - #endregion + readonly TypeStorage types = new TypeStorage(); + readonly ReaderWriterLockSlim readerWriterLock = new ReaderWriterLockSlim(); #region AssemblyAttributes - readonly List assemblyAttributes; - readonly ReadOnlyCollection readOnlyAssemblyAttributes; + readonly List assemblyAttributes = new List(); // mutable assembly attribute storage + + volatile IAttribute[] readOnlyAssemblyAttributes = {}; // volatile field with copy for reading threads /// - public virtual IList AssemblyAttributes { + public IList AssemblyAttributes { get { return readOnlyAssemblyAttributes; } } - void AddAssemblyAttributes(ICollection attributes) + void AddRemoveAssemblyAttributes(ICollection addedAttributes, ICollection removedAttributes) { // API uses ICollection instead of IEnumerable to discourage users from evaluating - // the list inside the lock (this method is called inside the ReaderWriterLock) - assemblyAttributes.AddRange(attributes); - } - - void RemoveAssemblyAttributes(ICollection attributes) - { - assemblyAttributes.RemoveAll(attributes.Contains); + // the list inside the lock (this method is called inside the write lock) + bool hasChanges = false; + if (removedAttributes != null && removedAttributes.Count > 0) { + if (assemblyAttributes.RemoveAll(removedAttributes.Contains) > 0) + hasChanges = true; + } + if (addedAttributes != null) { + assemblyAttributes.AddRange(addedAttributes); + hasChanges = true; + } + + if (hasChanges) + readOnlyAssemblyAttributes = assemblyAttributes.ToArray(); } #endregion @@ -96,8 +89,8 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation ICollection oldAssemblyAttributes = null, ICollection newAssemblyAttributes = null) { + readerWriterLock.EnterWriteLock(); try { - types.ReadWriteLock.EnterWriteLock(); if (oldTypes != null) { foreach (var element in oldTypes) { RemoveType(element); @@ -108,12 +101,93 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation AddType(element); } } - if (oldAssemblyAttributes != null) - RemoveAssemblyAttributes(oldAssemblyAttributes); - if (newAssemblyAttributes != null) - AddAssemblyAttributes(newAssemblyAttributes); + AddRemoveAssemblyAttributes(oldAssemblyAttributes, newAssemblyAttributes); + } finally { + readerWriterLock.ExitWriteLock(); + } + } + #endregion + + #region IProjectContent implementation + public ITypeDefinition GetClass(string fullTypeName, int typeParameterCount, StringComparer nameComparer) + { + readerWriterLock.EnterReadLock(); + try { + return types.GetClass(fullTypeName, typeParameterCount, nameComparer); + } finally { + readerWriterLock.ExitReadLock(); + } + } + + public IEnumerable GetClasses() + { + readerWriterLock.EnterReadLock(); + try { + // make a copy with ToArray() for thread-safe access + return types.GetClasses().ToArray(); + } finally { + readerWriterLock.ExitReadLock(); + } + } + + public IEnumerable GetClasses(string nameSpace, StringComparer nameComparer) + { + readerWriterLock.EnterReadLock(); + try { + // make a copy with ToArray() for thread-safe access + return types.GetClasses(nameSpace, nameComparer).ToArray(); + } finally { + readerWriterLock.ExitReadLock(); + } + } + + public IEnumerable GetNamespaces() + { + readerWriterLock.EnterReadLock(); + try { + // make a copy with ToArray() for thread-safe access + return types.GetNamespaces().ToArray(); } finally { - types.ReadWriteLock.ExitWriteLock(); + readerWriterLock.ExitReadLock(); + } + } + #endregion + + #region Synchronization + public object CacheToken { + get { return null; } + } + + public ISynchronizedTypeResolveContext Synchronize() + { + // don't acquire the lock on OutOfMemoryException etc. + ISynchronizedTypeResolveContext sync = new ReadWriteSynchronizedTypeResolveContext(types, readerWriterLock); + readerWriterLock.EnterReadLock(); + return sync; + } + + sealed class ReadWriteSynchronizedTypeResolveContext : ProxyTypeResolveContext, ISynchronizedTypeResolveContext + { + ReaderWriterLockSlim readerWriterLock; + + public ReadWriteSynchronizedTypeResolveContext(ITypeResolveContext target, ReaderWriterLockSlim readerWriterLock) + : base(target) + { + this.readerWriterLock = readerWriterLock; + } + + public void Dispose() + { + if (readerWriterLock != null) { + readerWriterLock.ExitReadLock(); + readerWriterLock = null; + } + } + + public override ISynchronizedTypeResolveContext Synchronize() + { + // nested Synchronize() calls don't need any locking + return new ReadWriteSynchronizedTypeResolveContext(target, null); } } #endregion diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeStorage.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeStorage.cs index 8117a5c03c..ae712b638d 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeStorage.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeStorage.cs @@ -10,16 +10,12 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { /// /// Stores a set of types and allows resolving them. - /// Synchronization is implemented using a . /// + /// + /// Concurrent read accesses are thread-safe, but a write access concurrent to any other access is not safe. + /// public sealed class TypeStorage : ITypeResolveContext { - ReaderWriterLockSlim readWriteLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - - public ReaderWriterLockSlim ReadWriteLock { - get { return readWriteLock; } - } - #region FullNameAndTypeParameterCount struct FullNameAndTypeParameterCount { @@ -57,7 +53,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation #endregion #region Type Dictionary Storage - Dictionary[] _typeDicts = { + volatile Dictionary[] _typeDicts = { new Dictionary(FullNameAndTypeParameterCountComparer.Ordinal) }; readonly object typeDictsLock = new object(); @@ -66,7 +62,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { // Gets the dictionary for the specified comparer, creating it if necessary. // New dictionaries might be added during read accesses, so this method needs to be thread-safe, - // as we allow concurrent read-accesses without locking. + // as we allow concurrent read-accesses. var typeDicts = this._typeDicts; foreach (var dict in typeDicts) { FullNameAndTypeParameterCountComparer comparer = (FullNameAndTypeParameterCountComparer)dict.Comparer; @@ -74,38 +70,33 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation return dict; } - try { - // ensure we have a read-lock on the PC so that no concurrent modifications are attempted - readWriteLock.EnterReadLock(); - // ensure that no other thread can try to lazy-create this (or another) dict - lock (typeDictsLock) { - typeDicts = this._typeDicts; // fetch fresh value after locking - // try looking for it again, maybe it was added while we were waiting for a lock - foreach (var dict in typeDicts) { - FullNameAndTypeParameterCountComparer comparer = (FullNameAndTypeParameterCountComparer)dict.Comparer; - if (comparer.NameComparer == nameComparer) - return dict; - } - - // now create new dict - var oldDict = typeDicts[0]; // Ordinal dict - var newDict = new Dictionary( - oldDict.Count, - new FullNameAndTypeParameterCountComparer(nameComparer)); - foreach (var pair in oldDict) { - // don't use Add() as there might be conflicts in the target language - newDict[pair.Key] = pair.Value; - } - - // add the new dict to the array of dicts - var newTypeDicts = new Dictionary[typeDicts.Length + 1]; - Array.Copy(typeDicts, 0, newTypeDicts, 0, typeDicts.Length); - newTypeDicts[typeDicts.Length] = newDict; - this._typeDicts = newTypeDicts; - return newDict; + // ensure that no other thread can try to lazy-create this (or another) dict + lock (typeDictsLock) { + typeDicts = this._typeDicts; // fetch fresh value after locking + // try looking for it again, maybe it was added while we were waiting for a lock + // (double-checked locking pattern) + foreach (var dict in typeDicts) { + FullNameAndTypeParameterCountComparer comparer = (FullNameAndTypeParameterCountComparer)dict.Comparer; + if (comparer.NameComparer == nameComparer) + return dict; } - } finally { - readWriteLock.ExitReadLock(); + + // now create new dict + var oldDict = typeDicts[0]; // Ordinal dict + var newDict = new Dictionary( + oldDict.Count, + new FullNameAndTypeParameterCountComparer(nameComparer)); + foreach (var pair in oldDict) { + // don't use Add() as there might be conflicts in the target language + newDict[pair.Key] = pair.Value; + } + + // add the new dict to the array of dicts + var newTypeDicts = new Dictionary[typeDicts.Length + 1]; + Array.Copy(typeDicts, 0, newTypeDicts, 0, typeDicts.Length); + newTypeDicts[typeDicts.Length] = newDict; + this._typeDicts = newTypeDicts; + return newDict; } } #endregion @@ -146,31 +137,21 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation #endregion #region Synchronize - /// + /// + /// TypeStorage is mutable and does not provide any means for synchronization, so this method + /// always throws a . + /// public ISynchronizedTypeResolveContext Synchronize() { - readWriteLock.EnterReadLock(); - return new ReadWriteSynchronizedTypeResolveContext(this, readWriteLock); + throw new NotSupportedException(); } - sealed class ReadWriteSynchronizedTypeResolveContext : ProxyTypeResolveContext, ISynchronizedTypeResolveContext - { - readonly ReaderWriterLockSlim readWriteLock; - bool disposed; - - public ReadWriteSynchronizedTypeResolveContext(ITypeResolveContext context, ReaderWriterLockSlim readWriteLock) - : base(context) - { - this.readWriteLock = readWriteLock; - } - - public void Dispose() - { - if (!disposed) { - disposed = true; - readWriteLock.ExitReadLock(); - } - } + /// + public object CacheToken { + // TypeStorage is mutable, so caching is a bad idea. + // We could provide a CacheToken if we update it on every modication, but + // that's not worth the effort as TypeStorage is rarely directly used in resolve operations. + get { return null; } } #endregion @@ -182,14 +163,9 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { if (fullName == null) throw new ArgumentNullException("fullName"); - try { - readWriteLock.EnterWriteLock(); - var key = new FullNameAndTypeParameterCount(fullName, typeParameterCount); - foreach (var dict in _typeDicts) { - dict.Remove(key); - } - } finally { - readWriteLock.ExitWriteLock(); + var key = new FullNameAndTypeParameterCount(fullName, typeParameterCount); + foreach (var dict in _typeDicts) { + dict.Remove(key); } } #endregion @@ -203,14 +179,9 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { if (typeDefinition == null) throw new ArgumentNullException("typeDefinition"); - try { - readWriteLock.EnterWriteLock(); - var key = new FullNameAndTypeParameterCount(typeDefinition.FullName, typeDefinition.TypeParameterCount); - foreach (var dict in _typeDicts) { - dict[key] = typeDefinition; - } - } finally { - readWriteLock.ExitWriteLock(); + var key = new FullNameAndTypeParameterCount(typeDefinition.FullName, typeDefinition.TypeParameterCount); + foreach (var dict in _typeDicts) { + dict[key] = typeDefinition; } } #endregion diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs index f1a7393456..7f4830555a 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs @@ -28,8 +28,8 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return elementType.FullName + NameSuffix; } } - public override string DotNetName { - get { return elementType.DotNetName + NameSuffix; } + public override string ReflectionName { + get { return elementType.ReflectionName + NameSuffix; } } public abstract string NameSuffix { get; } diff --git a/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs b/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs index aa884634da..f224b601b5 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs @@ -115,15 +115,15 @@ namespace ICSharpCode.NRefactory.TypeSystem get { return genericType.Namespace;} } - public string DotNetName { + public string ReflectionName { get { - StringBuilder b = new StringBuilder(genericType.DotNetName); + StringBuilder b = new StringBuilder(genericType.ReflectionName); b.Append('['); for (int i = 0; i < typeArguments.Length; i++) { if (i > 0) b.Append(','); b.Append('['); - b.Append(typeArguments[i].DotNetName); + b.Append(typeArguments[i].ReflectionName); b.Append(']'); } b.Append(']'); @@ -133,7 +133,7 @@ namespace ICSharpCode.NRefactory.TypeSystem public override string ToString() { - return DotNetName; + return ReflectionName; } public ReadOnlyCollection TypeArguments { diff --git a/ICSharpCode.NRefactory/TypeSystem/ReflectionHelper.cs b/ICSharpCode.NRefactory/TypeSystem/ReflectionHelper.cs index 78df709263..1ba0e104c8 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ReflectionHelper.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ReflectionHelper.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Text; using ICSharpCode.NRefactory.TypeSystem.Implementation; namespace ICSharpCode.NRefactory.TypeSystem @@ -22,6 +23,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// public sealed class Dynamic {} + #region ITypeResolveContext.GetClass(Type) /// /// Retrieves a class. /// @@ -55,7 +57,9 @@ namespace ICSharpCode.NRefactory.TypeSystem return context.GetClass(name, typeParameterCount, StringComparer.Ordinal); } } + #endregion + #region Type.ToTypeReference() /// /// Creates a reference to the specified type. /// @@ -90,7 +94,7 @@ namespace ICSharpCode.NRefactory.TypeSystem } return SharedTypes.UnknownType; } else { - ITypeDefinition c = (entity as ITypeDefinition) ?? (entity is IMember ? ((IMember)entity).DeclaringTypeDefinition : null); + ITypeDefinition c = (entity as ITypeDefinition) ?? (entity != null ? entity.DeclaringTypeDefinition : null); if (c != null && type.GenericParameterPosition < c.TypeParameters.Count) { if (c.TypeParameters[type.GenericParameterPosition].Name == type.Name) { return c.TypeParameters[type.GenericParameterPosition]; @@ -113,7 +117,9 @@ namespace ICSharpCode.NRefactory.TypeSystem return new GetClassTypeReference(name, typeParameterCount); } } + #endregion + #region SplitTypeParameterCountFromReflectionName /// /// Removes the ` with type parameter count from the reflection name. /// @@ -146,7 +152,9 @@ namespace ICSharpCode.NRefactory.TypeSystem return reflectionName; } } + #endregion + #region TypeCode.ToTypeReference() static readonly ITypeReference[] primitiveTypeReferences = { SharedTypes.UnknownType, // TypeCode.Empty new GetClassTypeReference("System.Object", 0), @@ -178,7 +186,9 @@ namespace ICSharpCode.NRefactory.TypeSystem { return primitiveTypeReferences[(int)typeCode]; } + #endregion + #region GetTypeCode static readonly Dictionary typeNameToCodeDict = new Dictionary { { "System.Object", TypeCode.Object }, { "System.DBNull", TypeCode.DBNull }, @@ -211,5 +221,180 @@ namespace ICSharpCode.NRefactory.TypeSystem else return TypeCode.Empty; } + #endregion + + #region ParseReflectionName + /// + /// Parses a reflection name into a type reference. + /// + public static ITypeReference ParseReflectionName(string reflectionTypeName, IEntity parentEntity = null) + { + int pos = 0; + ITypeReference r = ParseReflectionName(reflectionTypeName, ref pos, parentEntity); + if (pos < reflectionTypeName.Length) + throw new ReflectionNameParseException(pos, "Expected end of type name"); + return r; + } + + static bool IsReflectionNameSpecialCharacter(char c) + { + switch (c) { + case '+': + case '`': + case '[': + case ']': + case ',': + case '*': + case '&': + return true; + default: + return false; + } + } + + static ITypeReference ParseReflectionName(string reflectionTypeName, ref int pos, IEntity entity) + { + if (pos == reflectionTypeName.Length) + throw new ReflectionNameParseException(pos, "Unexpected end"); + if (reflectionTypeName[pos] == '`') { + // type parameter reference + pos++; + if (pos == reflectionTypeName.Length) + throw new ReflectionNameParseException(pos, "Unexpected end"); + if (reflectionTypeName[pos] == '`') { + // method type parameter reference + pos++; + int index = ReadTypeParameterCount(reflectionTypeName, ref pos); + IMethod method = entity as IMethod; + if (method != null && index >= 0 && index < method.TypeParameters.Count) + return method.TypeParameters[index]; + else + return SharedTypes.UnknownType; + } else { + // class type parameter reference + int index = ReadTypeParameterCount(reflectionTypeName, ref pos); + ITypeDefinition c = (entity as ITypeDefinition) ?? (entity != null ? entity.DeclaringTypeDefinition : null); + if (c != null && index >= 0 && index < c.TypeParameters.Count) + return c.TypeParameters[index]; + else + return SharedTypes.UnknownType; + } + } + // not a type parameter reference: read the actual type name + int tpc; + string typeName = ReadTypeName(reflectionTypeName, ref pos, out tpc); + ITypeReference reference = new GetClassTypeReference(typeName, tpc); + // read type suffixes + while (pos < reflectionTypeName.Length) { + switch (reflectionTypeName[pos++]) { + case '+': + typeName = ReadTypeName(reflectionTypeName, ref pos, out tpc); + reference = new NestedTypeReference(reference, typeName, tpc); + break; + case '*': + reference = new PointerTypeReference(reference); + break; + case '&': + reference = new ByReferenceTypeReference(reference); + break; + case '[': + // this might be an array or a generic type + if (pos == reflectionTypeName.Length) + throw new ReflectionNameParseException(pos, "Unexpected end"); + if (reflectionTypeName[pos] == '[') { + // it's a generic type + List typeArguments = new List(tpc); + pos++; + typeArguments.Add(ParseReflectionName(reflectionTypeName, ref pos, entity)); + if (pos < reflectionTypeName.Length && reflectionTypeName[pos] == ']') + pos++; + else + throw new ReflectionNameParseException(pos, "Expected end of type argument"); + + while (pos < reflectionTypeName.Length && reflectionTypeName[pos] == ',') { + pos++; + if (pos < reflectionTypeName.Length && reflectionTypeName[pos] == '[') + pos++; + else + throw new ReflectionNameParseException(pos, "Expected another type argument"); + + typeArguments.Add(ParseReflectionName(reflectionTypeName, ref pos, entity)); + + if (pos < reflectionTypeName.Length && reflectionTypeName[pos] == ']') + pos++; + else + throw new ReflectionNameParseException(pos, "Expected end of type argument"); + } + + if (pos < reflectionTypeName.Length && reflectionTypeName[pos] == ']') { + pos++; + reference = new ParameterizedTypeReference(reference, typeArguments); + } else { + throw new ReflectionNameParseException(pos, "Expected end of generic type"); + } + } else { + // it's an array + int dimensions = 1; + while (pos < reflectionTypeName.Length && reflectionTypeName[pos] == ',') { + dimensions++; + pos++; + } + if (pos < reflectionTypeName.Length && reflectionTypeName[pos] == ']') { + pos++; // end of array + reference = new ArrayTypeReference(reference, dimensions); + } else { + throw new ReflectionNameParseException(pos, "Expected array modifier"); + } + } + break; + case ',': + // assembly qualified name, ignore everything up to the end/next ']' + while (pos < reflectionTypeName.Length && reflectionTypeName[pos] != ']') + pos++; + break; + default: + pos--; // reset pos to the character we couldn't read + if (reflectionTypeName[pos] == ']') + return reference; // return from a nested generic + else + throw new ReflectionNameParseException(pos, "Unexpected character: '" + reflectionTypeName[pos] + "'"); + } + } + return reference; + } + + static string ReadTypeName(string reflectionTypeName, ref int pos, out int tpc) + { + int startPos = pos; + // skip the simple name portion: + while (pos < reflectionTypeName.Length && !IsReflectionNameSpecialCharacter(reflectionTypeName[pos])) + pos++; + if (pos == startPos) + throw new ReflectionNameParseException(pos, "Expected type name"); + string typeName = reflectionTypeName.Substring(startPos, pos - startPos); + if (pos < reflectionTypeName.Length && reflectionTypeName[pos] == '`') { + pos++; + tpc = ReadTypeParameterCount(reflectionTypeName, ref pos); + } else { + tpc = 0; + } + return typeName; + } + + static int ReadTypeParameterCount(string reflectionTypeName, ref int pos) + { + int startPos = pos; + while (pos < reflectionTypeName.Length) { + char c = reflectionTypeName[pos]; + if (c < '0' || c > '9') + break; + pos++; + } + int tpc; + if (!int.TryParse(reflectionTypeName.Substring(startPos, pos - startPos), out tpc)) + throw new ReflectionNameParseException(pos, "Expected type parameter count"); + return tpc; + } + #endregion } } diff --git a/ICSharpCode.NRefactory/TypeSystem/ReflectionNameParseException.cs b/ICSharpCode.NRefactory/TypeSystem/ReflectionNameParseException.cs new file mode 100644 index 0000000000..0ef6054272 --- /dev/null +++ b/ICSharpCode.NRefactory/TypeSystem/ReflectionNameParseException.cs @@ -0,0 +1,45 @@ + +using System; +using System.Runtime.Serialization; + +namespace ICSharpCode.NRefactory.TypeSystem +{ + /// + /// Represents an error while parsing a reflection name. + /// + public class ReflectionNameParseException : Exception + { + int position; + + public int Position { + get { return position; } + } + + public ReflectionNameParseException(int position) + { + this.position = position; + } + + public ReflectionNameParseException(int position, string message) : base(message) + { + this.position = position; + } + + public ReflectionNameParseException(int position, string message, Exception innerException) : base(message, innerException) + { + this.position = position; + } + + // This constructor is needed for serialization. + protected ReflectionNameParseException(SerializationInfo info, StreamingContext context) : base(info, context) + { + position = info.GetInt32("position"); + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue("position", position); + } + } +} \ No newline at end of file diff --git a/README b/README index efd004bbe2..37c95516ca 100644 --- a/README +++ b/README @@ -26,3 +26,101 @@ Null-Object pattern: The pattern also extends to the C# resolver, which always produces a ResolveResult, even in error cases. Use ResolveResult.IsError to detect resolver errors. Also note that many resolver errors still have a meaningful type attached, this allows code completion to work in the presence of minor semantic errors. + +FAQ: +Q: What is the difference between a type and a type definition? + +A: Basically, a type (IType) is any type in the .NET type system: + - an array (ArrayType) + - a pointer (PointerType) + - a managed reference (ByReferenceType) + - a parameterized type (ParameterizedType, e.g. List) + - a type parameter (ITypeParameter, e.g. T) + - or a type definition (ITypeDefiniton) + + Type definitions are only classes, structs, enums and delegates. + Every type definition is a type, but not every type is a type definition. + NRefactory's ITypeDefinition derives from IType, so you can directly use any type definition as a type. + In the other direction, you could try to cast a type to ITypeDefinition, or you can call the GetDefinition() + method. The GetDefinition() method will also return the underlying ITypeDefinition if given a parameterized type, + so "List".GetDefinition() is "List". + +Q: What is the difference betweent type references and types? + I've seen lots of duplicated classes (ArrayType vs. ArrayTypeReference, etc.) + + NRefactory has the concept of the "project content": every assembly/project is stored independently from other assemblies/projects. + It is possible to load some source code into a project which contains the type reference "int[]" without having to load + mscorlib into NRefactory. + So inside the entities stored for the project, the array type is only referenced using an ITypeReference. + This interface has a single method: + interface ITypeReference { + IType Resolve(ITypeResolutionContext context); + } + By calling the Resolve()-method, you will get back the actual ArrayType. + At this point, you have to provide the type resolution context: + + Note that every type can also be used as type reference - the IType interface derives from ITypeReference. + Every IType simply returns itself when the Resolve()-method is called. + Types are often directly used as references when source and target of the reference are within the same assembly. + +A: If you've previously used the .NET Reflection API, the concept of type references is new to you. + + +Q: How do I get the IType or ITypeReference for a primitive type such as string or int? + +A: Please use: + TypeCode.Int32.ToTypeReference().Resolve(context) + Skip the Resolve() call if you only need the type reference. + + ReflectionHelper.ToTypeReference is very fast if given a TypeCode (it simply looks up an existing type reference in an array), + and your code will benefit from caching of the resolve result (once that gets implemented for these primitive type references). + + Avoid using "context.GetClass(typeof(int))" - this call involves Reflection on the System.Type being passed, + cannot benefit from any caching implemented in the future, and most importantly: it may return null. + And do you always test your code in a scenario where mscorlib isn't contained in the resolve context? + The approach suggested above will return SharedTypes.UnknownType when the type cannot be resolved, so you + don't run into the risk of getting NullReferenceExceptions. + +Q: Is it thread-safe? + +A: This question is a bit difficult to answer. + NRefactory was designed to be usable in a multi-threaded IDE. + But of course, this does not mean that everything is thread-safe. + + First off, there's no hidden static state, so any two operations working on independent data can be executed concurrently. + TODO: what about the C# parser? gmcs is full of static state... + + Some instance methods may use hidden instance state, so it is not safe to e.g use an instance of the CSharp.Resolver.Conversions class + concurrently. Instead, you need to create an instance on every thread. + + In the case of project contents, it is desirable to be able to use them, and all the classes in that project content, + on multiple threads - for example to provide code completion in an IDE while a background thread parses more files and adds + them to the project content. + + For this reason, the entity interfaces (ITypeDefinition, IMember, etc.) are designed to be freezable. Once the Freeze() method is + called, an entity instance becomes immutable and thread-safe. + + Whether an ITypeResolveContext is thread-safe depends on the implementation: + TypeStorage: thread-safe for concurrent reads, but only if it's not written to (see XML documentation on TypeStorage) + CecilProjectContent: immutable and thread-safe + SimpleProjectContent: fully thread-safe + CompositeTypeResolveContext: depends on the child contexts + + Usually, you'll work with a set of loaded projects (SimpleProjectContents) + and loaded external assemblies (CecilProjectContent). + A CompositeTypeResolveContext representing such a set is thread-safe. + + Hoever, some algorithms can become confused if two GetClass() calls with same arguments produce different + results (e.g. because another thread updated a class definition). + Also, there's a performance problem: if you have a composite of 15 SimpleProjectContents and the resolve algorithm + requests 100 types, that's 1500 times entering and leaving the read-lock. + Moreoever, the ITypeResolveContext methods that return collections need to create a copy of the collection. + + The solution is to make the read lock more coarse-grained: + using (var syncContext = compositeTypeResolveContext.Synchronize()) { + resolver.ResolveStuff(syncContext); + } + On the call to Synchronize(), all 15 SimpleProjectContents are locked for reading. + The return value "syncContext" can then be used to access the type resolve context without further synchronization overhead. + Once the return value is disposed, the read-locks are released. +