cardinality

Assumptions and limits

Every number rests on two modelling assumptions. The calculator does not check that the code obeys them; a count describes the pure, total implementations of a signature and the values of a type.

The code has no side effects

Treat a var as a val. Reassignment is not modelled, so a var holds the value it was given: its initializer, or the argument passed for a var constructor parameter. A var field adds exactly what a val field of the same type would, and a captured var is an ordinary capture. Scala's own typing rules still apply: a var is not a stable path, so p.type through one does not typecheck.

Ignore throwing. A throw, or a call that may throw, is neither a value nor an implementation, so it never adds to a count. def make[A]: A has 0 implementations even though throw new Exception would compile. Exceptions used as data are ordinary values.

Exclude every other effect the same way: I/O, logging, divergence, a null the type does not declare, unsafe casts, reflection and unrestricted runtime type or equality tests. The members of Product and Equals (productElement, canEqual, …) are reflection and equality tests, so a trait extending Product with Serializable counts as one without those parents.

Further idealizations:

The sources are the whole program

The supplied sources, with the supplied dependency declarations, are the whole program. No subclass, override or implementation exists outside them.

Inline methods

An inline def counts like a def unless its body uses a compile-time operation: inline if, inline match, anything from scala.compiletime (summonFrom, summonInline, erasedValue, constValue, codeOf, …), quotes or splices. That includes an inline method it calls. A transparent result is ? unless its declared type is a singleton, because callers see the type of the expansion.

An implicit conversion or extension is a candidate only where the body may find it: in lexical scope, or as a member of an object in the implicit scope of a type the body can reach. A conversion that is inline and whose own expansion uses a compile-time operation makes the body ?. So do a type test or classOf[Array[A]] that reifies a type parameter, a given left to implicit search, and a member a supplied subclass overrides with an inline method whose expansion fails.

Opaque types

Outside its scope, an opaque type counts 1 on both sides: it reads as Unit, and as a type argument it keeps its own nominal identity. Inside its scope the representation is read as before. A count is never refused because a representation is hidden.

Constructors and factories

A receiver's fields hold what its public constructors build: the largest space any one of them builds, not the sum. A constructor counts where the measured declaration may call it. A class with no callable constructor is counted by its public factories, the methods and vals callable there whose result is a direct construction of it, read through at most two calls. With no factory either, it stays ? with receiver has no public constructor.

Approximations

The full contract is v1.md §2 and AGENTS.md. The evidence for each rule is in the research register.