From 7f63d65052c0e141a47ecec735a3b8909eaf4080 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Mon, 16 Sep 2019 00:35:26 +0200 Subject: [PATCH] Fix resolver around recursive function calls This fixes that the reference to the contained function was treated like a variable instead of a function call. --- .../Binding/Bindings/IndexDefaultBinding.cs | 61 ++++++++++------- .../ProcedureCoercionDefaultBinding.cs | 24 +++++-- .../Binding/DefaultBindingContext.cs | 10 +-- RubberduckTests/Grammar/ResolverTests.cs | 67 +++++++++++++++++++ .../DefaultMemberRequiredInspectionTests.cs | 30 +++++++++ .../ProcedureRequiredInspectionTests.cs | 29 ++++++++ 6 files changed, 185 insertions(+), 36 deletions(-) diff --git a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs index fc6f857337..cd5f010121 100644 --- a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs @@ -13,6 +13,7 @@ public sealed class IndexDefaultBinding : IExpressionBinding private readonly IExpressionBinding _lExpressionBinding; private IBoundExpression _lExpression; private readonly ArgumentList _argumentList; + private readonly Declaration _parent; private const int DEFAULT_MEMBER_RECURSION_LIMIT = 32; @@ -25,11 +26,13 @@ public sealed class IndexDefaultBinding : IExpressionBinding public IndexDefaultBinding( ParserRuleContext expression, IExpressionBinding lExpressionBinding, - ArgumentList argumentList) + ArgumentList argumentList, + Declaration parent) : this( expression, (IBoundExpression)null, - argumentList) + argumentList, + parent) { _lExpressionBinding = lExpressionBinding; } @@ -37,11 +40,13 @@ public sealed class IndexDefaultBinding : IExpressionBinding public IndexDefaultBinding( ParserRuleContext expression, IBoundExpression lExpression, - ArgumentList argumentList) + ArgumentList argumentList, + Declaration parent) { _expression = expression; _lExpression = lExpression; _argumentList = argumentList; + _parent = parent; } private static void ResolveArgumentList(Declaration calledProcedure, ArgumentList argumentList, bool isArrayAccess = false) @@ -60,10 +65,10 @@ public IBoundExpression Resolve() _lExpression = _lExpressionBinding.Resolve(); } - return Resolve(_lExpression, _argumentList, _expression); + return Resolve(_lExpression, _argumentList, _expression, _parent); } - private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth = 0, RecursiveDefaultMemberAccessExpression containedExpression = null) + private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, Declaration parent, int defaultMemberResolutionRecursionDepth = 0, RecursiveDefaultMemberAccessExpression containedExpression = null) { if (lExpression.Classification == ExpressionClassification.ResolutionFailed) { @@ -84,7 +89,7 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu switch (lExpression) { case IndexExpression indexExpression: - var doubleIndexExpression = ResolveLExpressionIsIndexExpression(indexExpression, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); + var doubleIndexExpression = ResolveLExpressionIsIndexExpression(indexExpression, argumentList, expression, parent, defaultMemberResolutionRecursionDepth, containedExpression); if (doubleIndexExpression != null) { return doubleIndexExpression; @@ -92,7 +97,7 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu break; case DictionaryAccessExpression dictionaryAccessExpression: - var indexOnBangExpression = ResolveLExpressionIsDictionaryAccessExpression(dictionaryAccessExpression, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); + var indexOnBangExpression = ResolveLExpressionIsDictionaryAccessExpression(dictionaryAccessExpression, argumentList, expression, parent, defaultMemberResolutionRecursionDepth, containedExpression); if (indexOnBangExpression != null) { return indexOnBangExpression; @@ -101,9 +106,11 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu break; } - if (IsVariablePropertyFunctionWithoutParameters(lExpression)) + if (IsVariablePropertyFunctionWithoutParameters(lExpression) + && !(lExpression.Classification == ExpressionClassification.Variable + && parent.Equals(lExpression.ReferencedDeclaration))) { - var parameterlessLExpressionAccess = ResolveLExpressionIsVariablePropertyFunctionNoParameters(lExpression, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); + var parameterlessLExpressionAccess = ResolveLExpressionIsVariablePropertyFunctionNoParameters(lExpression, argumentList, expression, parent, defaultMemberResolutionRecursionDepth, containedExpression); if (parameterlessLExpressionAccess != null) { return parameterlessLExpressionAccess; @@ -114,7 +121,9 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu if (lExpression.Classification == ExpressionClassification.Property || lExpression.Classification == ExpressionClassification.Function - || lExpression.Classification == ExpressionClassification.Subroutine) + || lExpression.Classification == ExpressionClassification.Subroutine + || lExpression.Classification == ExpressionClassification.Variable + && parent.Equals(lExpression.ReferencedDeclaration)) { var procedureDeclaration = lExpression.ReferencedDeclaration as IParameterizedDeclaration; var parameters = procedureDeclaration?.Parameters?.ToList(); @@ -126,12 +135,12 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu } ResolveArgumentList(null, argumentList); - return CreateFailedExpression(lExpression, argumentList, expression, defaultMemberResolutionRecursionDepth > 0); + return CreateFailedExpression(lExpression, argumentList, expression, parent, defaultMemberResolutionRecursionDepth > 0); } - private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext context, bool isDefaultMemberResolution) + private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext context, Declaration parent, bool isDefaultMemberResolution) { - if (IsFailedDefaultMemberResolution(lExpression)) + if (IsFailedDefaultMemberResolution(lExpression, parent)) { return CreateFailedDefaultMemberAccessExpression(lExpression, argumentList, context); } @@ -139,14 +148,16 @@ private static IBoundExpression CreateFailedExpression(IBoundExpression lExpress return CreateResolutionFailedExpression(lExpression, argumentList, context, isDefaultMemberResolution); } - private static bool IsFailedDefaultMemberResolution(IBoundExpression lExpression) + private static bool IsFailedDefaultMemberResolution(IBoundExpression lExpression, Declaration parent) { if (lExpression.Classification == ExpressionClassification.ResolutionFailed) { return false; } - if (IsVariablePropertyFunctionWithoutParameters(lExpression)) + if (IsVariablePropertyFunctionWithoutParameters(lExpression) + && !(lExpression.Classification == ExpressionClassification.Variable + && parent.Equals(lExpression.ReferencedDeclaration))) { return true; } @@ -191,7 +202,7 @@ private static IBoundExpression CreateFailedDefaultMemberAccessExpression(IBound return failedExpr.JoinAsFailedResolution(context, argumentExpressions.Concat(new[] { lExpression })); } - private IBoundExpression ResolveLExpressionIsVariablePropertyFunctionNoParameters(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) + private IBoundExpression ResolveLExpressionIsVariablePropertyFunctionNoParameters(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, Declaration parent, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { /* is classified as a variable, or is classified as a property or function @@ -215,7 +226,7 @@ private IBoundExpression ResolveLExpressionIsVariablePropertyFunctionNoParameter var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth + 1, containedExpression); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, parent, defaultMemberResolutionRecursionDepth + 1, containedExpression); } private static bool IsVariablePropertyFunctionWithoutParameters(IBoundExpression lExpression) @@ -232,7 +243,7 @@ private static bool IsVariablePropertyFunctionWithoutParameters(IBoundExpression } } - private IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression indexExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) + private IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression indexExpression, ArgumentList argumentList, ParserRuleContext expression, Declaration parent, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { /* is classified as an index expression and the argument list is not empty. @@ -256,10 +267,10 @@ private IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression ind var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth + 1, containedExpression); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, parent, defaultMemberResolutionRecursionDepth + 1, containedExpression); } - private IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(DictionaryAccessExpression dictionaryAccessExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) + private IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(DictionaryAccessExpression dictionaryAccessExpression, ArgumentList argumentList, ParserRuleContext expression, Declaration parent, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { //This is equivalent to the case in which the lExpression is an IndexExpression with the difference that it cannot be an array access. @@ -277,10 +288,10 @@ private IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(Dictiona var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth + 1, containedExpression); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, parent, defaultMemberResolutionRecursionDepth + 1, containedExpression); } - private IBoundExpression ResolveDefaultMember(string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) + private IBoundExpression ResolveDefaultMember(string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, Declaration parent, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { /* The declared type of is Object or Variant, and contains no @@ -328,7 +339,7 @@ declared type. if (parameters.All(parameter => parameter.IsOptional) && DEFAULT_MEMBER_RECURSION_LIMIT >= defaultMemberResolutionRecursionDepth) { - return ResolveRecursiveDefaultMember(defaultMember, defaultMemberClassification, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); + return ResolveRecursiveDefaultMember(defaultMember, defaultMemberClassification, argumentList, expression, parent, defaultMemberResolutionRecursionDepth, containedExpression); } } @@ -342,12 +353,12 @@ private static bool ArgumentListIsCompatible(ICollection p && parameters.Count(parameter => !parameter.IsOptional && !parameter.IsParamArray) <= (argumentList?.Arguments.Count ?? 0); } - private IBoundExpression ResolveRecursiveDefaultMember(Declaration defaultMember, ExpressionClassification defaultMemberClassification, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) + private IBoundExpression ResolveRecursiveDefaultMember(Declaration defaultMember, ExpressionClassification defaultMemberClassification, ArgumentList argumentList, ParserRuleContext expression, Declaration parent, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { var defaultMemberRecursionExpression = new RecursiveDefaultMemberAccessExpression(defaultMember, defaultMemberClassification, _lExpression.Context, defaultMemberResolutionRecursionDepth, containedExpression); var defaultMemberAsLExpression = new SimpleNameExpression(defaultMember, defaultMemberClassification, expression); - return Resolve(defaultMemberAsLExpression, argumentList, expression, defaultMemberResolutionRecursionDepth, defaultMemberRecursionExpression); + return Resolve(defaultMemberAsLExpression, argumentList, expression, parent, defaultMemberResolutionRecursionDepth, defaultMemberRecursionExpression); } private ExpressionClassification DefaultMemberExpressionClassification(Declaration defaultMember) diff --git a/Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs index 0df2d62a1c..f90a4d5122 100644 --- a/Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs @@ -11,6 +11,7 @@ public sealed class ProcedureCoercionDefaultBinding : IExpressionBinding private readonly ParserRuleContext _expression; private readonly IExpressionBinding _wrappedExpressionBinding; private readonly bool _hasExplicitCall; + private readonly Declaration _parent; private IBoundExpression _wrappedExpression; //This is a wrapper used to model procedure coercion in call statements without arguments. @@ -19,11 +20,13 @@ public sealed class ProcedureCoercionDefaultBinding : IExpressionBinding public ProcedureCoercionDefaultBinding( ParserRuleContext expression, IExpressionBinding wrappedExpressionBinding, - bool hasExplicitCall) + bool hasExplicitCall, + Declaration parent) : this( expression, (IBoundExpression)null, - hasExplicitCall) + hasExplicitCall, + parent) { _wrappedExpressionBinding = wrappedExpressionBinding; } @@ -31,11 +34,13 @@ public sealed class ProcedureCoercionDefaultBinding : IExpressionBinding public ProcedureCoercionDefaultBinding( ParserRuleContext expression, IBoundExpression wrappedExpression, - bool hasExplicitCall) + bool hasExplicitCall, + Declaration parent) { _expression = expression; _wrappedExpression = wrappedExpression; _hasExplicitCall = hasExplicitCall; + _parent = parent; } public IBoundExpression Resolve() @@ -45,10 +50,10 @@ public IBoundExpression Resolve() _wrappedExpression = _wrappedExpressionBinding.Resolve(); } - return Resolve(_wrappedExpression, _expression, _hasExplicitCall); + return Resolve(_wrappedExpression, _expression, _hasExplicitCall, _parent); } - private static IBoundExpression Resolve(IBoundExpression wrappedExpression, ParserRuleContext expression, bool hasExplicitCall) + private static IBoundExpression Resolve(IBoundExpression wrappedExpression, ParserRuleContext expression, bool hasExplicitCall, Declaration parent) { //Procedure coercion only happens for expressions classified as variables. if (wrappedExpression.Classification != ExpressionClassification.Variable) @@ -56,6 +61,7 @@ private static IBoundExpression Resolve(IBoundExpression wrappedExpression, Pars return wrappedExpression; } + //The wrapped declaration is not of a specific class type or Object. var wrappedDeclaration = wrappedExpression.ReferencedDeclaration; if (wrappedDeclaration == null || !wrappedDeclaration.IsObject @@ -66,7 +72,13 @@ private static IBoundExpression Resolve(IBoundExpression wrappedExpression, Pars return wrappedExpression; } - //The wrapped declaration is of a specific class type or Object. + //Recursive function call + //The reference to the function is originally resolved as a variable because that is appropriate for the return value variable of the same name. + if (wrappedExpression.Classification == ExpressionClassification.Variable + && wrappedDeclaration.Equals(parent)) + { + return wrappedExpression; + } var asTypeName = wrappedDeclaration.AsTypeName; var asTypeDeclaration = wrappedDeclaration.AsTypeDeclaration; diff --git a/Rubberduck.Parsing/Binding/DefaultBindingContext.cs b/Rubberduck.Parsing/Binding/DefaultBindingContext.cs index c71c96b4db..aa43fe4b65 100644 --- a/Rubberduck.Parsing/Binding/DefaultBindingContext.cs +++ b/Rubberduck.Parsing/Binding/DefaultBindingContext.cs @@ -67,13 +67,13 @@ private IExpressionBinding Visit(Declaration module, Declaration parent, VBAPars SetLeftMatch(lExpressionBinding, argList.Arguments.Count); if (argList.HasArguments) { - return new IndexDefaultBinding(expression.lExpression(), lExpressionBinding, argList); + return new IndexDefaultBinding(expression.lExpression(), lExpressionBinding, argList, parent); } - return new ProcedureCoercionDefaultBinding(expression.lExpression(), lExpressionBinding, false); + return new ProcedureCoercionDefaultBinding(expression.lExpression(), lExpressionBinding, false, parent); } - return new ProcedureCoercionDefaultBinding(expression.lExpression(), lExpressionBinding, true); + return new ProcedureCoercionDefaultBinding(expression.lExpression(), lExpressionBinding, true, parent); } private static void SetLeftMatch(IExpressionBinding binding, int argumentCount) @@ -217,7 +217,7 @@ private IExpressionBinding Visit(Declaration module, Declaration parent, VBAPars var lExpressionBinding = Visit(module, parent, lExpression, withBlockVariable, StatementResolutionContext.Undefined); var argumentListBinding = VisitArgumentList(module, parent, expression.argumentList(), withBlockVariable); SetLeftMatch(lExpressionBinding, argumentListBinding.Arguments.Count); - return new IndexDefaultBinding(expression, lExpressionBinding, argumentListBinding); + return new IndexDefaultBinding(expression, lExpressionBinding, argumentListBinding, parent); } private IExpressionBinding Visit(Declaration module, Declaration parent, VBAParser.WhitespaceIndexExprContext expression, IBoundExpression withBlockVariable) @@ -226,7 +226,7 @@ private IExpressionBinding Visit(Declaration module, Declaration parent, VBAPars var lExpressionBinding = Visit(module, parent, lExpression, withBlockVariable, StatementResolutionContext.Undefined); var argumentListBinding = VisitArgumentList(module, parent, expression.argumentList(), withBlockVariable); SetLeftMatch(lExpressionBinding, argumentListBinding.Arguments.Count); - return new IndexDefaultBinding(expression, lExpressionBinding, argumentListBinding); + return new IndexDefaultBinding(expression, lExpressionBinding, argumentListBinding, parent); } private ArgumentList VisitArgumentList(Declaration module, Declaration parent, VBAParser.ArgumentListContext argumentList, IBoundExpression withBlockVariable) diff --git a/RubberduckTests/Grammar/ResolverTests.cs b/RubberduckTests/Grammar/ResolverTests.cs index 099255004c..4479ff65bd 100644 --- a/RubberduckTests/Grammar/ResolverTests.cs +++ b/RubberduckTests/Grammar/ResolverTests.cs @@ -6503,5 +6503,72 @@ End Function Assert.IsFalse(failedAccesses.Any()); } } + + [Category("Grammar")] + [Category("Resolver")] + [Test] + [TestCase("String", "bar = \"Hello \" & Foo(Nothing)")] + [TestCase("Class1", "Set Foo = Foo(Nothing)")] + public void RecursiveFunctionCall_NoFailedIndexedDefaultMemberResolution(string functionReturnTypeName, string statement) + { + var classCode = @" +Public Function Foo(index As Variant) As Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo(ByVal cls As Class1) As {functionReturnTypeName} + If Not(cls Is Nothing) Then + Dim bar As Variant + {statement} + End If +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", classCode, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var failedAccesses = state.DeclarationFinder.FailedIndexedDefaultMemberAccesses(module); + + Assert.IsFalse(failedAccesses.Any()); + } + } + + [Category("Grammar")] + [Category("Resolver")] + [Test] + [TestCase("", "Call Foo")] + [TestCase("", "Call Foo()")] + [TestCase("", "Foo")] + [TestCase("ByVal arg As Variant", "Call Foo(arg)")] + [TestCase("ByVal arg As Variant", "Foo arg")] + public void RecursiveProcedureCall_NoFailedProcedureCoercionReference(string argumentList, string statement) + { + var classCode = @" +Public Function Foo(index As Variant) As Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo({argumentList}) As Class1 + {statement} +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", classCode, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + using (var state = Resolve(vbe.Object)) + { + var failedAccesses = state.DeclarationFinder.FailedProcedureCoercions(); + + Assert.IsFalse(failedAccesses.Any()); + } + } } } diff --git a/RubberduckTests/Inspections/DefaultMemberRequiredInspectionTests.cs b/RubberduckTests/Inspections/DefaultMemberRequiredInspectionTests.cs index f5f0c3f04b..01ec0969d8 100644 --- a/RubberduckTests/Inspections/DefaultMemberRequiredInspectionTests.cs +++ b/RubberduckTests/Inspections/DefaultMemberRequiredInspectionTests.cs @@ -367,6 +367,36 @@ End Function Assert.IsFalse(inspectionResults.Any()); } + [Category("Grammar")] + [Category("Resolver")] + [Test] + [TestCase("String", "bar = \"Hello \" & Foo(Nothing)")] + [TestCase("Class1", "Set Foo = Foo(Nothing)")] + public void RecursiveFunctionCall_NoResult(string functionReturnTypeName, string statement) + { + var classCode = @" +Public Function Foo(index As Variant) As Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo(ByVal cls As Class1) As {functionReturnTypeName} + If Not(cls Is Nothing) Then + Dim bar As Variant + {statement} + End If +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", classCode, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var inspectionResults = InspectionResults(vbe.Object); + + Assert.IsFalse(inspectionResults.Any()); + } + protected override IInspection InspectionUnderTest(RubberduckParserState state) { return new DefaultMemberRequiredInspection(state); diff --git a/RubberduckTests/Inspections/ProcedureRequiredInspectionTests.cs b/RubberduckTests/Inspections/ProcedureRequiredInspectionTests.cs index 7574eec846..569e79843d 100644 --- a/RubberduckTests/Inspections/ProcedureRequiredInspectionTests.cs +++ b/RubberduckTests/Inspections/ProcedureRequiredInspectionTests.cs @@ -476,6 +476,35 @@ End Sub Assert.IsFalse(inspectionResults.Any()); } + [Category("Grammar")] + [Category("Resolver")] + [Test] + [TestCase("", "Call Foo")] + [TestCase("", "Call Foo()")] + [TestCase("", "Foo")] + [TestCase("ByVal arg As Variant", "Call Foo(arg)")] + [TestCase("ByVal arg As Variant", "Foo arg")] + public void RecursiveProcedureCall_NoResult(string argumentList, string statement) + { + var classCode = @" +Public Function Foo(index As Variant) As Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo({argumentList}) As Class1 + {statement} +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", classCode, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var inspectionResults = InspectionResults(vbe.Object); + Assert.IsFalse(inspectionResults.Any()); + } + protected override IInspection InspectionUnderTest(RubberduckParserState state) {