Skip to content

Commit 936ff05

Browse files
committed
Feature: New hint - Function parameter is unused.
1 parent 762a7e5 commit 936ff05

File tree

6 files changed

+128
-11
lines changed

6 files changed

+128
-11
lines changed

ShaderShrinker/Shrinker.Parser/CodeHint.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class CodeHint
1616
public string Item { get; }
1717
public string Suggestion { get; }
1818

19-
public CodeHint(string item, string suggestion)
19+
protected CodeHint(string item, string suggestion)
2020
{
2121
Item = item;
2222
Suggestion = suggestion;

ShaderShrinker/Shrinker.Parser/Hinter.cs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
using System.Collections.Generic;
1313
using System.Linq;
14+
using Shrinker.Lexer;
1415
using Shrinker.Parser.SyntaxNodes;
1516

1617
namespace Shrinker.Parser
@@ -26,6 +27,15 @@ public static IEnumerable<CodeHint> GetHints(this SyntaxNode rootNode)
2627
if (!rootNode.HasEntryPointFunction())
2728
yield break;
2829

30+
foreach (var codeHint in DetectFunctionsToInline(rootNode))
31+
yield return codeHint;
32+
33+
foreach (var codeHint in DetectUnusedFunctionParam(rootNode))
34+
yield return codeHint;
35+
}
36+
37+
private static IEnumerable<CodeHint> DetectFunctionsToInline(SyntaxNode rootNode)
38+
{
2939
var localFunctions = rootNode.FunctionDefinitions().ToList();
3040
foreach (var function in localFunctions)
3141
{
@@ -38,10 +48,47 @@ public static IEnumerable<CodeHint> GetHints(this SyntaxNode rootNode)
3848
callSites += rootNode.TheTree.OfType<PragmaDefineSyntaxNode>().Count(o => o.ToCode().Contains(function.Name));
3949

4050
if (callSites == 0 && !function.IsMain())
41-
yield return new CodeHint(function.UiName, "Never used - Consider removing.");
51+
yield return new UnusedFunctionHint(function.UiName);
4252

4353
if (callSites == 1)
44-
yield return new CodeHint(function.UiName, "Only called once - Consider inlining.");
54+
yield return new FunctionToInlineHint(function.UiName);
55+
}
56+
}
57+
58+
private static IEnumerable<CodeHint> DetectUnusedFunctionParam(SyntaxNode rootNode)
59+
{
60+
var localFunctions = rootNode.FunctionDefinitions().ToList();
61+
foreach (var function in localFunctions.Where(o => !o.IsMain()))
62+
{
63+
foreach (var paramName in function.ParamNames.Select(o => o.Token.Content))
64+
{
65+
var isUsed = function.Braces.TheTree
66+
.Select(o => o.Token?.Content ?? (o as VariableAssignmentSyntaxNode)?.Name)
67+
.Any(o => o?.StartsWithVarName(paramName) == true);
68+
if (!isUsed)
69+
yield return new FunctionHasUnusedParam(function.UiName, paramName);
70+
}
71+
}
72+
}
73+
74+
public class UnusedFunctionHint : CodeHint
75+
{
76+
public UnusedFunctionHint(string function) : base(function, "Function is never called.")
77+
{
78+
}
79+
}
80+
81+
public class FunctionToInlineHint : CodeHint
82+
{
83+
public FunctionToInlineHint(string function) : base(function, "Function is called only once - Consider inlining.")
84+
{
85+
}
86+
}
87+
88+
public class FunctionHasUnusedParam : CodeHint
89+
{
90+
public FunctionHasUnusedParam(string function, string param) : base(function, $"Function parameter '{param}' is unused.")
91+
{
4592
}
4693
}
4794
}

ShaderShrinker/Shrinker.Parser/Optimizations/SimplifyFunctionDeclarationsExtension.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,7 @@ public static void SimplifyFunctionDeclarations(this SyntaxNode rootNode)
2323

2424
// Remove param names.
2525
foreach (var declaration in functionDeclarations.Where(o => o.Params.Children.Any()))
26-
{
27-
var paramNames = declaration.Params.Children
28-
.OfType<GenericSyntaxNode>()
29-
.Where(o => o.Token is AlphaNumToken)
30-
.ToList();
31-
paramNames.ForEach(o => o.Remove());
32-
}
26+
declaration.ParamNames.ForEach(o => o.Remove());
3327

3428
// Remove declaration if no matching definition.
3529
var functionDefinitionNames = rootNode.FunctionDefinitions().Select(o => o.Name);

ShaderShrinker/Shrinker.Parser/SyntaxNodes/FunctionSyntaxNodeBase.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// </summary>
1010
// -----------------------------------------------------------------------
1111

12+
using System.Collections.Generic;
1213
using System.Linq;
1314
using Shrinker.Lexer;
1415

@@ -23,6 +24,18 @@ public abstract class FunctionSyntaxNodeBase : SyntaxNode
2324

2425
public bool IsVoidParam() => Params.Children.Count == 1 && Params.Children[0].HasNodeContent("void");
2526

27+
public List<GenericSyntaxNode> ParamNames
28+
{
29+
get
30+
{
31+
if (!Params.Children.Any())
32+
return new List<GenericSyntaxNode>();
33+
var children = Params.Children.OfType<GenericSyntaxNode>().Where(o => o.Token is AlphaNumToken || o.Token is CommaToken).Append(new GenericSyntaxNode(new CommaToken())).ToList();
34+
var commaIndexes = children.Where(o => o.Token is CommaToken).Select(o => children.IndexOf(o));
35+
return commaIndexes.Select(i => children[i - 1]).ToList();
36+
}
37+
}
38+
2639
public bool IsMain() => Name.StartsWith("main");
2740
}
2841
}

ShaderShrinker/Shrinker.WpfApp/HintDialog.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
mc:Ignorable="d"
88
d:DesignHeight="450" d:DesignWidth="800"
99
d:DataContext="{d:DesignInstance WpfApp:AppViewModel}"
10-
Width="500" Height="400">
10+
Width="700" Height="480">
1111
<Grid.RowDefinitions>
1212
<RowDefinition Height="*"/>
1313
<RowDefinition Height="Auto"/>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="HinterTests.cs" company="Global Graphics Software Ltd">
3+
// Copyright (c) 2021 Global Graphics Software Ltd. All rights reserved.
4+
// </copyright>
5+
// <summary>
6+
// This example is provided on an "as is" basis and without warranty of any kind.
7+
// Global Graphics Software Ltd. does not warrant or make any representations regarding the use or
8+
// results of use of this example.
9+
// </summary>
10+
// -----------------------------------------------------------------------
11+
12+
using System.Linq;
13+
using NUnit.Framework;
14+
using Shrinker.Lexer;
15+
using Shrinker.Parser;
16+
17+
namespace UnitTests
18+
{
19+
[TestFixture]
20+
public class HinterTests
21+
{
22+
[Test]
23+
public void CheckDetectingRemoveToInline()
24+
{
25+
var lexer = new Lexer();
26+
lexer.Load("void f() { } void main() { return; }");
27+
28+
var rootNode = new Parser(lexer).Parse();
29+
30+
Assert.That(() => rootNode.GetHints().OfType<Hinter.UnusedFunctionHint>().ToList(), Has.Count.EqualTo(1));
31+
}
32+
33+
[Test]
34+
public void CheckDetectingFunctionToInline()
35+
{
36+
var lexer = new Lexer();
37+
lexer.Load("void f() { } void main() { f(); }");
38+
39+
var rootNode = new Parser(lexer).Parse();
40+
41+
Assert.That(() => rootNode.GetHints().OfType<Hinter.FunctionToInlineHint>().ToList(), Has.Count.EqualTo(1));
42+
}
43+
44+
[Test, Sequential]
45+
public void CheckDetectingFunctionWithUnusedParam(
46+
[Values(
47+
"int f(int a, int b) { return a * 2; } void main() { f(1, 2); }",
48+
"float f(vec2 a, vec2 b) { return a.x * 2.0; } void main() { f(vec2(1), vec2(2)); }",
49+
"void f(inout T a, int b) { a.a = 2.0; a.b = 1.0; } void main() { T m; f(m, 1); }")
50+
]
51+
string code)
52+
{
53+
var lexer = new Lexer();
54+
lexer.Load(code);
55+
56+
var rootNode = new Parser(lexer).Parse();
57+
58+
var hints = rootNode.GetHints().OfType<Hinter.FunctionHasUnusedParam>().ToList();
59+
Assert.That(hints, Has.Count.EqualTo(1));
60+
Assert.That(() => hints.Single().Suggestion, Does.Contain("'b'"));
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)