Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add std.compiler.Version #8750

Closed
wants to merge 1 commit into from
Closed

Conversation

Herringway
Copy link
Contributor

I am hesitant to call this a fix for issue 7417, because I think there is still some merit to the idea as proposed, but this is a common-enough workaround that I feel it is an appropriate for inclusion in Phobos. The rationale both for and against this has been discussed to death already, and can be found in the issue as well as the many forum threads that have come up over the years.

I am unsure if std.compiler is the ideal module for this, but it doesn't seem like any other module is a better fit, and I don't think adding a new module just for this is justifiable.

Note that the example in the changelog entry and the example in the unittest differ - this is due to a limitation of version specifications. They are only permitted at module scope.

@Herringway Herringway requested a review from andralex as a code owner May 11, 2023 17:26
@dlang-bot
Copy link
Contributor

Thanks for your pull request and interest in making D better, @Herringway! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please verify that your PR follows this checklist:

  • My PR is fully covered with tests (you can see the coverage diff by visiting the details link of the codecov check)
  • My PR is as minimal as possible (smaller, focused PRs are easier to review than big ones)
  • I have provided a detailed rationale explaining my changes
  • New or modified functions have Ddoc comments (with Params: and Returns:)

Please see CONTRIBUTING.md for more information.


If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment.

Bugzilla references

Your PR doesn't reference any Bugzilla issue.

If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub run digger -- build "master + phobos#8750"

@dkorpel
Copy link
Contributor

dkorpel commented May 11, 2023

The rationale both for and against this has been discussed to death already

As far as I know, the 'official' position is still that version logic is restricted by design. The standard library shouldn't endorse what the language is trying to avoid, that sends a mixed message.

In any case, this needs to be approved by @atilaneves

Personally I'm against this addition. The example in the changelog:

static if (Version.D_InlineAsm_X86 || Version.D_InlineAsm_X86_64)
{
    version = UseX86Assembly;
}

Can easily be expressed by:

version (D_InlineAsm_X86)
    version = UseX86Assembly;
version (D_InlineAsm_X86_64)
    version = UseX86Assembly;

Also, mixing static if and version doesn't work properly, and the changelog example should arguably be deprecated: Issue 7386

@Herringway
Copy link
Contributor Author

The rationale both for and against this has been discussed to death already

As far as I know, the 'official' position is still that version logic is restricted by design. The standard library shouldn't endorse what the language is trying to avoid, that sends a mixed message.

The linked issue has not been marked WONTFIX. It seems to me that this is very much unresolved.

In any case, this needs to be approved by @atilaneves

Personally I'm against this addition. The example in the changelog:

static if (Version.D_InlineAsm_X86 || Version.D_InlineAsm_X86_64)
{
    version = UseX86Assembly;
}

Can easily be expressed by:

version (D_InlineAsm_X86)
    version = UseX86Assembly;
version (D_InlineAsm_X86_64)
    version = UseX86Assembly;

DRY. Especially here, where a compiler will be unable to help you with a typo. While it is true that this PR doesn't solve that entirely, it can cut down on the number of error-prone version specifications and that is an improvement, IMO.

Also, mixing static if and version doesn't work properly, and the changelog example should arguably be deprecated: Issue 7386

I would personally argue for version specifications/conditions to be deprecated and removed entirely, as they require a valuable keyword (version), are redundant (static if) and dangerously error-prone (initial drafts of this PR had the wrong capitalization for D_InlineAsm_X86. very annoying). But I suspect that this would not be a popular move and pushing for it would not be a valuable use of my time.

@dkorpel
Copy link
Contributor

dkorpel commented May 11, 2023

The linked issue has not been marked WONTFIX. It seems to me that this is very much unresolved.

I can change that for you 🙂 although controversial issues tend to be reopened when that happens.

DRY

Has to do with redundant pieces of knowledge spread out over the project. It does not mean "factor out every common piece of source code you can", that results in an incomprehensible code golf submission. Otherwise I'd argue for support of version("D_InlineAsm_X86(_64)?".regex) and review this PR by saying this:

    static assert(Version.all);
    static assert(!Version.none);

Should become this:

    static assert(Version.all && !Version.none);

Because 'don't repeat static assert'.

@ryuukk
Copy link

ryuukk commented May 12, 2023

I aggree with Herringway, this should be supported by the compiler

Example i had to do in my code:

version(Windows)
    alias socket_t = size_t;
