Skip to content

Strict comparison for switch cases #3297

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

Closed
wants to merge 2 commits into from
Closed

Conversation

sgolemon
Copy link
Member

@sgolemon sgolemon commented Jun 14, 2018

For discussion...

switch ($a) {
  case FOO:
      // Works exactly as current behavior.
      break;
  case == FOO:
     // Nearly identical, though we don't use the ZEND_CASE optimization.
     // Can probably make this equivalent to `case FOO`, but it felt like an interesting direction.
     break;
  case === FOO:
     // Only triggers if `$a === FOO`, no type juggling
     break;
}

Jump table optimizations should be unimpacted since I believe they are already strict, but I haven't confirmed this yet.

if (!equality_op) {
equality_op = (expr_node.op_type & (IS_VAR|IS_TMP_VAR)) ? ZEND_CASE : ZEND_IS_EQUAL;
}
opline = zend_emit_op(NULL, equality_op, &expr_node, &cond_node);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't you be freeing expr_node many many times here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, like I said... it's quick and dirty and more for demonstration.
Curiously, my debug build isn't complaining about it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, duh, because it's a CV in my tests, I'll bet a TMP_VAR will choke.

@krakjoe krakjoe added the RFC label Jun 14, 2018
@linaori
Copy link

linaori commented Jun 14, 2018

What about >=, >, <, <= etc? It would make a nice addition

switch($someValue) {
    case < 10: break;
    case < 20: break;
    case < 30: break;
    case < 40: break;
    default: break;
}

@staabm
Copy link
Contributor

staabm commented Jun 14, 2018

from a reading POV I would prefer having the variable in the near of the conditon, e.g.

switch {
    case $someValue < 10: break;
    case $someValue > 20: break;
    case $someValue === 30: break;
    case $someValue < 40: break;
    default: break;
}

@imbrish
Copy link

imbrish commented Jun 14, 2018

@staabm you could already do that with:

switch (true) {
    case $someValue < 10: break;
    case $someValue === 20: break;
    case $someValue >= 30: break;
    default: break;
}

Just as I've seen this implemented in the user land occasionally. However from my understanding only a single reference to $someValue would be more efficient.

@Majkl578
Copy link
Contributor

@staabm Uhh, I wouldn't do that. If you do want that, too confusing and too easily abusable.
You can already use switch (true) + condition.

@Majkl578
Copy link
Contributor

Majkl578 commented Jun 16, 2018

I still don't like the syntax suggestions here. :(
If I want to use strict switches, I want to use them always and everywhere. I don't want to always write case === 123 - this is making things horribly more complex and hard to read/understand unfortunately. Using switch (true) + case $foo === 123 is much clearer and already possible today.

I think this should rather be done as part of introducing declare(strict_comparisons=1), together with strictening <, >, <=, >=, <=>, <>.

@brzuchal
Copy link
Contributor

brzuchal commented Jun 17, 2018

@Majkl578 That's exactly what I mentioned about having new declare on internals but it seems like nobody responded on this :/

@fprochazka
Copy link

I really like the idea of declare(strict_comparisons=1) 👍

@linaori
Copy link

linaori commented Jun 17, 2018

@fprochazka I like the idea, but does that include a == b? Because that's a comparison...

@brzuchal
Copy link
Contributor

brzuchal commented Jun 18, 2018

IMO declare(strict_comparisons=1); could cover all comparisons with type check and without type juggling at all, in this case, all control statements in a file would first type check and if types are same then compare them which means:

  • switch doesn't need === operator anymore because by default will behave like that
  • every if, else, elseif will turn every == into === and the rest of comparison operators to first check the same type and them do the comparison
  • every comparison expression used in ternary operator, etc. will behave like above with first type check and if they match do the comaprison

This way we're not changing language syntax at all, and it's easier in a future if it won't be accepted by the community to mute declare than remove syntax.

@Majkl578
Copy link
Contributor

Majkl578 commented Jun 18, 2018

In addition to those mentioned by @brzuchal, it would also be an opportunity to fix the following:

  • in_array - strict behavior
  • array_search - strict behavior
  • array_keys - strict behavior for search mode
  • base64_decode - strict behavior (probably not suitable for strict comparisons)

What else? :)

@EdaCZ
Copy link

EdaCZ commented Jun 21, 2018

I personally would be strict everywhere without any explicit declaration. Be non-strict is antipattern.

@brzuchal
Copy link
Contributor

@EdaCZ yeah, me to but you need to keep backward compatibility in major versions of PHP that's why introducing declare is the only way to change it per case/file.

@carusogabriel
Copy link
Contributor

Dropping some syntax suggestion: like $strict param for functions like in_array, we could use:

$foo = false;

switch ($foo, true) {
    case 0: // should not enter
    case null: // should not enter
    case false: // should enter \o/
}

@brzuchal
Copy link
Contributor

@carusogabriel switch is not a function, it looks like the next step will be more strict if ($foo == true, true) because we're dropping some syntax and we want more strict comparisons, you see it doesn't fit now?

@rtheunissen
Copy link
Contributor

rtheunissen commented Jun 28, 2018

@Majkl578 @sgolemon I personally don't like the idea of universal or file-scoped strict comparison, unless it's for scalar values only.

Objects are a special case. Consider the case where you want strict comparisons for scalars (no type juggling), where 1 == true is false, but you want to compare objects by their own defined equality rules, or the default by-property algorithm? If we define strict comparisons for a file, then we can only compare objects by reference, so cases like Decimal(1) == Decimal(1) would fail with no alternative other than Decimal(1)->equals(Decimal(1)) which seems like a massive problem because functions like in_array etc won't honour that, and in_array(new Decimal(1), $array) won't work.

Maybe strict comparison shouldn't affect objects? So that == is still object-by-property (or __equals in #3339) and === is still object-same-reference. Otherwise the only option we have is to compare reference-to-reference with no way to compare objects for equality like we do today.

@Majkl578
Copy link
Contributor

or the default by-property algorithm?

Simple: This thing would cease to exist as it shares the same garbage behavior as type juggling and ==: https://3v4l.org/Eqqkq

massive problem because functions like in_array

As said above, strict comparisons would apply to in_array too (it's weak behavior would no longer be default). (But who uses non-strict in_array anyway?)

@rtheunissen
Copy link
Contributor

rtheunissen commented Jun 28, 2018

As said above, strict comparisons would apply to in_array too

Yup, so the default value for the third param in in_array would become $strict = true? I don't mind that at all, my issue is more with in_array etc then not supporting object comparison behaviour like in_array(Decimal(1.0), $array) or GMP numbers or any value type.

Strict comparison (is_identical) has different semantics for objects and scalars:

Objects: Same exact instance, same object reference.
Scalars: Same type and same value.

Those aren't the same at all. Decimal(1.0) has the same type (Decimal) and value (1.0) as another Decimal(1.0), but strict comparison won't honour that. Therefore the only way I can see this working is if the strict comparison declaration does not affect objects.

@sgolemon sgolemon closed this Aug 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.