From 937cc4b7c6ba97ab7c090f48a5fa09f47e2b0e74 Mon Sep 17 00:00:00 2001 From: Petar Kirov Date: Wed, 10 Jun 2026 21:08:18 +0300 Subject: [PATCH] Fix #23241 - duplicate deprecation message for enum member accessed by name A deprecated enum member accessed by its unqualified name (an anonymous enum member, a member found via `with`, or an ImportC enumerator) reported the deprecation twice: once during name resolution and again in getVarExp(), which already performs the deprecated/disabled checks itself. Skip the redundant check for enum members in the two name-resolution paths so the diagnostic is emitted exactly once. --- compiler/src/dmd/expressionsem.d | 18 ++++--- .../test/compilable/deprecated_enum_member.d | 53 +++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 compiler/test/compilable/deprecated_enum_member.d diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index d97cee7769a3..6e3f4864e2ed 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -2703,7 +2703,9 @@ Lagain: { // functions are checked after overloading // templates are checked after matching constraints - if (!s.isFuncDeclaration() && !s.isTemplateDeclaration()) + // enum members are checked once in getVarExp() below; skip here to avoid + // emitting the deprecation diagnostic twice for an enum member accessed by name + if (!s.isFuncDeclaration() && !s.isTemplateDeclaration() && !s.isEnumMember()) { s.checkDeprecated(loc, sc); if (d) @@ -2715,7 +2717,7 @@ Lagain: s = s.toAlias(); //printf("s = '%s', s.kind = '%s', s.needThis() = %p\n", s.toChars(), s.kind(), s.needThis()); - if (s != olds && !s.isFuncDeclaration() && !s.isTemplateDeclaration()) + if (s != olds && !s.isFuncDeclaration() && !s.isTemplateDeclaration() && !s.isEnumMember()) { s.checkDeprecated(loc, sc); if (d) @@ -16006,14 +16008,18 @@ Expression dotIdSemanticProp(DotIdExp exp, Scope* sc, bool gag) // if 's' is a tuple variable, the tuple is returned. s = s.toAlias(); - s.checkDeprecated(exp.loc, sc); - if (auto d = s.isDeclaration()) - d.checkDisabled(exp.loc, sc); - if (auto em = s.isEnumMember()) { + // getVarExp() performs the deprecated/disabled checks itself; doing them + // here as well would emit the diagnostic twice for an enum member + // accessed by name (e.g. an anonymous enum member, or an ImportC enumerator). return em.getVarExp(exp.loc, sc); } + + s.checkDeprecated(exp.loc, sc); + if (auto d = s.isDeclaration()) + d.checkDisabled(exp.loc, sc); + if (auto v = s.isVarDeclaration()) { //printf("DotIdExp:: Identifier '%s' is a variable, type '%s'\n", toChars(), v.type.toErrMsg()); diff --git a/compiler/test/compilable/deprecated_enum_member.d b/compiler/test/compilable/deprecated_enum_member.d new file mode 100644 index 000000000000..04f674b2af76 --- /dev/null +++ b/compiler/test/compilable/deprecated_enum_member.d @@ -0,0 +1,53 @@ +/* +REQUIRED_ARGS: -verrors=simple +TEST_OUTPUT: +--- +compilable/deprecated_enum_member.d(33): Deprecation: enum member `deprecated_enum_member.anon` is deprecated +compilable/deprecated_enum_member.d(28): `anon` is declared here +compilable/deprecated_enum_member.d(36): Deprecation: enum member `deprecated_enum_member.E.named` is deprecated +compilable/deprecated_enum_member.d(29): `named` is declared here +compilable/deprecated_enum_member.d(39): Deprecation: enum member `deprecated_enum_member.E.named` is deprecated +compilable/deprecated_enum_member.d(29): `named` is declared here +compilable/deprecated_enum_member.d(42): Deprecation: enum member `deprecated_enum_member.F.withMsg` is deprecated - use namedB +compilable/deprecated_enum_member.d(30): `withMsg` is declared here +compilable/deprecated_enum_member.d(43): Deprecation: enum member `deprecated_enum_member.F.withMsg` is deprecated - use namedB +compilable/deprecated_enum_member.d(30): `withMsg` is declared here +compilable/deprecated_enum_member.d(47): Deprecation: enum member `deprecated_enum_member.anon` is deprecated +compilable/deprecated_enum_member.d(28): `anon` is declared here +compilable/deprecated_enum_member.d(47): Deprecation: enum member `deprecated_enum_member.anon` is deprecated +compilable/deprecated_enum_member.d(28): `anon` is declared here +--- +*/ + +// A deprecated enum member must emit its deprecation diagnostic exactly once per +// use, regardless of how it is named. Previously an enum member accessed by its +// unqualified name (an anonymous-enum member, a member found via `with`, or an +// ImportC enumerator) reported the deprecation twice, because the symbol was +// checked both during name resolution and again in getVarExp(). See issue 23241. + +enum { deprecated anon = 0, anonB } +enum E { deprecated named = 0, namedB } +enum F { deprecated("use namedB") withMsg = 0, plain } + +// Anonymous enum member, accessed by its unqualified name. +int useAnon() { return anon; } + +// Named enum member, accessed by its fully qualified name. +int useNamedQualified() { return E.named; } + +// Named enum member, accessed by its unqualified name brought into scope by `with`. +int useNamedWith() { E e; with (e) return named; } + +// A deprecated member carrying an explicit message reproduces that message. +int useWithMsgQualified() { return F.withMsg; } +int useWithMsgWith() { F f; with (f) return withMsg; } + +// Each separate use emits its own diagnostic (the fix removes a duplicate per use, +// it does not globally deduplicate distinct uses). +int useAnonAgain() { return anon + anon; } + +// Non-deprecated members of the same enums emit nothing. +int useClean() { return anonB + E.namedB + F.plain; } + +// A use from within a deprecated scope is suppressed entirely. +deprecated int useFromDeprecated() { return anon + E.named + F.withMsg; }