else
    alias socket_t = int;

4 lines with repetition that could be just one liner, you never know you made a typo or missed something until you compile for that target, this is just bad UX, wastes time

alias socket_t = version (Windows) size_t else int;

or

alias socket_t = version (Windows) ? size_t : int;

regex in version however is imo a bad idea, slow and cryptic

version should work as an expression, throw was made an expression recently, so what's preventing it?

As far as I know, the 'official' position is still that version logic is restricted by design.

restricting something doesn't mean making it impractical to use, actually it could mean plenty of things, but i'll always advocate for enabling nice and concise code, even if that mean going against the "official" stance

@dkorpel
Copy link
Contributor

dkorpel commented May 12, 2023

I'll bring this up in the next DLF monthly meeting, but without new compelling arguments, I don't expect a different outcome. If you can demonstrate problems (other than it not looking nice) in existing projects that the current logic causes, you'll have a stronger case.

@Herringway
Copy link
Contributor Author

I'll bring this up in the next DLF monthly meeting, but without new compelling arguments, I don't expect a different outcome. If you can demonstrate problems (other than it not looking nice) in existing projects that the current logic causes, you'll have a stronger case.

Consider this code:

version(linux) {
  version = ExtraFunctionality;
} else version(OSX) {
  version = ExtraFunctionaIity;
}
void foo() {
  version(ExtraFunctionality) {
    // fancy stuff here
  }
  // normal functionality
}

Did you spot the error? The compiler sure didn't! Let's try a different example.

version(linux) {
  enum ExtraFunctionality = true;
} else version(OSX) {
  enum ExtraFunctionaIity = true;
} else {
  enum ExtraFunctionality = false;
}

void foo() {
  static if (ExtraFunctionality) {
    // fancy stuff here
  }
  // normal functionality
}

Now we get a compiler error. Good. But only on one configuration, and it may not be the primary one used by the developers, so it can go unnoticed.
But we can do even better.

enum ExtraFunctionaIity = Version.linux || Version.OSX;

void foo() {
  static if (ExtraFunctionality) {
    // fancy stuff here
  }
  // normal functionality
}

Now the error is obvious on ALL systems, with the bonus of being much more concise and expressive. But there are still problems here. Adding more enums will naturally mean more Versions, which means more opportunities for undetectable typos. But we can fix this!

enum LinuxVersion = Version.linux;
enum OSXVersion = Version.OSX;
enum ExtraFunctionality = LinuxVersion || OSXVersion;
enum CoolFunctionaIity = LinuxVersion;
enum NiftyFunctionality = OSXVersion;

void foo() {
  static if (ExtraFunctionality) {
    // extra stuff here
  }
  static if (CoolFunctionality) {
    // cool stuff here
  }
  static if (NiftyFunctionality) {
    // nifty stuff here
  }
  // normal functionality
}

Is this ideal? No. But without language support giving us a tristate (ie undefined, true or false) version identifier in the future, I don't think we can do much better at this time.

In conclusion, version() is dangerous and redundant. We can accomplish what version()'s goal was far more effectively with static if and enum, and we can do much better by moving beyond it. This PR merely implements a common partial solution that the community has already been using.

@dkorpel
Copy link
Contributor

dkorpel commented May 12, 2023

That's a made up scenario.

Version.linux || Version.OSX

Should probably be version(Posix), but can't tell since there's no context for this code

This PR merely implements a common partial solution that the community has already been using.

Can you give an example of a dub project effectively using this?

@Herringway
Copy link
Contributor Author

That's a made up scenario.

Okay. Version typos do happen, and can go undetected for a pretty long time.

Version.linux || Version.OSX

Should probably be version(Posix), but can't tell since there's no context for this code

Perhaps, but there is precedence in Phobos here.

phobos/std/math/algebraic.d

Lines 598 to 602 in cf97c75

version (linux) version = GenericPosixVersion;
else version (FreeBSD) version = GenericPosixVersion;
else version (OpenBSD) version = GenericPosixVersion;
else version (Solaris) version = GenericPosixVersion;
else version (DragonFlyBSD) version = GenericPosixVersion;

This PR merely implements a common partial solution that the community has already been using.

Can you give an example of a dub project effectively using this?

No. Github does not make such a search feasible. But I can provide multiple forum posts and issues where this code has been suggested.

@dkorpel
Copy link
Contributor

dkorpel commented May 12, 2023

