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:
| Namespace | Holds | Written |
|---|---|---|
| values | top-level bindings, class members, parameters, pattern bindings | lowercase-initial |
| types | type declarations and class declarations | uppercase-initial, in a type expression, a constraint, or the head of a class or an instance |
| constructors | the variants of a type declaration | uppercase-initial, in an expression or a pattern |
| operators | infix declarations | operator characters, never letters |
| modules | the prefix of a qualified name | uppercase-initial, before the final . |
| labels | the fields of a record type | lowercase-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:
module Example exposing (Reading, Celsius, boiling)
type Reading
= Celsius
type Celsius
= Absolute
boiling : Reading
boiling =
CelsiusCelsius 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:
module Widget exposing (Size, label)
type Size
= Small
label : Size
label =
Smallmodule Main exposing (Named, here, there)
import Widget
type Named
= Widget
here : Named
here =
Widget
there : Widget.Size
there =
Widget.labelA 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:
- A binding position — a declaration's parameters, or a
casebranch's pattern. Each binds the variables of its patterns for the extent of one body, and these nest: acaseinside acasebranch is inside that branch's scope. - 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:
module Widget exposing (Size(..), label)
type Size
= Small
label : Size
label =
Smallmodule Main exposing (f)
import Widget exposing (Size(..), label)
f : Size -> Size
f label =
case label of
Small ->
Widget.labelA 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:
module Example exposing (Size, first, second)
type Size
= Small
first : Size
first =
Small
second : Size
second =
Example.firstA 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).
module Widget exposing (Size(..), label)
type Size
= Small
label : Size
label =
Smallmodule Main exposing (x)
import Widget exposing (Size, label)
type Size
= Big
label : Size
label =
Big
x : Size
x =
labelThe 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.
module Other exposing (y)
import Widget exposing (..)
label : Size
label =
Small
y : Size
y =
labelA 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).
module Example exposing (Size)
type Size
= Small
type Size
= BigUnresolved 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:
module Example exposing (Size, x)
type Size
= Small
x : Size
x =
missingA constructor is looked up in its own namespace, and an uppercase name that names no variant fails the same way:
module Example exposing (Size, x)
type Size
= Small
x : Size
x =
LargeA 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.
module Example exposing (Size, x)
type Size
= Small
x : Size
x =
Widget.labelKnown 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:
module Widget exposing (Size, label)
type Size
= Small
label : Size
label =
Smallmodule Main exposing (Size, x)
type Size
= Small
x : Size
x =
Widget.labelAmbiguous 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.
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 (+) = addmodule 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 (+) = mulmodule Main exposing (x)
import Widget exposing (label)
import Gadget exposing (label)
x : Widget.Size
x =
labelThe 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).
module Other exposing (y)
import Widget exposing (Size(..))
import Gadget exposing (Size(..))
y : Size
y =
SmallAn 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.
module Third exposing (z)
import Widget exposing (..)
import Gadget exposing (..)
z : Widget.Size
z =
one + oneNarrowing the Gadget import to what the file uses from it leaves Widget the only module
exposing +, and z compiles:
module Fourth exposing (z)
import Widget exposing (..)
import Gadget exposing (two)
z : Widget.Size
z =
one + oneOrder 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:
module Example exposing (Box, first, second)
type Box a
= Box a
first : a -> a
first x =
x
second : a -> Box a
second x =
Box xIn 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).
module Example exposing (Box)
type Box
= Box a