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

Strict comparison for switch cases #3297

Closed
wants to merge 2 commits into
base: master
from

Conversation

@sgolemon
Contributor

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);

This comment has been minimized.

@nikic

nikic Jun 14, 2018

Member

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

This comment has been minimized.

@sgolemon

sgolemon Jun 14, 2018

Contributor

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

This comment has been minimized.

@sgolemon

sgolemon Jun 14, 2018

Contributor

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

@iltar

This comment has been minimized.

iltar 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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

Majkl578 commented Jun 14, 2018

@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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

fprochazka commented Jun 17, 2018

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

@iltar

This comment has been minimized.

iltar commented Jun 17, 2018

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

@brzuchal

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

EdaCZ commented Jun 21, 2018

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

@brzuchal

This comment has been minimized.

Contributor

brzuchal commented Jun 21, 2018

@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

This comment has been minimized.

Contributor

carusogabriel commented Jun 21, 2018

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

This comment has been minimized.

Contributor

brzuchal commented Jun 21, 2018

@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

This comment has been minimized.

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

This comment has been minimized.

Contributor

Majkl578 commented Jun 28, 2018

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

This comment has been minimized.

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