diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index de7d8470bc..7c25be98f4 100644 --- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -286,6 +286,8 @@ + + diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs index f634f266c8..20255c6e05 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs @@ -119,6 +119,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// public CSharpResolver WithCheckForOverflow(bool checkForOverflow) { + if (checkForOverflow == this.checkForOverflow) + return this; return new CSharpResolver(compilation, conversions, context, checkForOverflow, isWithinLambdaExpression, currentTypeDefinitionCache, localVariableStack, objectInitializerStack); } @@ -431,7 +433,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver // evaluate as (E)(~(U)x); var U = compilation.FindType(expression.ConstantValue.GetType()); var unpackedEnum = new ConstantResolveResult(U, expression.ConstantValue); - return CheckErrorAndResolveCast(expression.Type, ResolveUnaryOperator(op, unpackedEnum)); + return CheckErrorAndResolveUncheckedCast(expression.Type, ResolveUnaryOperator(op, unpackedEnum)); } else { return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable); } @@ -911,7 +913,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver rhs = ResolveCast(elementType, rhs); if (rhs.IsError) return rhs; - return CheckErrorAndResolveCast(elementType, ResolveBinaryOperator(BinaryOperatorType.Subtract, lhs, rhs)); + return CheckErrorAndResolveUncheckedCast(elementType, ResolveBinaryOperator(BinaryOperatorType.Subtract, lhs, rhs)); } IType resultType = MakeNullable(elementType, isNullable); return BinaryOperatorResolveResult(resultType, lhs, BinaryOperatorType.Subtract, rhs, isNullable); @@ -937,7 +939,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver rhs = ResolveCast(elementType, rhs); if (rhs.IsError) return rhs; - return CheckErrorAndResolveCast(enumType, ResolveBinaryOperator(op, lhs, rhs)); + return CheckErrorAndResolveUncheckedCast(enumType, ResolveBinaryOperator(op, lhs, rhs)); } IType resultType = MakeNullable(enumType, isNullable); return BinaryOperatorResolveResult(resultType, lhs, op, rhs, isNullable); @@ -1260,7 +1262,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { if (c == Conversion.IdentityConversion) return rr; - else if (rr.IsCompileTimeConstant && c != Conversion.None) + else if (rr.IsCompileTimeConstant && c != Conversion.None && !c.IsUserDefined) return ResolveCast(targetType, rr); else return new ConversionResolveResult(targetType, rr, c, checkForOverflow); @@ -1269,7 +1271,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver public ResolveResult ResolveCast(IType targetType, ResolveResult expression) { // C# 4.0 spec: §7.7.6 Cast expressions - if (expression.IsCompileTimeConstant) { + Conversion c = conversions.ExplicitConversion(expression, targetType); + if (expression.IsCompileTimeConstant && !c.IsUserDefined) { TypeCode code = ReflectionHelper.GetTypeCode(targetType); if (code >= TypeCode.Boolean && code <= TypeCode.Decimal && expression.ConstantValue != null) { try { @@ -1293,7 +1296,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } } } - Conversion c = conversions.ExplicitConversion(expression, targetType); return new ConversionResolveResult(targetType, expression, c, checkForOverflow); } @@ -1302,12 +1304,12 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return Utils.CSharpPrimitiveCast.Cast(targetType, input, this.CheckForOverflow); } - ResolveResult CheckErrorAndResolveCast(IType targetType, ResolveResult expression) + ResolveResult CheckErrorAndResolveUncheckedCast(IType targetType, ResolveResult expression) { if (expression.IsError) return expression; else - return ResolveCast(targetType, expression); + return WithCheckForOverflow(false).ResolveCast(targetType, expression); } #endregion @@ -1596,7 +1598,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } if (target.Type.Kind == TypeKind.Dynamic) - return new ResolveResult(SpecialType.Dynamic); + return new DynamicMemberResolveResult(target, identifier); MemberLookup lookup = CreateMemberLookup(lookupMode); ResolveResult result; @@ -1893,8 +1895,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { // C# 4.0 spec: §7.6.5 - if (target.Type.Kind == TypeKind.Dynamic) - return new ResolveResult(SpecialType.Dynamic); + if (target.Type.Kind == TypeKind.Dynamic) { + return new DynamicInvocationResolveResult(target, arguments.Select((a, i) => new DynamicInvocationArgument(argumentNames != null ? argumentNames[i] : null, a)).ToList().AsReadOnly()); + } MethodGroupResolveResult mgrr = target as MethodGroupResolveResult; if (mgrr != null) { diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/DynamicInvocationResolveResult.cs b/ICSharpCode.NRefactory.CSharp/Resolver/DynamicInvocationResolveResult.cs new file mode 100644 index 0000000000..6139be02c0 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/Resolver/DynamicInvocationResolveResult.cs @@ -0,0 +1,73 @@ +// 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.Collections.Generic; +using System.Globalization; +using System.Linq; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.CSharp.Resolver +{ + /// + /// Represents a single argument in a dynamic invocation. + /// + public class DynamicInvocationArgument { + /// + /// Parameter name, if the argument is named. Null otherwise. + /// + public readonly string Name; + + /// + /// Value of the argument. + /// + public readonly ResolveResult Value; + + public DynamicInvocationArgument(string name, ResolveResult value) { + Name = name; + Value = value; + } + } + + /// + /// Represents the result of an invocation of a member of a dynamic object. + /// + public class DynamicInvocationResolveResult : ResolveResult + { + /// + /// Target of the invocation (a dynamic object). + /// + public readonly ResolveResult Target; + + /// + /// Arguments for the call. + /// + public readonly IList Arguments; + + public DynamicInvocationResolveResult(ResolveResult target, IList arguments) : base(SpecialType.Dynamic) { + this.Target = target; + this.Arguments = arguments ?? EmptyList.Instance; + } + + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "[Dynamic invocation ]"); + } + } +} diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/DynamicMemberResolveResult.cs b/ICSharpCode.NRefactory.CSharp/Resolver/DynamicMemberResolveResult.cs new file mode 100644 index 0000000000..13e2ba1de6 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/Resolver/DynamicMemberResolveResult.cs @@ -0,0 +1,57 @@ +// 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.Collections.Generic; +using System.Globalization; +using System.Linq; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.CSharp.Resolver +{ + /// + /// Represents the result of an access to a member of a dynamic object. + /// + public class DynamicMemberResolveResult : ResolveResult + { + /// + /// Target of the member access (a dynamic object). + /// + public readonly ResolveResult Target; + + /// + /// Name of the accessed member. + /// + public readonly string Member; + + public DynamicMemberResolveResult(ResolveResult target, string member) : base(SpecialType.Dynamic) { + this.Target = target; + this.Member = member; + } + + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "[Dynamic member '{0}']", Member); + } + + public override IEnumerable GetChildResults() { + return new[] { Target }; + } + } +} diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs b/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs index 84e8f74526..f160fcbc60 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs @@ -854,21 +854,20 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver resolver.CurrentTypeResolveContext, propertyOrIndexerDeclaration.EntityType, name, explicitInterfaceType, parameterTypeReferences: parameterTypeReferences); } + // We need to use the property as current member so that indexer parameters can be resolved correctly. + resolver = resolver.WithCurrentMember(member); + var resolverWithPropertyAsMember = resolver; for (AstNode node = propertyOrIndexerDeclaration.FirstChild; node != null; node = node.NextSibling) { if (node.Role == PropertyDeclaration.GetterRole && member is IProperty) { - resolver = resolver.PushBlock(); resolver = resolver.WithCurrentMember(((IProperty)member).Getter); Scan(node); - resolver = resolver.PopBlock(); - } - else if (node.Role == PropertyDeclaration.SetterRole && member is IProperty) { - resolver = resolver.PushBlock(); + resolver = resolverWithPropertyAsMember; + } else if (node.Role == PropertyDeclaration.SetterRole && member is IProperty) { resolver = resolver.WithCurrentMember(((IProperty)member).Setter); Scan(node); - resolver = resolver.PopBlock(); - } - else { + resolver = resolverWithPropertyAsMember; + } else { Scan(node); } } @@ -908,21 +907,19 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver explicitInterfaceAstType.ToTypeReference()); } } - + resolver = resolver.WithCurrentMember(member); + var resolverWithEventAsMember = resolver; + for (AstNode node = eventDeclaration.FirstChild; node != null; node = node.NextSibling) { if (node.Role == CustomEventDeclaration.AddAccessorRole && member is IEvent) { - resolver = resolver.PushBlock(); resolver = resolver.WithCurrentMember(((IEvent)member).AddAccessor); Scan(node); - resolver = resolver.PopBlock(); - } - else if (node.Role == CustomEventDeclaration.RemoveAccessorRole && member is IEvent) { - resolver = resolver.PushBlock(); + resolver = resolverWithEventAsMember; + } else if (node.Role == CustomEventDeclaration.RemoveAccessorRole && member is IEvent) { resolver = resolver.WithCurrentMember(((IEvent)member).RemoveAccessor); Scan(node); - resolver = resolver.PopBlock(); - } - else { + resolver = resolverWithEventAsMember; + } else { Scan(node); } } diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs b/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs index b562f03a9e..97a7c67849 100644 --- a/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs @@ -57,7 +57,7 @@ namespace ICSharpCode.NRefactory.ConsistencyCheck solution.AllFiles.Count(), solution.Projects.Count); - //using (new Timer("ID String test... ")) TypeSystemTests.IDStringConsistencyCheck(solution); + using (new Timer("ID String test... ")) TypeSystemTests.IDStringConsistencyCheck(solution); using (new Timer("Resolve unresolved members... ")) TypeSystemTests.ResolvedUnresolvedMembers(solution); //RunTestOnAllFiles("Roundtripping test", RoundtripTest.RunTest); RunTestOnAllFiles("Resolver test", ResolverTest.RunTest); diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/ResolverTest.cs b/ICSharpCode.NRefactory.ConsistencyCheck/ResolverTest.cs index a5b191edbb..ee367f8a68 100644 --- a/ICSharpCode.NRefactory.ConsistencyCheck/ResolverTest.cs +++ b/ICSharpCode.NRefactory.ConsistencyCheck/ResolverTest.cs @@ -71,7 +71,7 @@ namespace ICSharpCode.NRefactory.ConsistencyCheck if (CSharpAstResolver.IsUnresolvableNode(node)) throw new InvalidOperationException("Resolved unresolvable node"); if (!ParenthesizedExpression.ActsAsParenthesizedExpression(node)) - if (!resolveResults.Add(result)) + if (!resolveResults.Add(result) && result != ErrorResolveResult.UnknownError) throw new InvalidOperationException("Duplicate resolve result"); if (result.IsError && !allowErrors) { diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/DynamicTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/DynamicTests.cs new file mode 100644 index 0000000000..088064320c --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/DynamicTests.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.CSharp.Resolver { + [TestFixture] + public class DynamicTests : ResolverTestBase { + [Test] + public void AccessToDynamicMember() { + string program = @"using System; +class TestClass { + void F() { + dynamic obj = null; + $obj.SomeProperty$ = 10; + } +}"; + var rr = Resolve(program); + Assert.That(rr.Type.Kind, Is.EqualTo(TypeKind.Dynamic)); + Assert.That(rr.Target is LocalResolveResult && ((LocalResolveResult)rr.Target).Variable.Name == "obj"); + Assert.That(rr.Member, Is.EqualTo("SomeProperty")); + } + + [Test] + public void DynamicInvocation() { + string program = @"using System; +class TestClass { + void F() { + dynamic obj = null; + int a = 0; + string b = null; + $obj.SomeMethod(a, b)$; + } +}"; + var rr = Resolve(program); + Assert.That(rr.Type.Kind, Is.EqualTo(TypeKind.Dynamic)); + Assert.That(rr.Target, Is.InstanceOf()); + var dynamicMember = (DynamicMemberResolveResult)rr.Target; + Assert.That(dynamicMember.Target is LocalResolveResult && ((LocalResolveResult)dynamicMember.Target).Variable.Name == "obj"); + Assert.That(dynamicMember.Member, Is.EqualTo("SomeMethod")); + Assert.That(rr.Arguments.Count, Is.EqualTo(2)); + Assert.That(rr.Arguments[0].Name, Is.Null); + Assert.That(rr.Arguments[0].Value is LocalResolveResult && ((LocalResolveResult)rr.Arguments[0].Value).Variable.Name == "a"); + Assert.That(rr.Arguments[1].Name, Is.Null); + Assert.That(rr.Arguments[1].Value is LocalResolveResult && ((LocalResolveResult)rr.Arguments[1].Value).Variable.Name == "b"); + } + + [Test] + public void DynamicInvocationWithNamedArguments() { + string program = @"using System; +class TestClass { + void F() { + dynamic obj = null; + int a = 0, x = 0; + string b = null; + $obj.SomeMethod(x, param1: a, param2: b)$; + } +}"; + var rr = Resolve(program); + Assert.That(rr.Type.Kind, Is.EqualTo(TypeKind.Dynamic)); + Assert.That(rr.Target, Is.InstanceOf()); + var dynamicMember = (DynamicMemberResolveResult)rr.Target; + Assert.That(dynamicMember.Target is LocalResolveResult && ((LocalResolveResult)dynamicMember.Target).Variable.Name == "obj"); + Assert.That(dynamicMember.Member, Is.EqualTo("SomeMethod")); + Assert.That(rr.Arguments.Count, Is.EqualTo(3)); + Assert.That(rr.Arguments[0].Name, Is.Null); + Assert.That(rr.Arguments[0].Value is LocalResolveResult && ((LocalResolveResult)rr.Arguments[0].Value).Variable.Name == "x"); + Assert.That(rr.Arguments[1].Name, Is.EqualTo("param1")); + Assert.That(rr.Arguments[1].Value is LocalResolveResult && ((LocalResolveResult)rr.Arguments[1].Value).Variable.Name == "a"); + Assert.That(rr.Arguments[2].Name, Is.EqualTo("param2")); + Assert.That(rr.Arguments[2].Value is LocalResolveResult && ((LocalResolveResult)rr.Arguments[2].Value).Variable.Name == "b"); + } + + [Test] + public void TwoDynamicInvocationsInARow() { + string program = @"using System; +class TestClass { + void F() { + dynamic obj = null; + int a = 0, b = 0; + $obj.SomeMethod(a)(b)$; + } +}"; + var rr = Resolve(program); + Assert.That(rr.Type.Kind, Is.EqualTo(TypeKind.Dynamic)); + Assert.That(rr.Target, Is.InstanceOf()); + var innerInvocation = (DynamicInvocationResolveResult)rr.Target; + Assert.That(innerInvocation.Target, Is.InstanceOf()); + var dynamicMember = (DynamicMemberResolveResult)innerInvocation.Target; + Assert.That(dynamicMember.Target is LocalResolveResult && ((LocalResolveResult)dynamicMember.Target).Variable.Name == "obj"); + Assert.That(dynamicMember.Member, Is.EqualTo("SomeMethod")); + Assert.That(innerInvocation.Arguments.Count, Is.EqualTo(1)); + Assert.That(innerInvocation.Arguments[0].Name, Is.Null); + Assert.That(innerInvocation.Arguments[0].Value is LocalResolveResult && ((LocalResolveResult)innerInvocation.Arguments[0].Value).Variable.Name == "a"); + Assert.That(rr.Arguments.Count, Is.EqualTo(1)); + Assert.That(rr.Arguments[0].Name, Is.Null); + Assert.That(rr.Arguments[0].Value is LocalResolveResult && ((LocalResolveResult)rr.Arguments[0].Value).Variable.Name == "b"); + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/MethodTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/MethodTests.cs index 185faa7410..eb997bcac5 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/MethodTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/MethodTests.cs @@ -125,6 +125,21 @@ class TestClass { Assert.IsTrue(ReferenceEquals(j1, j2)); Assert.IsTrue(ReferenceEquals(value1, value2)); } + + [Test] + public void ResolveParameterDeclarationInIndexer() + { + string code = @"using System; +class TestClass { + int[,] myField; + int this[$int i$, int j] { + get { return myField[i, j]; } + set { myField[i, j] = value; } + } +}"; + var rr = Resolve(code); + Assert.IsTrue(rr.IsParameter); + } [Test] public void ParameterIdentityInEventAdder() diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/UnaryOperatorTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/UnaryOperatorTests.cs index 3635a4db1b..a87aa6d0f6 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/UnaryOperatorTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/UnaryOperatorTests.cs @@ -257,5 +257,22 @@ class Test { Assert.IsFalse(irr.IsError); Assert.IsTrue(irr.IsLiftedOperator); } + + [Test] + public void UShortEnumNegation() + { + string program = @" +class Test { + enum UShortEnum : ushort { Three = 3 } + static void Inc() { + checked { // even in checked context, the implicit cast back to enum is unchecked + var a = $~UShortEnum.Three$; + } + } +}"; + var rr = Resolve(program); + Assert.IsFalse(rr.IsError); + Assert.AreEqual(unchecked( (ushort)~3 ), rr.ConstantValue); + } } } diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 471fb98b03..e44c23b565 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -190,6 +190,7 @@ + @@ -221,6 +222,7 @@ + diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/LazyLoadedCecilLoaderTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/LazyLoadedCecilLoaderTests.cs new file mode 100644 index 0000000000..2000207509 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/LazyLoadedCecilLoaderTests.cs @@ -0,0 +1,36 @@ +// 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 ICSharpCode.NRefactory.TypeSystem.Implementation; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.TypeSystem +{ + [TestFixture] + public class LazyLoadedCecilLoaderTests : TypeSystemTests + { + [TestFixtureSetUp] + public void FixtureSetUp() + { + CecilLoader loader = new CecilLoader() { IncludeInternalMembers = true, LazyLoad = true }; + IUnresolvedAssembly pc = loader.LoadAssemblyFile(typeof(TestCase.SimplePublicClass).Assembly.Location); + base.compilation = new SimpleCompilation(pc, CecilLoaderTests.Mscorlib); + } + } +} diff --git a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs index aa60bd995c..8b97a43897 100644 --- a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs +++ b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs @@ -47,6 +47,16 @@ namespace ICSharpCode.NRefactory.TypeSystem /// public bool IncludeInternalMembers { get; set; } + /// + /// Specifies whether to use lazy loading. The default is false. + /// If this property is set to true, the CecilLoader will not copy all the relevant information + /// out of the Cecil object model, but will maintain references to the Cecil objects. + /// This speeds up the loading process and avoids loading unnecessary information, but it causes + /// the Cecil objects to stay in memory (which can significantly increase memory usage). + /// It also prevents serialization of the Cecil-loaded type system. + /// + public bool LazyLoad { get; set; } + /// /// Gets/Sets the documentation provider that is used to retrieve the XML documentation for all members. /// @@ -137,7 +147,7 @@ namespace ICSharpCode.NRefactory.TypeSystem this.CancellationToken.ThrowIfCancellationRequested(); if (this.IncludeInternalMembers || (td.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.Public) { string name = td.Name; - if (name.Length == 0 || name[0] == '<') + if (name.Length == 0) continue; var t = CreateTopLevelTypeDefinition(td); @@ -166,6 +176,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// This causes ReadTypeReference() to use for references /// in that module. /// + [CLSCompliant(false)] public void SetCurrentModule(ModuleDefinition module) { this.currentModule = module; @@ -1449,65 +1460,76 @@ namespace ICSharpCode.NRefactory.TypeSystem { string name = ReflectionHelper.SplitTypeParameterCountFromReflectionName(typeDefinition.Name); var td = new DefaultUnresolvedTypeDefinition(typeDefinition.Namespace, name); - InitTypeParameters(typeDefinition, td); + if (typeDefinition.HasGenericParameters) + InitTypeParameters(typeDefinition, td.TypeParameters); return td; } - static void InitTypeParameters(TypeDefinition typeDefinition, DefaultUnresolvedTypeDefinition td) + static void InitTypeParameters(TypeDefinition typeDefinition, IList typeParameters) { // Type parameters are initialized within the constructor so that the class can be put into the type storage // before the rest of the initialization runs - this allows it to be available for early binding as soon as possible. for (int i = 0; i < typeDefinition.GenericParameters.Count; i++) { if (typeDefinition.GenericParameters[i].Position != i) throw new InvalidOperationException("g.Position != i"); - td.TypeParameters.Add(new DefaultUnresolvedTypeParameter( + typeParameters.Add(new DefaultUnresolvedTypeParameter( EntityType.TypeDefinition, i, typeDefinition.GenericParameters[i].Name)); } } + void InitTypeParameterConstraints(TypeDefinition typeDefinition, IList typeParameters) + { + for (int i = 0; i < typeParameters.Count; i++) { + AddConstraints((DefaultUnresolvedTypeParameter)typeParameters[i], typeDefinition.GenericParameters[i]); + } + } + void InitTypeDefinition(TypeDefinition typeDefinition, DefaultUnresolvedTypeDefinition td) { + td.Kind = GetTypeKind(typeDefinition); InitTypeModifiers(typeDefinition, td); + InitTypeParameterConstraints(typeDefinition, td.TypeParameters); - if (typeDefinition.HasGenericParameters) { - for (int i = 0; i < typeDefinition.GenericParameters.Count; i++) { - AddConstraints((DefaultUnresolvedTypeParameter)td.TypeParameters[i], typeDefinition.GenericParameters[i]); - } - } - - InitNestedTypes(typeDefinition, td); // nested types can be initialized only after generic parameters were created + // nested types can be initialized only after generic parameters were created + InitNestedTypes(typeDefinition, td, td.NestedTypes); AddAttributes(typeDefinition, td); td.HasExtensionMethods = HasExtensionAttribute(typeDefinition); + InitBaseTypes(typeDefinition, td.BaseTypes); + + td.AddDefaultConstructorIfRequired = (td.Kind == TypeKind.Struct || td.Kind == TypeKind.Enum); + InitMembers(typeDefinition, td, td.Members); + if (HasCecilReferences) + typeSystemTranslationTable[td] = typeDefinition; + if (this.InterningProvider != null) { + td.ApplyInterningProvider(this.InterningProvider); + } + td.Freeze(); + } + + void InitBaseTypes(TypeDefinition typeDefinition, IList baseTypes) + { // set base classes if (typeDefinition.IsEnum) { foreach (FieldDefinition enumField in typeDefinition.Fields) { if (!enumField.IsStatic) { - td.BaseTypes.Add(ReadTypeReference(enumField.FieldType)); + baseTypes.Add(ReadTypeReference(enumField.FieldType)); break; } } } else { if (typeDefinition.BaseType != null) { - td.BaseTypes.Add(ReadTypeReference(typeDefinition.BaseType)); + baseTypes.Add(ReadTypeReference(typeDefinition.BaseType)); } if (typeDefinition.HasInterfaces) { foreach (TypeReference iface in typeDefinition.Interfaces) { - td.BaseTypes.Add(ReadTypeReference(iface)); + baseTypes.Add(ReadTypeReference(iface)); } } } - - InitMembers(typeDefinition, td); - if (HasCecilReferences) - typeSystemTranslationTable[td] = typeDefinition; - if (this.InterningProvider != null) { - td.ApplyInterningProvider(this.InterningProvider); - } - td.Freeze(); } - void InitNestedTypes(TypeDefinition typeDefinition, DefaultUnresolvedTypeDefinition td) + void InitNestedTypes(TypeDefinition typeDefinition, IUnresolvedTypeDefinition declaringTypeDefinition, IList nestedTypes) { if (!typeDefinition.HasNestedTypes) return; @@ -1522,33 +1544,35 @@ namespace ICSharpCode.NRefactory.TypeSystem int pos = name.LastIndexOf('/'); if (pos > 0) name = name.Substring(pos + 1); - if (name.Length == 0 || name[0] == '<') - continue; name = ReflectionHelper.SplitTypeParameterCountFromReflectionName(name); - var nestedType = new DefaultUnresolvedTypeDefinition(td, name); - InitTypeParameters(nestedTypeDef, nestedType); - td.NestedTypes.Add(nestedType); + var nestedType = new DefaultUnresolvedTypeDefinition(declaringTypeDefinition, name); + InitTypeParameters(nestedTypeDef, nestedType.TypeParameters); + nestedTypes.Add(nestedType); InitTypeDefinition(nestedTypeDef, nestedType); } } } - static void InitTypeModifiers(TypeDefinition typeDefinition, DefaultUnresolvedTypeDefinition td) + static TypeKind GetTypeKind(TypeDefinition typeDefinition) { // set classtype if (typeDefinition.IsInterface) { - td.Kind = TypeKind.Interface; + return TypeKind.Interface; } else if (typeDefinition.IsEnum) { - td.Kind = TypeKind.Enum; + return TypeKind.Enum; } else if (typeDefinition.IsValueType) { - td.Kind = TypeKind.Struct; + return TypeKind.Struct; } else if (IsDelegate(typeDefinition)) { - td.Kind = TypeKind.Delegate; + return TypeKind.Delegate; } else if (IsModule(typeDefinition)) { - td.Kind = TypeKind.Module; + return TypeKind.Module; } else { - td.Kind = TypeKind.Class; + return TypeKind.Class; } + } + + static void InitTypeModifiers(TypeDefinition typeDefinition, AbstractUnresolvedEntity td) + { td.IsSealed = typeDefinition.IsSealed; td.IsAbstract = typeDefinition.IsAbstract; switch (typeDefinition.Attributes & TypeAttributes.VisibilityMask) { @@ -1598,9 +1622,8 @@ namespace ICSharpCode.NRefactory.TypeSystem return false; } - void InitMembers(TypeDefinition typeDefinition, DefaultUnresolvedTypeDefinition td) + void InitMembers(TypeDefinition typeDefinition, IUnresolvedTypeDefinition td, IList members) { - td.AddDefaultConstructorIfRequired = (td.Kind == TypeKind.Struct || td.Kind == TypeKind.Enum); if (typeDefinition.HasMethods) { foreach (MethodDefinition method in typeDefinition.Methods) { if (IsVisible(method.Attributes) && !IsAccessor(method.SemanticsAttributes)) { @@ -1611,14 +1634,14 @@ namespace ICSharpCode.NRefactory.TypeSystem else if (method.Name.StartsWith("op_", StringComparison.Ordinal)) type = EntityType.Operator; } - td.Members.Add(ReadMethod(method, td, type)); + members.Add(ReadMethod(method, td, type)); } } } if (typeDefinition.HasFields) { foreach (FieldDefinition field in typeDefinition.Fields) { if (IsVisible(field.Attributes) && !field.IsSpecialName) { - td.Members.Add(ReadField(field, td)); + members.Add(ReadField(field, td)); } } } @@ -1634,14 +1657,14 @@ namespace ICSharpCode.NRefactory.TypeSystem bool setterVisible = property.SetMethod != null && IsVisible(property.SetMethod.Attributes); if (getterVisible || setterVisible) { EntityType type = property.Name == defaultMemberName ? EntityType.Indexer : EntityType.Property; - td.Members.Add(ReadProperty(property, td, type)); + members.Add(ReadProperty(property, td, type)); } } } if (typeDefinition.HasEvents) { foreach (EventDefinition ev in typeDefinition.Events) { if (ev.AddMethod != null && IsVisible(ev.AddMethod.Attributes)) { - td.Members.Add(ReadEvent(ev, td)); + members.Add(ReadEvent(ev, td)); } } } @@ -1653,6 +1676,140 @@ namespace ICSharpCode.NRefactory.TypeSystem } #endregion + #region Lazy-Loaded Type Definition + sealed class LazyCecilTypeDefinition : AbstractUnresolvedEntity, IUnresolvedTypeDefinition + { + readonly CecilLoader loader; + readonly string namespaceName; + readonly TypeDefinition cecilTypeDef; + readonly TypeKind kind; + readonly IList typeParameters; + + IList baseTypes; + IList nestedTypes; + IList members; + + public LazyCecilTypeDefinition(CecilLoader loader, TypeDefinition typeDefinition) + { + this.loader = loader; + this.cecilTypeDef = typeDefinition; + this.EntityType = EntityType.TypeDefinition; + this.namespaceName = cecilTypeDef.Namespace; + this.Name = ReflectionHelper.SplitTypeParameterCountFromReflectionName(cecilTypeDef.Name); + var tps = new List(); + InitTypeParameters(cecilTypeDef, tps); + this.typeParameters = FreezableHelper.FreezeList(tps); + + this.kind = GetTypeKind(typeDefinition); + InitTypeModifiers(typeDefinition, this); + loader.InitTypeParameterConstraints(typeDefinition, typeParameters); + + loader.AddAttributes(typeDefinition, this); + flags[FlagHasExtensionMethods] = HasExtensionAttribute(typeDefinition); + + if (loader.HasCecilReferences) + loader.typeSystemTranslationTable[this] = typeDefinition; + if (loader.InterningProvider != null) { + this.ApplyInterningProvider(loader.InterningProvider); + } + this.Freeze(); + } + + public override string Namespace { + get { return namespaceName; } + set { throw new NotSupportedException(); } + } + + public TypeKind Kind { + get { return kind; } + } + + public IList TypeParameters { + get { return typeParameters; } + } + + public IList BaseTypes { + get { + var result = LazyInit.VolatileRead(ref this.baseTypes); + if (result != null) { + return result; + } else { + result = new List(); + loader.InitBaseTypes(cecilTypeDef, result); + return LazyInit.GetOrSet(ref this.baseTypes, FreezableHelper.FreezeList(result)); + } + } + } + + public IList NestedTypes { + get { + var result = LazyInit.VolatileRead(ref this.nestedTypes); + if (result != null) { + return result; + } else { + result = new List(); + loader.InitNestedTypes(cecilTypeDef, this, result); + return LazyInit.GetOrSet(ref this.nestedTypes, FreezableHelper.FreezeList(result)); + } + } + } + + public IList Members { + get { + var result = LazyInit.VolatileRead(ref this.members); + if (result != null) { + return result; + } else { + result = new List(); + loader.InitMembers(cecilTypeDef, this, result); + return LazyInit.GetOrSet(ref this.members, FreezableHelper.FreezeList(result)); + } + } + } + + public IEnumerable Methods { + get { return Members.OfType(); } + } + + public IEnumerable Properties { + get { return Members.OfType(); } + } + + public IEnumerable Fields { + get { return Members.OfType(); } + } + + public IEnumerable Events { + get { return Members.OfType(); } + } + + public bool AddDefaultConstructorIfRequired { + get { return kind == TypeKind.Struct || kind == TypeKind.Enum; } + } + + public bool? HasExtensionMethods { + get { return flags[FlagHasExtensionMethods]; } + // we always return true or false, never null. + // FlagHasNoExtensionMethods is unused in LazyCecilTypeDefinition + } + + public IType Resolve(ITypeResolveContext context) + { + if (context == null) + throw new ArgumentNullException("context"); + if (context.CurrentAssembly == null) + throw new ArgumentException("An ITypeDefinition cannot be resolved in a context without a current assembly."); + return context.CurrentAssembly.GetTypeDefinition(this) + ?? (IType)new UnknownType(this.Namespace, this.Name, this.TypeParameters.Count); + } + + public ITypeResolveContext CreateResolveContext(ITypeResolveContext parentContext) + { + return parentContext; + } + } + #endregion + #region Read Method [CLSCompliant(false)] public IUnresolvedMethod ReadMethod(MethodDefinition method, IUnresolvedTypeDefinition parentType, EntityType methodType = EntityType.Method) diff --git a/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs b/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs index 508f9ce810..1438505d8b 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs @@ -48,6 +48,12 @@ namespace ICSharpCode.NRefactory.TypeSystem /// bool? HasExtensionMethods { get; } + /// + /// Gets whether this unresolved type definition causes the addition of a default constructor + /// if no other constructor is present. + /// + bool AddDefaultConstructorIfRequired { get; } + /// /// Looks up the resolved type definition from the corresponding to this unresolved /// type definition. diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractUnresolvedEntity.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractUnresolvedEntity.cs index d0a040e454..66d29b8c0c 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractUnresolvedEntity.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractUnresolvedEntity.cs @@ -50,7 +50,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation internal const ushort FlagShadowing = 0x0008; internal const ushort FlagSynthetic = 0x0010; internal const ushort FlagStatic = 0x0020; - // flags for DefaultUnresolvedTypeDefinition + // flags for DefaultUnresolvedTypeDefinition/LazyCecilTypeDefinition internal const ushort FlagAddDefaultConstructorIfRequired = 0x0040; internal const ushort FlagHasExtensionMethods = 0x0080; internal const ushort FlagHasNoExtensionMethods = 0x0100; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs index c4fb589e9b..4ea28dbe69 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs @@ -322,10 +322,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation } } - DefaultUnresolvedTypeDefinition dutd = part as DefaultUnresolvedTypeDefinition; - if (dutd != null) { - addDefaultConstructorIfRequired |= dutd.AddDefaultConstructorIfRequired; - } + addDefaultConstructorIfRequired |= part.AddDefaultConstructorIfRequired; } if (addDefaultConstructorIfRequired) { TypeKind kind = this.Kind;