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

Fix 22708 - Extend semantic for switch statements with invalid values #13617

Merged
merged 1 commit into from Mar 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
98 changes: 48 additions & 50 deletions src/dmd/statementsem.d
Expand Up @@ -2474,68 +2474,66 @@ package (dmd) extern (C++) final class StatementSemanticVisitor : Visitor
Expression initialExp = cs.exp;

// The switch'ed value has errors and doesn't provide the actual type
// Don't touch the case to not replace it with an `ErrorExp` even if it is valid
// Omit the cast to enable further semantic (exluding the check for matching types)
if (sw.condition.type && !sw.condition.type.isTypeError())
{
cs.exp = cs.exp.implicitCastTo(sc, sw.condition.type);
cs.exp = cs.exp.optimize(WANTvalue | WANTexpand);
cs.exp = cs.exp.optimize(WANTvalue | WANTexpand);

Expression e = cs.exp;
// Remove all the casts the user and/or implicitCastTo may introduce
// otherwise we'd sometimes fail the check below.
while (e.op == EXP.cast_)
e = (cast(CastExp)e).e1;

/* This is where variables are allowed as case expressions.
*/
if (e.op == EXP.variable)
{
VarExp ve = cast(VarExp)e;
MoonlightSentinel marked this conversation as resolved.
Show resolved Hide resolved
VarDeclaration v = ve.var.isVarDeclaration();
Type t = cs.exp.type.toBasetype();
if (v && (t.isintegral() || t.ty == Tclass))
{
/* Flag that we need to do special code generation
* for this, i.e. generate a sequence of if-then-else
*/
sw.hasVars = 1;

/* TODO check if v can be uninitialized at that point.
*/
if (!v.isConst() && !v.isImmutable())
{
cs.error("`case` variables have to be `const` or `immutable`");
}

Expression e = cs.exp;
// Remove all the casts the user and/or implicitCastTo may introduce
// otherwise we'd sometimes fail the check below.
while (e.op == EXP.cast_)
e = (cast(CastExp)e).e1;
if (sw.isFinal)
{
cs.error("`case` variables not allowed in `final switch` statements");
errors = true;
}

/* This is where variables are allowed as case expressions.
*/
if (e.op == EXP.variable)
{
VarExp ve = cast(VarExp)e;
VarDeclaration v = ve.var.isVarDeclaration();
Type t = cs.exp.type.toBasetype();
if (v && (t.isintegral() || t.ty == Tclass))
/* Find the outermost scope `scx` that set `sw`.
* Then search scope `scx` for a declaration of `v`.
*/
for (Scope* scx = sc; scx; scx = scx.enclosing)
{
/* Flag that we need to do special code generation
* for this, i.e. generate a sequence of if-then-else
*/
sw.hasVars = 1;

/* TODO check if v can be uninitialized at that point.
*/
if (!v.isConst() && !v.isImmutable())
{
cs.error("`case` variables have to be `const` or `immutable`");
}
if (scx.enclosing && scx.enclosing.sw == sw)
continue;
assert(scx.sw == sw);

if (sw.isFinal)
if (!scx.search(cs.exp.loc, v.ident, null))
{
cs.error("`case` variables not allowed in `final switch` statements");
cs.error("`case` variable `%s` declared at %s cannot be declared in `switch` body",
v.toChars(), v.loc.toChars());
errors = true;
}

/* Find the outermost scope `scx` that set `sw`.
* Then search scope `scx` for a declaration of `v`.
*/
for (Scope* scx = sc; scx; scx = scx.enclosing)
{
if (scx.enclosing && scx.enclosing.sw == sw)
continue;
assert(scx.sw == sw);

if (!scx.search(cs.exp.loc, v.ident, null))
{
cs.error("`case` variable `%s` declared at %s cannot be declared in `switch` body",
v.toChars(), v.loc.toChars());
errors = true;
}
break;
}
goto L1;
break;
}
goto L1;
}
else
cs.exp = cs.exp.ctfeInterpret();
}
else
cs.exp = cs.exp.ctfeInterpret();

if (StringExp se = cs.exp.toStringExp())
cs.exp = se;
Expand Down
78 changes: 78 additions & 0 deletions test/fail_compilation/test_switch_error.d
Expand Up @@ -99,3 +99,81 @@ void test5(int i)

}
}

/++
TEST_OUTPUT:
---
fail_compilation/test_switch_error.d(513): Error: undefined identifier `undefinedFunc`
fail_compilation/test_switch_error.d(517): Error: `case` must be a `string` or an integral constant, not `Strukt(1)`
fail_compilation/test_switch_error.d(518): Error: `case` variables have to be `const` or `immutable`
fail_compilation/test_switch_error.d(518): Error: `case` variables not allowed in `final switch` statements
fail_compilation/test_switch_error.d(519): Error: `case` variables not allowed in `final switch` statements
fail_compilation/test_switch_error.d(522): Error: undefined identifier `undefinedFunc2`
---
++/
#line 500

enum Foo
{
one, two
}

struct Strukt
{
int i;
}

void errorsWithErrors(int param, immutable int constant)
{
final switch(undefinedFunc())
{
case Foo.one: break;
case Foo.two: break;
case Strukt(1): break;
case param: break;
case constant: break;
}

switch (undefinedFunc2())
{
case constant: break;
}
}

/++
TEST_OUTPUT:
---
fail_compilation/test_switch_error.d(622): Error: undefined identifier `undefinedFunc`
fail_compilation/test_switch_error.d(624): Error: `case` must be a `string` or an integral constant, not `SubtypeOfInt(2)`
fail_compilation/test_switch_error.d(625): Error: `case` must be a `string` or an integral constant, not `SubtypeOfIntMethod()`
---
++/
#line 600

struct SubtypeOfInt
{
int i;
alias i this;
}

struct SubtypeOfIntMethod
{
int getI() { return 0; }
alias getI this;
}

void errorsWithErrors2(int param)
{
final switch(param)
{
case SubtypeOfInt(1): break;
case SubtypeOfIntMethod(): break;
}

// This snippet causes somewhat misleading error messages
final switch(undefinedFunc())
{
case SubtypeOfInt(2): break;
case SubtypeOfIntMethod(): break;
}
}