Okay. Version typos do happen, and can go undetected for a pretty long time.

That's a good example of DRY: the list of reserved version identifiers is duplicated between the compiler and spec, making it easy for them to be out of sync. It's not something that would be prevented by version algebra though, that only prevents inconsistent spelling of an identifier within 10 lines of code.

Perhaps, but there is precedence in Phobos here.

That's a good example. Consider the GenericPosixVersion was misspelled in the FreeBSD branch, and it wasn't spotted during code review. The CI that compiles FreeBSD would fail, because the version statement falls through to the last branch with static assert(0).

No. Github does not make such a search feasible. But I can provide multiple forum posts and issues where this code has been suggested.

That's a nice list! But just because people present it as a proof of concept, doesn't mean it has been used successfully and battle tested in production. I think that when push comes to shove, users still rather use version statements than import a template that's prone to trigger compiler bugs.

It's currently the norm that Phobos additions preferably first prove themselves successful as a dub package. For example, std.sumtype was developed and gained popularity on dub before its Phobos inclusion. Now, this PR's feature is a bit trivial for a dub package, but perhaps you can ask in the forum if anyone has successfully used a template like it.

Because here's the thing: Walter has decades of experience with #ifdef hell in C, and designed D's version system to try to avoid that. He does not have experience with "inconsistent version identifier spelling within 10 lines of code hell", so it probably takes a strong argument based on real world experience to change his mind. Not a hypothetical scenario designed to trigger the failure mode of the current way.

@atilaneves
Copy link
Contributor

I don't think this merits inclusion in Phobos, especially as there's no reason anyone can't use this themselves right now. As @dkorpel said, I don't think the standard library should enable something that the language explicitly makes difficult. I understand that not everyone might agree on that stance, but the beauty is that solutions such as this one are possible anyway.

@ljmf00
Copy link
Member

ljmf00 commented May 16, 2023

I don't think this merits inclusion in Phobos, especially as there's no reason anyone can't use this themselves right now. As @dkorpel said, I don't think the standard library should enable something that the language explicitly makes difficult. I understand that not everyone might agree on that stance, but the beauty is that solutions such as this one are possible anyway.

So, time to to change version() syntax ?

This behavior of version creates a lot of bloat in the users modules (and even on DMD/Phobos/DRuntime codebase) and this alternative solves that bloat, without doing a syntax breaking change.

The argument of "there's no reason anyone can't use this themselves right now" can be applied to the whole Phobos. And it ranges the simplest things as a fromStringz to std.windows.registry.

Do we really need Windows specific registry utility in a standard library? That's dubious and it drills down, in the end, to what people need and their general concerns.

@dkorpel
Copy link
Contributor

dkorpel commented May 18, 2023

This behavior of version creates a lot of bloat in the users modules (and even on DMD/Phobos/DRuntime codebase) and this alternative solves that bloat

Can you give examples of this?

@WalterBright
Copy link
Member

the 'official' position is still that version logic is restricted by design

That's correct.

@WalterBright
Copy link
Member

version(linux) {
  enum ExtraFunctionality = true;
} else version(OSX) {
  enum ExtraFunctionality = true;
} else {
  enum ExtraFunctionality = false;
}

The robust way to do this is:

version(linux) {
  enum ExtraFunctionality = true;
} else version(OSX) {
  enum ExtraFunctionality = true;
} else {
  static assert(0, "system not accounted for");
}

Then, when you inevitably add another OS, the compiler will point out where updates are needed.

As for misspelling ExtraFunctionaIity, the compilation on OSX will fail because ExtraFunctionality will be undefined.

@WalterBright
Copy link
Member

This PR is not approved.

@ljmf00
Copy link
Member

ljmf00 commented May 22, 2023

This behavior of version creates a lot of bloat in the users modules (and even on DMD/Phobos/DRuntime codebase) and this alternative solves that bloat

Can you give examples of this?

As mentioned:

phobos/std/math/algebraic.d

Lines 598 to 602 in cf97c75

version (linux) version = GenericPosixVersion;
else version (FreeBSD) version = GenericPosixVersion;
else version (OpenBSD) version = GenericPosixVersion;
else version (Solaris) version = GenericPosixVersion;
else version (DragonFlyBSD) version = GenericPosixVersion;

