Unions, Intersections, and Ranges
Whim builds larger types from smaller ones. These operators describe sets of values. They do not convert values.
Union types
A|B accepts a value that fits A or B.
type Number = int|float;
function square(Number $value): Number {
return $value * $value;
}
A union may contain any valid member except void or never. never adds no
value, so Whim rejects it as a redundant union member. mixed already contains
every value, so Whim rejects other members beside it.
Direct duplicate or covered members are also errors:
int|int
bool|true
Aliases can hide that two written members are equal. Whim still gives the union the right runtime meaning.
Intersection types
A&B accepts a value that fits both A and B.
interface Named {}
interface Stored {}
function save(Named&Stored $value): void {}
An intersection can combine class and interface contracts or refine any type:
type NonEmptyString = string&!'';
type SmallPositiveInt = int&1..=100;
mixed adds no rule to an intersection, so Whim rejects it there. A direct
duplicate member is also an error.
An impossible intersection is a valid empty type. For example,
int&SomeInterface has no value unless the two parts can overlap.
Negated types
!T accepts every value outside T.
function require_value(!null $value): !null {
return $value;
}
Negation works inside collections, callable types, bounds, catch clauses, and other composed types.
$values = vec[1, 'text'];
assert!($values is vec<!bool>);
Useful identities include:
!neveraccepts every value;!mixedaccepts no value;!!Thas the same values asT.
Whim does not allow negation of void or the wildcard _.
Precedence
Prefix ! and = bind first. & binds before |.
A&B|C means (A&B)|C
A|B&C means A|(B&C)
!(A|B) excludes the whole union
Use parentheses when they make the type easier to read.
Literal types
An integer, float, string, boolean, null, enum case, or constant can describe
one value.
type Answer = 'yes'|'no';
type Ordering = -1|0|1;
function enabled(true $value): void {}
Integer and float literal types remain distinct. 1 does not contain 1.0.
Integer range types
Range types accept integer intervals.
| Type | Accepted values |
|---|---|
1..10 | 1 through 9 |
1..=10 | 1 through 10 |
0.. | zero and all larger integers |
..0 | all integers below zero |
..=0 | zero and all smaller integers |
The lower bound is inclusive. .. excludes the upper bound, while ..=
includes it.
type Port = 1..=65535;
type Offset = 0..;
function connect(Port $port): void {}
Both bounds may be negative. A reversed or equal exclusive range is empty.
1..=1 contains only 1.
Ranges work as generic bounds and type arguments:
function clamp_input<T: 0..=100>(T $value): T {
return $value;
}
assert!(clamp_input::<25>(25) == 25);
never
never contains no values. A function that returns never must throw, exit,
or keep running.
function fail(string $message): never {
throw new Whim\Unwind\RuntimeException($message);
}
never may appear in parameters and generic types. Since no caller can supply
a value of that type, it helps express branches that cannot run. A method that
returns never can satisfy a contract with any return type.
void
void is only a return type. A void function returns no value.
void cannot appear in a parameter, property, union, negation, type argument,
or type alias.