E6025

E6025 — A placement naming a location the machine does not have: a memory space no declared topology holds, written either as the space or as the device that would hold it. The derivation between the two spellings has a like-named fallback, so an undeclared name resolves to a space that exists only in the placement that mentions it.

What the compiler reports

E6025
`Memory::Nowhere` in function 'as_a_parameter'
`Topology::Nowhere` in function 'as_a_device'
`Memory::Nowhere` in function 'as_a_return'
`Memory::Nowhere` in field 'absent' of struct 'Holder'
`Memory::Nowhere` in variable 'annotated'

The fragments the test suite holds the compiler to. An ellipsis marks text the test does not constrain; the emitted message also carries a source location.

A program that triggers it

// A placement has to name a space some declared topology holds.
//
// A placement carries a device and a space together and derives whichever half the source did not
// write. Both derivations fall back to the like-named other half when the declarations cannot
// answer: `Memory::Nowhere` derives `Topology::Nowhere`, and `Topology::Nowhere` derives
// `Memory::Nowhere`. The pair that comes out is internally consistent, compares equal to itself,
// and describes a machine nobody has. That is why writing a name nothing declares used to compile.
//
// Writing the device and writing the space are meant to be two ways of saying one thing, so they
// have to be refused alike -- refusing one and accepting the other would make the choice of
// spelling matter. Each is checked on the half the source wrote: a device names a place when the
// compilation declares it, a space when some declared topology holds it.
//
// Only the written half, because the derived one is not filled in yet. The derivation runs in name
// resolution, and a program that fails the type checker never gets that far, so a placement here
// still carries what the parser guessed -- for `Topology::Island` that is the like-named
// `Memory::Island` rather than the `memory:` its declaration gives. Reading the derived half would
// refuse `holds_by_device` below, which is correct code.
//
// Held is wider than owned: a space named only in a topology's `visible:` list has no single
// owning device and is still a real place, so `Vault` below is accepted through `Island`.
//
// All four positions a placed type is spellable in are covered, because each is reached by
// different code: parameters and return types come from the function table, fields from the struct
// table, and a `let` annotation from the statement walk, which no declaration table sees.
//
// The fail tier matches against the debug dump of the diagnostics rather than the rendered console
// output, so a code and its message are on separate lines and cannot be matched by one pattern.
// The code is matched bare: the dump writes `E6025,` and the console `Error[E6025]:`, and the RUN
// line above sees the second of those.
//
// Nothing declared is refused: five diagnostics, not six or seven. Counted on a pass of its own,
// because a count directive after the group above would ask for five more beyond the five matched.
// COUNT-COUNT-5: names no location
// COUNT-NOT: names no location

Memory Vault {
  capacity: 8 GB, bandwidth: 1 TB/s
}

Topology Island {
  memory: Memory::Vault,
  visible: [ Memory::Vault ],
  transfer Memory::CPU_DRAM -> Memory::Vault : 100,
}

struct Holder {
  absent : Tensor<f32, [ 4, 4 ], Memory::Nowhere>,
  present : Tensor<f32, [ 4, 4 ], Memory::Vault>,
}

fn as_a_parameter(t : Tensor<f32, [ 4, 4 ], Memory::Nowhere>) -> void {
}

fn as_a_device(t : Tensor<f32, [ 4, 4 ], Topology::Nowhere>) -> void {
}

fn as_a_return() -> Tensor<f32, [ 4, 4 ], Memory::Nowhere> {
  return Tensor<f32, [ 4, 4 ], Memory::Nowhere>::uninit();
}

// A declared space, held by a declared topology, in every position the ones above are refused in.
fn declared_is_fine(t : Tensor<f32, [ 4, 4 ], Memory::Vault>) -> Tensor<f32, [ 4, 4 ], Memory::Vault> {
  return Tensor<f32, [ 4, 4 ], Memory::Vault>::uninit();
}

// A built-in space needs no declaration: its owning device is stated in the compiler.
fn builtin_is_fine(t : Tensor<f32, [ 4, 4 ], Memory::GPU_HBM>) -> void {
}

// The same place written as the device that holds it. `Island`'s memory is `Vault`, not the
// like-named `Memory::Island`, so this is the case that fails if the check reads the derived half.
fn holds_by_device(t : Tensor<f32, [ 4, 4 ], Topology::Island>) -> void {
}

fn main() -> i32 {
  let annotated : Tensor<f32, [ 2, 2 ], Memory::Nowhere> = Tensor<f32, [ 2, 2 ], Memory::Nowhere>::uninit();
  return 0;
}

From tests/middle_end/fail/placement_names_no_place.vx, which asserts this diagnostic on every commit.

Related