version (X86) version = X86_Any;
version (X86_64) version = X86_Any;
version (PPC) version = PPC_Any;
version (PPC64) version = PPC_Any;
version (MIPS32) version = MIPS_Any;
version (MIPS64) version = MIPS_Any;
version (AArch64) version = ARM_Any;
version (ARM) version = ARM_Any;
version (S390) version = IBMZ_Any;
version (SPARC) version = SPARC_Any;
version (SPARC64) version = SPARC_Any;
version (SystemZ) version = IBMZ_Any;
version (RISCV32) version = RISCV_Any;
version (RISCV64) version = RISCV_Any;
version (D_InlineAsm_X86) version = InlineAsm_X86_Any;
version (D_InlineAsm_X86_64) version = InlineAsm_X86_Any;
version (X86_64) version = StaticallyHaveSSE;
version (X86) version (OSX) version = StaticallyHaveSSE;

And on druntime:

https://github.com/dlang/dmd/blob/1188adc84ff1777f4a0153a8a5d7729ab5866dce/druntime/src/object.d#L87-L105
https://github.com/dlang/dmd/blob/1188adc84ff1777f4a0153a8a5d7729ab5866dce/druntime/src/etc/linux/memoryerror.d#L17-L25
https://github.com/dlang/dmd/blob/1188adc84ff1777f4a0153a8a5d7729ab5866dce/druntime/src/core/vararg.d#L22-L45
https://github.com/dlang/dmd/blob/1188adc84ff1777f4a0153a8a5d7729ab5866dce/druntime/src/core/stdc/errno.d#L17-L40
https://github.com/dlang/dmd/blob/1188adc84ff1777f4a0153a8a5d7729ab5866dce/druntime/src/core/stdc/stdio.d#L18-L49

It's like everywhere.

At Weka, we have isVersion template, very similar to this: https://github.com/dlang-community/mecca/blob/master/src/mecca/lib/reflection.d#L565
PowerNex also have it https://github.com/PowerNex/PowerNex/blob/bcb6cf9e8ed9bb21f2ff9e3b388c80220915cb38/src/system/stl/stl/trait.d#LL46C1-L46C1
@CyberShadow ae library also uses it: https://github.com/CyberShadow/ae/blob/5d9728948d43110d3e07d028beca0734c954ad8f/sys/git.d#L693
@dkorpel your own code https://github.com/dkorpel/ctod/blob/3420551315d352c31100138270e564e2972f2fae/source/ctod/translate.d#L20-L24
raylib-d https://github.com/schveiguy/raylib-d/blob/30c97fa28777e7252c8c370a5f3799421329078a/source/raygui.d#L14

If you need some examples, just search for a few repos on github. I just did these simple searches: https://github.com/search?q=hasVersion+path%3A*.d&type=code and https://github.com/search?q=isVersion+path%3A*.d&type=code

@CyberShadow
Copy link
Member

CyberShadow commented May 22, 2023

I don't have a position on this particular change, but I do have a philosophical question.

What is the fundamental difference between version and static if, in that one allows using && and ||, and the other does not?

For that matter, what is the fundamental difference between version and regular if in this regard?

Why does the argument that complex boolean expressions make code hard to read not apply to static if and if?

It doesn't seem to me that a fundamental difference exists which would provide solid defense for the status quo. Perhaps we should either allow version expressions (which would achieve consistency), or discourage complex boolean expressions (which would achieve consistency, and enforce code clarity at the cost of verbosity).

@dkorpel
Copy link
Contributor

dkorpel commented May 22, 2023

Thanks for the clarifying examples. @ljmf00

And on druntime:

Note that Walter deliberately structured druntime to have simple duplication instead of complex factored out code:
https://forum.dlang.org/post/u4f6vo$2b5q$1@digitalmars.com

It's like everywhere.

druntime has a lot of version logic because it needs to inferface with several C runtimes on different platforms. It's not representative of user code.

At Weka, we have isVersion template, very similar to this: https://github.com/dlang-community/mecca/blob/master/src/mecca/lib/reflection.d#L565

It's imported once, but never used in that repository.

PowerNex also have it https://github.com/PowerNex/PowerNex/blob/bcb6cf9e8ed9bb21f2ff9e3b388c80220915cb38/src/system/stl/stl/trait.d#LL46C1-L46C1

It's used once:

static assert(isVersion!"PowerNex", ...);

Which could also be expressed as

version(PowerNex) {}
else static assert(0, ...);

@CyberShadow ae library also uses it: https://github.com/CyberShadow/ae/blob/5d9728948d43110d3e07d028beca0734c954ad8f/sys/git.d#L693

That's a nice example.

@dkorpel your own code https://github.com/dkorpel/ctod/blob/3420551315d352c31100138270e564e2972f2fae/source/ctod/translate.d#L20-L24

Yes, ctod unfortunately has the ability to translate C's #ifdef hell 🙂

raylib-d https://github.com/schveiguy/raylib-d/blob/30c97fa28777e7252c8c370a5f3799421329078a/source/raygui.d#L14

It inherited it from ctod, but it does provide usage examples.


So I collected these examples:

std.math.algebraic

Currently:

version (linux)             version = GenericPosixVersion;
else version (FreeBSD)      version = GenericPosixVersion;
else version (OpenBSD)      version = GenericPosixVersion;
else version (Solaris)      version = GenericPosixVersion;
else version (DragonFlyBSD) version = GenericPosixVersion;

This is 'bloat' and this is the proposed replacement:

static if (Version.linux || Version.FreeBSD || Version.OpenBSD || Version.Solaris || Version.DragonFlyBSD)
    version = GenericPosixVersion;

Of perhaps using the static if in-line without giving it the name GenericPosixVersion.

Inline version logic in ae

GitObject.TreeEntry(
    isVersion!`Posix` && (de.attributes & octal!111) ? octal!100755 : octal!100644,
    de.baseName,
    writer.write(GitObject(Hash.init, "blob", cast(immutable(ubyte)[])read(de.name)))
)

Translated C ifdefs in raylib-d:

static if (!HasVersion!"RAYGUI_NO_ICONS" && !HasVersion!"RAYGUI_CUSTOM_ICONS")

Or with this PR's syntax:

static if (!Version.rayguiNoIcons && Version.rayguiCustomIcons)

Are these good picks? I can show the examples to Walter, but I don't think they are very convincing, the tired old answer "it's not supposed to look nice and concise" still applies.

@dkorpel
Copy link
Contributor

dkorpel commented May 22, 2023

Why does the argument that complex boolean expressions make code hard to read not apply to static if and if?

It does apply. "no code" > "straightforward code" > "code with many branches". However, complex conditions can be required because your code solves a complex problem. It's best to have good test coverage where all paths are taken in that case.

Version logic is different because it's almost always accidental complexity, and it's hard to test. When you have complex version logic, that sucks, but writing it as a concise one-liner is pure window dressing.

@Herringway
Copy link
Contributor Author

One thing that I've noticed is that several of the examples used in the version() specification are much more concisely written with static if.
Exhibit A:

version (ProfessionalEdition)
{
    version = FeatureA;
    version = FeatureB;
    version = FeatureC;
}
version (HomeEdition)
{
    version = FeatureA;
}
...
version (FeatureB)
{
    // implement Feature B ...
}

Could be:

enum FeatureA = Version.ProfessionalEdition || Version.HomeEdition;
enum FeatureB = Version.ProfessionalEdition;
enum FeatureC = Version.ProfessionalEdition;
...
static if (FeatureB)
{
    // implement Feature B ...
}

Exhibit B:

class Foo
{
    int a, b;

    version(full)
    {
        int extrafunctionality()
        {
            //...
            return 1;  // extra functionality is supported
        }
    }
    else // demo
    {
        int extrafunctionality()
        {
            return 0;  // extra functionality is not supported
        }
    }
}

Could be:

class Foo
{
    int a, b;
    int extrafunctionality()
    {
        static if (Version.full)
        {
            // ...
        }
        return Version.full;
    }
}

Exhibit C:

version(linux) {
  enum ExtraFunctionality = true;
} else version(OSX) {
  enum ExtraFunctionality = true;
} else {
  static assert(0, "system not accounted for");
}

Could be:

static if(Version.linux || Version.OSX) {
  enum ExtraFunctionality = true;
} else {
  static assert(0, "system not accounted for");
}

What do we gain by having version()? Even if you prefer the original examples, they are still reproducible with static if in place of version(). I can tell you what we lose out on by having version(): a valuable identifier. It's a keyword, so we cannot have variables named 'version'. Do you know how often I see version fields in serialized structures?

version() also has a consistency problem. Why are some predefined versions lowercase while the majority are uppercase (linux? Windows?)? Why are the underscores only sometimes used as word separators? The D compiler can't help us when we misspell these identifiers at all. There is no Error: undefined identifier Linux, did you mean linux?.

