Name resolution and scoping

Every name a program writes is resolved statically, from the text around it. Resolution asks one question of each occurrence — which declaration does it name? — and there are only three answers: exactly one, which is the ordinary case; none, which is an unresolved name; or more than one, which is an ambiguous name. The last two are both errors: an unresolved name has to be declared or imported, an ambiguous one has to be spelled so that it picks.

Nothing in the answer depends on a value, so a reader has exactly the information the compiler does: the file, and the modules it imports.

Namespaces

A program has six namespaces, and a name in one never collides with a name in another:

NamespaceHoldsWritten
valuestop-level bindings, class members, parameters, pattern bindingslowercase-initial
typestype declarations and class declarationsuppercase-initial, in a type expression, a constraint, or the head of a class or an instance
constructorsthe variants of a type declarationuppercase-initial, in an expression or a pattern
operatorsinfix declarationsoperator characters, never letters
modulesthe prefix of a qualified nameuppercase-initial, before the final .
labelsthe fields of a record typelowercase-initial, before a :, an = or after a .

Which namespace an occurrence is looked up in is decided by how it is written and where it sits, never by what happens to be in scope. So one spelling can be several unrelated things at once, and each occurrence still has exactly one meaning:

expect=ok
module Example exposing (Reading, Celsius, boiling)

type Reading
  = Celsius

type Celsius
  = Absolute

boiling : Reading
boiling =
  Celsius

Celsius is both a constructor of Reading and the name of a second type. The annotation boiling : Reading names a type and the body Celsius names a constructor.

The one lowercase name that is not a value is a type variable, which is not resolved against a namespace at all: it is bound by the declaration it appears in.

A label is the one namespace with no declaration behind it. What puts a label in it is any record type mentioning that label, so two record types may both carry x without that being a clash, and which record a label belongs to is decided by the type it is read against rather than by what is in scope. Nothing below therefore reaches a label: it is not imported, not exposed, not shadowed, and never ambiguous. Records is the rest of it.

Module names are separate for the same reason. A module name is only ever read to the left of a ., so a constructor named Widget and an imported module named Widget coexist:

expect=okpackage=prefix
module Widget exposing (Size, label)

type Size
  = Small

label : Size
label =
  Small
expect=okpackage=prefix
module Main exposing (Named, here, there)

import Widget

type Named
  = Widget

here : Named
here =
  Widget

there : Widget.Size
there =
  Widget.label

A module name may contain dots and is still one name, so the module part of a qualified name is everything before its final . and the local part is what follows. Ui.Widget.label names label in Ui.Widget; there is no module Ui in it to look anything up in.

Scopes

A name is resolved in the scopes enclosing the place it is written, innermost first. There are two kinds, and no more:

  1. A binding position — a declaration's parameters, or a case branch's pattern. Each binds the variables of its patterns for the extent of one body, and these nest: a case inside a case branch is inside that branch's scope.
  2. The module's top level — everything the module declares, plus every name its imports bring in unqualified.

There is no scope outside the module: a name is in scope because this module declares it or imports it, and being declared in a neighbouring module — or in a dependency — is not being in scope. The default imports are the one thing a module gets without writing anything, and they are imports like any other.

Not implemented: let … in (docs/tickets/lang-33.md) and lambdas (docs/tickets/lang-34.md) each add a binding position of the same kind as the two above: bindings visible in one body, invisible outside it. Neither construct exists yet.

An inner binding shadows an outer name of the same spelling — Patterns carries that rule and its consequences — and it applies to imported names exactly as it does to a module's own declarations:

expect=okpackage=shadow
module Widget exposing (Size(..), label)

type Size
  = Small

label : Size
label =
  Small
expect=okpackage=shadow
module Main exposing (f)

import Widget exposing (Size(..), label)

f : Size -> Size
f label =
  case label of
    Small ->
      Widget.label

A qualified name cannot be shadowed

Widget.label in that body still reaches the imported value, even though label alone means the parameter. A binding position binds bare names — a pattern has no way to write a dotted one — so nothing an inner scope does can change what a qualified name means. It is the fix for a shadowed name and for an ambiguous one, and it works without knowing what else is in scope.

A module has no prefix for itself

A qualifier names an import, and a module does not import itself. Inside module Example, Example.first names nothing:

expect=canonical-error:VariableNotFound
module Example exposing (Size, first, second)

type Size
  = Small

first : Size
first =
  Small

second : Size
second =
  Example.first

A module's own declarations are in scope under their bare names throughout it, so the prefix would say nothing the bare name does not — and reading it as "this module" would make one prefix mean something different in every file it is written in.

Which prefix an import contributes is Modules' subject: the module's name as written, or its alias, which replaces that name rather than adding to it.

A top-level name comes from exactly one place

What a module declares and what it imports unqualified are one scope, not two, so a module that declares label and also imports label unqualified is rejected. Neither shadows the other. The fix is to pick one — drop the entry from the exposing list and write Widget.label where the imported value is wanted, or rename the declaration.

Known gap: the module's own declaration silently wins today, for values and for types alike. Both blocks below are accepted, and in each of them the imported name is dead without anything saying so (docs/tickets/lang-29.md).

expect=okpackage=clash
module Widget exposing (Size(..), label)

type Size
  = Small

label : Size
label =
  Small
expect=okpackage=clash
module Main exposing (x)

