A class is identified by the pair (module, short name). Two modules may each define Parser, and the importing side decides which one a bare Parser means.

All of it is resolved at compile time. The VM dispatches on class ids and knows nothing about modules, so namespacing costs nothing at run time.


Every file is a module

A file belongs to exactly one module — there is no file that belongs to none.

FileModule
declares Module name: 'X'X
a script with no declarationa module of its own, named after the file
the preloaded library (lib/Core/Core.st, lib/Collections.st)Core

Core holds the builtins and the preloaded library, and is every module's prelude. Two consequences worth knowing:

A module file is declarations only. Its top-level statements are not executed, so a module cannot run work as a side effect of being imported.


Resolution order

A bare name resolves against, in order:

  1. the classes of the current module
  2. its imports — exported names only
  3. Core, the prelude
  4. the builtins

There is no step 5. A name that is none of these fails to compile, rather than surviving to a doesNotUnderstand: somewhere far from the cause.

This includes the class an exception handler filters on. on: Errs.MyError do: with a name that does not resolve is a compile error — it used to compile to a catch-all, so a typo quietly swallowed every exception:

Module import: 'Errs'.

[ ... ] on: MyError do: [:ex | ... ].        "exported, so the short name works"
[ ... ] on: Errs.MyError do: [:ex | ... ].   "qualified works too"
[ ... ] on: Errs.NoSuchError do: [:ex | ... ].
"compile error: cannot resolve Errs.NoSuchError - module or class not found"

Declaring a module

"lib/Shapes/Shapes.st"
Module name: 'Shapes'.
Module export: #(Circle Square).

Object subclass: #Circle instanceVariableNames: 'r'.
Object subclass: #Square instanceVariableNames: 'side'.
Object subclass: #Helper instanceVariableNames: ''.   "not exported: private"

Using it from another file:

Module import: 'Shapes'.

Circle new.           "exported, so the short name is bound"
Shapes.Circle new.    "the same class, reached qualified"

Shapes.Helper new.    "compile error: cannot resolve Shapes.Helper"
Helper new.           "compile error: undefined identifier 'Helper'"

export: is enforced. A name left off the list is reachable only inside its own module — by its short name or qualified, it makes no difference.


Where a module lives

A module is located by its name, and a dot is a directory separator:

ImportFile
Module import: 'Shapes'lib/Shapes/Shapes.st
Module import: 'Maybe'lib/Maybe.st
Module import: 'Http.Router'lib/Http/Router.st
Module import: './local/Thing.st'that path

Which directories are searched, and in what order, is covered under library and plugin search paths.

A qualified name splits at its last segment, so a module name may itself be dotted: Http.Router.WebApp is the class WebApp of the module Http.Router.


One file, one module

A module is identified by the file it resolves to, canonicalized — not by the string used to import it. Importing the same file as 'Shapes' and as 'lib/Shapes/Shapes.st' names one module and compiles it once.

Its name comes from its own Module name: declaration, so a module imported by path is still reachable as Shapes.Circle.


Re-export: the facade pattern

Module export: lists a module's public names, which need not be names it defines. An exported name is resolved in the exporting module's own scope, so a module can pass on what it imported. The standard library's HTTP facade is exactly this:

"lib/Http/Http.st"
Module name: 'Http'.
Module import: 'Http.Server'.
Module import: 'Http.Router'.
Module export: #(Route WebApp HttpRequest HttpResponse HttpServer).

One import binds all five:

Module import: 'Http'.

WebApp new.                "bound through the facade"
Http.WebApp fullName.      "=> 'Http.Router.WebApp'"

Note what fullName reports: the module that defines the class, not the facade that re-exported it.


Extending an existing class

Two forms reach a class that already exists — including a builtin — instead of defining a new one:

StringStream >> myMethod                     "attach a method; no declaration needed"
Template instanceVariableNames: 'pattern'    "give it state"

The existing class is what receives the methods, so every user of that class sees them. This is how a module adds to a builtin, and it is why a builtin name is never qualified by a module: String is String everywhere.

By contrast, Object subclass: #Parser in a module whose scope already has Parser from another module is legal and defines a separate class. Shadowing is a namespace's job.


When two imports export the same name

Importing two modules that both export Thing is fine. Using the bare name is not — it fails to compile, and the error names the candidates:

Module import: 'ModA'.   "exports Thing"
Module import: 'ModB'.   "exports Thing too"

ModA.Thing new.   "fine — say which one you mean"
ModB.Thing new.   "fine"

Thing new.
"compile error: class name 'Thing' is ambiguous - imported modules export it
 as ModA.Thing and ModB.Thing. Use the qualified name."

The collision is reported where the bare name is used, not at the import, so adding an import cannot break a file that qualifies what it reaches for.

A name the current module defines is never ambiguous: step 1 of the resolution order wins outright, so Object subclass: #Thing alongside an imported Thing shadows it deliberately.


Reflection

ExpressionResult
Foo name'Circle' — the short name. Not unique
Foo fullName'Shapes.Circle'; the plain name for a builtin or a Core class
System allClassesthe qualified name of every class, exported or not
System classNamed:takes a qualified name; a short name works only when exactly one class carries it, otherwise nil

export: governs name resolution at compile time, not reflection: System allClasses lists a private class too.


Diagnostics

A method body that fails to compile fails the compile, and all such errors are reported together. A method that does not compile is not installed, so letting the build succeed would leave the program one doesNotUnderstand: away from the real cause — or passing, if nothing happened to call it.


fileIn: is not an import

Smalltalk fileIn: 'helpers.st'.

fileIn: compiles a file into the current module, as if its text were pasted in place. It is a source-inclusion mechanism, not a module import: the included classes land in the including file's module, and no export: list applies.