December 29
struct A(T) { }

pragma(msg, is(const A!int == A!int)); // false
pragma(msg, is(const A!int == A!T, T)); // true

Intuitively, const A!int cannot be == A!T for any T, but DMD insists it is. If you are curious, it infers T to be int.

The same happens with the implicit-convertibility check, though we need an indirection to observe it:

struct B(T) { int* p; }

pragma(msg, is(const B!int: B!int)); // false
pragma(msg, is(const B!int: B!T, T)); // true

So, if a TemplateParameterList is present, the type checker ignores all qualifiers (even shared) on both LHS and RHS. The spec does not mention such behavior in either is expressions or template-parameter deduction.

I wonder if it is a bug or a deliberate decision. If former, fixing it can potentially break a lot of code dependent on the buggy behavior (so yet another -preview?..). For example, std.sumtype is affected:

enum bool isSumType(T) = is(T : SumType!Args, Args...);

(This should be is(immutable T : immutable SumType!Args, Args...): we use immutable to erase other qualifiers.)

If it was intended to work this way, then IMO it’s a huge gotcha that should be described in the docs. If so, could somebody explain why it was designed such?

Discovered by Andrey Zherikov and me.

December 29
I haven't looked deeply into this yet, but the pattern matching design and implementation of IsExpressions was done for D1. D1 did not have const, shared, etc. The behavior of the pattern matcher probably remained the same through the transition to const, etc.

There's been so much water under the bridge since it would be very risky to change its behavior now.