Lattice options and reports

The lattice operations take two extra parameters beyond the types and the world: LatticeOptions and a &mut LatticeReport. This chapter documents both.

LatticeOptions

The configuration knobs for a single lattice query. All fields default to "strict reading"; the analyser sets them based on the file's strictness mode and any per-call overrides.

use suffete::lattice::LatticeOptions;

let opts = LatticeOptions::default();
let opts = opts.with_php_runtime_coerce(false);  // strict mode
let opts = opts.with_ignore_null(true);          // ignore null in unions
let opts = opts.with_ignore_false(true);         // ignore false in unions

The full set of options:

FieldDefaultEffect
php_runtime_coercetrueAdmit PHP runtime coercion edges (int → float, numeric-string → int/float). Records CoercionCauses::PHP_RUNTIME_COERCE on the report when fired. Set to false for strict-types mode.
ignore_nullfalseWhen checking refines(τ, σ), drop null from τ before the per-Element check. Used by analyser code that has separately verified non-nullability.
ignore_falsefalseSame for false. Used in nullable-via-false patterns.
merge_list_element_typestrueIn join, merge sealed-list element types where possible.
merge_keyed_array_paramstrueIn join, merge keyed-array key/value parameters where possible.
int_literal_collapse_threshold(small)The number of distinct int literals at which join collapses to int or a range.
string_literal_collapse_threshold(small)Same for string literals.

The with_* methods return a new LatticeOptions ; the type is Copy, so chaining is cheap.

LatticeReport

A buffer the lattice writes into during a query. It carries:

  • Coercion causes ; a bitset of which special rules fired, suitable for the analyser to surface in a diagnostic.
use suffete::lattice::{LatticeOptions, LatticeReport, CoercionCauses};

let mut report = LatticeReport::new();
// ... call lattice operations, passing &mut report ...

if report.causes.contains(CoercionCauses::PHP_RUNTIME_COERCE) {
    // The check passed but used a runtime-coercion edge.
    // Surface a warning if the analyser's policy requires.
}

if report.causes.contains(CoercionCauses::TEMPLATE_DEFAULT) {
    // The check passed but used a default-filled template parameter.
}

CoercionCauses

A bitflags type:

CauseMeaning
PHP_RUNTIME_COERCEA PHP-runtime coercion edge was used (e.g. int → float).
TEMPLATE_DEFAULTA default-filled template parameter was tolerated.
IGNORE_NULLThe query ran with null ignored.
IGNORE_FALSEThe query ran with false ignored.

The bits are non-zero when the corresponding rule contributed to the answer. The analyser reads the bitset after the query and decides what to do.

Reusing reports

LatticeReport::new() is cheap (just a zeroed bitset). For analyser hot loops, reuse one instance:

let mut report = LatticeReport::new();
for query in queries {
    report.reset();   // clear the bitset
    let _ = lattice::refines(query.a, query.b, &world, opts, &mut report);
    // ... handle the result and the report ...
}

reset clears the bitset in O(1).

FlowFlags

A 16-bit bitset that rides on every TypeId. Stored in the flags field of the handle's u64 representation.

use suffete::FlowFlags;

let f = FlowFlags::EMPTY;
let f = f.with_from_template_default(true);
let f = f.with_isset_from_loop(true);
let f = f.with_explicitly_nullable(true);

The full set of flags:

FlagMeaning
from_template_defaultThis type-arg was filled with the parameter's upper bound rather than the user's value. The variance check tolerates it (recording CoercionCauses::TEMPLATE_DEFAULT).
isset_from_loopThis value flowed through a loop body. Used by the analyser's flow-typing to track variables introduced inside a loop.
explicitly_nullableThe type was constructed as nullable via ?T syntax (rather than as a separate T | null union). Carried for diagnostic precision.

The flags do not affect lattice operations directly ; they are metadata the analyser reads out via TypeId::flags(). The exception is from_template_default, which the lattice's variance check consults.

A worked example

use suffete::{TypeBuilder, prelude::{INT, FLOAT}};
use suffete::{lattice::{self, LatticeOptions, LatticeReport, CoercionCauses}, world::NullWorld};

let int_t = TypeBuilder::new().push(INT).build();
let float_t = TypeBuilder::new().push(FLOAT).build();

let world = NullWorld;
let mut report = LatticeReport::new();

// Strict mode: int does not refine float.
let opts_strict = LatticeOptions::default().with_php_runtime_coerce(false);
assert!(!lattice::refines(int_t, float_t, &world, opts_strict, &mut report));

// Non-strict mode: int coerces.
report.reset();
let opts_loose = LatticeOptions::default();  // php_runtime_coerce = true
assert!(lattice::refines(int_t, float_t, &world, opts_loose, &mut report));
assert!(report.causes.contains(CoercionCauses::PHP_RUNTIME_COERCE));

The same int <: float query, two different opts, two different answers. The report tells the analyser why the loose answer is loose.

See also: refines, meet, join, subtract, narrow, overlaps for the operations that consume these.