diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs index c28ff0d3f4..e826a9a9e4 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs @@ -189,6 +189,29 @@ namespace ICSharpCode.NRefactory.CSharp.Completion } } + IEnumerable HandleMemberReferenceCompletion(ExpressionResult expr) + { + if (expr == null) + return null; + + // do not complete . (but ..) + if (expr.Node is PrimitiveExpression) { + var pexpr = (PrimitiveExpression)expr.Node; + if (!(pexpr.Value is string || pexpr.Value is char) && !pexpr.LiteralValue.Contains('.')) { + return null; + } + } + + var resolveResult = ResolveExpression (expr); + if (resolveResult == null) { + return null; + } + if (expr.Node is AstType) { + return CreateTypeAndNamespaceCompletionData(location, resolveResult.Item1, expr.Node, resolveResult.Item2); + } + return CreateCompletionData(location, resolveResult.Item1, expr.Node, resolveResult.Item2); + } + IEnumerable MagicKeyCompletion(char completionChar, bool controlSpace) { ExpressionResult expr; @@ -201,26 +224,7 @@ namespace ICSharpCode.NRefactory.CSharp.Completion if (IsInsideCommentOrString()) { return Enumerable.Empty(); } - expr = GetExpressionBeforeCursor(); - if (expr == null) { - return null; - } - // do not complete . (but ..) - if (expr.Node is PrimitiveExpression) { - var pexpr = (PrimitiveExpression)expr.Node; - if (!(pexpr.Value is string || pexpr.Value is char) && !pexpr.LiteralValue.Contains('.')) { - return null; - } - } - - resolveResult = ResolveExpression(expr); - if (resolveResult == null) { - return null; - } - if (expr.Node is AstType) { - return CreateTypeAndNamespaceCompletionData(location, resolveResult.Item1, expr.Node, resolveResult.Item2); - } - return CreateCompletionData(location, resolveResult.Item1, expr.Node, resolveResult.Item2); + return HandleMemberReferenceCompletion(GetExpressionBeforeCursor()); case '#': if (IsInsideCommentOrString()) { return null; @@ -506,20 +510,32 @@ namespace ICSharpCode.NRefactory.CSharp.Completion var contextList = new CompletionDataWrapper (this); var identifierStart = GetExpressionAtCursor(); + if (identifierStart != null) { - if (identifierStart != null && identifierStart.Node is TypeParameterDeclaration) { - return null; - } + + if (identifierStart.Node is TypeParameterDeclaration) { + return null; + } - if (identifierStart != null && identifierStart.Node is VariableInitializer && location <= ((VariableInitializer)identifierStart.Node).NameToken.EndLocation) { - return controlSpace ? HandleAccessorContext() ?? DefaultControlSpaceItems(identifierStart) : null; - } + if (identifierStart.Node is MemberReferenceExpression) { + return HandleMemberReferenceCompletion(new ExpressionResult (((MemberReferenceExpression)identifierStart.Node).Target, identifierStart.Unit)); + } - if (identifierStart != null && identifierStart.Node is CatchClause) { - if (((CatchClause)identifierStart.Node).VariableNameToken.Contains(location)) { - return null; + if (identifierStart.Node is Identifier) { + // May happen in variable names + return controlSpace ? DefaultControlSpaceItems(identifierStart) : null; + } + + if (identifierStart.Node is VariableInitializer && location <= ((VariableInitializer)identifierStart.Node).NameToken.EndLocation) { + return controlSpace ? HandleAccessorContext() ?? DefaultControlSpaceItems(identifierStart) : null; + } + + if (identifierStart.Node is CatchClause) { + if (((CatchClause)identifierStart.Node).VariableNameToken.Contains(location)) { + return null; + } + identifierStart = null; } - identifierStart = null; } if (!(char.IsLetter(completionChar) || completionChar == '_') && (!controlSpace || identifierStart == null || !(identifierStart.Node.Parent is ArrayInitializerExpression))) { return controlSpace ? HandleAccessorContext() ?? DefaultControlSpaceItems(identifierStart) : null; @@ -2307,14 +2323,14 @@ namespace ICSharpCode.NRefactory.CSharp.Completion ExpressionResult GetExpressionAtCursor() { -// TextLocation memberLocation; -// if (currentMember != null) { -// memberLocation = currentMember.Region.Begin; -// } else if (currentType != null) { -// memberLocation = currentType.Region.Begin; -// } else { -// memberLocation = location; -// } + // TextLocation memberLocation; + // if (currentMember != null) { + // memberLocation = currentMember.Region.Begin; + // } else if (currentType != null) { + // memberLocation = currentType.Region.Begin; + // } else { + // memberLocation = location; + // } var baseUnit = ParseStub("a"); var tmpUnit = baseUnit; @@ -2322,7 +2338,8 @@ namespace ICSharpCode.NRefactory.CSharp.Completion if (expr == null) { expr = baseUnit.GetNodeAt(location.Line, location.Column - 1); } - + if (expr == null) + expr = baseUnit.GetNodeAt(location.Line, location.Column - 1); // try insertStatement if (expr == null && baseUnit.GetNodeAt(location.Line, location.Column) != null) { tmpUnit = baseUnit = ParseStub("a();", false); diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngineBase.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngineBase.cs index fc7d21b746..4e84fa5533 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngineBase.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngineBase.cs @@ -415,9 +415,9 @@ namespace ICSharpCode.NRefactory.CSharp.Completion wrapper.Append (';'); } - protected CompilationUnit ParseStub (string continuation, bool appendSemicolon = true, string afterContinuation = null) + protected CompilationUnit ParseStub(string continuation, bool appendSemicolon = true, string afterContinuation = null) { - var mt = GetMemberTextToCaret (); + var mt = GetMemberTextToCaret(); if (mt == null) { return null; } @@ -429,7 +429,7 @@ namespace ICSharpCode.NRefactory.CSharp.Completion var wrapper = new StringBuilder (); bool wrapInClass = memberLocation != new TextLocation (1, 1); if (wrapInClass) { - var nodeAtLocation = Unit.GetNodeAt (memberLocation, n => n is TypeDeclaration || n is NamespaceDeclaration); + var nodeAtLocation = Unit.GetNodeAt(memberLocation, n => n is TypeDeclaration || n is NamespaceDeclaration); if (nodeAtLocation != null) { foreach (var n in nodeAtLocation.AncestorsAndSelf) { if (memberLocation == n.StartLocation) { @@ -438,25 +438,25 @@ namespace ICSharpCode.NRefactory.CSharp.Completion if (n is TypeDeclaration) { var t = (TypeDeclaration)n; switch (t.ClassType) { - case ClassType.Class: - wrapper.Append ("class"); - break; - case ClassType.Struct: - wrapper.Append ("struct"); - break; - case ClassType.Interface: - wrapper.Append ("interface"); - break; - case ClassType.Enum: - wrapper.Append ("enum"); - break; + case ClassType.Class: + wrapper.Append("class"); + break; + case ClassType.Struct: + wrapper.Append("struct"); + break; + case ClassType.Interface: + wrapper.Append("interface"); + break; + case ClassType.Enum: + wrapper.Append("enum"); + break; } - wrapper.Append (" " + t.Name + " {"); - wrapper.AppendLine (); + wrapper.Append(" " + t.Name + " {"); + wrapper.AppendLine(); closingBrackets++; generatedLines++; } else { - Console.WriteLine (n); + Console.WriteLine(n); } } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index 01201b019b..245d996866 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -265,6 +265,8 @@ + + @@ -322,6 +324,19 @@ + + + + + + + + + + + + + @@ -332,6 +347,7 @@ + diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/BaseRefactoringContext.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/BaseRefactoringContext.cs new file mode 100644 index 0000000000..6cc9214561 --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/BaseRefactoringContext.cs @@ -0,0 +1,103 @@ +// +// BaseRefactoringContext.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Linq; +using System.Threading; +using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.CSharp.TypeSystem; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem.Implementation; +using ICSharpCode.NRefactory.Editor; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + public abstract class BaseRefactoringContext + { + protected readonly CSharpAstResolver resolver; + readonly CancellationToken cancellationToken; + + public virtual bool Supports(Version version) + { + return true; + } + + public CancellationToken CancellationToken { + get { return cancellationToken; } + } + + public virtual AstNode RootNode { + get { return resolver.RootNode; } + } + + public ICompilation Compilation { + get { return resolver.Compilation; } + } + + public BaseRefactoringContext (ICSharpCode.NRefactory.CSharp.Resolver.CSharpAstResolver resolver, System.Threading.CancellationToken cancellationToken) + { + this.resolver = resolver; + this.cancellationToken = cancellationToken; + } + + #region Resolving + public ResolveResult Resolve (AstNode node) + { + return resolver.Resolve (node, cancellationToken); + } + + public CSharpResolver GetResolverStateBefore(AstNode node) + { + return resolver.GetResolverStateBefore (node, cancellationToken); + } + + public CSharpResolver GetResolverStateAfter(AstNode node) + { + return resolver.GetResolverStateAfter (node, cancellationToken); + } + + public IType ResolveType (AstType type) + { + return resolver.Resolve (type, cancellationToken).Type; + } + + public IType GetExpectedType (Expression expression) + { + return resolver.GetExpectedType(expression, cancellationToken); + } + + public Conversion GetConversion (Expression expression) + { + return resolver.GetConversion(expression, cancellationToken); + } + #endregion + + public abstract Script StartScript(); + + } + +} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CheckIfParameterIsNull.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CheckIfParameterIsNull.cs index f658d06a96..c77f0d6a4f 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CheckIfParameterIsNull.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CheckIfParameterIsNull.cs @@ -46,10 +46,10 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring if (bodyStatement == null) return false; - if (parameter.Type is PrimitiveType) - return (((PrimitiveType)parameter.Type).Keyword == "object" || ((PrimitiveType)parameter.Type).Keyword == "string") && !HasNullCheck (parameter); + var type = context.ResolveType(parameter.Type); + if (type.IsReferenceType == false) + return false; - // TODO: check for structs return !HasNullCheck (parameter); } @@ -83,25 +83,20 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring class CheckNullVisitor : DepthFirstAstVisitor { - ParameterDeclaration parameter; + readonly Expression pattern; - public bool ContainsNullCheck { - get; - set; - } + internal bool ContainsNullCheck; public CheckNullVisitor (ParameterDeclaration parameter) { - this.parameter = parameter; + this.pattern = PatternHelper.CommutativeOperator(new IdentifierExpression(parameter.Name), BinaryOperatorType.Any, new NullReferenceExpression()); } public override object VisitIfElseStatement (IfElseStatement ifElseStatement, object data) { if (ifElseStatement.Condition is BinaryOperatorExpression) { var binOp = ifElseStatement.Condition as BinaryOperatorExpression; - if ((binOp.Operator == BinaryOperatorType.Equality || binOp.Operator == BinaryOperatorType.InEquality) && - binOp.Left.IsMatch (new IdentifierExpression (parameter.Name)) && binOp.Right.IsMatch (new NullReferenceExpression ()) || - binOp.Right.IsMatch (new IdentifierExpression (parameter.Name)) && binOp.Left.IsMatch (new NullReferenceExpression ())) { + if ((binOp.Operator == BinaryOperatorType.Equality || binOp.Operator == BinaryOperatorType.InEquality) && pattern.IsMatch(binOp)) { ContainsNullCheck = true; } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/ConvertForeachToFor.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/ConvertForeachToFor.cs index 37af97b91c..41fd864496 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/ConvertForeachToFor.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/ConvertForeachToFor.cs @@ -54,6 +54,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring var result = context.Resolve (foreachStatement.InExpression); var countProperty = GetCountProperty (result.Type); + // TODO: use another variable name if 'i' is already in use var initializer = new VariableDeclarationStatement (new PrimitiveType ("int"), "i", new PrimitiveExpression (0)); var id1 = new IdentifierExpression ("i"); var id2 = id1.Clone (); @@ -88,7 +89,10 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring if (astNode == null) return null; var result = (astNode as ForeachStatement) ?? astNode.Parent as ForeachStatement; - if (result == null || context.Resolve (result.InExpression) == null) + if (result == null) + return null; + var collection = context.Resolve (result.InExpression); + if (collection.Type.Kind != TypeKind.Array && !collection.Type.GetProperties(p => p.IsIndexer).Any()) return null; return result; } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/IInspector.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/IInspector.cs new file mode 100644 index 0000000000..50d3cf42ce --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/IInspector.cs @@ -0,0 +1,36 @@ +// +// IInspector.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + public interface IInspector + { + IEnumerable Run (BaseRefactoringContext context); + } +} + diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/InspectionIssue.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/InspectionIssue.cs new file mode 100644 index 0000000000..946483b6e7 --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/InspectionIssue.cs @@ -0,0 +1,61 @@ +// +// InspectionIssue.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + public class InspectionIssue + { + public string Title { + get; + private set; + } + + public TextLocation Start { + get; + private set; + } + + public TextLocation End { + get; + private set; + } + + public System.Action Fix { + get; + private set; + } + + public InspectionIssue (string title, TextLocation start, TextLocation end, System.Action fix) + { + this.Title = title; + this.Start = start; + this.End = end; + this.Fix = fix; + } + } +} + diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/ConditionalToNullCoalescingInspector.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/ConditionalToNullCoalescingInspector.cs new file mode 100644 index 0000000000..1211b659e3 --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/ConditionalToNullCoalescingInspector.cs @@ -0,0 +1,98 @@ +// +// ConditionalToNullCoalescingInspector.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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.Linq; +using ICSharpCode.NRefactory.PatternMatching; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + /// + /// Checks for "a != null ? a : other" + /// Converts to: "a ?? other" + /// + public class ConditionalToNullCoalescingInspector : IInspector + { + static readonly Pattern pattern = new Choice { + // a != null ? a : other + new ConditionalExpression( + PatternHelper.CommutativeOperator(new AnyNode("a"), BinaryOperatorType.InEquality, new NullReferenceExpression()), + new Backreference("a"), + new AnyNode("other") + ), + // a == null ? other : a + new ConditionalExpression( + PatternHelper.CommutativeOperator(new AnyNode("a"), BinaryOperatorType.Equality, new NullReferenceExpression()), + new AnyNode("other"), + new Backreference("a") + ), + }; + + string title = "Convert to '??' expression"; + + public string Title { + get { + return title; + } + set { + title = value; + } + } + + public IEnumerable Run (BaseRefactoringContext context) + { + var visitor = new GatherVisitor (context, this); + context.RootNode.AcceptVisitor (visitor); + return visitor.FoundIssues; + } + + class GatherVisitor : GatherVisitorBase + { + readonly ConditionalToNullCoalescingInspector inspector; + + public GatherVisitor (BaseRefactoringContext ctx, ConditionalToNullCoalescingInspector inspector) : base (ctx) + { + this.inspector = inspector; + } + + public override void VisitConditionalExpression(ConditionalExpression conditionalExpression) + { + Match m = pattern.Match(conditionalExpression); + if (m.Success) { + var a = m.Get("a").Single(); + var other = m.Get("other").Single(); + AddIssue(conditionalExpression, inspector.Title, delegate { + using (var script = ctx.StartScript ()) { + var expr = new BinaryOperatorExpression (a.Clone (), BinaryOperatorType.NullCoalescing, other.Clone ()); + script.Replace (conditionalExpression, expr); + } + }); + } + base.VisitConditionalExpression (conditionalExpression); + } + } + } +} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/GatherVisitorBase.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/GatherVisitorBase.cs new file mode 100644 index 0000000000..d5f9bcbcef --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/GatherVisitorBase.cs @@ -0,0 +1,63 @@ +// +// GatherVisitorBase.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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.CSharp.Refactoring; +using System.Collections.Generic; + +namespace ICSharpCode.NRefactory.CSharp +{ + class GatherVisitorBase : DepthFirstAstVisitor + { + protected readonly BaseRefactoringContext ctx; + + public readonly List FoundIssues = new List (); + + public GatherVisitorBase (BaseRefactoringContext ctx) + { + this.ctx = ctx; + } + + protected override void VisitChildren (AstNode node) + { + if (ctx.CancellationToken.IsCancellationRequested) + return; + base.VisitChildren (node); + } + + protected void AddIssue (AstNode node, string title, System.Action fix = null) + { + FoundIssues.Add (new InspectionIssue (title, node.StartLocation, node.EndLocation, fix)); + } + + protected void AddIssue(TextLocation start, TextLocation end, string title, System.Action fix = null) + { + FoundIssues.Add (new InspectionIssue (title, start, end, fix)); + } + } + + +} + diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/NotImplementedExceptionInspector.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/NotImplementedExceptionInspector.cs new file mode 100644 index 0000000000..2eea3f03da --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/NotImplementedExceptionInspector.cs @@ -0,0 +1,77 @@ +// +// NotImplementedExceptionInspector.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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.PatternMatching; +using System.Collections.Generic; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + /// + /// This inspector just shows that there is a not implemented exception. It doesn't offer a fix. + /// Should only be shown in overview bar, no underlining. + /// + public class NotImplementedExceptionInspector : IInspector + { + string title = "NotImplemented exception thrown"; + + public string Title { + get { + return title; + } + set { + title = value; + } + } + + public IEnumerable Run (BaseRefactoringContext context) + { + var visitor = new GatherVisitor (context, this); + context.RootNode.AcceptVisitor (visitor); + return visitor.FoundIssues; + } + + class GatherVisitor : GatherVisitorBase + { + readonly NotImplementedExceptionInspector inspector; + + public GatherVisitor (BaseRefactoringContext ctx, NotImplementedExceptionInspector inspector) : base (ctx) + { + this.inspector = inspector; + } + + public override void VisitThrowStatement(ThrowStatement throwStatement) + { + var result = ctx.Resolve (throwStatement.Expression); + if (result.Type.Equals (ctx.Compilation.FindType (typeof(System.NotImplementedException)))) + AddIssue (throwStatement, inspector.Title); + + base.VisitThrowStatement(throwStatement); + } + } + } +} + diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantInternalInspector.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantInternalInspector.cs new file mode 100644 index 0000000000..31e1ee4ac3 --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantInternalInspector.cs @@ -0,0 +1,82 @@ +// +// RedundantInternalInspector.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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.PatternMatching; +using System.Collections.Generic; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + /// + /// Finds redundant internal modifiers. + /// + public class RedundantInternalInspector : IInspector + { + string title = "Remove redundant 'internal' modifier"; + + public string Title { + get { + return title; + } + set { + title = value; + } + } + + public IEnumerable Run (BaseRefactoringContext context) + { + var visitor = new GatherVisitor (context, this); + context.RootNode.AcceptVisitor (visitor); + return visitor.FoundIssues; + } + + class GatherVisitor : GatherVisitorBase + { + readonly RedundantInternalInspector inspector; + + public GatherVisitor (BaseRefactoringContext ctx, RedundantInternalInspector inspector) : base (ctx) + { + this.inspector = inspector; + } + + public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration) + { + foreach (var token_ in typeDeclaration.ModifierTokens) { + var token = token_; + if (token.Modifier == Modifiers.Internal) { + AddIssue(token, inspector.Title, delegate { + using (var script = ctx.StartScript ()) { + script.Remove(token); + } + }); + } + } + } + } + } +} + diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantNamespaceUsageInspector.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantNamespaceUsageInspector.cs new file mode 100644 index 0000000000..25ff718d82 --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantNamespaceUsageInspector.cs @@ -0,0 +1,95 @@ +// +// RedundantNamespaceUsageInspector.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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.Linq; + +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + /// + /// Finds redundant namespace usages. + /// + public class RedundantNamespaceUsageInspector : IInspector + { + string title = "Remove redundant namespace usage"; + + public string Title { + get { + return title; + } + set { + title = value; + } + } + + public IEnumerable Run (BaseRefactoringContext context) + { + var visitor = new GatherVisitor (context, this); + context.RootNode.AcceptVisitor (visitor); + return visitor.FoundIssues; + } + + class GatherVisitor : GatherVisitorBase + { + readonly RedundantNamespaceUsageInspector inspector; + + public GatherVisitor (BaseRefactoringContext ctx, RedundantNamespaceUsageInspector inspector) : base (ctx) + { + this.inspector = inspector; + } + + public override void VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression) + { + base.VisitMemberReferenceExpression(memberReferenceExpression); + + var result = ctx.Resolve(memberReferenceExpression.Target); + if (!(result is NamespaceResolveResult)) { + return; + } + var wholeResult = ctx.Resolve(memberReferenceExpression); + if (!(wholeResult is TypeResolveResult)) { + return; + } + + var state = ctx.GetResolverStateBefore(memberReferenceExpression); + var lookupName = state.LookupSimpleNameOrTypeName(memberReferenceExpression.MemberName, new List (), SimpleNameLookupMode.Expression); + + if (lookupName is TypeResolveResult && !lookupName.IsError && wholeResult.Type.Equals(lookupName.Type)) { + AddIssue(memberReferenceExpression.StartLocation, memberReferenceExpression.MemberNameToken.StartLocation, inspector.Title, delegate { + using (var script = ctx.StartScript ()) { + script.Replace(memberReferenceExpression, RefactoringAstHelper.RemoveTarget(memberReferenceExpression)); + } + } + ); + } + } + } + } +} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantPrivateInspector.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantPrivateInspector.cs new file mode 100644 index 0000000000..48d0ff5261 --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantPrivateInspector.cs @@ -0,0 +1,143 @@ +// +// RedundantPrivateInspector.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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.PatternMatching; +using System.Collections.Generic; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + /// + /// Finds redundant internal modifiers. + /// + public class RedundantPrivateInspector : IInspector + { + string title = "Remove redundant 'private' modifier"; + + public string Title { + get { + return title; + } + set { + title = value; + } + } + + public IEnumerable Run (BaseRefactoringContext context) + { + var visitor = new GatherVisitor (context, this); + context.RootNode.AcceptVisitor (visitor); + return visitor.FoundIssues; + } + + class GatherVisitor : GatherVisitorBase + { + readonly RedundantPrivateInspector inspector; + + public GatherVisitor (BaseRefactoringContext ctx, RedundantPrivateInspector inspector) : base (ctx) + { + this.inspector = inspector; + } + + void CheckNode(EntityDeclaration node) + { + foreach (var token_ in node.ModifierTokens) { + var token = token_; + if (token.Modifier == Modifiers.Private) { + AddIssue(token, inspector.Title, delegate { + using (var script = ctx.StartScript ()) { + script.Remove(token); + } + }); + } + } + } + + public override void VisitMethodDeclaration(MethodDeclaration methodDeclaration) + { + base.VisitMethodDeclaration(methodDeclaration); + CheckNode(methodDeclaration); + } + + public override void VisitFieldDeclaration(FieldDeclaration fieldDeclaration) + { + base.VisitFieldDeclaration(fieldDeclaration); + CheckNode(fieldDeclaration); + } + + public override void VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration) + { + base.VisitPropertyDeclaration(propertyDeclaration); + CheckNode(propertyDeclaration); + } + + public override void VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration) + { + base.VisitIndexerDeclaration(indexerDeclaration); + CheckNode(indexerDeclaration); + } + + public override void VisitEventDeclaration(EventDeclaration eventDeclaration) + { + base.VisitEventDeclaration(eventDeclaration); + CheckNode(eventDeclaration); + } + + public override void VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration) + { + base.VisitCustomEventDeclaration(eventDeclaration); + CheckNode(eventDeclaration); + } + + public override void VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration) + { + base.VisitConstructorDeclaration(constructorDeclaration); + CheckNode(constructorDeclaration); + } + + public override void VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration) + { + base.VisitOperatorDeclaration(operatorDeclaration); + CheckNode(operatorDeclaration); + } + + public override void VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclaration) + { + base.VisitFixedFieldDeclaration(fixedFieldDeclaration); + CheckNode(fixedFieldDeclaration); + } + + public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration) + { + if (!(typeDeclaration.Parent is TypeDeclaration)) { + CheckNode(typeDeclaration); + } + base.VisitTypeDeclaration(typeDeclaration); + } + } + } +} \ No newline at end of file diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantThisInspector.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantThisInspector.cs new file mode 100644 index 0000000000..0f8a66e80e --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantThisInspector.cs @@ -0,0 +1,119 @@ +// +// RedundantThisInspector.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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.PatternMatching; +using System.Collections.Generic; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.CSharp.Resolver; +using System.Linq; + + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + /// + /// Finds redundant namespace usages. + /// + public class RedundantThisInspector : IInspector + { + string title = "Remove redundant 'this.'"; + + public string Title { + get { + return title; + } + set { + title = value; + } + } + + public IEnumerable Run (BaseRefactoringContext context) + { + var visitor = new GatherVisitor (context, this); + context.RootNode.AcceptVisitor (visitor); + return visitor.FoundIssues; + } + + class GatherVisitor : GatherVisitorBase + { + readonly RedundantThisInspector inspector; + + public GatherVisitor (BaseRefactoringContext ctx, RedundantThisInspector inspector) : base (ctx) + { + this.inspector = inspector; + } + + static IMember GetMember (ResolveResult result) + { + if (result is MemberResolveResult) { + return ((MemberResolveResult)result).Member; + } else if (result is MethodGroupResolveResult) { + return ((MethodGroupResolveResult)result).Methods.FirstOrDefault (); + } + + return null; + } + + public override void VisitThisReferenceExpression (ThisReferenceExpression thisReferenceExpression) + { + base.VisitThisReferenceExpression (thisReferenceExpression); + var memberReference = thisReferenceExpression.Parent as MemberReferenceExpression; + if (memberReference == null) { + return; + } + + var state = ctx.GetResolverStateAfter (thisReferenceExpression); + var wholeResult = ctx.Resolve (memberReference); + + IMember member = GetMember (wholeResult); + if (member == null) { + return; + } + + var result = state.LookupSimpleNameOrTypeName (memberReference.MemberName, EmptyList.Instance, SimpleNameLookupMode.Expression); + + bool isRedundant; + if (result is MemberResolveResult) { + isRedundant = ((MemberResolveResult)result).Member.Region.Equals(member.Region); + } else if (result is MethodGroupResolveResult) { + isRedundant = ((MethodGroupResolveResult)result).Methods.Any(m => m.Region.Equals(member.Region)); + } else { + return; + } + + if (isRedundant) { + AddIssue(thisReferenceExpression.StartLocation, memberReference.MemberNameToken.StartLocation, inspector.Title, delegate { + using (var script = ctx.StartScript ()) { + script.Replace(memberReference, RefactoringAstHelper.RemoveTarget(memberReference)); + } + } + ); + } + } + } + } +} \ No newline at end of file diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantUsingInspector.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantUsingInspector.cs new file mode 100644 index 0000000000..4800b07b30 --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/RedundantUsingInspector.cs @@ -0,0 +1,77 @@ +// +// RedundantUsingInspector.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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.PatternMatching; +using System.Collections.Generic; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.Semantics; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + /// + /// Finds redundant using declarations. + /// + public class RedundantUsingInspector : IInspector + { + string title = "Remove redundant using"; + + public string Title { + get { + return title; + } + set { + title = value; + } + } + + public IEnumerable Run (BaseRefactoringContext context) + { + var visitor = new GatherVisitor (context, this); + context.RootNode.AcceptVisitor (visitor); + return visitor.FoundIssues; + } + + class GatherVisitor : GatherVisitorBase + { + readonly RedundantUsingInspector inspector; + + public GatherVisitor (BaseRefactoringContext ctx, RedundantUsingInspector inspector) : base (ctx) + { + this.inspector = inspector; + } + + public override void VisitUsingDeclaration(UsingDeclaration usingDeclaration) + { + base.VisitUsingDeclaration(usingDeclaration); + // TODO + // return cSharpResolver.usedScopes + // .OfType () + // .Any (u => u.ResolveNamespace (ctx).NamespaceName == ns) || additionalNamespaces.Contains (ns); + } + } + } +} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/StringIsNullOrEmptyInspector.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/StringIsNullOrEmptyInspector.cs new file mode 100644 index 0000000000..ba79438e43 --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/StringIsNullOrEmptyInspector.cs @@ -0,0 +1,121 @@ +// +// StringIsNullOrEmptyInspector.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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.Linq; +using ICSharpCode.NRefactory.PatternMatching; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + /// + /// Checks for str == null && str == "" + /// Converts to: string.IsNullOrEmpty (str) + /// + public class StringIsNullOrEmptyInspector : IInspector + { + static readonly Pattern pattern = new Choice { + // str == null || str == "" + new BinaryOperatorExpression ( + PatternHelper.CommutativeOperator(new AnyNode ("str"), BinaryOperatorType.Equality, new NullReferenceExpression ()), + BinaryOperatorType.ConditionalOr, + PatternHelper.CommutativeOperator(new Backreference ("str"), BinaryOperatorType.Equality, new PrimitiveExpression ("")) + ), + // str == "" || str == null + new BinaryOperatorExpression ( + PatternHelper.CommutativeOperator(new Backreference ("str"), BinaryOperatorType.Equality, new PrimitiveExpression ("")), + BinaryOperatorType.ConditionalOr, + PatternHelper.CommutativeOperator(new AnyNode ("str"), BinaryOperatorType.Equality, new NullReferenceExpression ()) + ), + }; + + static readonly Pattern negPattern = new Choice { + // str != null && str != "" + new BinaryOperatorExpression ( + PatternHelper.CommutativeOperator(new AnyNode ("str"), BinaryOperatorType.InEquality, new NullReferenceExpression ()), + BinaryOperatorType.ConditionalAnd, + PatternHelper.CommutativeOperator(new Backreference ("str"), BinaryOperatorType.InEquality, new PrimitiveExpression ("")) + ), + // str != "" && str != null + new BinaryOperatorExpression ( + PatternHelper.CommutativeOperator(new Backreference ("str"), BinaryOperatorType.InEquality, new PrimitiveExpression ("")), + BinaryOperatorType.ConditionalAnd, + PatternHelper.CommutativeOperator(new AnyNode ("str"), BinaryOperatorType.InEquality, new NullReferenceExpression ()) + ), + }; + + string title = "Use string.IsNullOrEmpty"; + + public string Title { + get { + return title; + } + set { + title = value; + } + } + + public IEnumerable Run (BaseRefactoringContext context) + { + var visitor = new GatherVisitor (context, this); + context.RootNode.AcceptVisitor (visitor); + return visitor.FoundIssues; + } + + class GatherVisitor : GatherVisitorBase + { + readonly StringIsNullOrEmptyInspector inspector; + + public GatherVisitor (BaseRefactoringContext ctx, StringIsNullOrEmptyInspector inspector) : base (ctx) + { + this.inspector = inspector; + } + + public override void VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression) + { + base.VisitBinaryOperatorExpression(binaryOperatorExpression); + Match m = pattern.Match(binaryOperatorExpression); + bool isNegated = false; + if (!m.Success) { + m = negPattern.Match(binaryOperatorExpression); + isNegated = true; + } + if (m.Success) { + var str = m.Get("str").Single(); + AddIssue(binaryOperatorExpression, inspector.Title, delegate { + using (var script = ctx.StartScript ()) { + Expression expr = new PrimitiveType ("string").Invoke("IsNullOrEmpty", str.Clone()); + if (isNegated) + expr = new UnaryOperatorExpression (UnaryOperatorType.Not, expr); + script.Replace(binaryOperatorExpression, expr); + } + }); + return; + } + } + + } + } +} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/UseVarKeywordInspector.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/UseVarKeywordInspector.cs new file mode 100644 index 0000000000..23d3457c2d --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Inspector/UseVarKeywordInspector.cs @@ -0,0 +1,113 @@ +// +// UseVarKeywordInspector.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin +// +// 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.PatternMatching; +using System.Collections.Generic; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.Semantics; +using System.Linq; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + /// + /// Checks for places where the 'var' keyword can be used. Note that the action is actually done with a context + /// action. + /// + public class UseVarKeywordInspector : IInspector + { + string title = "Use 'var' keyword"; + + public string Title { + get { + return title; + } + set { + title = value; + } + } + + public IEnumerable Run (BaseRefactoringContext context) + { + var visitor = new GatherVisitor (context, this); + context.RootNode.AcceptVisitor (visitor); + return visitor.FoundIssues; + } + + class GatherVisitor : GatherVisitorBase + { + readonly UseVarKeywordInspector inspector; + + public GatherVisitor (BaseRefactoringContext ctx, UseVarKeywordInspector inspector) : base (ctx) + { + this.inspector = inspector; + } + + public override void VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement) + { + base.VisitVariableDeclarationStatement(variableDeclarationStatement); + if (variableDeclarationStatement.Type is PrimitiveType) { + return; + } + if (variableDeclarationStatement.Type is SimpleType && ((SimpleType)variableDeclarationStatement.Type).Identifier == "var") { + return; + } + if (variableDeclarationStatement.Variables.Count != 1) { + return; + } + + //only checks for cases where the type would be obvious - assignment of new, cast, etc. + //also check the type actually matches else the user might want to assign different subclasses later + var v = variableDeclarationStatement.Variables.Single(); + + var arrCreate = v.Initializer as ArrayCreateExpression; + if (arrCreate != null) { + var n = variableDeclarationStatement.Type as ComposedType; + //FIXME: check the specifier compatibility + if (n != null && n.ArraySpecifiers.Any() && n.BaseType.IsMatch(arrCreate.Type)) { + AddIssue(variableDeclarationStatement); + } + } + var objCreate = v.Initializer as ObjectCreateExpression; + if (objCreate != null && objCreate.Type.IsMatch(variableDeclarationStatement.Type)) { + AddIssue(variableDeclarationStatement); + } + var asCast = v.Initializer as AsExpression; + if (asCast != null && asCast.Type.IsMatch(variableDeclarationStatement.Type)) { + AddIssue(variableDeclarationStatement); + } + var cast = v.Initializer as CastExpression; + if (cast != null && cast.Type.IsMatch(variableDeclarationStatement.Type)) { + AddIssue(variableDeclarationStatement); + } + } + + void AddIssue(VariableDeclarationStatement variableDeclarationStatement) + { + AddIssue(variableDeclarationStatement.Type, inspector.Title); + } + } + } +} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/PatternHelper.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/PatternHelper.cs new file mode 100644 index 0000000000..9370a86cde --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/PatternHelper.cs @@ -0,0 +1,40 @@ +// 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.PatternMatching; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + /// + /// Helper class for constructing pattern ASTs. + /// + public class PatternHelper + { + /// + /// Produces a choice pattern for expr1 op expr2 or expr2 op expr1. + /// + public static Expression CommutativeOperator(Expression expr1, BinaryOperatorType op, Expression expr2) + { + return new Choice { + new BinaryOperatorExpression(expr1, op, expr2), + new BinaryOperatorExpression(expr2.Clone(), op, expr1.Clone()) + }; + } + } +} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringAstHelper.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringAstHelper.cs new file mode 100644 index 0000000000..25486c06ed --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringAstHelper.cs @@ -0,0 +1,49 @@ +// 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.Linq; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + /// + /// Helper methods for constructing ASTs for refactoring. + /// These helpers work with frozen ASTs, i.e. they clone input nodes. + /// + public class RefactoringAstHelper + { + /// + /// Removes the target from a member reference while preserving the identifier and type arguments. + /// + public static IdentifierExpression RemoveTarget(MemberReferenceExpression mre) + { + IdentifierExpression ident = new IdentifierExpression(mre.MemberName); + ident.TypeArguments.AddRange(mre.TypeArguments.Select(t => t.Clone())); + return ident; + } + + /// + /// Removes the target from a member reference while preserving the identifier and type arguments. + /// + public static SimpleType RemoveTarget(MemberType memberType) + { + return new SimpleType(memberType.MemberName, memberType.TypeArguments.Select(t => t.Clone())); + } + } +} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs index 0de65e549c..e066dcbde7 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs @@ -36,36 +36,14 @@ using ICSharpCode.NRefactory.Editor; namespace ICSharpCode.NRefactory.CSharp.Refactoring { - public abstract class RefactoringContext + public abstract class RefactoringContext : BaseRefactoringContext { - readonly protected CSharpAstResolver resolver; - readonly CancellationToken cancellationToken; - - public RefactoringContext(CSharpAstResolver resolver, CancellationToken cancellationToken) + public RefactoringContext(CSharpAstResolver resolver, CancellationToken cancellationToken) : base (resolver, cancellationToken) { - this.resolver = resolver; - this.cancellationToken = cancellationToken; - } - - public CancellationToken CancellationToken { - get { return cancellationToken; } - } - - public virtual AstNode RootNode { - get { return resolver.RootNode; } } public abstract TextLocation Location { get; } - - public virtual bool Supports(Version version) - { - return true; - } - - public ICompilation Compilation { - get { return resolver.Compilation; } - } - + public virtual AstType CreateShortType (IType fullType) { var csResolver = resolver.GetResolverStateBefore(GetNode()); @@ -132,29 +110,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring public abstract string GetText (ISegment segment); #endregion - - #region Resolving - public ResolveResult Resolve (AstNode node) - { - return resolver.Resolve (node, cancellationToken); - } - - public IType ResolveType (AstType type) - { - return resolver.Resolve (type, cancellationToken).Type; - } - - public IType GetExpectedType (Expression expression) - { - return resolver.GetExpectedType(expression, cancellationToken); - } - - public Conversion GetConversion (Expression expression) - { - return resolver.GetConversion(expression, cancellationToken); - } - #endregion - + public virtual string GetNameProposal (string name, bool camelCase = true) { string baseName = (camelCase ? char.ToLower (name [0]) : char.ToUpper (name [0])) + name.Substring (1); @@ -175,8 +131,6 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring { return baseName + (number > 0 ? (number + 1).ToString () : ""); } - - public abstract Script StartScript(); } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpConversions.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpConversions.cs index d498aa65ae..e818b6a5a0 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpConversions.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpConversions.cs @@ -805,10 +805,14 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } } var or = rr.PerformOverloadResolution(compilation, args, allowExpandingParams: false, conversions: this); - if (or.FoundApplicableCandidate) - return Conversion.MethodGroupConversion((IMethod)or.GetBestCandidateWithSubstitutedTypeArguments()); - else + if (or.FoundApplicableCandidate) { + IMethod method = (IMethod)or.GetBestCandidateWithSubstitutedTypeArguments(); + var thisRR = rr.TargetResult as ThisResolveResult; + bool isVirtual = method.IsOverridable && !(thisRR != null && thisRR.CausesNonVirtualInvocation); + return Conversion.MethodGroupConversion(method, isVirtual); + } else { return Conversion.None; + } } #endregion diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs index ae391f25d3..c9989360c1 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs @@ -1167,13 +1167,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver internal readonly IParameterizedMember nonLiftedOperator; public LiftedUserDefinedOperator(IMethod nonLiftedMethod) - : base(nonLiftedMethod.DeclaringType, (IMethod)nonLiftedMethod.MemberDefinition, - EmptyList.Instance, new MakeNullableVisitor(nonLiftedMethod.Compilation)) + : base(nonLiftedMethod, TypeParameterSubstitution.Identity) { this.nonLiftedOperator = nonLiftedMethod; + var substitution = new MakeNullableVisitor(nonLiftedMethod.Compilation); + this.Parameters = base.CreateParameters(substitution); // Comparison operators keep the 'bool' return type even when lifted. if (IsComparisonOperator(nonLiftedMethod)) this.ReturnType = nonLiftedMethod.ReturnType; + else + this.ReturnType = nonLiftedMethod.ReturnType.AcceptVisitor(substitution); } public IList NonLiftedParameters { @@ -1726,13 +1729,13 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (typeArguments != null && typeArguments.Count > 0) { if (method.TypeParameters.Count != typeArguments.Count) continue; - SpecializedMethod sm = new SpecializedMethod(method.DeclaringType, method, typeArguments); + SpecializedMethod sm = new SpecializedMethod(method, new TypeParameterSubstitution(null, typeArguments)); if (IsEligibleExtensionMethod(targetType, method, false, out inferredTypes)) outputGroup.Add(sm); } else { if (IsEligibleExtensionMethod(targetType, method, true, out inferredTypes)) { if (substituteInferredTypes && inferredTypes != null) { - outputGroup.Add(new SpecializedMethod(method.DeclaringType, method, inferredTypes)); + outputGroup.Add(new SpecializedMethod(method, new TypeParameterSubstitution(null, inferredTypes))); } else { outputGroup.Add(method); } @@ -1854,7 +1857,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (mgrr != null) { OverloadResolution or = mgrr.PerformOverloadResolution(compilation, arguments, argumentNames, conversions: conversions); if (or.BestCandidate != null) { - if (or.BestCandidate.IsStatic && !(mgrr.TargetResult is TypeResolveResult)) + if (or.BestCandidate.IsStatic && !or.IsExtensionMethodInvocation && !(mgrr.TargetResult is TypeResolveResult)) return or.CreateResolveResult(new TypeResolveResult(mgrr.TargetResult.Type)); else return or.CreateResolveResult(mgrr.TargetResult); diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/FindReferencedEntities.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/FindReferencedEntities.cs index 2971839eda..41bf2344bf 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/FindReferencedEntities.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/FindReferencedEntities.cs @@ -27,13 +27,39 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// public sealed class FindReferencedEntities : IResolveVisitorNavigator { - readonly Action referenceFound; + readonly Action memberReferenceFound; + readonly Action typeReferenceFound; + /// + /// Creates a new FindReferencedEntities instance that + /// looks for entity definitions. + /// The visitor will report type definitions and member definitions (not specialized members). + /// public FindReferencedEntities(Action referenceFound) { if (referenceFound == null) throw new ArgumentNullException("referenceFound"); - this.referenceFound = referenceFound; + this.memberReferenceFound = (node, member) => referenceFound(node, member.MemberDefinition); + this.typeReferenceFound = (node, type) => { + var def = type.GetDefinition(); + if (def != null) + referenceFound(node, def); + }; + } + + /// + /// Creates a new FindReferencedEntities instance that + /// looks for types and members. + /// The visitor will report parameterized types and potentially specialized members. + /// + public FindReferencedEntities(Action typeReferenceFound, Action memberReferenceFound) + { + if (typeReferenceFound == null) + throw new ArgumentNullException("typeReferenceFound"); + if (memberReferenceFound == null) + throw new ArgumentNullException("memberReferenceFound"); + this.typeReferenceFound = typeReferenceFound; + this.memberReferenceFound = memberReferenceFound; } public ResolveVisitorNavigationMode Scan(AstNode node) @@ -48,28 +74,26 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver MemberResolveResult mrr = result as MemberResolveResult; if (mrr != null) { - referenceFound(node, mrr.Member.MemberDefinition); + memberReferenceFound(node, mrr.Member); } TypeResolveResult trr = result as TypeResolveResult; if (trr != null) { - ITypeDefinition typeDef = trr.Type.GetDefinition(); - if (typeDef != null) - referenceFound(node, typeDef); + typeReferenceFound(node, trr.Type); } ForEachResolveResult ferr = result as ForEachResolveResult; if (ferr != null) { Resolved(node, ferr.GetEnumeratorCall); if (ferr.CurrentProperty != null) - referenceFound(node, ferr.CurrentProperty.MemberDefinition); + memberReferenceFound(node, ferr.CurrentProperty); if (ferr.MoveNextMethod != null) - referenceFound(node, ferr.MoveNextMethod.MemberDefinition); + memberReferenceFound(node, ferr.MoveNextMethod); } } public void ProcessConversion(Expression expression, ResolveResult result, Conversion conversion, IType targetType) { if (conversion.IsUserDefined || conversion.IsMethodGroupConversion) { - referenceFound(expression, conversion.Method.MemberDefinition); + memberReferenceFound(expression, conversion.Method); } } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/FindReferences.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/FindReferences.cs index ce95664bb5..4bdd9d9dd2 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/FindReferences.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/FindReferences.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using ICSharpCode.NRefactory.CSharp.Refactoring; @@ -42,9 +43,42 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { #region Properties /// - /// Gets/Sets whether to find type references even if an alias is being used. + /// Specifies whether to find type references even if an alias is being used. + /// Aliases may be var or using Alias = ...;. /// public bool FindTypeReferencesEvenIfAliased { get; set; } + + /// + /// Specifies whether find references should only look for specialized matches + /// with equal type parameter substitution to the member we are searching for. + /// + public bool FindOnlySpecializedReferences { get; set; } + + /// + /// If this option is enabled, find references on a overridden member + /// will find calls to the base member. + /// + public bool FindCallsThroughVirtualBaseMethod { get; set; } + + /// + /// If this option is enabled, find references on a member implementing + /// an interface will also find calls to the interface. + /// + public bool FindCallsThroughInterface { get; set; } + + /// + /// If this option is enabled, find references will look for all references + /// to the virtual method slot. + /// + public bool WholeVirtualSlot { get; set; } + + /// + /// Specifies whether to look for references in documentation comments. + /// This will find entity references in cref attributes and + /// parameter references in <param> and <paramref> tags. + /// TODO: implement this feature. + /// + public bool SearchInDocumentationComments { get; set; } #endregion #region GetEffectiveAccessibility @@ -110,6 +144,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } internal string searchTerm; + internal FindReferences findReferences; internal ICompilation declarationCompilation; internal Accessibility accessibility; internal ITypeDefinition topLevelTypeDefinition; @@ -119,6 +154,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver FindReferenceNavigator n = factory(compilation); if (n != null) { n.callback = callback; + n.findReferences = findReferences; return n; } else { return new ConstantModeResolveVisitorNavigator(ResolveVisitorNavigationMode.Skip, null); @@ -145,6 +181,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver abstract class FindReferenceNavigator : IResolveVisitorNavigator { internal FoundReferenceCallback callback; + internal FindReferences findReferences; internal abstract bool CanMatch(AstNode node); internal abstract bool IsMatch(ResolveResult rr); @@ -185,6 +222,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { if (entity == null) throw new ArgumentNullException("entity"); + if (entity is IMember) + entity = NormalizeMember((IMember)entity); Accessibility effectiveAccessibility = GetEffectiveAccessibility(entity); ITypeDefinition topLevelTypeDefinition = entity.DeclaringTypeDefinition; while (topLevelTypeDefinition != null && topLevelTypeDefinition.DeclaringTypeDefinition != null) @@ -219,7 +258,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver scope = GetSearchScopeForOperator((IMethod)entity); break; case EntityType.Constructor: - IMethod ctor = (IMethod)((IMethod)entity).MemberDefinition; + IMethod ctor = (IMethod)entity; scope = FindObjectCreateReferences(ctor); additionalScope = FindChainedConstructorReferences(ctor); break; @@ -233,11 +272,13 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver scope.accessibility = effectiveAccessibility; scope.declarationCompilation = entity.Compilation; scope.topLevelTypeDefinition = topLevelTypeDefinition; + scope.findReferences = this; if (additionalScope != null) { if (additionalScope.accessibility == Accessibility.None) additionalScope.accessibility = effectiveAccessibility; additionalScope.declarationCompilation = entity.Compilation; additionalScope.topLevelTypeDefinition = topLevelTypeDefinition; + additionalScope.findReferences = this; return new[] { scope, additionalScope }; } else { return new[] { scope }; @@ -385,17 +426,14 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (searchTerm.Length > 9 && searchTerm.EndsWith("Attribute", StringComparison.Ordinal)) { // The type might be an attribute, so we also need to look for the short form: string shortForm = searchTerm.Substring(0, searchTerm.Length - 9); - additionalScope = new SearchScope( - shortForm, - delegate (ICompilation compilation) { - ITypeDefinition imported = compilation.Import(typeDefinition); - if (imported != null) - return new FindTypeDefinitionReferencesNavigator(imported, shortForm); - else - return null; - }); + additionalScope = FindTypeDefinitionReferences(typeDefinition, shortForm); } } + return FindTypeDefinitionReferences(typeDefinition, searchTerm); + } + + SearchScope FindTypeDefinitionReferences(ITypeDefinition typeDefinition, string searchTerm) + { return new SearchScope( searchTerm, delegate (ICompilation compilation) { @@ -462,11 +500,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver SearchScope FindMemberReferences(IEntity member, Func factory) { string searchTerm = member.Name; - IMember memberDefinition = ((IMember)member).MemberDefinition; return new SearchScope( searchTerm, delegate(ICompilation compilation) { - IMember imported = compilation.Import(memberDefinition); + IMember imported = compilation.Import((IMember)member); return imported != null ? factory(imported) : null; }); } @@ -478,7 +515,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver public FindMemberReferencesNavigator(IMember member) { - this.member = member.MemberDefinition; + this.member = member; this.searchTerm = member.Name; } @@ -506,8 +543,58 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver internal override bool IsMatch(ResolveResult rr) { MemberResolveResult mrr = rr as MemberResolveResult; - return mrr != null && member == mrr.Member.MemberDefinition; + return mrr != null && findReferences.IsMemberMatch(member, mrr.Member, mrr.IsVirtualCall); + } + } + + IMember NormalizeMember(IMember member) + { + if (WholeVirtualSlot && member.IsOverride) + member = InheritanceHelper.GetBaseMembers(member, false).FirstOrDefault(m => !m.IsOverride) ?? member; + if (!FindOnlySpecializedReferences) + member = member.MemberDefinition; + return member; + } + + bool IsMemberMatch(IMember member, IMember referencedMember, bool isVirtualCall) + { + referencedMember = NormalizeMember(referencedMember); + if (member.Equals(referencedMember)) + return true; + if (!isVirtualCall) + return false; + bool isInterfaceCall = referencedMember.DeclaringTypeDefinition != null && referencedMember.DeclaringTypeDefinition.Kind == TypeKind.Interface; + if (FindCallsThroughVirtualBaseMethod && member.IsOverride && !WholeVirtualSlot && !isInterfaceCall) { + // Test if 'member' overrides 'referencedMember': + foreach (var baseMember in InheritanceHelper.GetBaseMembers(member, false)) { + if (FindOnlySpecializedReferences) { + if (baseMember.Equals(referencedMember)) + return true; + } else { + if (baseMember.MemberDefinition.Equals(referencedMember)) + return true; + } + if (!baseMember.IsOverride) + break; + } + return false; + } else if (FindCallsThroughInterface && isInterfaceCall) { + // Test if 'member' implements 'referencedMember': + if (FindOnlySpecializedReferences) { + return member.ImplementedInterfaceMembers.Contains(referencedMember); + } else { + return member.ImplementedInterfaceMembers.Any(m => m.MemberDefinition.Equals(referencedMember)); + } } + return false; + } + + bool PerformVirtualLookup(IMember member, IMember referencedMember) + { + if (FindCallsThroughVirtualBaseMethod && member.IsOverride && !WholeVirtualSlot) + return true; + var typeDef = referencedMember.DeclaringTypeDefinition; + return FindCallsThroughInterface && typeDef != null && typeDef.Kind == TypeKind.Interface; } sealed class FindFieldReferences : FindMemberReferencesNavigator @@ -568,10 +655,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #region Find References to IEnumerator.Current SearchScope FindEnumeratorCurrentReferences(IProperty property) { - IProperty propertyDefinition = (IProperty)property.MemberDefinition; return new SearchScope( delegate(ICompilation compilation) { - IProperty imported = compilation.Import(propertyDefinition); + IProperty imported = compilation.Import(property); return imported != null ? new FindEnumeratorCurrentReferencesNavigator(imported) : null; }); } @@ -593,7 +679,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver internal override bool IsMatch(ResolveResult rr) { ForEachResolveResult ferr = rr as ForEachResolveResult; - return ferr != null && ferr.CurrentProperty != null && ferr.CurrentProperty.MemberDefinition == property; + return ferr != null && ferr.CurrentProperty != null && findReferences.IsMemberMatch(property, ferr.CurrentProperty, true); } } #endregion @@ -601,8 +687,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #region Find Method References SearchScope GetSearchScopeForMethod(IMethod method) { - method = (IMethod)method.MemberDefinition; - Type specialNodeType; switch (method.Name) { case "Add": @@ -708,18 +792,18 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver var ferr = rr as ForEachResolveResult; if (ferr != null) { return IsMatch(ferr.GetEnumeratorCall) - || (ferr.MoveNextMethod != null && method == ferr.MoveNextMethod.MemberDefinition); + || (ferr.MoveNextMethod != null && findReferences.IsMemberMatch(method, ferr.MoveNextMethod, true)); } } var mrr = rr as MemberResolveResult; - return mrr != null && method == mrr.Member.MemberDefinition; + return mrr != null && findReferences.IsMemberMatch(method, mrr.Member, mrr.IsVirtualCall); } internal override void NavigatorDone(CSharpAstResolver resolver, CancellationToken cancellationToken) { foreach (var expr in potentialMethodGroupConversions) { var conversion = resolver.GetConversion(expr, cancellationToken); - if (conversion.IsMethodGroupConversion && conversion.Method.MemberDefinition == method) { + if (conversion.IsMethodGroupConversion && findReferences.IsMemberMatch(method, conversion.Method, conversion.IsVirtualMethodLookup)) { IType targetType = resolver.GetExpectedType(expr, cancellationToken); ResolveResult result = resolver.Resolve(expr, cancellationToken); ReportMatch(expr, new ConversionResolveResult(targetType, result, conversion)); @@ -733,7 +817,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #region Find Indexer References SearchScope FindIndexerReferences(IProperty indexer) { - indexer = (IProperty)indexer.MemberDefinition; return new SearchScope( delegate (ICompilation compilation) { IProperty imported = compilation.Import(indexer); @@ -761,7 +844,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver internal override bool IsMatch(ResolveResult rr) { MemberResolveResult mrr = rr as MemberResolveResult; - return mrr != null && indexer == mrr.Member.MemberDefinition; + return mrr != null && findReferences.IsMemberMatch(indexer, mrr.Member, mrr.IsVirtualCall); } } #endregion @@ -834,7 +917,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver SearchScope FindOperator(IMethod op, Func factory) { - op = (IMethod)op.MemberDefinition; return new SearchScope( delegate (ICompilation compilation) { IMethod imported = compilation.Import(op); @@ -875,7 +957,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver internal override bool IsMatch(ResolveResult rr) { MemberResolveResult mrr = rr as MemberResolveResult; - return mrr != null && op == mrr.Member.MemberDefinition; + return mrr != null && findReferences.IsMemberMatch(op, mrr.Member, mrr.IsVirtualCall); } } @@ -907,7 +989,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver internal override bool IsMatch(ResolveResult rr) { MemberResolveResult mrr = rr as MemberResolveResult; - return mrr != null && op == mrr.Member.MemberDefinition; + return mrr != null && findReferences.IsMemberMatch(op, mrr.Member, mrr.IsVirtualCall); } } @@ -928,12 +1010,12 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver internal override bool IsMatch(ResolveResult rr) { MemberResolveResult mrr = rr as MemberResolveResult; - return mrr != null && op == mrr.Member.MemberDefinition; + return mrr != null && findReferences.IsMemberMatch(op, mrr.Member, mrr.IsVirtualCall); } public override void ProcessConversion(Expression expression, ResolveResult result, Conversion conversion, IType targetType) { - if (conversion.IsUserDefined && conversion.Method.MemberDefinition == op) { + if (conversion.IsUserDefined && findReferences.IsMemberMatch(op, conversion.Method, conversion.IsVirtualMethodLookup)) { ReportMatch(expression, result); } } @@ -956,7 +1038,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver internal override bool IsMatch(ResolveResult rr) { ConversionResolveResult crr = rr as ConversionResolveResult; - return crr != null && crr.Conversion.IsUserDefined && crr.Conversion.Method.MemberDefinition == op; + return crr != null && crr.Conversion.IsUserDefined + && findReferences.IsMemberMatch(op, crr.Conversion.Method, crr.Conversion.IsVirtualMethodLookup); } } #endregion @@ -964,7 +1047,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #region Find Constructor References SearchScope FindObjectCreateReferences(IMethod ctor) { - ctor = (IMethod)ctor.MemberDefinition; string searchTerm = null; if (KnownTypeReference.GetCSharpNameByTypeCode(ctor.DeclaringTypeDefinition.KnownTypeCode) == null) { // not a built-in type @@ -1002,13 +1084,12 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver internal override bool IsMatch(ResolveResult rr) { MemberResolveResult mrr = rr as MemberResolveResult; - return mrr != null && ctor == mrr.Member.MemberDefinition; + return mrr != null && findReferences.IsMemberMatch(ctor, mrr.Member, mrr.IsVirtualCall); } } SearchScope FindChainedConstructorReferences(IMethod ctor) { - ctor = (IMethod)ctor.MemberDefinition; SearchScope searchScope = new SearchScope( delegate (ICompilation compilation) { IMethod imported = compilation.Import(ctor); @@ -1042,7 +1123,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver internal override bool IsMatch(ResolveResult rr) { MemberResolveResult mrr = rr as MemberResolveResult; - return mrr != null && ctor == mrr.Member.MemberDefinition; + return mrr != null && findReferences.IsMemberMatch(ctor, mrr.Member, mrr.IsVirtualCall); } } #endregion @@ -1050,22 +1131,17 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #region Find Destructor References SearchScope GetSearchScopeForDestructor(IMethod dtor) { - dtor = (IMethod)dtor.MemberDefinition; - string searchTerm = null; - if (KnownTypeReference.GetCSharpNameByTypeCode(dtor.DeclaringTypeDefinition.KnownTypeCode) == null) { - // not a built-in type - searchTerm = dtor.DeclaringTypeDefinition.Name; - } - return new SearchScope ( - searchTerm, + var scope = new SearchScope ( delegate (ICompilation compilation) { - IMethod imported = compilation.Import(dtor); - if (imported != null) { - return new FindDestructorReferencesNavigator (imported); - } else { - return null; - } - }); + IMethod imported = compilation.Import(dtor); + if (imported != null) { + return new FindDestructorReferencesNavigator (imported); + } else { + return null; + } + }); + scope.accessibility = Accessibility.Private; + return scope; } sealed class FindDestructorReferencesNavigator : FindReferenceNavigator @@ -1085,7 +1161,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver internal override bool IsMatch(ResolveResult rr) { MemberResolveResult mrr = rr as MemberResolveResult; - return mrr != null && dtor == mrr.Member.MemberDefinition; + return mrr != null && findReferences.IsMemberMatch(dtor, mrr.Member, mrr.IsVirtualCall); } } #endregion diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs index 22e39eccc2..45754dff3d 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs @@ -164,7 +164,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver this.TargetType, method, true, out inferredTypes)) { if (substituteInferredTypes && inferredTypes != null) { - outputGroup.Add(new SpecializedMethod(method.DeclaringType, method, inferredTypes)); + outputGroup.Add(new SpecializedMethod(method, new TypeParameterSubstitution(null, inferredTypes))); } else { outputGroup.Add(method); } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs index c5974b0de6..63c880bf81 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs @@ -778,11 +778,20 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { if (bestCandidate == null) return arguments; + else + return GetArgumentsWithConversions(null); + } + + IList GetArgumentsWithConversions(ResolveResult targetResolveResult) + { var conversions = this.ArgumentConversions; ResolveResult[] args = new ResolveResult[arguments.Length]; for (int i = 0; i < args.Length; i++) { + var argument = arguments[i]; + if (this.IsExtensionMethodInvocation && i == 0 && targetResolveResult != null) + argument = targetResolveResult; if (conversions[i] == Conversion.IdentityConversion) { - args[i] = arguments[i]; + args[i] = argument; } else { int parameterIndex = bestCandidate.ArgumentToParameterMap[i]; IType parameterType; @@ -792,9 +801,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver parameterType = SpecialType.UnknownType; } if (arguments[i].IsCompileTimeConstant && conversions[i] != Conversion.None) { - args[i] = new CSharpResolver(compilation).ResolveCast(parameterType, arguments[i]); + args[i] = new CSharpResolver(compilation).ResolveCast(parameterType, argument); } else { - args[i] = new ConversionResolveResult(parameterType, arguments[i], conversions[i]); + args[i] = new ConversionResolveResult(parameterType, argument, conversions[i]); } } } @@ -807,7 +816,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return null; IMethod method = bestCandidate.Member as IMethod; if (method != null && method.TypeParameters.Count > 0) { - return new SpecializedMethod(method.DeclaringType, (IMethod)method.MemberDefinition, bestCandidate.InferredTypes); + SpecializedMethod sm = method as SpecializedMethod; + if (sm != null) { + // Do not compose the substitutions, but merge them. + // This is required for InvocationTests.SubstituteClassAndMethodTypeParametersAtOnce + return new SpecializedMethod( + (IMethod)method.MemberDefinition, + new TypeParameterSubstitution(sm.Substitution.ClassTypeArguments, bestCandidate.InferredTypes)); + } else { + return new SpecializedMethod(method, new TypeParameterSubstitution(null, bestCandidate.InferredTypes)); + } } else { return bestCandidate.Member; } @@ -830,9 +848,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver throw new InvalidOperationException(); return new CSharpInvocationResolveResult( - targetResolveResult, + this.IsExtensionMethodInvocation ? new TypeResolveResult(member.DeclaringType) : targetResolveResult, member, - GetArgumentsWithConversions(), + GetArgumentsWithConversions(targetResolveResult), this.BestCandidateErrors, this.IsExtensionMethodInvocation, this.BestCandidateIsExpandedForm, diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs index d74f2fd102..c24f65f4bb 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs @@ -650,7 +650,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver ResolveResult result = errorResult; if (variableInitializer.Parent is FieldDeclaration || variableInitializer.Parent is EventDeclaration) { if (resolver.CurrentMember != null) { - result = new MemberResolveResult(null, resolver.CurrentMember); + result = new MemberResolveResult(null, resolver.CurrentMember, false); } } else { string identifier = variableInitializer.Name; @@ -707,7 +707,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (resolverEnabled) { ResolveResult result = errorResult; if (resolver.CurrentMember != null) { - result = new MemberResolveResult(null, resolver.CurrentMember); + result = new MemberResolveResult(null, resolver.CurrentMember, false); } ResolveAndProcessConversion(fixedVariableInitializer.CountExpression, resolver.Compilation.FindType(KnownTypeCode.Int32)); return result; @@ -726,7 +726,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver ScanChildren(member); if (resolver.CurrentMember != null) - return new MemberResolveResult(null, resolver.CurrentMember); + return new MemberResolveResult(null, resolver.CurrentMember, false); else return errorResult; } finally { @@ -772,7 +772,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } } if (resolver.CurrentMember != null) - return new MemberResolveResult(null, resolver.CurrentMember); + return new MemberResolveResult(null, resolver.CurrentMember, false); else return errorResult; } finally { @@ -805,7 +805,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } if (resolver.CurrentMember != null) - return new MemberResolveResult(null, resolver.CurrentMember); + return new MemberResolveResult(null, resolver.CurrentMember, false); else return errorResult; } finally { @@ -885,7 +885,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (resolverEnabled && resolver.CurrentTypeDefinition != null) { ResolveAndProcessConversion(enumMemberDeclaration.Initializer, resolver.CurrentTypeDefinition.EnumUnderlyingType); if (resolverEnabled && resolver.CurrentMember != null) - return new MemberResolveResult(null, resolver.CurrentMember); + return new MemberResolveResult(null, resolver.CurrentMember, false); else return errorResult; } else { @@ -1310,8 +1310,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { if (resolverEnabled || !objectCreateExpression.Initializer.IsNull) { var typeResolveResult = Resolve(objectCreateExpression.Type); - if (typeResolveResult.IsError) + if (typeResolveResult.IsError) { + ScanChildren (objectCreateExpression); return typeResolveResult; + } IType type = typeResolveResult.Type; List initializerStatements = null; diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/TypeInference.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/TypeInference.cs index 0d92589307..5a7118ec55 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/TypeInference.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/TypeInference.cs @@ -629,15 +629,12 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver // Handle array types: ArrayType arrU = U as ArrayType; ArrayType arrV = V as ArrayType; - ParameterizedType pV = V as ParameterizedType; if (arrU != null && arrV != null && arrU.Dimensions == arrV.Dimensions) { MakeLowerBoundInference(arrU.ElementType, arrV.ElementType); return; - } else if (arrU != null && IsIEnumerableCollectionOrList(pV) && arrU.Dimensions == 1) { - MakeLowerBoundInference(arrU.ElementType, pV.GetTypeArgument(0)); - return; } // Handle parameterized types: + ParameterizedType pV = V as ParameterizedType; if (pV != null) { ParameterizedType uniqueBaseType = null; foreach (IType baseU in U.GetAllBaseTypes()) { @@ -677,20 +674,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver Log.Unindent(); } } - - static bool IsIEnumerableCollectionOrList(ParameterizedType rt) - { - if (rt == null || rt.TypeParameterCount != 1) - return false; - switch (rt.GetDefinition().FullName) { - case "System.Collections.Generic.IList": - case "System.Collections.Generic.ICollection": - case "System.Collections.Generic.IEnumerable": - return true; - default: - return false; - } - } #endregion #region MakeUpperBoundInference (§7.5.2.10) @@ -713,15 +696,12 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver // Handle array types: ArrayType arrU = U as ArrayType; ArrayType arrV = V as ArrayType; - ParameterizedType pU = U as ParameterizedType; if (arrV != null && arrU != null && arrU.Dimensions == arrV.Dimensions) { MakeUpperBoundInference(arrU.ElementType, arrV.ElementType); return; - } else if (arrV != null && IsIEnumerableCollectionOrList(pU) && arrV.Dimensions == 1) { - MakeUpperBoundInference(pU.GetTypeArgument(0), arrV.ElementType); - return; } // Handle parameterized types: + ParameterizedType pU = U as ParameterizedType; if (pU != null) { ParameterizedType uniqueBaseType = null; foreach (IType baseV in V.GetAllBaseTypes()) { diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CodeCompletion/CodeCompletionBugTests.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CodeCompletion/CodeCompletionBugTests.cs index 6b80734c51..f9f5b2c25c 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CodeCompletion/CodeCompletionBugTests.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CodeCompletion/CodeCompletionBugTests.cs @@ -4789,5 +4789,27 @@ class B : A Assert.AreEqual (2, provider.Data.Where (d => d.DisplayText == "Method").Count ()); } + /// + /// Bug 3973 - code completion forgets context if text is deleted + /// + [Test()] + public void TestBug3973 () + { + var provider = CreateProvider ( +@" +using System; + +class A +{ + public static void Main (string[] args) + { + Console.$W$ + } +} + +"); + Assert.IsNotNull (provider.Find ("WriteLine"), "'WriteLine' not found."); + } + } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CodeCompletion/NameContextTests.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CodeCompletion/NameContextTests.cs index fc73c5f374..a7718813e9 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CodeCompletion/NameContextTests.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/CodeCompletion/NameContextTests.cs @@ -122,6 +122,19 @@ namespace ICSharpCode.NRefactory.CSharp.CodeCompletion Assert.IsTrue (provider == null || provider.Count == 0, "provider should be empty."); } + [Test()] + public void TestForLoopLocalVariableName () + { + + var provider = CodeCompletionBugTests.CreateProvider (@"class MyClass { + void Test() + { + $for (int f$ + } +}"); + Assert.IsTrue (provider == null || provider.Count == 0, "provider should be empty."); + } + [Test()] public void TestCatchExceptionName () { diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/ContextAction/ConvertForeachToForTests.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/ContextAction/ConvertForeachToForTests.cs index d2e587888e..2df5b20763 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/ContextAction/ConvertForeachToForTests.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/ContextAction/ConvertForeachToForTests.cs @@ -1,4 +1,4 @@ -// +// // ConvertForeachToForTests.cs // // Author: @@ -64,7 +64,7 @@ namespace ICSharpCode.NRefactory.CSharp.ContextActions } [Test()] - public void TestEnumeration () + public void TestListOfT () { string result = RunContextAction ( new ConvertForeachToFor (), @@ -95,5 +95,24 @@ namespace ICSharpCode.NRefactory.CSharp.ContextActions " }" + Environment.NewLine + "}", result); } + + [Test()] + public void TestEnumerableOfT () + { + TestWrongContext ( + new ConvertForeachToFor (), + "using System;" + Environment.NewLine + + "using System.Collections.Generic;" + Environment.NewLine + + "class TestClass" + Environment.NewLine + + "{" + Environment.NewLine + + " void Test (IEnumerable args)" + Environment.NewLine + + " {" + Environment.NewLine + + " $foreach (var v in args) {" + Environment.NewLine + + " Console.WriteLine (v);" + Environment.NewLine + + " }" + Environment.NewLine + + " }" + Environment.NewLine + + "}" + ); + } } } \ No newline at end of file diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/FindReferencesTest.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/FindReferencesTest.cs index 30b76d561e..1e38e226d5 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/FindReferencesTest.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/FindReferencesTest.cs @@ -33,24 +33,26 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver CompilationUnit compilationUnit; CSharpParsedFile parsedFile; ICompilation compilation; + FindReferences findReferences; void Init(string code) { compilationUnit = new CSharpParser().Parse(new StringReader(code), "test.cs"); parsedFile = compilationUnit.ToTypeSystem(); compilation = TypeSystemHelper.CreateCompilation(parsedFile); + findReferences = new FindReferences(); } AstNode[] FindReferences(IEntity entity) { var result = new List(); - var findReferences = new FindReferences(); var searchScopes = findReferences.GetSearchScopes(entity); findReferences.FindReferencesInFile(searchScopes, parsedFile, compilationUnit, compilation, (node, rr) => result.Add(node), CancellationToken.None); return result.OrderBy(n => n.StartLocation).ToArray(); } + #region Method Group [Test] public void FindMethodGroupReference() { @@ -107,7 +109,9 @@ class Test { Assert.AreEqual(new [] { new TextLocation(4, 49), new TextLocation(7, 2) }, FindReferences(m_string).Select(n => n.StartLocation).ToArray()); } + #endregion + #region GetEnumerator [Test] public void FindReferenceToGetEnumeratorUsedImplicitlyInForeach() { @@ -129,7 +133,9 @@ class Test { Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 3 && r is MethodDeclaration)); Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 8 && r is ForeachStatement)); } + #endregion + #region Op_Implicit [Test] public void FindReferencesForOpImplicitInLocalVariableInitialization() { @@ -147,5 +153,63 @@ class Test { Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 4 && r is ObjectCreateExpression)); Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 6 && r is OperatorDeclaration)); } + #endregion + + #region Inheritance + const string inheritanceTest = @"using System; +class A { public virtual void M() {} } +class B : A { public override void M() {} } +class C : A { public override void M() {} } +class Calls { + void Test(A a, B b, C c) { + a.M(); + b.M(); + c.M(); + } +}"; + + [Test] + public void InheritanceTest1() + { + Init(inheritanceTest); + var test = compilation.MainAssembly.TopLevelTypeDefinitions.Single(t => t.Name == "B"); + var BM = test.Methods.Single(m => m.Name == "M"); + var actual = FindReferences(BM).ToList(); + Assert.AreEqual(2, actual.Count); + Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 3 && r is MethodDeclaration)); + Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 8 && r is InvocationExpression)); + } + + [Test] + public void InheritanceTest2() + { + Init(inheritanceTest); + findReferences.FindCallsThroughVirtualBaseMethod = true; + var test = compilation.MainAssembly.TopLevelTypeDefinitions.Single(t => t.Name == "B"); + var BM = test.Methods.Single(m => m.Name == "M"); + var actual = FindReferences(BM).ToList(); + Assert.AreEqual(3, actual.Count); + Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 3 && r is MethodDeclaration)); + Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 7 && r is InvocationExpression)); + Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 8 && r is InvocationExpression)); + } + + [Test] + public void InheritanceTest3() + { + Init(inheritanceTest); + findReferences.WholeVirtualSlot = true; + var test = compilation.MainAssembly.TopLevelTypeDefinitions.Single(t => t.Name == "B"); + var BM = test.Methods.Single(m => m.Name == "M"); + var actual = FindReferences(BM).ToList(); + Assert.AreEqual(6, actual.Count); + Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 2 && r is MethodDeclaration)); + Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 3 && r is MethodDeclaration)); + Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 4 && r is MethodDeclaration)); + Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 7 && r is InvocationExpression)); + Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 8 && r is InvocationExpression)); + Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 9 && r is InvocationExpression)); + } + #endregion } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/InvocationTests.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/InvocationTests.cs index c01f877ebd..82067d6978 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/InvocationTests.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/InvocationTests.cs @@ -493,5 +493,33 @@ class Test : IVisitor { Assert.AreEqual("System.Object", typeArguments[0].ReflectionName); Assert.AreEqual("System.Object", typeArguments[1].ReflectionName); } + + [Test] + public void FirstParameterToExtensionMethod() + { + string program = @" +public class X {} +public static class Ex { + public static void F(this X x, int y, int z) {} +} +class C { + public void M() { + X a = null; + int b = 0, c = 0; + $a.F(b, c)$; + } +}"; + var rr = Resolve(program); + Assert.IsFalse(rr.IsError); + Assert.That(rr.IsExtensionMethodInvocation, Is.True); + Assert.That(rr.Arguments[0], Is.InstanceOf()); + Assert.That(((LocalResolveResult)rr.Arguments[0]).Variable.Name, Is.EqualTo("a")); + Assert.That(rr.Arguments[1], Is.InstanceOf()); + Assert.That(((LocalResolveResult)rr.Arguments[1]).Variable.Name, Is.EqualTo("b")); + Assert.That(rr.Arguments[2], Is.InstanceOf()); + Assert.That(((LocalResolveResult)rr.Arguments[2]).Variable.Name, Is.EqualTo("c")); + + Assert.That(rr.TargetResult, Is.InstanceOf()); + } } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ObjectCreationTests.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ObjectCreationTests.cs index 982fff8486..3d47cc88bf 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ObjectCreationTests.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ObjectCreationTests.cs @@ -260,5 +260,18 @@ class Test { Assert.AreEqual("Test", rr.Type.ReflectionName); Assert.AreEqual(5, rr.InitializerStatements.Count); } + + [Test] + public void CreateGeneric() + { + string program = @"using System; +class Test where T : new() { + object x = $new T()$; +}"; + var rr = Resolve(program); + Assert.IsFalse(rr.IsError); + Assert.AreEqual(TypeKind.TypeParameter, rr.Type.Kind); + Assert.AreEqual(TypeKind.TypeParameter, rr.Member.DeclaringType.Kind); + } } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/TypeInferenceTests.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/TypeInferenceTests.cs index d37f7311c4..81481006c5 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/TypeInferenceTests.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Resolver/TypeInferenceTests.cs @@ -70,6 +70,25 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver Assert.IsTrue(success); } + [Test] + public void ArrayToReadOnlyList() + { + ITypeParameter tp = new DefaultTypeParameter(compilation, EntityType.Method, 0, "T"); + IType stringType = compilation.FindType(KnownTypeCode.String); + ITypeDefinition readOnlyListType = compilation.FindType(KnownTypeCode.IReadOnlyListOfT).GetDefinition(); + if (readOnlyListType == null) + Assert.Ignore(".NET 4.5 IReadOnlyList not available"); + + bool success; + Assert.AreEqual( + new [] { stringType }, + ti.InferTypeArguments(new [] { tp }, + new [] { new ResolveResult(new ArrayType(compilation, stringType)) }, + new [] { new ParameterizedType(readOnlyListType, new [] { tp }) }, + out success)); + Assert.IsTrue(success); + } + [Test] public void EnumerableToArrayInContravariantType() { diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs index 7345240918..8365a632be 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs @@ -182,6 +182,7 @@ namespace ICSharpCode.NRefactory.TypeSystem IMethod m = testClass.Methods.Single(me => me.Name == "GetIndex"); Assert.AreEqual("T", m.TypeParameters[0].Name); Assert.AreEqual(EntityType.Method, m.TypeParameters[0].OwnerType); + Assert.AreSame(m, m.TypeParameters[0].Owner); ParameterizedType constraint = (ParameterizedType)m.TypeParameters[0].DirectBaseTypes.First(); Assert.AreEqual("IEquatable", constraint.Name); @@ -190,6 +191,53 @@ namespace ICSharpCode.NRefactory.TypeSystem Assert.AreSame(m.TypeParameters[0], constraint.TypeArguments[0]); } + [Test] + public void GetIndexSpecializedTypeParameter() + { + var testClass = GetTypeDefinition(typeof(GenericClass<,>)); + var methodDef = testClass.Methods.Single(me => me.Name == "GetIndex"); + var m = new SpecializedMethod(methodDef, new TypeParameterSubstitution( + new[] { compilation.FindType(KnownTypeCode.Int16), compilation.FindType(KnownTypeCode.Int32) }, + null + )); + + Assert.AreEqual("T", m.TypeParameters[0].Name); + Assert.AreEqual(EntityType.Method, m.TypeParameters[0].OwnerType); + Assert.AreSame(m, m.TypeParameters[0].Owner); + + ParameterizedType constraint = (ParameterizedType)m.TypeParameters[0].DirectBaseTypes.First(); + Assert.AreEqual("IEquatable", constraint.Name); + Assert.AreEqual(1, constraint.TypeParameterCount); + Assert.AreEqual(1, constraint.TypeArguments.Count); + Assert.AreSame(m.TypeParameters[0], constraint.TypeArguments[0]); + } + + [Test] + public void GetIndexDoubleSpecialization() + { + var testClass = GetTypeDefinition(typeof(GenericClass<,>)); + // GenericClass.GetIndex + var methodDef = testClass.Methods.Single(me => me.Name == "GetIndex"); + + // GenericClass.GetIndex + var m1 = new SpecializedMethod(methodDef, new TypeParameterSubstitution( + new[] { testClass.TypeParameters[1], testClass.TypeParameters[0] }, + new[] { testClass.TypeParameters[0] } + )); + // GenericClass.GetIndex + var m2 = new SpecializedMethod(m1, new TypeParameterSubstitution( + new[] { compilation.FindType(KnownTypeCode.Int32), compilation.FindType(KnownTypeCode.String) }, + null + )); + + // GenericClass.GetIndex + var m12 = new SpecializedMethod(methodDef, new TypeParameterSubstitution( + new[] { compilation.FindType(KnownTypeCode.String), compilation.FindType(KnownTypeCode.Int32) }, + new[] { compilation.FindType(KnownTypeCode.Int32) } + )); + Assert.AreEqual(m12, m2); + } + [Test] public void GenericEnum() { diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Documentation/IdStringProvider.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Documentation/IdStringProvider.cs index 9be979de08..12b9616cd3 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Documentation/IdStringProvider.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Documentation/IdStringProvider.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.NRefactory.Documentation /// public static class IdStringProvider { - #region GetIDString + #region GetIdString /// /// Gets the ID string (C# 4.0 spec, §A.3.1) for the specified entity. /// diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/ITextSource.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/ITextSource.cs index 8bb028a5f4..52f7aced1d 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/ITextSource.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/ITextSource.cs @@ -168,7 +168,7 @@ namespace ICSharpCode.NRefactory.Editor /// /// Verions can be used to efficiently detect whether a document has changed and needs reparsing; /// or even to implement incremental parsers. - /// It is a separate class from ITextBuffer to allow the GC to collect the text buffer while + /// It is a separate class from ITextSource to allow the GC to collect the text source while /// the version checkpoint is still in use. /// public interface ITextSourceVersion diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/Conversion.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/Conversion.cs index 716aa92044..a68a7f407f 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/Conversion.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/Conversion.cs @@ -88,11 +88,11 @@ namespace ICSharpCode.NRefactory.Semantics return new UserDefinedConversion(false, operatorMethod, isLifted); } - public static Conversion MethodGroupConversion(IMethod chosenMethod) + public static Conversion MethodGroupConversion(IMethod chosenMethod, bool isVirtualMethodLookup) { if (chosenMethod == null) throw new ArgumentNullException("chosenMethod"); - return new MethodGroupConv(chosenMethod); + return new MethodGroupConv(chosenMethod, isVirtualMethodLookup); } #endregion @@ -302,10 +302,12 @@ namespace ICSharpCode.NRefactory.Semantics sealed class MethodGroupConv : Conversion { readonly IMethod method; + readonly bool isVirtualMethodLookup; - public MethodGroupConv(IMethod method) + public MethodGroupConv(IMethod method, bool isVirtualMethodLookup) { this.method = method; + this.isVirtualMethodLookup = isVirtualMethodLookup; } public override bool IsImplicit { @@ -316,6 +318,10 @@ namespace ICSharpCode.NRefactory.Semantics get { return true; } } + public override bool IsVirtualMethodLookup { + get { return isVirtualMethodLookup; } + } + public override IMethod Method { get { return method; } } @@ -434,6 +440,13 @@ namespace ICSharpCode.NRefactory.Semantics get { return false; } } + /// + /// For method-group conversions, gets whether to perform a virtual method lookup at runtime. + /// + public virtual bool IsVirtualMethodLookup { + get { return false; } + } + /// /// Gets whether this conversion is an anonymous function conversion. /// diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs index ed03205c31..c0991eef8d 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs @@ -34,12 +34,30 @@ namespace ICSharpCode.NRefactory.Semantics readonly bool isConstant; readonly object constantValue; readonly ResolveResult targetResult; + readonly bool isVirtualCall; public MemberResolveResult(ResolveResult targetResult, IMember member) : base(member.EntityType == EntityType.Constructor ? member.DeclaringType : member.ReturnType) { this.targetResult = targetResult; this.member = member; + var thisRR = targetResult as ThisResolveResult; + this.isVirtualCall = member.IsOverridable && !(thisRR != null && thisRR.CausesNonVirtualInvocation); + + IField field = member as IField; + if (field != null) { + isConstant = field.IsConst; + if (isConstant) + constantValue = field.ConstantValue; + } + } + + public MemberResolveResult(ResolveResult targetResult, IMember member, bool isVirtualCall) + : base(member.EntityType == EntityType.Constructor ? member.DeclaringType : member.ReturnType) + { + this.targetResult = targetResult; + this.member = member; + this.isVirtualCall = isVirtualCall; IField field = member as IField; if (field != null) { isConstant = field.IsConst; @@ -69,6 +87,13 @@ namespace ICSharpCode.NRefactory.Semantics get { return member; } } + /// + /// Gets whether this MemberResolveResult is a virtual call. + /// + public bool IsVirtualCall { + get { return isVirtualCall; } + } + public override bool IsCompileTimeConstant { get { return isConstant; } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/ThisResolveResult.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/ThisResolveResult.cs index 831b47638a..3666bf30be 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/ThisResolveResult.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Semantics/ThisResolveResult.cs @@ -27,8 +27,18 @@ namespace ICSharpCode.NRefactory.Semantics /// public class ThisResolveResult : ResolveResult { - public ThisResolveResult(IType type) : base(type) + bool causesNonVirtualInvocation; + + public ThisResolveResult(IType type, bool causesNonVirtualInvocation = false) : base(type) { + this.causesNonVirtualInvocation = causesNonVirtualInvocation; + } + + /// + /// Gets whether this resolve result causes member invocations to be non-virtual. + /// + public bool CausesNonVirtualInvocation { + get { return causesNonVirtualInvocation; } } } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs index da3ec9f14e..c6cfb7e072 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs @@ -246,19 +246,10 @@ namespace ICSharpCode.NRefactory.TypeSystem { if (type == null) throw new ArgumentNullException("type"); - ITypeDefinition def = type.GetDefinition(); - if (def != null && def.Kind == TypeKind.Delegate) { - foreach (IMember member in def.Members) { - if (member.Name == "Invoke" && member is IMethod) { - ParameterizedType pt = type as ParameterizedType; - if (pt != null) { - return new SpecializedMethod(pt, (IMethod)member); - } - return (IMethod)member; - } - } - } - return null; + if (type.Kind == TypeKind.Delegate) + return type.GetMethods(m => m.Name == "Invoke", GetMemberOptions.IgnoreInheritedMembers).FirstOrDefault(); + else + return null; } #endregion diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/IMember.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/IMember.cs index 398ea5ff67..8b5cb1e575 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/IMember.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/IMember.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem.Implementation; namespace ICSharpCode.NRefactory.TypeSystem { diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractResolvedMember.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractResolvedMember.cs index 335f3413d6..bae1d5b9db 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractResolvedMember.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractResolvedMember.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Linq; using ICSharpCode.NRefactory.Documentation; using ICSharpCode.NRefactory.Utils; @@ -65,18 +66,6 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation } } - public override DocumentationComment Documentation { - get { - IUnresolvedDocumentationProvider docProvider = unresolved.ParsedFile as IUnresolvedDocumentationProvider; - if (docProvider != null) { - var doc = docProvider.GetDocumentation(unresolved, this); - if (doc != null) - return doc; - } - return base.Documentation; - } - } - IList FindImplementedInterfaceMembers() { if (unresolved.IsExplicitInterfaceImplementation) { @@ -87,8 +76,25 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation result.Add(member); } return result.ToArray(); + } else if (unresolved.IsStatic) { + return EmptyList.Instance; } else { - throw new NotImplementedException(); + // TODO: implement interface member mappings correctly + return InheritanceHelper.GetBaseMembers(this, true) + .Where(m => m.DeclaringTypeDefinition != null && m.DeclaringTypeDefinition.Kind == TypeKind.Interface) + .ToArray(); + } + } + + public override DocumentationComment Documentation { + get { + IUnresolvedDocumentationProvider docProvider = unresolved.ParsedFile as IUnresolvedDocumentationProvider; + if (docProvider != null) { + var doc = docProvider.GetDocumentation(unresolved, this); + if (doc != null) + return doc; + } + return base.Documentation; } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractResolvedTypeParameter.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractResolvedTypeParameter.cs index 924987b33b..bd18c9fe51 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractResolvedTypeParameter.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractResolvedTypeParameter.cs @@ -261,7 +261,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation if (this.HasDefaultConstructorConstraint || this.HasValueTypeConstraint) { if (filter == null || filter(dummyConstructor)) { var resolvedCtor = GetDummyConstructor(compilation); - IMethod m = new SpecializedMethod(this, resolvedCtor, EmptyList.Instance); + IMethod m = new SpecializedMethod(resolvedCtor, TypeParameterSubstitution.Identity) { DeclaringType = this }; return new [] { m }; } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs index 6bee7d0964..d0087a010b 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs @@ -781,7 +781,8 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation IType coClass = ComHelper.GetCoClass(this); using (var busyLock = BusyManager.Enter(this)) { if (busyLock.Success) { - return coClass.GetConstructors(filter, options).Select(m => new SpecializedMethod(this, m)); + return coClass.GetConstructors(filter, options) + .Select(m => new SpecializedMethod(m, TypeParameterSubstitution.Identity) { DeclaringType = this }); } } return EmptyList.Instance; diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/GetMembersHelper.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/GetMembersHelper.cs index 0ee77ad74a..18d072b53b 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/GetMembersHelper.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/GetMembersHelper.cs @@ -132,7 +132,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation else substitution = new TypeParameterSubstitution(null, methodTypeArguments); } - yield return new SpecializedMethod(baseType, m, methodTypeArguments, substitution); + yield return new SpecializedMethod(m, substitution); } } else { foreach (IMethod m in declaredMethods) { @@ -162,7 +162,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation ParameterizedType pt = baseType as ParameterizedType; if (pt != null) { var substitution = pt.GetSubstitution(); - return declaredCtors.Select(m => new SpecializedMethod(pt, m, null, substitution)); + return declaredCtors.Select(m => new SpecializedMethod(m, substitution) { DeclaringType = pt }); } else { return declaredCtors; } @@ -189,7 +189,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation ParameterizedType pt = baseType as ParameterizedType; if (pt != null) { var substitution = pt.GetSubstitution(); - return declaredProperties.Select(m => new SpecializedProperty(pt, m, substitution)); + return declaredProperties.Select(m => new SpecializedProperty(m, substitution) { DeclaringType = pt }); } else { return declaredProperties; } @@ -216,7 +216,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation ParameterizedType pt = baseType as ParameterizedType; if (pt != null) { var substitution = pt.GetSubstitution(); - return declaredFields.Select(m => new SpecializedField(pt, m, substitution)); + return declaredFields.Select(m => new SpecializedField(m, substitution) { DeclaringType = pt }); } else { return declaredFields; } @@ -243,7 +243,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation ParameterizedType pt = baseType as ParameterizedType; if (pt != null) { var substitution = pt.GetSubstitution(); - return declaredEvents.Select(m => new SpecializedEvent(pt, m, substitution)); + return declaredEvents.Select(m => new SpecializedEvent(m, substitution) { DeclaringType = pt }); } else { return declaredEvents; } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedEvent.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedEvent.cs index db0f39776e..bab18349c8 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedEvent.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedEvent.cs @@ -27,18 +27,11 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { readonly IEvent eventDefinition; - public SpecializedEvent(IType declaringType, IEvent eventDefinition) - : base(declaringType, eventDefinition) + public SpecializedEvent(IEvent eventDefinition, TypeParameterSubstitution substitution) + : base(eventDefinition) { - this.eventDefinition = eventDefinition; - Initialize(GetSubstitution(declaringType)); - } - - internal SpecializedEvent(IType declaringType, IEvent eventDefinition, TypeVisitor substitution) - : base(declaringType, eventDefinition) - { - this.eventDefinition = eventDefinition; - Initialize(substitution); + AddSubstitution(substitution); + this.eventDefinition = (IEvent)base.MemberDefinition; } public bool CanAdd { @@ -53,16 +46,18 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return eventDefinition.CanInvoke; } } + IMethod addAccessor, removeAccessor, invokeAccessor; + public IMethod AddAccessor { - get { return WrapAccessor(eventDefinition.AddAccessor); } + get { return WrapAccessor(ref this.addAccessor, eventDefinition.AddAccessor); } } public IMethod RemoveAccessor { - get { return WrapAccessor(eventDefinition.RemoveAccessor); } + get { return WrapAccessor(ref this.removeAccessor, eventDefinition.RemoveAccessor); } } public IMethod InvokeAccessor { - get { return WrapAccessor(eventDefinition.InvokeAccessor); } + get { return WrapAccessor(ref this.invokeAccessor, eventDefinition.InvokeAccessor); } } } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedField.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedField.cs index 5e26d2cdec..d79d1b3ab2 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedField.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedField.cs @@ -27,18 +27,11 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { readonly IField fieldDefinition; - public SpecializedField(IType declaringType, IField fieldDefinition) - : base(declaringType, fieldDefinition) + public SpecializedField(IField fieldDefinition, TypeParameterSubstitution substitution) + : base(fieldDefinition) { - this.fieldDefinition = fieldDefinition; - Initialize(GetSubstitution(declaringType)); - } - - internal SpecializedField(IType declaringType, IField fieldDefinition, TypeVisitor substitution) - : base(declaringType, fieldDefinition) - { - this.fieldDefinition = fieldDefinition; - Initialize(substitution); + AddSubstitution(substitution); + this.fieldDefinition = (IField)base.MemberDefinition; } public bool IsReadOnly { diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMember.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMember.cs index 6d1e04b0e6..b616783ff4 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMember.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMember.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; @@ -31,58 +32,115 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// public abstract class SpecializedMember : IMember { - readonly IType declaringType; readonly IMember memberDefinition; + TypeParameterSubstitution substitution; + + IType declaringType; IType returnType; - protected SpecializedMember(IType declaringType, IMember memberDefinition) + protected SpecializedMember(IMember memberDefinition) { - if (declaringType == null) - throw new ArgumentNullException("declaringType"); if (memberDefinition == null) throw new ArgumentNullException("memberDefinition"); - this.declaringType = declaringType; - this.memberDefinition = memberDefinition; + SpecializedMember sm = memberDefinition as SpecializedMember; + if (sm != null) { + this.memberDefinition = sm.memberDefinition; + this.substitution = sm.substitution; + } else { + this.memberDefinition = memberDefinition; + this.substitution = TypeParameterSubstitution.Identity; + } } - protected virtual void Initialize(TypeVisitor substitution) + /// + /// Performs a substitution. This method may only be called by constructors in derived classes. + /// + protected void AddSubstitution(TypeParameterSubstitution newSubstitution) { - this.returnType = Substitute(memberDefinition.ReturnType, substitution); + Debug.Assert(declaringType == null); + Debug.Assert(returnType == null); + this.substitution = TypeParameterSubstitution.Compose(newSubstitution, this.substitution); } - public virtual IMemberReference ToMemberReference() + public static SpecializedMember Create(IMember memberDefinition, TypeParameterSubstitution substitution) { - return new SpecializingMemberReference(declaringType.ToTypeReference(), memberDefinition.ToMemberReference()); + if (memberDefinition == null) { + return null; + } else if (memberDefinition is IMethod) { + return new SpecializedMethod((IMethod)memberDefinition, substitution); + } else if (memberDefinition is IProperty) { + return new SpecializedProperty((IProperty)memberDefinition, substitution); + } else if (memberDefinition is IField) { + return new SpecializedField((IField)memberDefinition, substitution); + } else if (memberDefinition is IEvent) { + return new SpecializedEvent((IEvent)memberDefinition, substitution); + } else { + throw new NotSupportedException("Unknown IMember: " + memberDefinition); + } } - internal static TypeVisitor GetSubstitution(IType declaringType) + public IMemberReference ToMemberReference() { - ParameterizedType pt = declaringType as ParameterizedType; - if (pt != null) - return pt.GetSubstitution(); - else - return null; + return new SpecializingMemberReference( + memberDefinition.ToMemberReference(), + ToTypeReference(substitution.ClassTypeArguments), + ToTypeReference(substitution.MethodTypeArguments)); } - internal static IType Substitute(IType type, TypeVisitor substitution) + static IList ToTypeReference(IList typeArguments) { - if (substitution == null) - return type; + if (typeArguments == null) + return null; else - return type.AcceptVisitor(substitution); + return typeArguments.Select(t => t.ToTypeReference()).ToArray(); } - internal IMethod WrapAccessor(IMethod accessorDefinition) + internal IMethod WrapAccessor(ref IMethod cachingField, IMethod accessorDefinition) { if (accessorDefinition == null) return null; + var result = LazyInit.VolatileRead(ref cachingField); + if (result != null) + return result; else - return new SpecializedMethod(declaringType, accessorDefinition); + return LazyInit.GetOrSet(ref cachingField, new SpecializedMethod(accessorDefinition, substitution)); + } + + /// + /// Gets the substitution belonging to this specialized member. + /// + public TypeParameterSubstitution Substitution { + get { return substitution; } } public IType DeclaringType { - get { return declaringType; } + get { + var result = LazyInit.VolatileRead(ref this.declaringType); + if (result != null) + return result; + IType definitionDeclaringType = memberDefinition.DeclaringType; + ITypeDefinition definitionDeclaringTypeDef = definitionDeclaringType as ITypeDefinition; + if (definitionDeclaringTypeDef != null && definitionDeclaringType.TypeParameterCount > 0) { + if (substitution.ClassTypeArguments != null && substitution.ClassTypeArguments.Count == definitionDeclaringType.TypeParameterCount) { + result = new ParameterizedType(definitionDeclaringTypeDef, substitution.ClassTypeArguments); + } else { + result = new ParameterizedType(definitionDeclaringTypeDef, definitionDeclaringTypeDef.TypeParameters).AcceptVisitor(substitution); + } + } else { + result = definitionDeclaringType.AcceptVisitor(substitution); + } + return LazyInit.GetOrSet(ref this.declaringType, result); + } + internal set { + // This setter is used as an optimization when the code constructing + // the SpecializedMember already knows the declaring type. + Debug.Assert(this.declaringType == null); + Debug.Assert(value != null); + // As this setter is used only during construction before the member is published + // to other threads, we don't need a volatile write. + this.declaringType = value; + } } public IMember MemberDefinition { @@ -94,8 +152,21 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation } public IType ReturnType { - get { return returnType; } - protected set { returnType = value; } + get { + var result = LazyInit.VolatileRead(ref this.returnType); + if (result != null) + return result; + else + return LazyInit.GetOrSet(ref this.returnType, memberDefinition.ReturnType.AcceptVisitor(substitution)); + } + protected set { + // This setter is used for LiftedUserDefinedOperator, a special case of specialized member + // (not a normal type parameter substitution). + + // As this setter is used only during construction before the member is published + // to other threads, we don't need a volatile write. + this.returnType = value; + } } public bool IsVirtual { @@ -143,19 +214,11 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation var definitionImplementations = memberDefinition.ImplementedInterfaceMembers; IMember[] result = new IMember[definitionImplementations.Count]; for (int i = 0; i < result.Length; i++) { - result[i] = Specialize(definitionImplementations[i]); + result[i] = SpecializedMember.Create(definitionImplementations[i], substitution); } return result; } - /// - /// Specialize another member using the same type arguments as this member. - /// - protected virtual IMember Specialize(IMember otherMember) - { - return SpecializingMemberReference.CreateSpecializedMember(declaringType, memberDefinition, null); - } - public bool IsExplicitInterfaceImplementation { get { return memberDefinition.IsExplicitInterfaceImplementation; } } @@ -241,13 +304,13 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation SpecializedMember other = obj as SpecializedMember; if (other == null) return false; - return this.declaringType.Equals(other.declaringType) && this.memberDefinition.Equals(other.memberDefinition); + return this.memberDefinition.Equals(other.memberDefinition) && this.substitution.Equals(other.substitution); } public override int GetHashCode() { unchecked { - return 1000000007 * declaringType.GetHashCode() + 1000000009 * memberDefinition.GetHashCode(); + return 1000000007 * memberDefinition.GetHashCode() + 1000000009 * substitution.GetHashCode(); } } @@ -256,11 +319,11 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation StringBuilder b = new StringBuilder("["); b.Append(GetType().Name); b.Append(' '); - b.Append(declaringType.ToString()); + b.Append(this.DeclaringType.ToString()); b.Append('.'); b.Append(this.Name); b.Append(':'); - b.Append(returnType.ToString()); + b.Append(this.ReturnType.ToString()); b.Append(']'); return b.ToString(); } @@ -270,36 +333,48 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { IList parameters; - protected SpecializedParameterizedMember(IType declaringType, IParameterizedMember memberDefinition) - : base(declaringType, memberDefinition) + protected SpecializedParameterizedMember(IParameterizedMember memberDefinition) + : base(memberDefinition) { } - protected override void Initialize(TypeVisitor substitution) + public IList Parameters { + get { + var result = LazyInit.VolatileRead(ref this.parameters); + if (result != null) + return result; + else + return LazyInit.GetOrSet(ref this.parameters, CreateParameters(this.Substitution)); + } + protected set { + // This setter is used for LiftedUserDefinedOperator, a special case of specialized member + // (not a normal type parameter substitution). + + // As this setter is used only during construction before the member is published + // to other threads, we don't need a volatile write. + this.parameters = value; + } + } + + protected IList CreateParameters(TypeVisitor substitution) { - base.Initialize(substitution); - var paramDefs = ((IParameterizedMember)this.MemberDefinition).Parameters; if (paramDefs.Count == 0) { - this.parameters = EmptyList.Instance; + return EmptyList.Instance; } else { var parameters = new IParameter[paramDefs.Count]; for (int i = 0; i < parameters.Length; i++) { - IType newType = Substitute(paramDefs[i].Type, substitution); + IType newType = paramDefs[i].Type.AcceptVisitor(substitution); if (newType != paramDefs[i].Type) { parameters[i] = new SpecializedParameter(paramDefs[i], newType); } else { parameters[i] = paramDefs[i]; } } - this.parameters = Array.AsReadOnly(parameters); + return Array.AsReadOnly(parameters); } } - public IList Parameters { - get { return parameters; } - } - public override string ToString() { StringBuilder b = new StringBuilder("["); @@ -309,9 +384,9 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation b.Append('.'); b.Append(this.Name); b.Append('('); - for (int i = 0; i < parameters.Count; i++) { + for (int i = 0; i < this.Parameters.Count; i++) { if (i > 0) b.Append(", "); - b.Append(parameters[i].ToString()); + b.Append(this.Parameters[i].ToString()); } b.Append("):"); b.Append(this.ReturnType.ToString()); @@ -326,7 +401,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation public SpecializedParameter(IParameter originalParameter, IType newType) { - this.originalParameter = originalParameter; + if (originalParameter is SpecializedParameter) + this.originalParameter = ((SpecializedParameter)originalParameter).originalParameter; + else + this.originalParameter = originalParameter; this.newType = newType; } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMethod.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMethod.cs index 18584147fd..3e8a0e920d 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMethod.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMethod.cs @@ -32,50 +32,36 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation public class SpecializedMethod : SpecializedParameterizedMember, IMethod { readonly IMethod methodDefinition; - readonly IList typeArguments; - readonly IList specializedTypeParameters; + readonly ITypeParameter[] specializedTypeParameters; - public SpecializedMethod(IType declaringType, IMethod methodDefinition, IList typeArguments = null) - : this(declaringType, methodDefinition, typeArguments, GetSubstitution(declaringType, typeArguments)) + public SpecializedMethod(IMethod methodDefinition, TypeParameterSubstitution substitution) + : base(methodDefinition) { - } - - internal protected SpecializedMethod(IType declaringType, IMethod methodDefinition, IList typeArguments, TypeVisitor substitution) - : base(declaringType, methodDefinition) - { - if (declaringType == null) - throw new ArgumentNullException("declaringType"); - if (methodDefinition == null) - throw new ArgumentNullException("methodDefinition"); - + // The base ctor might have unpacked a SpecializedMember + // (in case we are specializing an already-specialized method) + methodDefinition = (IMethod)base.MemberDefinition; this.methodDefinition = methodDefinition; - this.typeArguments = typeArguments ?? EmptyList.Instance; - if (methodDefinition.TypeParameters.Any(ConstraintNeedsSpecialization)) { // The method is generic, and we need to specialize the type parameters specializedTypeParameters = new ITypeParameter[methodDefinition.TypeParameters.Count]; - for (int i = 0; i < specializedTypeParameters.Count; i++) { + for (int i = 0; i < specializedTypeParameters.Length; i++) { ITypeParameter tp = methodDefinition.TypeParameters[i]; if (ConstraintNeedsSpecialization(tp)) - tp = new SpecializedTypeParameter(tp, this, substitution); + tp = new SpecializedTypeParameter(tp, this); specializedTypeParameters[i] = tp; } + // add substitution that replaces the base method's type parameters with our specialized version + AddSubstitution(new TypeParameterSubstitution(null, specializedTypeParameters)); } - if (typeArguments != null && typeArguments.Count > 0) { - if (typeArguments.Count != methodDefinition.TypeParameters.Count) - throw new ArgumentException("Incorrect number of type arguments"); - } else if (specializedTypeParameters != null) { - // No type arguments were specified, but the method is generic. - // -> substitute original type parameters with the specialized ones - substitution = GetSubstitution(declaringType, specializedTypeParameters.ToArray()); - for (int i = 0; i < specializedTypeParameters.Count; i++) { - if (ConstraintNeedsSpecialization(methodDefinition.TypeParameters[i])) { - ((SpecializedTypeParameter)specializedTypeParameters[i]).substitution = substitution; - } + // Add the main substitution after the method type parameter specialization. + AddSubstitution(substitution); + if (specializedTypeParameters != null) { + // Set the substitution on the type parameters to the final composed substitution + foreach (var tp in specializedTypeParameters.OfType()) { + if (tp.Owner == this) + tp.substitution = base.Substitution; } } - - Initialize(substitution); } static bool ConstraintNeedsSpecialization(ITypeParameter tp) @@ -95,53 +81,13 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation return null; } - public override IMemberReference ToMemberReference() - { - return new SpecializingMemberReference( - this.DeclaringType.ToTypeReference(), - this.MemberDefinition.ToMemberReference(), - typeArguments.Select(ta => ta.ToTypeReference()).ToList() - ); - } - - protected override IMember Specialize(IMember otherMember) - { - return SpecializingMemberReference.CreateSpecializedMember(this.DeclaringType, this.MemberDefinition, typeArguments); - } - /// /// Gets the type arguments passed to this method. /// If only the type parameters for the class were specified and the generic method /// itself is not specialized yet, this property will return an empty list. /// public IList TypeArguments { - get { return typeArguments; } - } - - public override int GetHashCode() - { - int hashCode = base.GetHashCode(); - unchecked { - for (int i = 0; i < typeArguments.Count; i++) { - hashCode *= 362631391; - hashCode += typeArguments[i].GetHashCode(); - } - } - return hashCode; - } - - public override bool Equals(object obj) - { - SpecializedMethod other = obj as SpecializedMethod; - if (!base.Equals(other)) - return false; - if (typeArguments.Count != other.typeArguments.Count) - return false; - for (int i = 0; i < typeArguments.Count; i++) { - if (!typeArguments[i].Equals(other.typeArguments[i])) - return false; - } - return true; + get { return this.Substitution.MethodTypeArguments ?? EmptyList.Instance; } } public IList Parts { @@ -182,11 +128,11 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation b.Append(this.DeclaringType.ToString()); b.Append('.'); b.Append(this.Name); - if (typeArguments.Count > 0) { + if (this.TypeArguments.Count > 0) { b.Append('['); - for (int i = 0; i < typeArguments.Count; i++) { + for (int i = 0; i < this.TypeArguments.Count; i++) { if (i > 0) b.Append(", "); - b.Append(typeArguments[i].ToString()); + b.Append(this.TypeArguments[i].ToString()); } b.Append(']'); } @@ -205,14 +151,15 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { readonly ITypeParameter baseTp; - // not readonly: The substition may be replaced at the end of SpecializedMethod constructor + // The substition is set at the end of SpecializedMethod constructor internal TypeVisitor substitution; - public SpecializedTypeParameter(ITypeParameter baseTp, IMethod specializedOwner, TypeVisitor substitution) + public SpecializedTypeParameter(ITypeParameter baseTp, IMethod specializedOwner) : base(specializedOwner, baseTp.Index, baseTp.Name, baseTp.Variance, baseTp.Attributes, baseTp.Region) { + // We don't have to consider already-specialized baseTps because + // we read the baseTp directly from the unpacked memberDefinition. this.baseTp = baseTp; - this.substitution = substitution; } public override int GetHashCode() diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedProperty.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedProperty.cs index f3a63ada23..c35c9e1a68 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedProperty.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedProperty.cs @@ -27,18 +27,11 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { readonly IProperty propertyDefinition; - public SpecializedProperty(IType declaringType, IProperty propertyDefinition) - : base(declaringType, propertyDefinition) + public SpecializedProperty(IProperty propertyDefinition, TypeParameterSubstitution substitution) + : base(propertyDefinition) { - this.propertyDefinition = propertyDefinition; - Initialize(GetSubstitution(declaringType)); - } - - internal SpecializedProperty(IType declaringType, IProperty propertyDefinition, TypeVisitor substitution) - : base(declaringType, propertyDefinition) - { - this.propertyDefinition = propertyDefinition; - Initialize(substitution); + AddSubstitution(substitution); + this.propertyDefinition = (IProperty)base.MemberDefinition; } public bool CanGet { @@ -49,12 +42,14 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return propertyDefinition.CanSet; } } + IMethod getter, setter; + public IMethod Getter { - get { return WrapAccessor(propertyDefinition.Getter); } + get { return WrapAccessor(ref this.getter, propertyDefinition.Getter); } } public IMethod Setter { - get { return WrapAccessor(propertyDefinition.Setter); } + get { return WrapAccessor(ref this.setter, propertyDefinition.Setter); } } public bool IsIndexer { diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializingMemberReference.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializingMemberReference.cs index 6faecc92a8..d560fa4f85 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializingMemberReference.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializingMemberReference.cs @@ -24,50 +24,29 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation [Serializable] public sealed class SpecializingMemberReference : IMemberReference { - ITypeReference declaringTypeReference; IMemberReference memberDefinitionReference; - IList typeArgumentReferences; + IList classTypeArgumentReferences; + IList methodTypeArgumentReferences; - public SpecializingMemberReference(ITypeReference declaringTypeReference, IMemberReference memberDefinitionReference, IList typeArgumentReferences = null) + public SpecializingMemberReference(IMemberReference memberDefinitionReference, IList classTypeArgumentReferences = null, IList methodTypeArgumentReferences = null) { - if (declaringTypeReference == null) - throw new ArgumentNullException("declaringTypeReference"); if (memberDefinitionReference == null) throw new ArgumentNullException("memberDefinitionReference"); - this.declaringTypeReference = declaringTypeReference; this.memberDefinitionReference = memberDefinitionReference; - this.typeArgumentReferences = typeArgumentReferences; + this.classTypeArgumentReferences = classTypeArgumentReferences; + this.methodTypeArgumentReferences = methodTypeArgumentReferences; } public IMember Resolve(ITypeResolveContext context) { - var declaringType = declaringTypeReference.Resolve(context); var memberDefinition = memberDefinitionReference.Resolve(context); - IType[] typeArguments = null; - if (typeArgumentReferences != null) { - typeArguments = new IType[typeArgumentReferences.Count]; - for (int i = 0; i < typeArguments.Length; i++) { - typeArguments[i] = typeArgumentReferences[i].Resolve(context); - } - } - return CreateSpecializedMember(declaringType, memberDefinition, typeArguments); - } - - internal static IMember CreateSpecializedMember(IType declaringType, IMember memberDefinition, IList typeArguments) - { - if (memberDefinition == null) { - return null; - } else if (memberDefinition is IMethod) { - return new SpecializedMethod(declaringType, (IMethod)memberDefinition, typeArguments); - } else if (memberDefinition is IProperty) { - return new SpecializedProperty(declaringType, (IProperty)memberDefinition); - } else if (memberDefinition is IField) { - return new SpecializedField(declaringType, (IField)memberDefinition); - } else if (memberDefinition is IEvent) { - return new SpecializedEvent(declaringType, (IEvent)memberDefinition); - } else { - throw new NotSupportedException("Unknown IMember: " + memberDefinition); - } + return SpecializedMember.Create( + memberDefinition, + new TypeParameterSubstitution( + classTypeArgumentReferences != null ? classTypeArgumentReferences.Resolve(context) : null, + methodTypeArgumentReferences != null ? methodTypeArgumentReferences.Resolve(context) : null + ) + ); } } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeParameterSubstitution.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeParameterSubstitution.cs index 925e9e2d64..6571c6f3d6 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeParameterSubstitution.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeParameterSubstitution.cs @@ -27,6 +27,11 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// public class TypeParameterSubstitution : TypeVisitor { + /// + /// The identity function. + /// + public static readonly TypeParameterSubstitution Identity = new TypeParameterSubstitution(null, null); + readonly IList classTypeArguments; readonly IList methodTypeArguments; @@ -47,6 +52,95 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation this.methodTypeArguments = methodTypeArguments; } + /// + /// Gets the list of class type arguments. + /// Returns null if this substitution keeps class type parameter unmodified. + /// + public IList ClassTypeArguments { + get { return classTypeArguments; } + } + + public IList MethodTypeArguments { + get { return methodTypeArguments; } + } + + #region Compose + /// + /// Computes a single TypeParameterSubstitution so that for all types t: + /// t.AcceptVisitor(Compose(g, f)) equals t.AcceptVisitor(f).AcceptVisitor(g) + /// + /// If you consider type parameter substitution to be a function, this is function composition. + public static TypeParameterSubstitution Compose(TypeParameterSubstitution g, TypeParameterSubstitution f) + { + if (g == null) + return f; + if (f == null || (f.classTypeArguments == null && f.methodTypeArguments == null)) + return g; + // The composition is a copy of 'f', with 'g' applied on the array elements. + // If 'f' has a null list (keeps type parameters unmodified), we have to treat it as + // the identity function, and thus use the list from 'g'. + var classTypeArguments = f.classTypeArguments != null ? GetComposedTypeArguments(f.classTypeArguments, g) : g.classTypeArguments; + var methodTypeArguments = f.methodTypeArguments != null ? GetComposedTypeArguments(f.methodTypeArguments, g) : g.methodTypeArguments; + return new TypeParameterSubstitution(classTypeArguments, methodTypeArguments); + } + + static IList GetComposedTypeArguments(IList input, TypeParameterSubstitution substitution) + { + IType[] result = new IType[input.Count]; + for (int i = 0; i < result.Length; i++) { + result[i] = input[i].AcceptVisitor(substitution); + } + return result; + } + #endregion + + #region Equals and GetHashCode implementation + public override bool Equals(object obj) + { + TypeParameterSubstitution other = obj as TypeParameterSubstitution; + if (other == null) + return false; + return TypeListEquals(classTypeArguments, other.classTypeArguments) + && TypeListEquals(methodTypeArguments, other.methodTypeArguments); + } + + public override int GetHashCode() + { + unchecked { + return 1124131 * TypeListHashCode(classTypeArguments) + 1821779 * TypeListHashCode(methodTypeArguments); + } + } + + static bool TypeListEquals(IList a, IList b) + { + if (a == b) + return true; + if (a == null || b == null) + return false; + if (a.Count != b.Count) + return false; + for (int i = 0; i < a.Count; i++) { + if (!a[i].Equals(b[i])) + return false; + } + return true; + } + + static int TypeListHashCode(IList obj) + { + if (obj == null) + return 0; + unchecked { + int hashCode = 1; + foreach (var element in obj) { + hashCode *= 27; + hashCode += element.GetHashCode(); + } + return hashCode; + } + } + #endregion + public override IType VisitTypeParameter(ITypeParameter type) { int index = type.Index; diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/InheritanceHelper.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/InheritanceHelper.cs index fccebe5443..3866974fa1 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/InheritanceHelper.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/InheritanceHelper.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Linq; +using ICSharpCode.NRefactory.TypeSystem.Implementation; namespace ICSharpCode.NRefactory.TypeSystem { @@ -50,13 +51,15 @@ namespace ICSharpCode.NRefactory.TypeSystem if (member == null) throw new ArgumentNullException("member"); - member = member.MemberDefinition; if (member.IsExplicitInterfaceImplementation && member.ImplementedInterfaceMembers.Count == 1) { // C#-style explicit interface implementation - yield return member.ImplementedInterfaceMembers[0]; - member = member.ImplementedInterfaceMembers[0].MemberDefinition; + member = member.ImplementedInterfaceMembers[0]; + yield return member; } + SpecializedMember specializedMember = member as SpecializedMember; + member = member.MemberDefinition; + IEnumerable allBaseTypes; if (includeImplementedInterfaces) { allBaseTypes = member.DeclaringTypeDefinition.GetAllBaseTypes(); @@ -68,8 +71,12 @@ namespace ICSharpCode.NRefactory.TypeSystem continue; foreach (IMember baseMember in baseType.GetMembers(m => m.Name == member.Name, GetMemberOptions.IgnoreInheritedMembers)) { - if (SignatureComparer.Ordinal.Equals(member, baseMember)) - yield return baseMember; + if (SignatureComparer.Ordinal.Equals(member, baseMember)) { + if (specializedMember != null) + yield return SpecializedMember.Create(baseMember, specializedMember.Substitution); + else + yield return baseMember; + } } } }