Skip to content

Exhaustive switch operator #428

@vkaverin

Description

@vkaverin

Problem

class Var {
  public const A = 0x1;
  public const B = 0x2;
}

...

switch ($var) {
  case Var::A:
    return a_work();  
  case Var::B:
    return b_work();
}

Here we have a class that is basically an enum (though PHP doesn't have one). it has two variants, A and B. Also there's a switch that is supposed to cover all possible variants of Var. Now you want to add new variant C = 0x3, but you forget to add it to the switch by some reason - now you have a bug with uncovered variant.

Solution

Inspired by Rust's match operator.

Introduce new annotation @kphp-match Type $v like following:

/** @kphp-match Var $var */
switch ($var) {
  ...
}

Now the compiler will check whether all of the possible are covered by the switch and fail compilation if they are not.

Of course there are cases when only a subset of variant is need to be processed. In this case we can still use default to cover them:

class Var {
  public const A = 0x1;
  public const B = 0x2;
  public const C = 0x3;
  public const D = 0x4;
}

...

/** @kphp-match Var $var */
switch ($var) {
  case Var::A:
    return a_work();  
  case Var::B:
    return b_work();
  default:
    // We do not care about anything except but A and B.
    // Or can raise a warning, if we want to.
}

More use cases

Switch by class

Except const as enum use case, another possible case is coverage of all variants for switch (get_class($o)), because if $o has type T, compiler knows all possible subtypes (child classes) or implementation classes (if T is an interface`):

/** @kphp-match-type $o */ // Or use @kphp-match here too, depending on implementation details.
switch (get_class($o)) {
  case A::class:
    // $o can be smart-casted to A here.
    return a_work();  
  case B::class:
    return b_work();
}

Const map keys

Sometimes it may be necessary to require a map to have all possible keys:

class Mappings {
  
  /** @kphp-match Var */
  private const MAPPING = [
    Var::A => 'a',
    Var::B => 'b',
    Var::C => 'c',
    // Compile time error if there's no Var::D
  ];

  public static map(int $v): ?string {
    return self::MAPPING[$v] ?? null;
  }
}

Other languages support

Languages that support similar feature:

  • Rust
  • Kotlin
  • Java
  • Go (only linter support yet)
  • Scala (match on sealed trait)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions