Skip to main content

Element Type Restrictions

Spec collections (Map, Set, Seq, Multiset) only accept spec-immutable key, value, and element types in verification mode, except opaque/token handles by identity. This page explains what types are allowed, what types are rejected, and why.

What is Spec-Immutable?

A type is spec-immutable if it contains no mutable state. The verifier checks this recursively: a record is spec-immutable only if all its fields are immutable and their types are also spec-immutable.

Spec-immutability is required because collections represent mathematical values. In mathematics, a set containing an element is well-defined. But if that element could mutate, the set's membership would become ambiguous. Restricting element types to immutable values preserves sound verification reasoning over trusted primitive collections. Opaque/token handles are admitted as sealed identities: collection equality and exact descriptor-backed snapshots prove the same handles are present, not that owner payload models are unchanged.

Allowed Types

The following types can be used as collection elements, map keys, and map values:

Primitive Types

All primitive types are spec-immutable:

ghost {
let s1 : Set<Nat> = Set.singleton(42);
let s2 : Set<Int> = Set.singleton(-10);
let s3 : Set<Bool> = Set.singleton(true);
let s4 : Set<Text> = Set.singleton("hello");
}

Primitives include: Nat, Int, Nat8-Nat64, Int8-Int64, Bool, Text, Char, Float.

Immutable Records

Records without var fields are spec-immutable:

type Point = { x : Nat; y : Nat };  // No var fields

ghost {
let s : Set<Point> = Set.singleton({ x = 0; y = 0 });
assert Set.card(s) == 1;
}

The record must have all immutable fields, and the field types themselves must be spec-immutable.

Tuples

Tuples are spec-immutable when all their elements are:

ghost {
let m : Map<(Nat, Nat), Int> = Map.empty();
let m2 = Map.update(m, (1, 2), 100);
assert Map.get(m2, (1, 2)) == 100;
}

Options

Options (?T) are spec-immutable when their inner type is:

ghost {
let s : Set<?Nat> = Set.singleton(?5);
assert Set.contains(?5, s);
}

Variants

Variant types are spec-immutable when all constructor payloads are:

type Status = { #pending; #complete : Nat };

ghost {
let s : Set<Status> = Set.singleton(#pending);
let s2 = Set.union(s, Set.singleton(#complete(42)));
}

Nested Collections

Spec collections themselves are immutable values, so they can be nested:

ghost {
let m : Map<Nat, Set<Nat>> = Map.empty();
let inner = Set.singleton(1);
let m2 = Map.update(m, 0, inner);
}

Rejected Types

The following types are rejected as collection elements in verification/Viper mode. Using them produces error M0242.

Mutable Records (var fields)

Records with var fields are rejected:

set-immutable-reject_reject.sr9
// Set element types must be spec-immutable
persistent actor {
type MutableCell = { var value : Int }; // Has var field

public func check() : async () {
ghost {
// ERROR: Mutable record cannot be Set element
let s : Set<MutableCell> = Set.empty();
};
};
}

Error message:

type error [M0242], Set element type must be immutable (field value is mutable; no var fields or mutable arrays)

The var value field makes MutableCell mutable, so it cannot be a Set element.

Mutable Arrays

Both mutable arrays ([var T]) and immutable arrays ([T]) are rejected:

seq-immutable-reject_reject.sr9
// ERROR: Seq elements must be immutable
persistent actor {
type MutArr = [var Int];

public func demo() : async () {
ghost {
// This is rejected - mutable arrays cannot be Seq elements
let s : Seq<MutArr> = Seq.empty();
assert true;
};
};
}

Error message:

type error [M0242], Seq element type must be immutable (contains a mutable array; no var fields or mutable arrays)

Arrays are rejected because they have reference semantics: two variables can point to the same array, and modifications through one affect the other. This aliasing breaks the mathematical model.

Functions

Function types cannot be collection elements:

// REJECTED
type Callback = (Nat) -> Int;
ghost {
let s : Set<Callback> = Set.empty(); // Error M0242
}

Functions are not comparable or serializable in the verification model.

Actors and Modules

Actor types are rejected:

// REJECTED
type A = actor {};
ghost {
let m : Map<A, Int> = Map.empty(); // Error M0242
}

Actors have identity-based semantics that are incompatible with mathematical collections. Module values are proof/code structure rather than comparable mathematical data, so model the specific immutable facts you need instead of placing modules in spec collections.

Async Types

Async types represent pending computations and are rejected:

// REJECTED
ghost {
let s : Set<async Nat> = Set.empty(); // Error M0242
}

Nested Mutability

The check is recursive. A record containing a mutable array is rejected even if the record itself has no var fields:

// REJECTED
type Container = { data : [var Int] }; // Contains mutable array
ghost {
let s : Set<Container> = Set.empty(); // Error M0242
}

Similarly, a record containing another record with var fields is rejected:

// REJECTED
type Inner = { var x : Int };
type Outer = { inner : Inner }; // Contains mutable record
ghost {
let s : Set<Outer> = Set.empty(); // Error M0242
}

Map Keys vs Values

For Maps, both the key type and value type must be spec-immutable, except opaque/token handle values by identity:

map-immutable-reject_reject.sr9
// REJECT: Map keys and values must be immutable types
persistent actor {
public func demo() : async () {
ghost {
// This is rejected: mutable record as Map value
type MutableBox = { var x : Nat };
let m : Map<Nat, MutableBox> = Map.empty();
};
};
}

This applies to keys:

type error [M0242], Map key type must be immutable (...; no var fields or mutable arrays)

And to values:

type error [M0242], Map value type must be immutable (...; no var fields or mutable arrays)

Why This Restriction?

Spec collections are verification-only constructs that model mathematical collections. They require immutable elements for several reasons:

  1. Equality semantics: Mathematical sets and maps compare elements by value. Mutable elements would break this: if you add an element to a set and then mutate it, is it still in the set?

  2. Value semantics: Updates produce new collection values. Mutable elements could change across versions without any collection update, breaking reasoning about snapshots.

  3. Sound verification: The SMT solver reasons about collections using mathematical axioms. These axioms assume elements do not change. Mutable elements would make the axioms unsound.

  4. Serialization: Spec collections are modeled using backend theories (Viper sequences, sets, maps). These theories require elements to be pure values.

Quick Reference

Type CategorySpec-ImmutableReason
Nat, Int, Bool, TextYesPrimitives are immutable
Nat8-Nat64, Int8-Int64YesPrimitives are immutable
Record without varYesAll fields immutable
Record with varNoContains mutable state
TupleYes (if elements are)Recursively checked
Option ?TYes (if T is)Recursively checked
VariantYes (if payloads are)Recursively checked
Array [T]NoReference semantics
Mutable array [var T]NoMutable reference
Function T -> UNoNot comparable
ActorNoIdentity semantics
Module valueNoProof/code structure, not comparable data
AsyncNoTemporal type
Nested collectionsYesCollections are values

Summary

  • Spec collection elements must be spec-immutable: no mutable state, checked recursively, except opaque/token handles by identity
  • Allowed: primitives, immutable records, tuples, options, variants, nested collections, opaque/token handles by identity
  • Rejected: records with var fields, arrays (all), functions, actors, modules, async types
  • The restriction ensures sound mathematical reasoning about collection membership
  • Error code M0242 indicates a spec-immutability violation in verification/Viper mode