But that can't be done, because instead of the tri-state undefined/false/true you can achieve with typical booleans, we only have undefined/true. These are the things we were supposed to be leaving behind. The compiler can't help you if you misspell a version definition on the command line because of this.

Why are users forced to "avoid #ifdef hell" instead of discovering and choosing that path on their own? I know I wouldn't willingly choose it from looking at the examples provided. If there's a benefit to this, it should be demonstrated, not simply asserted. Show, don't tell. Not only am I unconvinced by the status quo here, I find myself actively resenting it due to the feature's shortcomings mentioned above.

Personally, I would rather see all predefined versions being made into simple namespaced boolean enums usable with a similar syntax as this PR. Version.nonexistent would be an error unless defined on the command line, Version.Windows would be true/false as appropriate.

The only reasonable course of action is clear. version() must be eradicated in its entirety. It is dangerous and redundant. It will not be missed.

@ljmf00
Copy link
Member

ljmf00 commented May 22, 2023

"it's not supposed to look nice and concise"

That is kind of a no-argument to me. It's very workaround-ish to me and the reason for people to use it is maybe because they need it.

Plus, this is the level of workarounds I need to deal with:

version (A) version = A_;
else version (B) {}
else version (C) {}
else version = A_;

version (A_) version = AorB;
version (B)  version = AorB;

And no, this doesn't work:

version (A) {}
else version (B) {}
else version (C) {}
else version = A;

version (A) version = AorB;
version (B)  version = AorB;

@dkorpel
Copy link
Contributor

dkorpel commented May 22, 2023

Plus, this is the level of workarounds I need to deal with:

Is that code private? If you add back the context, and show how it would be improved by std.compiler.Version, it could be a stronger example in support of this PR.

@ljmf00
Copy link
Member

ljmf00 commented May 22, 2023

Plus, this is the level of workarounds I need to deal with:

Is that code private? If you add back the context, and show how it would be improved by std.compiler.Version, it could be a stronger example in support of this PR.

It is private. I can try to rewrite it with Version., but I think this is rather a bug on version logic, preventing me to use it before assignment, even though it can be assigned via a compiler flag.

But, briefly, this is to be able to define a "default" version flag on the module, if no version flags are passed to the compiler.

@dkorpel
Copy link
Contributor

dkorpel commented May 22, 2023

we cannot have variables named 'version'

version() also has a consistency problem

version() must be eradicated in its entirety

Is the argument for this PR now based on paving the way to replacing the version system entirely? That's a big breaking change, and would require even stronger rationale.

@dkorpel
Copy link
Contributor

dkorpel commented May 22, 2023

But, briefly, this is to be able to define a "default" version flag on the module, if no version flags are passed to the compiler.

Heh, that's funny. I recently did something very similar: dlang/dmd#15253

It would be nice to have a way to define a version flag as 'default' indeed, but that wouldn't require logical operators to be supported on version flags.

@Herringway
Copy link
Contributor Author

we cannot have variables named 'version'

version() also has a consistency problem

version() must be eradicated in its entirety

Is the argument for this PR now based on paving the way to replacing the version system entirely? That's a big breaking change, and would require even stronger rationale.

No, I'm just thinking long-term. If adopted, a PR like this would make that less of a breaking change, though.

@WalterBright
Copy link
Member

Why does the argument that complex boolean expressions make code hard to read not apply to static if and if?

It does apply: https://forum.dlang.org/post/u4f6vo$2b5q$1@digitalmars.com

Complex conditionals are a rich source of bugs.

@WalterBright
Copy link
Member

Why are some predefined versions lowercase while the majority are uppercase (linux? Windows?)?

They were generally selected to match the way that C compilers commonly cased their predefined macros. I received some flak years ago for naming the Linux version linux as "denigrating" Linux. I had simply named it linux to match the gcc's use of it.

@dkorpel
Copy link
Contributor

dkorpel commented Jun 11, 2023

Since this PR discussion and my newsgroup post failed to find compelling examples to change the leadership's minds (#8750 (comment)), I don't think this is PR has a future.

We'll have to agree to disagree here unfortunately.

@dkorpel dkorpel closed this Jun 11, 2023
@ljmf00
Copy link
Member

ljmf00 commented Jun 14, 2023

Since this PR discussion and my newsgroup post failed to find compelling examples to change the leadership's minds (#8750 (comment)), I don't think this is PR has a future.

We'll have to agree to disagree here unfortunately.

