From 7aa1f618c3580c88b835f70b2a44dbb1440b6bf6 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 5 Mar 2026 19:00:53 +1000 Subject: [PATCH] Warn on unused function args. --- changelog/dmd.warn-unused-params.dd | 22 ++++++++++++++ compiler/src/dmd/cli.d | 3 ++ compiler/src/dmd/declaration.d | 1 + compiler/src/dmd/expressionsem.d | 2 ++ compiler/src/dmd/frontend.h | 7 ++++- compiler/src/dmd/globals.d | 1 + compiler/src/dmd/globals.h | 1 + compiler/src/dmd/semantic3.d | 10 +++++++ compiler/test/compilable/previewhelp.d | 1 + .../test/fail_compilation/unused_params.d | 30 +++++++++++++++++++ 10 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 changelog/dmd.warn-unused-params.dd create mode 100644 compiler/test/fail_compilation/unused_params.d diff --git a/changelog/dmd.warn-unused-params.dd b/changelog/dmd.warn-unused-params.dd new file mode 100644 index 000000000000..faf66757d437 --- /dev/null +++ b/changelog/dmd.warn-unused-params.dd @@ -0,0 +1,22 @@ +New preview flag ``-preview=warnunusedparams`` to warn on unused function parameters + +A new ``-preview=warnunusedparams`` introduces warnings for named function parameters that are not referenced by the function body. + +```d +void foo(int x, int y) // Warning: function parameter `y` is never used +{ + return x * 2; +} +``` + +To suppress the warning, either omit the parameter name or use ``cast(void)``: + +```d +void callback(int x, int) {} // unnamed parameter inhibits warning + +void bar(int x, int y) +{ + cast(void)y; // explicitly mark as unused, useful in conditional compilation + use(x); +} +``` diff --git a/compiler/src/dmd/cli.d b/compiler/src/dmd/cli.d index 558f18689058..9279ada4c276 100644 --- a/compiler/src/dmd/cli.d +++ b/compiler/src/dmd/cli.d @@ -1076,6 +1076,9 @@ dmd -cov -unittest myprog.d "https://dlang.org/spec/attribute.html#system-variables"), Feature("fastdfa", "useFastDFA", "Fast dataflow analysis engine, experimental"), + Feature("warnunusedparams", "warnUnused", + "enable warnings for unused function parameters", + "https://dlang.org/spec/function.html#unused-params"), ]; } diff --git a/compiler/src/dmd/declaration.d b/compiler/src/dmd/declaration.d index e1a4f9bf656c..5ac23203dd6d 100644 --- a/compiler/src/dmd/declaration.d +++ b/compiler/src/dmd/declaration.d @@ -469,6 +469,7 @@ extern (C++) class VarDeclaration : Declaration bool dllExport; /// __declspec(dllexport) mixin VarDeclarationExtra; bool systemInferred; /// @system was inferred from initializer + bool wasUsed; /// variable was referenced } import dmd.common.bitfields : generateBitFields; diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 725fc718ad3c..bb8aa20db0ab 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -7163,6 +7163,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor if (auto v = e.var.isVarDeclaration()) { + v.wasUsed = true; if (v.checkNestedReference(sc, e.loc)) return setError(); } @@ -7212,6 +7213,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor if (vd) { + vd.wasUsed = true; if (vd.checkNestedReference(sc, e.loc)) return setError(); diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index c4439b55b655..3523a0ef0513 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -6639,6 +6639,8 @@ class VarDeclaration : public Declaration bool inAlignSection(bool v); bool systemInferred() const; bool systemInferred(bool v); + bool wasUsed() const; + bool wasUsed(bool v); private: uint32_t bitFields; public: @@ -8198,6 +8200,7 @@ struct Param final FeatureState dtorFields; FeatureState systemVariables; bool useFastDFA; + bool warnUnused; CHECKENABLE useInvariants; CHECKENABLE useIn; CHECKENABLE useOut; @@ -8282,6 +8285,7 @@ struct Param final fixImmutableConv(), fix16997(true), useFastDFA(), + warnUnused(), useInvariants((CHECKENABLE)0u), useIn((CHECKENABLE)0u), useOut((CHECKENABLE)0u), @@ -8327,7 +8331,7 @@ struct Param final timeTraceFile() { } - Param(bool obj, bool readStdin = false, bool multiobj = false, bool trace = false, bool tracegc = false, bool vcg_ast = false, DiagnosticReporting useDeprecated = (DiagnosticReporting)1u, bool useUnitTests = false, bool useInline = false, bool release = false, bool preservePaths = false, DiagnosticReporting useWarnings = (DiagnosticReporting)2u, bool cov = false, uint8_t covPercent = 0u, bool ctfe_cov = false, bool ignoreUnsupportedPragmas = true, bool useModuleInfo = true, bool useTypeInfo = true, bool useExceptions = true, bool useGC = true, bool betterC = false, bool addMain = false, bool allInst = false, bool bitfields = true, bool rewriteNoExceptionToSeq = false, CppStdRevision cplusplus = (CppStdRevision)201103u, Help help = Help(), Verbose v = Verbose(), Edition edition = (Edition)2023u, void* editionFiles = nullptr, FeatureState useDIP25 = (FeatureState)2u, FeatureState useDIP1000 = (FeatureState)0u, bool ehnogc = false, bool useDIP1021 = false, FeatureState fieldwise = (FeatureState)0u, bool fixAliasThis = false, FeatureState rvalueRefParam = (FeatureState)0u, FeatureState safer = (FeatureState)0u, FeatureState noSharedAccess = (FeatureState)0u, bool previewIn = false, bool inclusiveInContracts = false, bool shortenedMethods = true, bool fixImmutableConv = false, bool fix16997 = true, FeatureState dtorFields = (FeatureState)0u, FeatureState systemVariables = (FeatureState)0u, bool useFastDFA = false, CHECKENABLE useInvariants = (CHECKENABLE)0u, CHECKENABLE useIn = (CHECKENABLE)0u, CHECKENABLE useOut = (CHECKENABLE)0u, CHECKENABLE useArrayBounds = (CHECKENABLE)0u, CHECKENABLE useNullCheck = (CHECKENABLE)0u, CHECKENABLE useAssert = (CHECKENABLE)0u, CHECKENABLE useSwitchError = (CHECKENABLE)0u, CHECKENABLE boundscheck = (CHECKENABLE)0u, CHECKACTION checkAction = (CHECKACTION)0u, CLIIdentifierTable dIdentifierTable = (CLIIdentifierTable)0u, CLIIdentifierTable cIdentifierTable = (CLIIdentifierTable)0u, _d_dynamicArray< const char > argv0 = {}, Array modFileAliasStrings = Array(), Array imppath = Array(), Array fileImppath = Array(), _d_dynamicArray< const char > objdir = {}, _d_dynamicArray< const char > objname = {}, _d_dynamicArray< const char > libname = {}, Output ddoc = Output(), Output dihdr = Output(), Output cxxhdr = Output(), Output json = Output(), JsonFieldFlags jsonFieldFlags = (JsonFieldFlags)0u, Output makeDeps = Output(), Output mixinOut = Output(), Output moduleDeps = Output(), bool debugEnabled = false, bool run = false, Array runargs = Array(), Array cppswitches = Array(), const char* cpp = nullptr, Array objfiles = Array(), Array linkswitches = Array(), Array linkswitchIsForCC = Array(), Array libfiles = Array(), Array dllfiles = Array(), _d_dynamicArray< const char > deffile = {}, _d_dynamicArray< const char > resfile = {}, _d_dynamicArray< const char > exefile = {}, _d_dynamicArray< const char > mapfile = {}, bool fullyQualifiedObjectFiles = false, bool timeTrace = false, uint32_t timeTraceGranularityUs = 500u, const char* timeTraceFile = nullptr) : + Param(bool obj, bool readStdin = false, bool multiobj = false, bool trace = false, bool tracegc = false, bool vcg_ast = false, DiagnosticReporting useDeprecated = (DiagnosticReporting)1u, bool useUnitTests = false, bool useInline = false, bool release = false, bool preservePaths = false, DiagnosticReporting useWarnings = (DiagnosticReporting)2u, bool cov = false, uint8_t covPercent = 0u, bool ctfe_cov = false, bool ignoreUnsupportedPragmas = true, bool useModuleInfo = true, bool useTypeInfo = true, bool useExceptions = true, bool useGC = true, bool betterC = false, bool addMain = false, bool allInst = false, bool bitfields = true, bool rewriteNoExceptionToSeq = false, CppStdRevision cplusplus = (CppStdRevision)201103u, Help help = Help(), Verbose v = Verbose(), Edition edition = (Edition)2023u, void* editionFiles = nullptr, FeatureState useDIP25 = (FeatureState)2u, FeatureState useDIP1000 = (FeatureState)0u, bool ehnogc = false, bool useDIP1021 = false, FeatureState fieldwise = (FeatureState)0u, bool fixAliasThis = false, FeatureState rvalueRefParam = (FeatureState)0u, FeatureState safer = (FeatureState)0u, FeatureState noSharedAccess = (FeatureState)0u, bool previewIn = false, bool inclusiveInContracts = false, bool shortenedMethods = true, bool fixImmutableConv = false, bool fix16997 = true, FeatureState dtorFields = (FeatureState)0u, FeatureState systemVariables = (FeatureState)0u, bool useFastDFA = false, bool warnUnused = false, CHECKENABLE useInvariants = (CHECKENABLE)0u, CHECKENABLE useIn = (CHECKENABLE)0u, CHECKENABLE useOut = (CHECKENABLE)0u, CHECKENABLE useArrayBounds = (CHECKENABLE)0u, CHECKENABLE useNullCheck = (CHECKENABLE)0u, CHECKENABLE useAssert = (CHECKENABLE)0u, CHECKENABLE useSwitchError = (CHECKENABLE)0u, CHECKENABLE boundscheck = (CHECKENABLE)0u, CHECKACTION checkAction = (CHECKACTION)0u, CLIIdentifierTable dIdentifierTable = (CLIIdentifierTable)0u, CLIIdentifierTable cIdentifierTable = (CLIIdentifierTable)0u, _d_dynamicArray< const char > argv0 = {}, Array modFileAliasStrings = Array(), Array imppath = Array(), Array fileImppath = Array(), _d_dynamicArray< const char > objdir = {}, _d_dynamicArray< const char > objname = {}, _d_dynamicArray< const char > libname = {}, Output ddoc = Output(), Output dihdr = Output(), Output cxxhdr = Output(), Output json = Output(), JsonFieldFlags jsonFieldFlags = (JsonFieldFlags)0u, Output makeDeps = Output(), Output mixinOut = Output(), Output moduleDeps = Output(), bool debugEnabled = false, bool run = false, Array runargs = Array(), Array cppswitches = Array(), const char* cpp = nullptr, Array objfiles = Array(), Array linkswitches = Array(), Array linkswitchIsForCC = Array(), Array libfiles = Array(), Array dllfiles = Array(), _d_dynamicArray< const char > deffile = {}, _d_dynamicArray< const char > resfile = {}, _d_dynamicArray< const char > exefile = {}, _d_dynamicArray< const char > mapfile = {}, bool fullyQualifiedObjectFiles = false, bool timeTrace = false, uint32_t timeTraceGranularityUs = 500u, const char* timeTraceFile = nullptr) : obj(obj), readStdin(readStdin), multiobj(multiobj), @@ -8375,6 +8379,7 @@ struct Param final dtorFields(dtorFields), systemVariables(systemVariables), useFastDFA(useFastDFA), + warnUnused(warnUnused), useInvariants(useInvariants), useIn(useIn), useOut(useOut), diff --git a/compiler/src/dmd/globals.d b/compiler/src/dmd/globals.d index 020d24786cb6..d360cfabe0f2 100644 --- a/compiler/src/dmd/globals.d +++ b/compiler/src/dmd/globals.d @@ -219,6 +219,7 @@ extern (C++) struct Param // https://issues.dlang.org/show_bug.cgi?id=14246 FeatureState systemVariables; // limit access to variables marked @system from @safe code bool useFastDFA; // Use fast data flow analysis engine + bool warnUnused; // warn about unused function parameters CHECKENABLE useInvariants = CHECKENABLE._default; // generate class invariant checks CHECKENABLE useIn = CHECKENABLE._default; // generate precondition checks diff --git a/compiler/src/dmd/globals.h b/compiler/src/dmd/globals.h index 40894f256a69..f9d1f67c45e0 100644 --- a/compiler/src/dmd/globals.h +++ b/compiler/src/dmd/globals.h @@ -240,6 +240,7 @@ struct Param // https://issues.dlang.org/show_bug.cgi?id=14246 FeatureState systemVariables; // limit access to variables marked @system from @safe code d_bool useFastDFA; // Use fast data flow analysis engine + d_bool warnUnused; // warn about unused function parameters CHECKENABLE useInvariants; // generate class invariant checks CHECKENABLE useIn; // generate precondition checks diff --git a/compiler/src/dmd/semantic3.d b/compiler/src/dmd/semantic3.d index 234782645487..74b259383d6d 100644 --- a/compiler/src/dmd/semantic3.d +++ b/compiler/src/dmd/semantic3.d @@ -1441,6 +1441,16 @@ private extern(C++) final class Semantic3Visitor : Visitor } } + // Warn about unused named parameters + if (global.params.warnUnused && funcdecl.parameters && funcdecl.fbody) + { + foreach (v; *funcdecl.parameters) + { + if (!v.wasUsed && !(v.storage_class & STC.temp)) + .warning(v.loc, "function parameter `%s` is never used", v.ident.toChars()); + } + } + /* If this function had instantiated with gagging, error reproduction will be * done by TemplateInstance::semantic. * Otherwise, error gagging should be temporarily ungagged by functionSemantic3. diff --git a/compiler/test/compilable/previewhelp.d b/compiler/test/compilable/previewhelp.d index 94a01ced14bf..457fb97e2bfc 100644 --- a/compiler/test/compilable/previewhelp.d +++ b/compiler/test/compilable/previewhelp.d @@ -20,5 +20,6 @@ Upcoming language changes listed by -preview=name: =fixImmutableConv disallow `void[]` data from holding immutable data (https://dlang.org/changelog/2.101.0.html#dmd.fix-immutable-conv, https://issues.dlang.org/show_bug.cgi?id=17148) =systemVariables disable access to variables marked '@system' from @safe code (https://dlang.org/spec/attribute.html#system-variables) =fastdfa Fast dataflow analysis engine, experimental + =warnunusedparams enable warnings for unused function parameters (https://dlang.org/spec/function.html#unused-params) ---- */ diff --git a/compiler/test/fail_compilation/unused_params.d b/compiler/test/fail_compilation/unused_params.d new file mode 100644 index 000000000000..4fcff25e891e --- /dev/null +++ b/compiler/test/fail_compilation/unused_params.d @@ -0,0 +1,30 @@ +// REQUIRED_ARGS: -w -preview=warnunusedparams -o- + +/* +TEST_OUTPUT: +--- +fail_compilation/unused_params.d(21): Warning: function parameter `x` is never used +fail_compilation/unused_params.d(21): Warning: function parameter `y` is never used +fail_compilation/unused_params.d(24): Warning: function parameter `x` is never used +Error: warnings are treated as errors + Use -wi if you wish to treat warnings only as informational. +--- +*/ + +// No warning for used parameter +void used(int x) { auto z = x + 1; } + +// No warning for cast(void)suppression +void suppressed(int x) { cast(void)x; } + +// Warn for unused named parameters +void unused(int x, int y) {} + +// No warning for unnamed parameter, warn for named +void partial(int x, int) {} + +// No warning when no body +interface I +{ + void iface(int x); +}