Annoying deprecation messages when using EnumMembers with enum that has deprecated members

Jonathan M Davis newsgroup.d at jmdavisprog.com
Tue Oct 8 10:06:18 UTC 2019


On Tuesday, October 8, 2019 12:39:28 AM MDT uranuz via Digitalmars-d wrote:
> On Tuesday, 8 October 2019 at 04:31:22 UTC, FeepingCreature wrote:
> > On Sunday, 6 October 2019 at 07:58:41 UTC, uranuz wrote:
> >> [...]
> >
> > I also think it would be useful to have some way to suppress
> > deprecations in a block of code. This is going to become
> > important in the next version of D, where deprecated unittests
> > will become basically impossible to use with unit-threaded
> > because there'll be no way for unit-threaded to call a
> > deprecated unittest without incurring a deprecation message.
> > (That I can see.)
>
> It is curious if it needs some language support to suppress
> deprecation messages or if it is just a library issue?

Any non-deprecated code that uses deprecated symbols will result in a
deprecation message when it's compiled. This includes stuff like template
constraints and type introspection. So, when you start doing things like
using EnumMembers on an enum type which has a deprecated member, then it's
definitely going to result in deprecation messages, because the deprecated
member is actually used in the code.

This isn't really a new problem, but finally being able to deprecate enum
members has certainly made it worse. And there isn't an obvious solution
either. After all, just because an enum member was deprecated doesn't mean
that it shouldn't be used, and if it is used, there really should be a
deprecation message indicating that it was used. The main problem is that
you're using it indirectly, so you don't really have any control over it.

A related issue would be what should happen with final switch. A final
switch is supposed to have a case for _every_ enum member, and that would
include deprecated enum members. If it didn't, then you have an enum member
that's perfectly valid (just deprecated) which could be given to the final
switch, and it wouldn't be able to handle it, since it wouldn't think that
it was a valid value for that enum type. You can't even say that it should
do one thing in a function that's deprecated and another thing in a function
that's not deprecated, because not only could a deprecated enum value be
passed to a non-deprecated function, but code isn't compiled differently
based on whether it's in a deprecated context or not. A function's internals
are going to compile exactly the same way whether the function is deprecated
or not. The only difference is that if the function isn't deprecated, and it
uses deprecated symbols, then you're going to get a deprecation message (or
an error if -de is used). The generated code is the same.

If we had a trait for testing whether a symbol was deprecated or not, and
using it on a deprecated symbol did not trigger a deprecation message, then
code could choose to test whether something was deprecated and skip it if it
were, so we could then have a version of EnumMembers which ignored
deprecated members, but again, that would cause problems with final switch,
and code like std.conv.to isn't going to use it, because code that worked
before that enum member was deprecated needs to continue to work (and work
the same way) as long as that enum member still exists. Compiling a piece of
code differently based on whether a symbol is deprecated or not could be
useful in some circumstances, but it also makes it so that there's a real
risk of code breaking simply because a symbol was deprecated, and
deprecations result in messages by default rather than errors precisely
because we're trying to give people a chance to fix their code before the
symbol is removed rather than forcing them to change their code immediately.

If you're looking for something like std.conv.to to not spit out deprecation
messages when compiled to convert to or from an enum, you're basically
asking it to generate a deprecation message based on whether you use the
deprecated enum member or not, which can't std.conv.to can't know until
runtime.

A related issue is the -de flag, which arguably is really bad in that it
makes it so that is expressions and __traits(compiles, ...) expressions have
a different result depending on whether -de is used or not, because -de
turns deprecation messages into errors. Meaning that code that compiled just
fine before the deprecation may not compile anymore, or it may compile with
different behavior.

So, there are a number of subtleties to this whole situation that make it
kind of hard to figure out what the best approach is. We don't want
deprecation messages being printed when the code isn't really doing anything
that needs to be changed, but we also don't want the code's behavior to
change just because something was deprecated. And how to tweak what we're
doing to fix that in a reasonable way is not obvious.

Regardless, there are really only two ways to shut up deprecation messages
when -de isn't used:

1. Change the code so that it doesn't use the deprecated symbol (which we
don't really have a way to do when type introspection is involved and
whether we would want to not see those messages with type introspection
depends on what the type introspection is doing; it's also not always
possible or reasonable to change generated code like with std.conv.to).

2. Use -d to shut up all deprecation messages (which means that you won't
see them and won't know when you actually need to update your code, so in
all likelihood, your code will just end up breaking when the deprecated
symbol is finally removed).

If -de is used, then code using deprecated symbols in is expressions or
__traits(compiles, ...) expressions shouldn't trigger deprecation messages,
because compilation errors are ignored in such expressions beyond
determining whether the code compiles or not so that the compiler can give
you a boolean result.

- Jonathan M Davis





More information about the Digitalmars-d mailing list