No proper justification for why it is not accepted, again. Another discussion to be added to the endless number of leadership's decisions with a biased opinion rather than logical pros and cons.

This, in particular, is advocating for bloated source code with an horrible way to make branching with version identifiers. In a perspective of a possible newcomer, I would look at the way we do these branching as a workaround, specially when it is "the official way" to do it.

I believe we are slowly killing Phobos by deferring such essential helpers to third-party libraries.

@dkorpel
Copy link
Contributor

dkorpel commented Jun 14, 2023

Another discussion to be added to the endless number of leadership's decisions with a biased opinion rather than logical pros and cons.

That's a very unfair summary

No proper justification for why it is not accepted

This PR should come with good justification why it should be accepted. With my inquiries I tried to help gather examples of real bugs caused by D's current version design that this PR prevents, or snippets of "problematic code before this PR vs. how it's solved after this PR", but didn't get any. All I got was:

  • examples that are constructed / stripped of all context
  • a bug that's related, but not actually solved by this PR (issue 15452)
  • definitions of the version template, without much usage of it
  • examples of 'bloat'. Concretely: version logic expressed in ~10 lines of code. I'm sorry, but here's where we differ on what 'problematic' is.

@ljmf00
Copy link
Member

ljmf00 commented Jun 14, 2023

Most of the examples where referenced from official code, not just a random library that uses this that way, and for me, seeing this the official way to do it, is concerning to me.

examples of 'bloat'. Concretely: version logic expressed in ~10 lines of code. I'm sorry, but here's where we differ on what 'problematic' is.

Maybe I see the definition of problematic more broadly, because problematic code is not just code that produces buggy results. Bloated code means more code to read. There are approaches to this that can still be maintainable and very readable, with less code.

You might not see a problem on a language that would enforce you to not use logical operators in replacement to this idea, but I would. Imagine ifs with that approach:

Instead of having:

if ((A || B) && C)
    return 4;
return 5;
if (A)
    if (C)
        return 4;
if (B)
    if (C)
        return 4;
return 5;

I'm sure there would be more bugs if such a language was extensively used by a a large codebase.

@dkorpel
Copy link
Contributor

dkorpel commented Jun 14, 2023

Imagine ifs with that approach:

#8750 (comment)

I'm sure there would be more bugs if such a language was extensively used by a a large codebase.

Then where are the bugs caused by D being such a language with regards to version conditions?

@ljmf00
Copy link
Member

ljmf00 commented Jun 15, 2023

Then where are the bugs caused by D being such a language with regards to version conditions?

There's many reasons for why you shouldn't do code duplication, one of them is human errors. Code duplication is universally considered a code smell. See https://en.wikipedia.org/wiki/Duplicate_code . There's even academic studies on this https://elib.uni-stuttgart.de/handle/11682/3640 .

e.g. getting a char wrong among a list of a few version defines can lead to a silent different behavior, for critical codebases can even lead to security vulnerabilities.

That's not my biggest issue, although. People, as referenced by the links I sent, use kind of the same template to check version identifier presence in form of a primary expression, rather than, having a version block to mimic it. Either having version.identifier or having this template would help in such cases.


This is a bit off-topic, but related to the negative feedback we often get: The same way there's other code smells that are devalued by D leadership. There are some very common code smells, like having unused variables, that I and my coworkers have already stumbled upon. And currently we have no way to reliably check them other than have a linter check after the semantic analysis in the compiler. Any decent compiler has built-in linter checks, so people don't need to re-run semantic checks on each build. And this is a concern on industry codebase, where build times get really important. Last time I had this conversation somewhere, @WalterBright denied the proposal of adding such rules to DMD.

@dkorpel
Copy link
Contributor

dkorpel commented Jun 16, 2023

There's many reasons for why you shouldn't do code duplication

This isn't code duplication. #8750 (comment)

Code duplication is universally considered a code smell

The paper you linked says the opposite. "there are contradictory results regarding the connection of cloning and faults"

e.g. getting a char wrong among a list of a few version defines can lead to a silent different behavior, for critical codebases can even lead to security vulnerabilities.

It leads to a compilation error, #8750 (comment) (second paragraph)

@dkorpel
Copy link
Contributor

dkorpel commented Jun 16, 2023

And currently we have no way to reliably check them other than have a linter check after the semantic analysis in the compiler.

Noted. Do you want to discuss this somewhere else?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants