Expand description

As explained in super::usefulness, values and patterns are made from constructors applied to fields. This file defines a Constructor enum, a Fields struct, and various operations to manipulate them and convert them from/to patterns.

There are two important bits of core logic in this file: constructor inclusion and constructor splitting. Constructor inclusion, i.e. whether a constructor is included in/covered by another, is straightforward and defined in Constructor::is_covered_by.

Constructor splitting is mentioned in super::usefulness but not detailed. We describe it precisely here.

Constructor grouping and splitting

As explained in the corresponding section in super::usefulness, to make usefulness tractable we need to group together constructors that have the same effect when they are used to specialize the matrix.

Example:

ⓘ
match (0, false) {
    (0 ..=100, true) => {}
    (50..=150, false) => {}
    (0 ..=200, _) => {}
}

In this example we can restrict specialization to 5 cases: 0..50, 50..=100, 101..=150, 151..=200 and 200...

In super::usefulness, we had said that specialize only takes value-only constructors. We now relax this restriction: we allow specialize to take constructors like 0..50 as long as we’re careful to only do that with constructors that make sense. For example, specialize(0..50, (0..=100, true)) is sensible, but specialize(50..=200, (0..=100, true)) is not.

Constructor splitting looks at the constructors in the first column of the matrix and constructs such a sensible set of constructors. Formally, we want to find a smallest disjoint set of constructors:

  • Whose union covers the whole type, and
  • That have no non-trivial intersection with any of the constructors in the column (i.e. they’re each either disjoint with or covered by any given column constructor).

We compute this in two steps: first ConstructorSet::for_ty determines the set of all possible constructors for the type. Then ConstructorSet::split looks at the column of constructors and splits the set into groups accordingly. The precise invariants of ConstructorSet::split is described in SplitConstructorSet.

Constructor splitting has two interesting special cases: integer range splitting (see IntRange::split) and slice splitting (see Slice::split).

The Missing constructor

We detail a special case of constructor splitting that is a bit subtle. Take the following:

enum Direction { North, South, East, West }
match wind {
    (Direction::North, 50..) => {}
    (_, _) => {}
}

Here we expect constructor splitting to output two cases: North, and “everything else”. This “everything else” is represented by Constructor::Missing. Unlike other constructors, it’s a bit contextual: to know the exact list of constructors it represents we have to look at the column. In practice however we don’t need to, because by construction it only matches rows that have wildcards. This is how this constructor is special: the only constructor that covers it is Wildcard.

The only place where we care about which constructors Missing represents is in diagnostics (see super::usefulness::WitnessMatrix::apply_constructor).

We choose whether to specialize with Missing in super::usefulness::compute_exhaustiveness_and_reachability.

Opaque patterns

Some patterns, such as constants that are not allowed to be matched structurally, cannot be inspected, which we handle with Constructor::Opaque. Since we know nothing of these patterns, we assume they never cover each other. In order to respect the invariants of SplitConstructorSet, we give each Opaque constructor a unique id so we can recognize it.

Structs

  • Values and patterns can be represented as a constructor applied to some fields. This represents a pattern in this form. This also uses interior mutability to keep track of whether the pattern has been found reachable during analysis. For this reason they cannot be cloned. A DeconstructedPat will almost always come from user input; the only exception are some Wildcards introduced during specialization.
  • Fields 🔒
    A value can be decomposed into a constructor applied to some fields. This struct represents those fields, generalized to allow patterns in each field. See also Constructor.
  • IntRange 🔒
    An exclusive interval, used for precise integer exhaustiveness checking. IntRanges always store a contiguous range.
  • OpaqueId 🔒
    A globally unique id to distinguish Opaque patterns.
  • Slice 🔒
    A constructor for array and slice patterns.
  • Describes the result of analyzing the constructors in a column of a match.
  • WitnessPat 🔒
    Same idea as DeconstructedPat, except this is a fictitious pattern built up for diagnostics purposes. As such they don’t use interning and can be cloned.

Enums

  • A value can be decomposed into a constructor applied to some fields. This struct represents the constructor. See also Fields.
  • Describes the set of all constructors for a type.
  • A possibly infinite integer. Values are encoded such that the ordering on u128 matches the natural order on the original type. For example, -128i8 is encoded as 0 and 127i8 as 255. See signed_bias for details.
  • Presence 🔒
    Whether we have seen a constructor in the column or not.
  • SliceKind 🔒

Functions

  • Recursively expand this pattern into its subpatterns. Only useful for or-patterns.