import Widget exposing (Size, label)

type Size
  = Big

label : Size
label =
  Big

x : Size
x =
  label

The rule holds for exposing (..), which collides the same way a named entry does; the error is reported on the import line, there being no entry to point at. So a module gaining an export can break a file that imports it openly.

expect=okpackage=clash
module Other exposing (y)

import Widget exposing (..)

label : Size
label =
  Small

y : Size
y =
  label

A name is declared at most once

Within one module, each type is declared once and each operator carries one infix declaration: two declarations of one name introduce two things with one name, and no rule chooses between them.

Repeated value bindings are not an exception. They are not two declarations but the clauses of one, which is why they must agree on their parameter count.

Known gap: a second type declaration of a name silently replaces the first, taking its constructors with it — after the block below the module has one type Size, built by Big, and Small no longer resolves anywhere (docs/tickets/lang-32.md).

expect=ok
module Example exposing (Size)

type Size
  = Small

type Size
  = Big

Unresolved names

A name that no scope binds is an error where it is written, with the caret under the name rather than under the declaration containing it. A bare name that nothing declares or imports:

expect=canonical-error:VariableNotFound
module Example exposing (Size, x)

type Size
  = Small

x : Size
x =
  missing

A constructor is looked up in its own namespace, and an uppercase name that names no variant fails the same way:

expect=canonical-error:VariantNotFound
module Example exposing (Size, x)

type Size
  = Small

x : Size
x =
  Large

A qualified name resolves only if its prefix names an import and that module exposes the name. A prefix that names no import is not a hint to go looking for the module.

expect=canonical-error:VariableNotFound
module Example exposing (Size, x)

type Size
  = Small

x : Size
x =
  Widget.label

Known gap: the diagnostic there reports a missing value called Widget.label. What is missing is the import, and the message never says the word (docs/tickets/err-14.md).

Widget below is compiled alongside Main, and Main still cannot see it:

expect=okpackage=notimported
module Widget exposing (Size, label)

type Size
  = Small

label : Size
label =
  Small
expect=canonical-error:VariableNotFoundpackage=notimported
module Main exposing (Size, x)

type Size
  = Small

x : Size
x =
  Widget.label

Ambiguous rather than unresolved

A name brought into scope unqualified by two different imports is not an error at either import line. It becomes one at each place the bare name is written, and only there. The report names both candidates, and the qualified spelling of either is the fix.

expect=okpackage=ambiguous
module Widget exposing (Size(..), label, one, add, (+))

type Size
  = Small

label : Size
label =
  Small

one : Size
one =
  Small

add : Size -> Size -> Size
add a b =
  a

infix left 6 (+) = add
expect=okpackage=ambiguous
module Gadget exposing (Size(..), label, two, mul, (+))

type Size
  = Small

label : Size
label =
  Small

two : Size
two =
  Small

mul : Size -> Size -> Size
mul a b =
  a

infix left 6 (+) = mul
expect=canonical-error:AmbiguousVariablespackage=ambiguous
module Main exposing (x)

import Widget exposing (label)
import Gadget exposing (label)

x : Widget.Size
x =
  label

The same rule holds in every namespace: two imports, one spelling, no reason to prefer either. Widget.Size and Widget.Small are the fixes for a type and for a constructor.

Known gap: ambiguity is detected for values only. A type, a constructor or an operator arriving from two imports is taken from whichever import line is written last, silently, so the meaning of the next two blocks depends on the order of two lines that look interchangeable (docs/tickets/lang-30.md).

expect=okpackage=ambiguous
module Other exposing (y)

import Widget exposing (Size(..))
import Gadget exposing (Size(..))

y : Size
y =
  Small

An operator is the one case with no qualified spelling to fall back on — Widget.(+) is not writable — so the fix there is an edit to an import line: name what the file wants. Here the last import wins +, so + is Gadget's while one is Widget's, and z is a type error.

expect=type-error:UnificationFailedpackage=ambiguous
module Third exposing (z)

import Widget exposing (..)
import Gadget exposing (..)

z : Widget.Size
z =
  one + one

Narrowing the Gadget import to what the file uses from it leaves Widget the only module exposing +, and z compiles:

expect=okpackage=ambiguous
module Fourth exposing (z)

import Widget exposing (..)
import Gadget exposing (two)

z : Widget.Size
z =
  one + one

Order never decides

Once ambiguity is an error, nothing about resolution depends on the order things are written in. Declarations are unordered, and so are imports: reordering either never changes what a name in the file means.

Type variables

A type variable is bound by the declaration it appears in, and by nothing outside it. No lowercase spelling is privileged: a, number and comparable are the same kind of name.

In an annotation, every lowercase name is a variable of that annotation. Two annotations that happen to use the same letter share nothing:

expect=ok
module Example exposing (Box, first, second)

type Box a
  = Box a

first : a -> a
first x =
  x

second : a -> Box a
second x =
  Box x

In a type declaration, the parameters between the type's name and the = are the binders, and they are in scope throughout the variants. A variant may use those and no others: a variable the declaration does not bind has no value to stand for, since there is no argument position that would ever fix it.

Known gap: an unbound variable in a variant is accepted, and Box below is a type whose constructor takes an argument of a type nothing can name (docs/tickets/lang-31.md).

expect=ok
module Example exposing (Box)

type Box
  = Box a