Modules and packages
Partly written. §1 was written with milestone M3 of the Lyric 5 plan (slice S6), §2 to §4 with M7 (slices S1, S2a–S2c, S3a, S3b), §5 with the review of M8a (R3a); the rest follows with M7, spec-first: each rule lands here with its conformance case before or with its implementation. Source of the decisions: 07 Modules.
Scope
Module names from paths, visibility (private, internal, pub), imports, the prelude, editions, packages and their resolution; module-level bindings.
1. Module-level bindings
A let or a var at the top of a module
(07 V5).
Conformance. conformance/cases/04-modules/.
- A module has no body: it holds declarations, and importing it runs nothing (G1). A
module-level
letorvaris a declaration with an initializer. - The module's bindings are filled eagerly, before the entry runs, in declaration order
within the module (G2): a later binding may read an earlier one, and the initializer is an
ordinary expression — a call, an object, an array. A
static leton a type that is generic in nothing is the same mechanism under the type's name (rule 6 for a generic type's). Across modules a module's bindings are filled before those of every module that imports it (G2) — imports form no cycle (§2,LYR-RES0005) —, so an initializer may read what a module it imports declares. A binding read before it is filled — a later one of its own module — is refused (LYR-SEM0057). - A module-level
varis written from any function that sees it, as a local is; a module-levelletand astatic letare not (LYR-SEM0019) (G5). What aletholds may still be written where it is a place: the fields of an object it references, the elements of an array (03 §2.2). - A module-level binding is a root: what it references lives as long as the binding does (01 L1).
- An initializer does not throw (G3) — with the errors (06) — and there is no
init()hook (G6).Lazy<T>is a library (12). - A constant of a generic type — a
static letin the body of a generic struct, class or enum, or in a generic block (05 §6 rule 2) — is one per instance (G2; the review's M8a-3):Crate<int>.emptyandCrate<string>.emptyare two constants, each with its instance's arguments for the type's parameters,static let empty: ?T = null;. It is read on an instance (LYR-SEM0063on the bare name of the type); inside its type also by its bare name, the constant of the instance the code runs for; and through a constraint, where it answers an interface's (05 §7 rule 2). Its value is a constant: a literal,null, an empty literal[], another constant —T.zerounder a constraint, a unit variant —, a struct initializer of these. Nothing runs for it before the entry, and such constants have no order: one may name another that stands later, and none names itself. Anything else — a call, an operation, an object of a class — is refused (LYR-SEM0169): a value that is computed is astatic fn. (Richer constant expressions come withcomptime.)
2. Module names
A module is a file, and its name comes from one source: its path (07 V1). A package gathers modules under one name (15 §1).
Conformance. conformance/cases/04-modules/ — a rule about a package as a package case, a
directory (the format); what only the command line shows, in the
toolchain's own tests (tests/Lyric5.Tests, PackageTests).
- A package is a directory with a manifest,
lyric.toml; its modules are the.lyrfiles under itssrc/. A module's path is its file's path belowsrc/without.lyr, with the package's name in front:src/net/http.lyrin the packageappisapp.net.http(M1). The package's own modules write the path the same way — there is no second, package-relative spelling.
1a. One file is named apart: src/lib.lyr is the package's root module, and its path is the
package's name alone — import geo { Circle } (the review's M7-1). There is no module
geo.lib (LYR-RES0003), and a directory src/lib/ is a namespace like any other (rule 3).
The package's own modules import the root module the same way, and it may stand beside
src/main.lyr.
2. A file names no module: a module header with a path is refused (LYR-RES0008), and the
name is the path's alone (M2). module; without a path names nothing; it carries the module's
attributes (11 §3 rule 4).
3. A directory is a namespace, not a module: src/net.lyr beside src/net/ is the module
app.net, one of its own, and a directory without such a file is no module to import
(LYR-RES0003) (M5).
4. A module path names its file exactly, case included, on every platform: import app.util
does not find src/Util.lyr (LYR-RES0003), also where the file system ignores case (M6).
5. A file is one module: a module that several others import is loaded once, and its
bindings exist once (M4). Imports form no cycle (LYR-RES0005).
6. A single file outside every package's src/ is a package of its own, named after the
file; it imports std and nothing else (LYR-RES0003)
(11 C8).
7. A program starts at the main of its entry module — a package's src/main.lyr
(15 §1), or the module the command line names (14 §1) (M7a).
That main is fn main(): void or fn main(): int and may declare throws
(06); it takes no parameters (M7b) — the program's arguments are the
standard library's (12). An entry module without main, or with one of
another form, is refused (LYR-SEM0021).
8. A main in any other module is an ordinary function: a module may be a library and a
program at once (M7a).
9. A declaration is its module's: two modules may each declare one name — app.a.Cat and
app.b.Cat — and the two are two declarations. Two such types stay two everywhere, also as
the type arguments of one generic function, block or type: first([a.Cat { … }]) and
first([b.Cat { … }]) call two instances, each with its own type's fields (M1).
3. Visibility
Who may name a declaration and a member (07 V2).
Conformance. conformance/cases/04-modules/; the routes one by one in the toolchain's own
tests (tests/Lyric5.Tests, VisibilityTests).
- A declaration carries at most one visibility word, ahead of every other:
pub fn,internal struct,private let,pub static fn,pub mut fn(08 D3). A second word is refused (LYR-PAR0053), and so is a word before an enum's variant, which is as visible as its enum. private— the declaring module alone names it;internal— every module of the declaring package;pub— every module of every package: it is exported. A declaration without a word isinternal. The standard library is a package like any: what it does not markpubis its own.- Visibility is asked at every route a name takes out of its module, by one rule: a
selective import; a name qualified by a module in an expression (
util.helper(),util.LIMIT); a type named through a module in a signature, a field, a constraint, a conformance list, anextendtarget or a body; an initializer (util.Point { … }); a pattern; an interface-qualified call (util.Walker.walk(x)). A name that may not be named where it stands is refused (LYR-RES0009) (S1). Of an overload set, the functions that may not be named are not there. - An import is its module's own:
import std.io { println }inutilbringsprintlnintoutil, and no other module reaches it asutil.println(LYR-RES0009) (I4). - A module itself is visible to every module (B1): what it offers is what its declarations' words allow.
- A member takes its word as a declaration does (S0): a field, a method, a
staticfunction orlet; without a word it isinternal. It is named — read, written, called, destructured in a pattern — where its word allows (LYR-RES0009), whatever route reached its type. Of a member's overload set, what may not be named is not there. - A type with a field a module may not name is built in no such module: its initializer
is refused there, and so is a copy with
with(LYR-RES0009) — a factory builds it (04 D11, 02 W3). - An enum's variants are as visible as the enum. An interface's members — what it
requires and its defaults — are as visible as the interface; a word on one is refused
(
LYR-SEM0118) (S4). The one exception isprivate fnwith a body, a helper: the interface's defaults call it, on any value of the interface, and nothing else does (LYR-RES0009) — a child interface's defaults no more than other code; no conformer answers it, and a conformer's method of its name is the conformer's own, which the defaults do not call. Aprivatemember without a body is refused (LYR-SEM0118). - An
extendblock's word is the default of the methods in it:private extend Point { … }keeps them to its module; a method's own word wins. A method of a block that a module may not name is not there for it (S5).
9a. A block of methods reaches a module that imports the block's module — directly, or
through a chain of pub imports that pass the module on as they pass on names (§4 rule 2):
pub import lib.error { Thing } passes on lib.error's blocks with Thing — and every
module where the prelude names its module (§4 rule 5); where it does not reach, its methods
are not there. Where blocks of two modules both reach a call and add
its name at a count the call takes, the call is refused (LYR-SEM0235): taking one would
follow the order of the imports
(07 S5′).
10. A conformance — extend Point :: [Shape] { … } — is visible wherever its type is, its
methods with it, whether or not the module that holds the block is imported: the conformance
is the program's (05 §13 rule 4), so a type that comes through a
re-export, a return value or a field brings every conformance its modules give it, the
synthesized ones (05 §14 rule 9) among them, and std.codec's blocks on
std.time's types reach a program that names neither module (the sweep after M10, W7). A
word on the block or on one of its methods is refused (LYR-SEM0150) (S5).
10a. A member that answers an interface for its type from outside a conformance block — a
method, a static function or a constant in the type's body, or in a block of rule 9 — is
written at least as visible as the conformance, the narrower of its type's word and its
interface's (LYR-SEM0167; the review's M7-2): in pub struct Sq :: [Shape] with a pub
interface Shape it is pub fn area(); where the type or the interface is internal, no
word is enough, and private is not. A conformance cannot be narrowed (rule 10), so a member
written narrower would be called through the interface by code its own name refuses.
11. A declaration is no more visible than the types it names (S2): a function's parameters,
result, thrown types and constraints, a field's type, an alias's, a variant's payload, a
module binding's or a static let's type — written or inferred —, a type's constraints and an
interface's parents. A pub fn whose result is an internal type, an internal fn taking a
private one, is refused (LYR-SEM0151): another module could reach it and not name what
it hands out. A member counts as its own word narrowed to its type's.
12. A member whose written word is wider than its type's — pub x in an internal struct — is
allowed and warned (LYR-SEM0152): it is exported only once the type is (S3). A member
without a word follows its type and is not warned. The same holds in a block of rule 9: for
a member's own word, and for the block's — said once, at the block, for its members.
4. Imports
How a module names what other modules declare (07 V3).
Conformance. conformance/cases/04-modules/; the forms one by one in the toolchain's own
tests (tests/Lyric5.Tests, ImportTests).
- An import takes one of three forms (I1): selective,
import app.util { answer, Point }, binds the names it lists; qualified,import app.util;, binds the module under its last segment —util.answer(); aliased,import app.util as u;, under another name. A selective item may be renamed:{ answer as reply }bindsreply, andansweris not bound (I2). There is no import of everything (I3). 1a. The forms of one module stand side by side (I6):import app.util;besideimport app.util { answer }and{ answer as reply }. An import is written once: a name bound again to what it already names — a selective name listed twice, in one list or in two imports, a qualified or an aliased import repeated — is refused (LYR-RES0001). pub importpasses on what it binds (I4):pub import app.util { answer };inapp.facademakesanswerapubmember ofapp.facade— imported from there, named asfacade.answer, under the name it was renamed to; a module imported qualified or aliased is passed on as a namespace (facade.util.answer()). It passes on what ispuband nothing narrower (LYR-RES0010). No other word stands beforeimport(LYR-PAR0053); withoutpub, an import is its module's own (§3 rule 4). A package's root module (§2 rule 1a) is where a library passes on what its modules declare:pub import geo.shapes { rect };insrc/lib.lyrmakesimport geo { rect }of it.- An import a module never uses is warned, in every form (
LYR-SEM0072) (I5): a qualified import is used when its module is named, or a method of a block it brings (§3 rule 9a) called. Apub importis used by being passed on. A selective import is used by its names; one of which no name is used is warned even where it brings a block the file calls. The warning's change leaves the program compiling: where a method the file calls reaches it through this import alone — no other import that stays, not the prelude — the change keeps the module (import std.io;), and elsewhere takes the import out. Of two imports that bring the same blocks, one stays. - The prelude, the module
std.prelude, is named in every module without an import (I8; 10 B2):panic,assert,unreachable,todo,same,sequence;Error,Exception,Result,Box,Ordering,Range,RangeInclusive,RangeFrom,RangeTo,RangeFull;Equatable,Hashable,Ordered,TotalOrder,Display,Debug,Default,Clone,VariantName,Variants,Closeable,Num,Integer,Float,Iterator,Iterable,FromIterator,Into,From,Index,IndexSet,Length;List,Map,Set, the collections' vocabulary — what signatures and the language need, not effects:printlnisstd.io's. The rest of the standard library is imported,std.core's operator interfaces (Add…) andAnyamong it. The prelude grows with the library (12). - A name a module declares or imports hides the prelude's of that name in the module, and is
warned (
LYR-SEM0153) (K6). An import of the very symbol the prelude passes on hides nothing. A block of a module the prelude takes names from stands in every module, asstd.core's do:Map<K, V>.new()is reached whereverMapis — and the modules the prelude imports without a name:std.iter, whose adapters every iterator has everywhere, andstd.string, whose operations every text has (12). - A builtin type's name —
int,string,bool… — is no declaration's (LYR-RES0011) (K5). - A name a module declares or imports is not also one of its submodules:
app.netdeclaringhttpbeside the moduleapp.net.httpis refused (LYR-RES0012) (K1, K3). The question is asked of the package's files, not of what a program imports: a submodule nobody imported is one all the same. The root module's names stand beside every module of its package —geodeclaringshapesbesidesrc/shapes.lyr— but not beside its own file: there is no modulegeo.lib. An import that binds a module under its own name —pub import geo.shapes;ingeo— is that module, and one meaning.stdis the standard library's name: a single filestd.lyris refused (LYR-RES0013) (D1). - What a failed lookup could not see is said in a note of its error (M7-3, M7-11, K4, K7):
an import of a module that does not exist (
LYR-RES0003) suggests the module path a typo away, as a change that needs a reader (16 §6); a member a local does not have (LYR-SEM0012), where the local hides a module the file imports whole or a type of the prelude that has the member, says that the name is the local here and what it hides — hiding is legal and silent until then (rule 5 warns of a declaration or an import, never of a local); a method a block adds to the type — a block on the type, on a shape, or a blanket block whose constraints the type satisfies — in a module this one cannot see — not imported, or kept to its module — names it, its module and why. A module the program does not hold has no blocks to name.
5. A type's members by their bare names
What a bare name means between the braces of a type (07 V6, the review's M6-30).
Conformance. conformance/cases/04-modules/.
- The body of a struct, a class, an enum or an interface is a scope inside its module's: in
the bodies of its methods a member's name hides a module's, an import's and the prelude's of
that name, and a local or a parameter hides the member, whole. An
extendblock is such a scope for the members it declares itself; the members of the type's body are not named bare there. - A static member is reached by its bare name there —
of(2),step— as it is through the type. - A member of an instance — a field, a method that is not
static— is reached through its receiver:this.label,this.size(). Its bare name is refused (LYR-SEM0055). So is a variant's, in its enum's own methods too: a variant is written.RedorColor.Red, as in a pattern (09). - In a call,
label(…), a field counts only when it can be called — when its type is a function type, or an optional one: a fieldlabel: stringdoes not hide the functionlabelthere, and the call means the function. A field that can be called is what the call means (rule 3). Where a field that cannot be called is all the name means, the call is refused as one of something that is no function (LYR-SEM0013).