Skip to main content

zelkova_compiler/canonical/
environment.rs

1//! env module
2
3use super::{parser, Pattern, PatternKind};
4use super::{ClassSignature, PublishedInstance};
5use super::{Infix, Interface, ModuleName, Name, QualName, Type, TypeConstructor, UnionType};
6use crate::default_imports;
7use crate::scalars;
8use crate::utils::{collect_accumulate, suggest};
9use crate::{PhaseError, SourceSpan, SpanLabel};
10use log::trace;
11use std::collections::HashMap;
12use zelkova_syntax::position::NodeSpan;
13
14#[derive(Debug)]
15pub(crate) enum ValueType {
16    Local,
17    TopLevel,
18    /// A value found through exactly one import, together with where it was
19    /// declared in that module — `None` when the interface it came from cannot
20    /// say (a hand-built one, or one built before its file was known) — and
21    /// whether that import was written or supplied by [the default import
22    /// list](ImportOrigin).
23    Foreign(ModuleName, Option<SourceSpan>, Type, ImportOrigin),
24    /// A value exposed unqualified by more than one import — `AmbiguousVariables`
25    /// once looked up — each candidate paired the same way as `Foreign`.
26    Foreigns(Vec<(ModuleName, Option<SourceSpan>, ImportOrigin)>),
27}
28
29/// Whether a contributor to a module's scope was named by an `import` line the
30/// file wrote, or supplied by [the default import
31/// list](crate::default_imports) because the file wrote none for that
32/// module.
33///
34/// Carried alongside `ValueType::Foreign`/`Foreigns` purely for diagnostics:
35/// [`super::Error::AmbiguousVariables`]'s note calls a `Default` contributor out
36/// as implicit, since the collision it causes sits in a module the file never
37/// named — [*The default
38/// imports*](../../../docs/spec/modules.md#the-default-imports) states the rule
39/// that a default entry participates in ambiguity exactly as a written import
40/// does; this is what lets the diagnostic say which kind of import it was.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum ImportOrigin {
43    /// Named by an `import` line in the module under check.
44    Written,
45    /// Not written by the module under check — supplied because the module
46    /// wrote no import for it.
47    Default,
48}
49
50/// An operator's `infix` declaration as the environment holds it: what it declares,
51/// where that declaration was written, and how the function it names resolves.
52///
53/// The first two are separate because [`Infix::span`] on its own is not enough to
54/// underline: an infix reaches this environment either from the module under check
55/// or cloned out of an imported module's [`Interface`], and in the second case the
56/// span is a byte range in the *exporting* module's file. `declaration` is what
57/// records which of the two it was — see [`InfixDeclaration`].
58///
59/// `function` is the third, and is what makes an operator usable across a module
60/// boundary: it is resolved in the module that wrote the `infix` declaration, so
61/// whether the importing module also has the backing function in scope never enters
62/// into it — see [`InfixFunction`].
63#[derive(Debug, Clone)]
64pub(crate) struct InfixEntry {
65    pub infix: Infix,
66    pub declaration: InfixDeclaration,
67    pub function: InfixFunction,
68}
69
70/// The function an operator's `infix` declaration names, resolved once — when the
71/// declaration enters the environment — in the module that wrote it.
72///
73/// An operator has no qualified spelling, so naming it in an `exposing` list is the
74/// only way to use one from another module, and whether that module's *backing*
75/// function is also in scope is not something the importer chose or can see. Binding
76/// the function to the declaration here is what decouples the two: `resolve_infix_operator`
77/// (`canonical/mod.rs`) builds the operator's canonical value out of this variant
78/// and never looks the function's name up in the importing module's own scope.
79#[derive(Debug, Clone)]
80pub(crate) enum InfixFunction {
81    /// Declared by the module under check, as a top-level value. `do_infixes`
82    /// registers an `infix` only after finding the declaration it names among this
83    /// module's functions, so nothing further has to be checked at the use site.
84    Local,
85    /// Declared by the module that exported the operator, with the type that
86    /// module's [`Interface`] carries for it — from
87    /// [`Interface::values`] when the header exposes the function by name, and from
88    /// [`Interface::infix_functions`] when it exposes only the operator.
89    Imported(ModuleName, Type),
90    /// Exported by a module whose [`Interface`] carries no type for the backing
91    /// function, which happens when that declaration was never annotated. There is
92    /// nothing to type a use of the operator against, so `resolve_infix_operator`
93    /// reports the function as the unresolved name.
94    ImportedUntyped(ModuleName),
95}
96
97/// Where an operator's `infix` declaration was written, to the extent
98/// canonicalization can say — the three cases a [`SpanLabel`] under it has to
99/// distinguish.
100///
101/// A phase only ever sees one module and never learns which file it was read from,
102/// so a label about the module under check leaves `SpanLabel::file` as `None` and
103/// the driver fills it in at render time. A label about an *imported* operator
104/// cannot do that: its span belongs to
105/// the exporting module's file, and rendering it against the importing one
106/// underlines unrelated text. That is what [`SourceSpan`] and `SpanLabel::file`
107/// were built for (`ERR-5`), and this enum is what picks between them.
108#[derive(Debug, Clone, Copy, PartialEq)]
109pub enum InfixDeclaration {
110    /// Declared by the module under check. Its span is a byte range in the file
111    /// the diagnostic is already being rendered against.
112    InThisModule(NodeSpan),
113    /// Declared by an imported module whose [`Interface`] knew both the span and
114    /// the file it was written in.
115    InImportedModule(SourceSpan),
116    /// Declared by an imported module whose [`Interface`] could not say which file
117    /// it came from — a hand-built one, or one built before its file was known
118    /// (see [`Interface::source_span`]). There is nothing honest to underline, so
119    /// a diagnostic renders no label rather than one against the wrong file.
120    Unknown,
121}
122
123impl InfixDeclaration {
124    /// A secondary label under this declaration, when there is one to point at.
125    ///
126    /// `None` covers both the unknown case and a [`NodeSpan`] with no position
127    /// behind it — a label at byte 0 of the module under check is worse than no
128    /// label at all.
129    pub fn label(&self, message: String) -> Option<SpanLabel> {
130        match self {
131            InfixDeclaration::InThisModule(span) => span.span().map(|span| SpanLabel {
132                span,
133                message,
134                primary: false,
135                file: None,
136            }),
137            InfixDeclaration::InImportedModule(source) => Some(SpanLabel {
138                span: source.span,
139                message,
140                primary: false,
141                file: Some(source.file),
142            }),
143            InfixDeclaration::Unknown => None,
144        }
145    }
146}
147
148/// What the environment records for a resolvable type *name*, as opposed to a type
149/// expression built from one.
150///
151/// A `type Maybe a = …` declaration is a type *constructor*: applying it to
152/// arguments is what produces a `Type`, and `Maybe` on its own is not one — there is
153/// no such thing as the type `Maybe` with `a` left dangling. Storing the fully
154/// applied `Type::Type("Maybe.Maybe", [Variable("a")])` and handing it back verbatim on
155/// every lookup, which is what this replaced, conflated the two: it made every
156/// application of `Maybe` collapse to the same declaration-shaped type regardless of
157/// what was actually written, which is the defect `BUG-17` describes in full.
158///
159/// `name` is the *declaration's* qualified name, which is never the name the
160/// lookup used: one union is inserted under every spelling an import makes
161/// available for it, so `Lib.Option`, `Option` and an alias' `L.Option` are three
162/// keys onto one entry. `Type::from_parser_type` builds the canonical type out of
163/// this name rather than the written one, which is what keeps the three spellings
164/// unifiable — and what keeps two modules' `Size` apart, since the module is part
165/// of the name each records (`AST-4`).
166///
167/// `variables` is the declaration's own type variables, in the order the `type`
168/// line wrote them — its length is the arity a written application is checked
169/// against in `Type::from_parser_type`. The names themselves are not needed for
170/// that check (a union type has no body to substitute them into — see
171/// `TypeConstructor::type_parameters`, which each variant carries directly and
172/// already recorded independently), but keeping them rather than a bare count is
173/// what the entry would need were a form with a body — a type alias — to reuse this
174/// same map.
175///
176/// Nothing on the entry says whether the module under check declared the type or an
177/// import brought it in, because `name` already does: it is the declaration's own, so
178/// its module is the module under check for a declaration and another module for an
179/// import. A third source of entries is `new_environment`'s seeding of the scalar
180/// types in `zelkova-core`, under the qualified names `scalars::SCALARS` holds; each
181/// records the module that declares that scalar, so the same reading holds. `do_exports` reads it that way (`declares_type`), as it reads a class's
182/// qualified name — where `ValueType` needs variants, a type's entry carries the answer.
183#[derive(Debug, Clone, PartialEq)]
184pub(crate) struct TypeArity {
185    pub name: QualName,
186    pub variables: Vec<Name>,
187}
188
189impl TypeArity {
190    pub(crate) fn arity(&self) -> usize {
191        self.variables.len()
192    }
193}
194
195/// Environment represent the set of values/types available to a compilation unit.
196/// It is derived from previous successful units and is provisioned through a
197/// module imports.
198///
199/// As a whole, the aim of the canonical AST is to not have to worry about the
200/// Environment in later phases. We still need one to translate the parser AST.
201/// The good news being, it can be local to the canonicalization function.
202pub(crate) trait Environment<'parent>: std::fmt::Debug {
203    fn find_type(&self, name: &Name) -> Option<&TypeArity>;
204
205    fn module_name(&self) -> &ModuleName;
206
207    fn find_value(&self, name: &Name) -> Option<&ValueType>;
208
209    fn find_type_constructor(&self, name: &Name) -> Option<&TypeConstructor>;
210
211    /// The value names resolvable from this scope right now, for building a
212    /// "did you mean …?" suggestion once `find_value` has already failed
213    /// (`ERR-7`). Not on the lookup path itself: this walks the whole table,
214    /// `find_value` a single key.
215    ///
216    /// A `Vec` and not a set: [`ScopedEnvironment`] appends its own bindings
217    /// to whatever the parent returned, so a name a scope shadows appears
218    /// twice. That is harmless here — both entries are the same string, and
219    /// [`suggest`](crate::utils::suggest) breaks ties by comparing the
220    /// candidates themselves rather than by the order they arrive in, so
221    /// neither the duplication nor the parent-before-locals order changes
222    /// which name comes back.
223    ///
224    /// One thing this does *not* mirror from `find_value` is the ordering:
225    /// `find_value` checks a scope's own bindings before the parent's, this
226    /// appends them last.
227    fn value_names(&self) -> Vec<Name>;
228
229    /// Same as [`value_names`](Environment::value_names), for type
230    /// constructors. A scope never binds its own, so this is always the root
231    /// environment's `constructors` and never carries duplicates.
232    fn type_constructor_names(&self) -> Vec<Name>;
233
234    fn local_infix_exists(&self, name: &Name) -> bool;
235
236    /// The [`InfixEntry`] an operator resolves to from this scope — its declared
237    /// precedence and associativity, which module declared it and where, and the
238    /// backing function it names — or `None` when no `infix` declaration for it
239    /// is in scope.
240    ///
241    /// Canonicalization's infix re-association is the only caller
242    /// (`resolve_infix_operator` in `canonical/mod.rs`), and this is the whole
243    /// of how an operator resolves: the entry decides both how a flat run of
244    /// operator applications (`ExpressionKind::InfixChain`) nests into
245    /// `Application` nodes, and what each operator position becomes as a value.
246    /// So a lookup that fails here is an operator nothing in scope declares, and
247    /// `resolve_infix_operator` reports it with the same `VariableNotFound` a
248    /// plain unresolvable variable would get.
249    fn find_infix(&self, name: &Name) -> Option<&InfixEntry>;
250
251    #[allow(dead_code)]
252    fn insert_local_value(&mut self, name: &Name);
253
254    // 'parent must lives at least as long as 'a
255    fn new_scope<'a>(&'a self) -> ScopedEnvironment<'parent, 'a>
256    where
257        'parent: 'a;
258}
259
260/// Build a "did you mean …?" suggestion for a name that failed to resolve,
261/// from the full set of names available at the point of failure (`ERR-7`).
262///
263/// Respects the qualified/unqualified distinction called out on
264/// [`super::Error::VariableNotFound`]: a qualified `target` (`Widget.map`)
265/// only considers candidates qualified with that exact same module prefix,
266/// comparing distance on the local part once the prefix is confirmed equal;
267/// an unqualified `target` only considers unqualified candidates. Crossing
268/// that boundary would let a typo in one module's name get "fixed" by
269/// pointing at an entirely different module's value of the same local name,
270/// which is the wrong-module case the ticket calls out explicitly as worse
271/// than no suggestion at all.
272///
273/// The `Environment`'s own candidate sets (`RootEnvironment::variables`,
274/// `RootEnvironment::constructors`) hold both forms as distinct keys already
275/// — `map` and `Maybe.map` are two separate entries once `Maybe` is
276/// imported — so this needs no lookup beyond string-splitting on the last
277/// `.`. The three `EnvError` sites that use this — `ValueNotFound`,
278/// `UnionNotFound`, `InfixNotFound` — draw from a single `Interface`'s own
279/// namespace (`interface.values`, `.unions`, `.infixes`), which never holds
280/// a qualified name, so they always take the unqualified branch.
281///
282/// `InterfaceNotFound` deliberately does *not* come through here: its
283/// candidates are module names, where a `.` separates two segments of one
284/// identifier rather than a module from a value. `process_import` calls
285/// [`suggest`](crate::utils::suggest) over the whole string instead.
286pub(crate) fn suggest_name(target: &Name, candidates: impl Iterator<Item = Name>) -> Option<Name> {
287    match target.as_str().rsplit_once('.') {
288        Some((prefix, local)) => {
289            let locals: Vec<String> = candidates
290                .filter_map(|c| {
291                    c.as_str().rsplit_once('.').and_then(|(p, l)| {
292                        if p == prefix {
293                            Some(l.to_string())
294                        } else {
295                            None
296                        }
297                    })
298                })
299                .collect();
300            let refs: Vec<&str> = locals.iter().map(String::as_str).collect();
301            suggest(local, refs).map(|l| Name::new(format!("{}.{}", prefix, l)))
302        }
303        None => {
304            let unqualified: Vec<String> = candidates
305                .filter(|c| !c.as_str().contains('.'))
306                .map(|c| c.to_string())
307                .collect();
308            let refs: Vec<&str> = unqualified.iter().map(String::as_str).collect();
309            suggest(target.as_str(), refs).map(Name::new)
310        }
311    }
312}
313
314/// Build a module's scope out of the imports it wrote and the ones it did not have
315/// to.
316///
317/// What comes back is the scope and the error of every import that did not resolve, in
318/// the order the imports were processed. An import that failed does not end the scope:
319/// every import that resolved is in it, and so is every entry of a failed import's
320/// `exposing` list that did resolve. The scope is [`incomplete`](RootEnvironment::is_incomplete)
321/// when any import failed, and when any import resolved to an [`Interface`] whose own
322/// [`incomplete`](Interface::incomplete) is set.
323///
324/// The implicit half comes first, and goes through `process_import` exactly as a
325/// written `import` does, so a name arriving through
326/// [`default_imports`](crate::default_imports) resolves exactly like one
327/// the file named — including for `insert_foreign_value`, which is what makes two
328/// imports of one name ambiguous regardless of which half either came from. The
329/// only place the two are told apart is [`ImportOrigin`], carried alongside each
330/// candidate purely so `AmbiguousVariables`' note can say which contributor the
331/// module never wrote (SPEC-32). That is also why a module that *writes* one of
332/// the default imports gets only what it wrote:
333/// [`implicit_imports`](crate::default_imports::implicit_imports) drops
334/// an entry the file already names, rather than registering `Basics` twice over and
335/// turning every use of `+` into an `AmbiguousVariables`.
336///
337/// `is_core` is [`module_name.package().is_core()`](crate::PackageName::is_core)
338/// — the same package-level answer `dependencies::add_default_import_edges` uses to
339/// decide this package's implicit edges. A module of `zelkova-core` gets none of the
340/// eight, whatever it is named — and, in their place, the five scalar type names
341/// ([`LANG-58`](../../../docs/tickets/README.md), [`DEC-15` decision
342/// 3](../../../docs/decisions/dec-15.md#3--a-module-that-loses-the-basics-default-import-receives-the-scalar-type-names-in-its-place)):
343/// `Int`, `Float`, `Char`, `String` and `Bool`, bound to the qualified names
344/// [`scalars::SCALARS`] already holds rather than looked up in `Basics`'
345/// `Interface`. A facade underneath `Basics` — `Js.Basics`, `Js.Utils` — has no
346/// other way to name the types its signatures use, since writing the import
347/// out would be [a cycle](../../../docs/spec/modules.md#imports-may-not-form-a-cycle)
348/// and the implicit one is exactly what this package does not receive. Only
349/// the type names arrive this way: no constructor and no value, so a module
350/// reached by this can annotate a `Bool` and cannot write a `True`.
351pub(crate) fn new_environment(
352    module_name: &ModuleName,
353    interfaces: &HashMap<Name, Interface>,
354    imports: &[parser::Import],
355) -> (RootEnvironment, Vec<EnvError>) {
356    let mut env = RootEnvironment {
357        module_name: module_name.clone(),
358        infixes: HashMap::new(),
359        types: HashMap::new(),
360        constructors: HashMap::new(),
361        variables: HashMap::new(),
362        classes: HashMap::new(),
363        class_signatures: HashMap::new(),
364        unions: HashMap::new(),
365        instances: Vec::new(),
366        incomplete: false,
367    };
368    let mut errors = vec![];
369
370    let is_core = module_name.package().is_core();
371
372    // The compiler knows each scalar's qualified name and arity without
373    // reading the module that declares it (`scalars::SCALARS`), so seeding
374    // them here consults neither `interfaces` nor `Basics`' own declarations —
375    // doing either would recreate the dependency `add_default_import_edges`
376    // withholds for this package, and turn this into a diagnostic-free way
377    // back into the cycle the default imports are dropped to avoid.
378    if is_core {
379        for scalar in scalars::SCALARS {
380            env.types.insert(
381                Name::new(scalar.name),
382                TypeArity {
383                    name: scalar.qual_name(),
384                    variables: vec![],
385                },
386            );
387        }
388    }
389
390    let implicit = default_imports::implicit_imports(imports, interfaces, is_core);
391
392    let tagged = implicit
393        .iter()
394        .map(|import| (import, ImportOrigin::Default))
395        .chain(imports.iter().map(|import| (import, ImportOrigin::Written)));
396
397    for (
398        parser::Import {
399            name,
400            alias,
401            exposing,
402            span,
403        },
404        origin,
405    ) in tagged
406    {
407        match process_import(&mut env, interfaces, name, alias, exposing, *span, origin) {
408            Ok(_) => (),
409            Err(err) => {
410                // A name the import would have brought in may be missing from the
411                // scope, and the error just collected is what says so.
412                env.incomplete = true;
413                errors.push(err);
414            }
415        }
416    }
417
418    (env, errors)
419}
420
421fn process_import(
422    env: &mut RootEnvironment,
423    interfaces: &HashMap<Name, Interface>,
424    imported_module_name: &Name,
425    alias: &Option<Name>,
426    exposing: &parser::Exposing,
427    // The `import` line this is resolving. Only `InterfaceNotFound` uses it: the
428    // module name itself is not part of any `parser::Exposed`, so the `import` line
429    // is the finest span there is for it. Every other error raised further down
430    // names one entry of the exposing list and carries that entry's own span
431    // instead (`ERR-9`) — `bar` in `import Foo exposing (bar)`, not the line it
432    // sits on.
433    span: NodeSpan,
434    // Whether this import was written by the module under check or supplied by
435    // the default import list — carried onto every `ValueType::Foreign` this
436    // call produces, purely for `AmbiguousVariables`' diagnostic (SPEC-32).
437    origin: ImportOrigin,
438) -> Result<(), EnvError> {
439    let interface = interfaces.get(imported_module_name).ok_or_else(|| {
440        // Module names live in a flat namespace and a dotted one — `Js.Basics` —
441        // is a single opaque identifier, not a prefix plus a local part. So this
442        // measures the distance over the whole string with `suggest` rather than
443        // going through `suggest_name`, whose qualified/unqualified split is
444        // about `Name` vs `QualName` for values and constructors. Splitting here
445        // would silently drop every typo outside the last segment: `Jz.Basics`
446        // and `JsBasics` are both one edit from `Js.Basics`, and neither shares
447        // its prefix.
448        let suggestion = suggest(
449            imported_module_name.as_str(),
450            interfaces.keys().map(Name::as_str),
451        )
452        .map(Name::new);
453        EnvError::InterfaceNotFound(imported_module_name.clone(), span, suggestion)
454    })?;
455
456    trace!(
457        "process_import(imported_module_name={:?}, alias={:?}, exposing: {:?})",
458        imported_module_name,
459        alias,
460        exposing
461    );
462
463    // A name this module's importers look for here may be one it declares and could not
464    // publish, so nothing missing from the scope can be blamed on this module's
465    // importer alone.
466    if interface.incomplete {
467        env.incomplete = true;
468    }
469
470    // Every instance the interface carries is in scope, whatever this import exposes:
471    // an instance has no name for an `exposing` list to carry, and coherence needs it
472    // wherever its class and its type are
473    // ([Type classes](../../../docs/spec/type-classes.md#declaring-an-instance)).
474    for published in &interface.instances {
475        env.insert_imported_instance(published);
476    }
477
478    // Every union the interface carries is known by its declaration, which is how a
479    // derived instance finds the constructors of a type however it was spelled.
480    for (union_name, union) in &interface.unions {
481        env.unions.insert(
482            interface.module_name.qualify_name(union_name),
483            union.clone(),
484        );
485    }
486
487    // First we insert all values/types from the module, prefixed with the module name or its alias
488    let prefix = alias.as_ref().unwrap_or(imported_module_name);
489
490    // A class and its members are reachable qualified whatever the import exposes, as
491    // a value and a type are.
492    for (class_name, signature) in &interface.classes {
493        insert_foreign_class(env, Some(prefix), interface, class_name, signature, origin);
494    }
495
496    for (value_name, signature) in &interface.values {
497        insert_foreign_value(
498            env,
499            value_name.clone().qualify_with(prefix.to_string()),
500            signature.tpe.clone(),
501            interface.source_span(signature.span),
502            &interface.module_name,
503            origin,
504        );
505    }
506
507    for (union_name, union) in &interface.unions {
508        insert_foreign_union_type(
509            env,
510            Some(prefix),
511            &interface.module_name,
512            union_name,
513            &union.variables,
514            union.variants.iter(),
515        );
516    }
517
518    // TODO Infix ?
519
520    // Then we insert unqualified values/types
521    match exposing {
522        parser::Exposing::Open => {
523            // We add everything to the current environment
524
525            for (value_name, signature) in &interface.values {
526                insert_foreign_value(
527                    env,
528                    value_name.clone(),
529                    signature.tpe.clone(),
530                    interface.source_span(signature.span),
531                    &interface.module_name,
532                    origin,
533                );
534            }
535
536            for (op_name, infix) in &interface.infixes {
537                env.infixes
538                    .insert(op_name.clone(), imported_infix(interface, infix));
539            }
540
541            for (class_name, signature) in &interface.classes {
542                insert_foreign_class(env, None, interface, class_name, signature, origin);
543            }
544
545            // We need to insert the type without any qualifier, including variants
546            for (union_name, union) in &interface.unions {
547                insert_foreign_union_type(
548                    env,
549                    None,
550                    &interface.module_name,
551                    union_name,
552                    &union.variables,
553                    union.variants.iter(),
554                );
555            }
556        }
557
558        parser::Exposing::Explicit(exposeds) => {
559            // We only add the explicitly named types/values/infixes
560            let iter = exposeds.iter().map(|exposed| {
561                match &exposed.kind {
562                    parser::ExposedKind::Lower(value_name) => {
563                        let Some(signature) = interface.values.get(value_name) else {
564                            // A class member may be named on its own, as any exposed
565                            // value may.
566                            if let Some(member) = interface
567                                .classes
568                                .values()
569                                .flat_map(|signature| signature.members.iter())
570                                .find(|member| &member.name == value_name)
571                            {
572                                insert_foreign_value(
573                                    env,
574                                    value_name.clone(),
575                                    member.tpe.clone(),
576                                    interface.source_span(member.span),
577                                    &interface.module_name,
578                                    origin,
579                                );
580                                return Ok(());
581                            }
582
583                            // Missing from an interface that is missing names it
584                            // could not publish: the entry restates that failure.
585                            if interface.incomplete {
586                                return Ok(());
587                            }
588                            let suggestion =
589                                suggest_name(value_name, interface.values.keys().cloned());
590                            return Err(EnvError::ValueNotFound(
591                                value_name.clone(),
592                                exposed.span,
593                                suggestion,
594                            ));
595                        };
596
597                        insert_foreign_value(
598                            env,
599                            value_name.clone(),
600                            signature.tpe.clone(),
601                            interface.source_span(signature.span),
602                            &interface.module_name,
603                            origin,
604                        );
605                    }
606                    parser::ExposedKind::Upper(type_name, parser::Privacy::Private) => {
607                        // `Size` and `Size(..)` are one entry differing only in
608                        // whether the constructors come along, so the name itself is
609                        // checked the same way in both and reports the same
610                        // `UnionNotFound`. Only the `Public` arm below also asks
611                        // whether the module exposed the constructors: this one
612                        // never reads `variants`, so a type exposed opaquely is
613                        // exactly what it asks for.
614                        //
615                        // A bare uppercase name may also be a class, which brings its
616                        // members along unqualified.
617                        let Some(union) = interface.unions.get(type_name) else {
618                            if let Some(signature) = interface.classes.get(type_name) {
619                                insert_foreign_class(
620                                    env, None, interface, type_name, signature, origin,
621                                );
622                                return Ok(());
623                            }
624                            if interface.incomplete {
625                                return Ok(());
626                            }
627                            let suggestion =
628                                suggest_name(type_name, interface.unions.keys().cloned());
629                            return Err(EnvError::UnionNotFound(
630                                type_name.clone(),
631                                exposed.span,
632                                suggestion,
633                            ));
634                        };
635
636                        // Add the type without qualifier and without constructors
637                        // (they are private), but with the declaration's own type
638                        // variables: an opaque `Option` is still `Option a`, and
639                        // every later application of it is measured against this
640                        // arity.
641                        env.types.insert(
642                            type_name.clone(),
643                            TypeArity {
644                                name: interface.module_name.qualify_name(type_name),
645                                variables: union.variables.clone(),
646                            },
647                        );
648                    }
649                    parser::ExposedKind::Upper(type_name, parser::Privacy::Public) => {
650                        let Some(union) = interface.unions.get(type_name) else {
651                            // A class has no constructors for `(..)` to bring in.
652                            if interface.classes.contains_key(type_name) {
653                                return Err(EnvError::ClassHasNoConstructors(
654                                    type_name.clone(),
655                                    exposed.span,
656                                ));
657                            }
658                            if interface.incomplete {
659                                return Ok(());
660                            }
661                            let suggestion =
662                                suggest_name(type_name, interface.unions.keys().cloned());
663                            return Err(EnvError::UnionNotFound(
664                                type_name.clone(),
665                                exposed.span,
666                                suggestion,
667                            ));
668                        };
669
670                        // An opaque export's `variants` is empty, as is that of a
671                        // type declared with none (which LANG-10 is to reject);
672                        // `opaque_unions` is what tells the two apart. Inserting the first would hand over a
673                        // constructor-less type in place of what `(..)` asked for.
674                        if interface.opaque_unions.contains(type_name) {
675                            return Err(EnvError::ConstructorsNotExposed(
676                                type_name.clone(),
677                                imported_module_name.clone(),
678                                exposed.span,
679                            ));
680                        }
681
682                        insert_foreign_union_type(
683                            env,
684                            None,
685                            &interface.module_name,
686                            type_name,
687                            &union.variables,
688                            union.variants.iter(),
689                        );
690                    }
691                    parser::ExposedKind::Operator(variable_name) => {
692                        let Some(infix) = interface.infixes.get(variable_name) else {
693                            if interface.incomplete {
694                                return Ok(());
695                            }
696                            let suggestion =
697                                suggest_name(variable_name, interface.infixes.keys().cloned());
698                            return Err(EnvError::InfixNotFound(
699                                variable_name.clone(),
700                                exposed.span,
701                                suggestion,
702                            ));
703                        };
704
705                        // The entry is the whole of what the importer gets: it
706                        // carries the backing function alongside the precedence
707                        // and associativity, so naming the operator here is
708                        // enough on its own. Nothing is inserted into
709                        // `env.variables` — an operator is not a value name.
710                        env.infixes
711                            .insert(variable_name.clone(), imported_infix(interface, infix));
712                    }
713                };
714
715                Ok(())
716            });
717
718            collect_accumulate::<_, _, _, ()>(iter).map_err(EnvError::Multiple)?;
719        }
720    };
721
722    Ok(())
723}
724
725/// An [`InfixEntry`] for an operator taken out of an imported module's
726/// [`Interface`], carrying the file that interface was read from so a diagnostic
727/// about it underlines the *exporting* module's source and not the importing one's.
728///
729/// `Interface::source_span` declines when either half is missing, and
730/// [`InfixDeclaration::Unknown`] is that answer: the label is dropped rather than
731/// rendered against the wrong file.
732///
733/// The backing function is resolved here too, against the exporting interface,
734/// which is what lets an `exposing` list name the operator on its own — see
735/// [`InfixFunction`]. Two places in that interface can carry its type, and they are
736/// disjoint by construction ([`Interface::infix_functions`] holds only what
737/// [`Interface::values`] does not), so the order they are consulted in does not
738/// matter.
739fn imported_infix(interface: &Interface, infix: &Infix) -> InfixEntry {
740    let declaration = match interface.source_span(infix.span) {
741        Some(source) => InfixDeclaration::InImportedModule(source),
742        None => InfixDeclaration::Unknown,
743    };
744
745    // A member of a class the module exposes may back an operator too, and its type is
746    // its member signature, with the class's constraint in front, which the typer declares
747    // for the member and so for a use of the operator.
748    let member = || {
749        interface
750            .classes
751            .values()
752            .flat_map(|signature| signature.members.iter())
753            .find(|member| member.name == infix.function_name)
754            .map(|member| &member.tpe)
755    };
756
757    let function = match interface
758        .values
759        .get(&infix.function_name)
760        .or_else(|| interface.infix_functions.get(&infix.function_name))
761        .map(|signature| &signature.tpe)
762        .or_else(member)
763    {
764        Some(tpe) => InfixFunction::Imported(interface.module_name.clone(), tpe.clone()),
765        None => InfixFunction::ImportedUntyped(interface.module_name.clone()),
766    };
767
768    InfixEntry {
769        infix: infix.clone(),
770        declaration,
771        function,
772    }
773}
774
775/// Register an imported union under one spelling — `qualifier` is the prefix the
776/// importing module reaches it by (the module's own name, or the alias it wrote),
777/// or `None` for the unqualified spelling an `exposing` list adds.
778///
779/// `declaring_module` is a separate parameter precisely because it is not the
780/// qualifier: the key a lookup uses is the spelling, and the [`TypeArity`] it
781/// finds records the declaration. `import Widget as W` inserts `W.Size` as a key
782/// and `Widget.Size` as the name, so an annotation written `W.Size` canonicalizes
783/// to the type `Widget` declared (`AST-4`).
784fn insert_foreign_union_type<'a, I: Iterator<Item = &'a TypeConstructor>>(
785    env: &mut RootEnvironment,
786    qualifier: Option<&Name>,
787    declaring_module: &ModuleName,
788    union_name: &Name,
789    variables: &[Name],
790    variants: I,
791) {
792    // If there is a given qualifier use it, otherwise use the name as is
793    let qualify = |n: &Name| match qualifier {
794        Some(q) => n.clone().qualify_with(q.to_string()),
795        None => n.clone(),
796    };
797
798    env.types.insert(
799        qualify(union_name),
800        TypeArity {
801            name: declaring_module.qualify_name(union_name),
802            variables: variables.to_vec(),
803        },
804    );
805
806    for variant in variants {
807        // Variant are not qualified, which means we have to alias/qualify them as needed
808        env.constructors
809            .insert(qualify(&variant.name), variant.clone());
810    }
811}
812
813/// Register a class `interface` exposes under one spelling, as
814/// [`insert_foreign_union_type`] registers a union: `qualifier` is the prefix the
815/// importing module reaches it by, or `None` for the unqualified spelling an `exposing`
816/// list adds. Every member of the class arrives with it under the same spelling, as a
817/// value whose type is its member signature.
818///
819/// The members are not in [`Interface::values`], so the typer declares them from the class
820/// instead: each is its signature with the class's constraint in front.
821fn insert_foreign_class(
822    env: &mut RootEnvironment,
823    qualifier: Option<&Name>,
824    interface: &Interface,
825    class_name: &Name,
826    signature: &ClassSignature,
827    origin: ImportOrigin,
828) {
829    let qualify = |n: &Name| match qualifier {
830        Some(q) => n.clone().qualify_with(q.to_string()),
831        None => n.clone(),
832    };
833
834    let declared = interface.module_name.qualify_name(class_name);
835    env.classes.insert(qualify(class_name), declared.clone());
836    env.class_signatures.insert(declared, signature.clone());
837
838    for member in &signature.members {
839        insert_foreign_value(
840            env,
841            qualify(&member.name),
842            member.tpe.clone(),
843            interface.source_span(member.span),
844            &interface.module_name,
845            origin,
846        );
847    }
848}
849
850fn insert_foreign_value(
851    env: &mut RootEnvironment,
852    name: Name,
853    tpe: Type,
854    source: Option<SourceSpan>,
855    module_name: &ModuleName,
856    origin: ImportOrigin,
857) {
858    let vt = ValueType::Foreign(module_name.clone(), source, tpe.clone(), origin);
859
860    // Can it be done more efficiently by using get_mut ?
861    match env.variables.remove(&name) {
862        Some(ValueType::Foreign(module, prev_source, _, prev_origin)) => {
863            env.variables.insert(
864                name,
865                ValueType::Foreigns(vec![
866                    (module_name.clone(), source, origin),
867                    (module, prev_source, prev_origin),
868                ]),
869            );
870        }
871        Some(ValueType::Foreigns(mut vec)) => {
872            vec.push((module_name.clone(), source, origin));
873            env.variables.insert(name, ValueType::Foreigns(vec));
874        }
875        None => {
876            env.variables.insert(name, vt);
877        }
878        _ => todo!("find out what to do in those cases"),
879    }
880}
881
882#[derive(Debug)]
883pub enum EnvError {
884    /// No module of that name was available to import, where the `import` was
885    /// written, and — when one candidate module name is a close enough
886    /// typo-distance match (`ERR-7`) — a suggestion for what was meant.
887    InterfaceNotFound(Name, NodeSpan, Option<Name>),
888    /// A `TypeIdent(..)` in an `exposing` list naming a type the imported module
889    /// does not declare, where that name was written (`ERR-9`), and an optional
890    /// "did you mean …?" suggestion (`ERR-7`).
891    UnionNotFound(Name, NodeSpan, Option<Name>),
892    /// A `TypeIdent(..)` in an `exposing` list naming a type the imported module
893    /// exposes only opaquely — a bare `TypeIdent` in its own header — so the
894    /// constructors it asks for are not there to import. Carries the type's name,
895    /// the module name as the `import` wrote it, and where the entry was written.
896    ConstructorsNotExposed(Name, Name, NodeSpan),
897    /// An `(op)` in an `exposing` list naming an infix the imported module does not
898    /// declare, where that name was written (`ERR-9`), and an optional "did you
899    /// mean …?" suggestion (`ERR-7`).
900    InfixNotFound(Name, NodeSpan, Option<Name>),
901    /// A lowercase name in an `exposing` list naming a value the imported module
902    /// does not declare, where that name was written (`ERR-9`), and an optional
903    /// "did you mean …?" suggestion (`ERR-7`).
904    ValueNotFound(Name, NodeSpan, Option<Name>),
905    /// `Name(..)` in an `exposing` list naming a class the imported module exposes, and
906    /// where the entry was written. A class has no constructors for `(..)` to bring in.
907    ClassHasNoConstructors(Name, NodeSpan),
908    Multiple(Vec<EnvError>),
909}
910
911/// A "did you mean `X`?" suffix for a label message, when a suggestion was
912/// found — empty otherwise, so callers can always append the result without
913/// checking `is_some()` first (`ERR-7`).
914fn suggestion_suffix(suggestion: &Option<Name>) -> String {
915    match suggestion {
916        Some(name) => format!(" — did you mean `{}`?", name),
917        None => String::new(),
918    }
919}
920
921/// An import that could not be resolved always names the thing it could not find,
922/// so the message can quote it back.
923impl PhaseError for EnvError {
924    fn message(&self) -> String {
925        match self {
926            EnvError::InterfaceNotFound(name, _, _) => {
927                format!("cannot find a module named `{}` to import", name)
928            }
929            EnvError::UnionNotFound(name, _, _) => {
930                format!(
931                    "the imported module does not expose a type named `{}`",
932                    name
933                )
934            }
935            EnvError::ConstructorsNotExposed(name, module, _) => format!(
936                "`{}` exposes the type `{}` but not its constructors",
937                module, name
938            ),
939            EnvError::InfixNotFound(name, _, _) => format!(
940                "the imported module does not expose an infix operator named `{}`",
941                name
942            ),
943            EnvError::ValueNotFound(name, _, _) => format!(
944                "the imported module does not expose a value named `{}`",
945                name
946            ),
947            EnvError::ClassHasNoConstructors(name, _) => format!(
948                "`{}` is a class, and a class has no constructors to import",
949                name
950            ),
951            EnvError::Multiple(errors) => match errors.as_slice() {
952                [only] => only.message(),
953                many => format!("{} imports could not be resolved", many.len()),
954            },
955        }
956    }
957
958    fn labels(&self) -> Vec<SpanLabel> {
959        let primary = |span: &NodeSpan, message: String| match span.span() {
960            Some(span) => vec![SpanLabel {
961                span,
962                message,
963                primary: true,
964                file: None,
965            }],
966            None => Vec::new(),
967        };
968
969        match self {
970            EnvError::InterfaceNotFound(_, span, suggestion) => primary(
971                span,
972                format!(
973                    "no module of this name was found{}",
974                    suggestion_suffix(suggestion)
975                ),
976            ),
977            EnvError::UnionNotFound(name, span, suggestion) => primary(
978                span,
979                format!(
980                    "`{}` is not exposed by the imported module{}",
981                    name,
982                    suggestion_suffix(suggestion)
983                ),
984            ),
985            EnvError::ConstructorsNotExposed(name, module, span) => primary(
986                span,
987                format!(
988                    "`{}(..)` asks for constructors `{}` does not expose",
989                    name, module
990                ),
991            ),
992            EnvError::InfixNotFound(name, span, suggestion) => primary(
993                span,
994                format!(
995                    "`{}` is not exposed by the imported module{}",
996                    name,
997                    suggestion_suffix(suggestion)
998                ),
999            ),
1000            EnvError::ValueNotFound(name, span, suggestion) => primary(
1001                span,
1002                format!(
1003                    "`{}` is not exposed by the imported module{}",
1004                    name,
1005                    suggestion_suffix(suggestion)
1006                ),
1007            ),
1008            EnvError::ClassHasNoConstructors(name, span) => primary(
1009                span,
1010                format!("`{}(..)` asks for constructors a class does not have", name),
1011            ),
1012            EnvError::Multiple(errors) => errors.iter().flat_map(|e| e.labels()).collect(),
1013        }
1014    }
1015
1016    fn notes(&self) -> Vec<String> {
1017        match self {
1018            EnvError::Multiple(errors) => match errors.as_slice() {
1019                [only] => only.notes(),
1020                many => many.iter().flat_map(|e| e.message_and_notes()).collect(),
1021            },
1022            EnvError::ConstructorsNotExposed(name, _, _) => vec![format!(
1023                "write `{}` without `(..)` to import the type alone",
1024                name
1025            )],
1026            EnvError::ClassHasNoConstructors(name, _) => vec![format!(
1027                "write `{}` without `(..)`: naming a class imports it with every member",
1028                name
1029            )],
1030            _ => Vec::new(),
1031        }
1032    }
1033}
1034
1035/// RootEnvironment represents the top level module and contains information accessible
1036/// from the entire module.
1037///
1038/// This is opposed to a ScopedEnvironment which contains
1039/// additional information available only to a scoped expression (eg. local variable)
1040#[derive(Debug)]
1041pub(crate) struct RootEnvironment {
1042    module_name: ModuleName,
1043    infixes: HashMap<Name, InfixEntry>,
1044    types: HashMap<Name, TypeArity>,
1045    constructors: HashMap<Name, TypeConstructor>,
1046    variables: HashMap<Name, ValueType>,
1047    /// Every class name in scope, under each spelling that reaches it, to the class's
1048    /// declaration. A class shares the types namespace and is kept apart from `types`,
1049    /// because a class name is not a type.
1050    classes: HashMap<Name, QualName>,
1051    /// What each class in scope declares, by its declaration's name. A class of this
1052    /// module is here once its signature canonicalized, and not before.
1053    class_signatures: HashMap<QualName, ClassSignature>,
1054    /// Every union in reach, by its declaration's name: this module's own, and each one an
1055    /// import's interface carries, with no variants when the interface exposes it opaque.
1056    /// What a derived instance reads its type's constructors off.
1057    unions: HashMap<QualName, UnionType>,
1058    /// Every instance an import brought into scope, each once however many routes
1059    /// reached it.
1060    instances: Vec<PublishedInstance>,
1061    /// Whether a name could be missing from this scope for a reason already reported.
1062    /// See [`is_incomplete`](Self::is_incomplete).
1063    incomplete: bool,
1064}
1065
1066impl RootEnvironment {
1067    /// Whether a name could be missing from this scope for a reason that has already
1068    /// been reported: an import that did not resolve, an import of a module whose own
1069    /// [`Interface::incomplete`] is set, or, once `canonicalize_recovering` says so, a
1070    /// `type` or `infix` declaration of this module that failed.
1071    ///
1072    /// A not-found error raised against an incomplete scope restates one of those
1073    /// failures, and `canonicalize_recovering` drops it (`without_restated`).
1074    pub(crate) fn is_incomplete(&self) -> bool {
1075        self.incomplete
1076    }
1077
1078    /// Mark this scope [incomplete](Self::is_incomplete).
1079    pub(crate) fn set_incomplete(&mut self) {
1080        self.incomplete = true;
1081    }
1082
1083    // TODO Do we need a local/foreign distinction for infixes ? (or in general ?)
1084    pub(crate) fn insert_local_infix(&mut self, name: Name, infix: Infix) {
1085        let declaration = InfixDeclaration::InThisModule(infix.span);
1086
1087        self.infixes.insert(
1088            name,
1089            InfixEntry {
1090                infix,
1091                declaration,
1092                function: InfixFunction::Local,
1093            },
1094        );
1095    }
1096
1097    // TODO Use insert_foreign_value (and rename to remove the foreign part)
1098    // TODO Return an error if declaration already exists
1099    pub(crate) fn insert_top_level_value(&mut self, name: Name) {
1100        self.variables.insert(name, ValueType::TopLevel);
1101    }
1102
1103    /// Register a `type` declaration of the module under check by name and
1104    /// arity alone, before anything has canonicalized its body.
1105    ///
1106    /// A declared type name is in scope throughout its own module, so the
1107    /// bodies being canonicalized may already name it — a declaration naming
1108    /// itself (`type Never = JustOneMore Never`) or a sibling declared further
1109    /// down the file. Only the arity is needed for that, and the `type` line
1110    /// carries it, so the whole set can be registered up front and each entry's
1111    /// constructors filled in by [`insert_union_type`](Self::insert_union_type)
1112    /// once the bodies exist.
1113    pub(crate) fn insert_declared_type(&mut self, name: &Name, variables: Vec<Name>) {
1114        // The declaration is the module under check's own, so this environment's
1115        // module name is the one it belongs to (`AST-4`).
1116        let qualified = self.module_name.qualify_name(name);
1117
1118        self.types.insert(
1119            name.clone(),
1120            TypeArity {
1121                name: qualified,
1122                variables,
1123            },
1124        );
1125    }
1126
1127    /// Register the name of a `class` declaration of the module under check, before
1128    /// anything has canonicalized it, so that a superclass or an instance can name a
1129    /// class written further down the file.
1130    pub(crate) fn insert_declared_class(&mut self, name: &Name) {
1131        let qualified = self.module_name.qualify_name(name);
1132        self.classes.insert(name.clone(), qualified);
1133    }
1134
1135    /// Record the signature of a `class` declaration of the module under check.
1136    pub(crate) fn insert_class_signature(&mut self, name: &Name, signature: ClassSignature) {
1137        let qualified = self.module_name.qualify_name(name);
1138        self.class_signatures.insert(qualified, signature);
1139    }
1140
1141    /// The declaration a class name written in this module resolves to.
1142    pub(crate) fn find_class(&self, name: &Name) -> Option<&QualName> {
1143        self.classes.get(name)
1144    }
1145
1146    /// What the class `name` declares, when it is in scope and its declaration
1147    /// canonicalized.
1148    pub(crate) fn class_signature(&self, name: &QualName) -> Option<&ClassSignature> {
1149        self.class_signatures.get(name)
1150    }
1151
1152    /// The union `name` declares, when it is this module's own or an import's interface
1153    /// carries it. Its variants are empty when it was imported opaque.
1154    pub(crate) fn find_union(&self, name: &QualName) -> Option<&UnionType> {
1155        self.unions.get(name)
1156    }
1157
1158    /// Every instance the imports brought into scope.
1159    pub(crate) fn imported_instances(&self) -> &[PublishedInstance] {
1160        &self.instances
1161    }
1162
1163    /// Bring an imported instance into scope, unless another route already did.
1164    fn insert_imported_instance(&mut self, published: &PublishedInstance) {
1165        if !self
1166            .instances
1167            .iter()
1168            .any(|known| known.same_declaration(published))
1169        {
1170            self.instances.push(published.clone());
1171        }
1172    }
1173
1174    // TODO Use insert_foreign_union_type (and rename to remove the foreign part)
1175    pub(crate) fn insert_union_type(&mut self, name: Name, union: UnionType) {
1176        self.unions
1177            .insert(self.module_name.qualify_name(&name), union.clone());
1178        self.insert_declared_type(&name, union.variables);
1179
1180        for tctor in union.variants {
1181            self.constructors.insert(tctor.name.clone(), tctor.clone());
1182        }
1183    }
1184}
1185
1186impl<'p> Environment<'p> for RootEnvironment {
1187    fn module_name(&self) -> &ModuleName {
1188        &self.module_name
1189    }
1190
1191    fn find_type(&self, name: &Name) -> Option<&TypeArity> {
1192        self.types.get(name)
1193    }
1194
1195    /// Value names only. An operator symbol is never a key here — an `infix`
1196    /// declaration goes into `infixes`, and no other insertion can produce one,
1197    /// since a function's own name always comes from `VarIdent`, a distinct token
1198    /// from `Op`. Operators resolve through [`find_infix`](Environment::find_infix)
1199    /// and [`InfixFunction`] instead.
1200    fn find_value(&self, name: &Name) -> Option<&ValueType> {
1201        self.variables.get(name)
1202    }
1203
1204    fn find_type_constructor(&self, name: &Name) -> Option<&TypeConstructor> {
1205        self.constructors.get(name)
1206    }
1207
1208    fn value_names(&self) -> Vec<Name> {
1209        self.variables.keys().cloned().collect()
1210    }
1211
1212    fn type_constructor_names(&self) -> Vec<Name> {
1213        self.constructors.keys().cloned().collect()
1214    }
1215
1216    fn local_infix_exists(&self, name: &Name) -> bool {
1217        self.infixes.contains_key(name)
1218    }
1219
1220    fn find_infix(&self, name: &Name) -> Option<&InfixEntry> {
1221        self.infixes.get(name)
1222    }
1223
1224    // TODO Return error if name already exists
1225    // Use import_foreign_value ?
1226    fn insert_local_value(&mut self, name: &Name) {
1227        self.variables.insert(name.clone(), ValueType::Local);
1228    }
1229
1230    fn new_scope<'a>(&'a self) -> ScopedEnvironment<'p, 'a>
1231    where
1232        'p: 'a,
1233    {
1234        ScopedEnvironment {
1235            parent: self,
1236            variables: HashMap::new(),
1237        }
1238    }
1239}
1240
1241/// An Environment scoped to a module's sub expression (`let`, function, etc…)
1242#[derive(Debug)]
1243pub(crate) struct ScopedEnvironment<'root, 'parent> {
1244    parent: &'parent dyn Environment<'root>,
1245    variables: HashMap<Name, ValueType>,
1246}
1247
1248impl<'root, 'parent> Environment<'parent> for ScopedEnvironment<'root, 'parent> {
1249    fn find_type(&self, name: &Name) -> Option<&TypeArity> {
1250        self.parent.find_type(name)
1251    }
1252
1253    fn module_name(&self) -> &ModuleName {
1254        self.parent.module_name()
1255    }
1256
1257    /// A plain lookup in this scope's own bindings, falling back to the
1258    /// parent's `find_value` unchanged — the key handed in is the key looked
1259    /// up, at both levels, with no redirect or alias in between (audited for
1260    /// `BUG-27`, closed: the class of bug that ticket described needs a
1261    /// lookup that lands on a *different* name than the one passed in, and
1262    /// nothing on this path does that. `self.variables` only ever holds
1263    /// [`ValueType::Local`] entries a pattern bound directly, under their own
1264    /// name — see [`ScopedEnvironment::expose_pattern`]).
1265    fn find_value(&self, name: &Name) -> Option<&ValueType> {
1266        self.variables.get(name).or(self.parent.find_value(name))
1267    }
1268
1269    fn find_type_constructor(&self, name: &Name) -> Option<&TypeConstructor> {
1270        self.parent.find_type_constructor(name)
1271    }
1272
1273    fn value_names(&self) -> Vec<Name> {
1274        let mut names = self.parent.value_names();
1275        names.extend(self.variables.keys().cloned());
1276        names
1277    }
1278
1279    fn type_constructor_names(&self) -> Vec<Name> {
1280        // A scope never binds its own constructors — only patterns bind
1281        // values (`expose_pattern`) — so this always delegates.
1282        self.parent.type_constructor_names()
1283    }
1284
1285    fn local_infix_exists(&self, name: &Name) -> bool {
1286        self.parent.local_infix_exists(name)
1287    }
1288
1289    fn find_infix(&self, name: &Name) -> Option<&InfixEntry> {
1290        self.parent.find_infix(name)
1291    }
1292
1293    // TODO Return error if name already exists
1294    // Needs to reuse the logic in import_foreign_value
1295    fn insert_local_value(&mut self, name: &Name) {
1296        self.variables.insert(name.clone(), ValueType::Local);
1297    }
1298
1299    fn new_scope<'a>(&'a self) -> ScopedEnvironment<'parent, 'a>
1300    where
1301        'parent: 'a,
1302    {
1303        let parent: &'a dyn Environment<'parent> = self;
1304
1305        ScopedEnvironment {
1306            parent,
1307            variables: HashMap::new(),
1308        }
1309    }
1310}
1311
1312impl<'root, 'parent> ScopedEnvironment<'root, 'parent> {
1313    pub(crate) fn expose_pattern(&mut self, pattern: &Pattern) {
1314        match &pattern.kind {
1315            PatternKind::Anything => (),
1316            PatternKind::Int(_) => (),
1317            PatternKind::Float(_) => (),
1318            PatternKind::Char(_) => (),
1319            PatternKind::String(_) => (),
1320            PatternKind::Unit => (),
1321
1322            PatternKind::Variable(n) => {
1323                self.variables.insert(n.clone(), ValueType::Local);
1324            }
1325            PatternKind::Tuple(tuple) => {
1326                for pattern in tuple.iter() {
1327                    self.expose_pattern(pattern);
1328                }
1329            }
1330            // A constructor that did not resolve still binds what its arguments bind, so
1331            // a use of one of those names is not reported as a second missing name.
1332            PatternKind::Constructor { args, .. } | PatternKind::Hole(args) => {
1333                for arg in args {
1334                    self.expose_pattern(arg);
1335                }
1336            }
1337            // A record pattern binds what its entries' patterns bind, and nothing for its
1338            // labels: a label is not a value, and `{ x }` binds `x` only because the
1339            // grammar made it `x = x`.
1340            PatternKind::Record(fields) => {
1341                for field in fields {
1342                    self.expose_pattern(&field.pattern);
1343                }
1344            }
1345        }
1346    }
1347}
1348
1349// TODO Tests needs some love to make them easier to read. Currently the assert_eq are making it
1350// hard to grok what is being verified.
1351#[cfg(test)]
1352mod tests {
1353    use super::*;
1354    use crate::canonical::*;
1355
1356    /// [`new_environment`], read as all-or-nothing: the scope when every import
1357    /// resolved, otherwise the errors of the ones that did not.
1358    fn resolved(
1359        module_name: &ModuleName,
1360        interfaces: &HashMap<Name, Interface>,
1361        imports: &[parser::Import],
1362    ) -> Result<RootEnvironment, Vec<EnvError>> {
1363        let (env, errors) = new_environment(module_name, interfaces, imports);
1364        if errors.is_empty() {
1365            Ok(env)
1366        } else {
1367            Err(errors)
1368        }
1369    }
1370
1371    fn module_name() -> ModuleName {
1372        ModuleName::new(PackageName::new("author-project").unwrap(), "module".into())
1373    }
1374
1375    fn import(name: Name, alias: Option<Name>, exposing: parser::Exposing) -> parser::Import {
1376        parser::Import {
1377            name,
1378            alias,
1379            exposing,
1380            // Hand-built, not parsed: there is no source text behind it.
1381            span: NodeSpan::none(),
1382        }
1383    }
1384
1385    fn exposing_open() -> parser::Exposing {
1386        parser::Exposing::Open
1387    }
1388
1389    fn exposing_explicit(exposeds: Vec<parser::Exposed>) -> parser::Exposing {
1390        parser::Exposing::Explicit(exposeds)
1391    }
1392
1393    // module Maybe exposing (andThen, map, withDefault, Maybe(..))
1394    fn maybe_interface() -> (Name, Interface) {
1395        let type_var = |name: &str| Type::Variable(name.into());
1396
1397        // A type's canonical head names the module that declared it, so the
1398        // `Maybe` this interface exports is `Maybe.Maybe`.
1399        let type_hk = |name: &str, params| {
1400            Type::Type(
1401                QualName::parse(PackageName::core(), format!("Maybe.{}", name)).unwrap(),
1402                params,
1403            )
1404        };
1405
1406        let type_fun = |t1, t2| Type::Arrow(Box::new(t1), Box::new(t2));
1407
1408        let mut values = HashMap::new();
1409        // andThen : (a -> Maybe b) -> Maybe a -> Maybe b
1410        values.insert(
1411            "andThen".into(),
1412            ValueSignature::unconstrained(
1413                // Hand-built, not canonicalized from source: no position behind it.
1414                NodeSpan::none(),
1415                type_fun(
1416                    type_fun(type_var("a"), type_hk("Maybe", vec![type_var("b")])),
1417                    type_fun(
1418                        type_hk("Maybe", vec![type_var("a")]),
1419                        type_hk("Maybe", vec![type_var("b")]),
1420                    ),
1421                ),
1422            ),
1423        );
1424        // map : (a -> b) -> Maybe a -> Maybe b
1425        values.insert(
1426            "map".into(),
1427            ValueSignature::unconstrained(
1428                NodeSpan::none(),
1429                type_fun(
1430                    type_fun(type_var("a"), type_var("b")),
1431                    type_fun(
1432                        type_hk("Maybe", vec![type_var("a")]),
1433                        type_hk("Maybe", vec![type_var("b")]),
1434                    ),
1435                ),
1436            ),
1437        );
1438        // withDefault : a -> Maybe a -> a
1439        values.insert(
1440            "withDefault".into(),
1441            ValueSignature::unconstrained(
1442                NodeSpan::none(),
1443                type_fun(
1444                    type_var("a"),
1445                    type_fun(type_hk("Maybe", vec![type_var("a")]), type_var("a")),
1446                ),
1447            ),
1448        );
1449
1450        let mut unions = HashMap::new();
1451        unions.insert(
1452            "Maybe".into(),
1453            UnionType {
1454                span: NodeSpan::none(),
1455                variables: vec![],
1456                variants: vec![
1457                    TypeConstructor {
1458                        name: "Just".into(),
1459                        type_parameters: vec![Type::Variable("a".into())],
1460                        tpe: QualName::parse(PackageName::core(), "Maybe.Maybe").unwrap(),
1461                    },
1462                    TypeConstructor {
1463                        name: "Nothing".into(),
1464                        type_parameters: vec![],
1465                        tpe: QualName::parse(PackageName::core(), "Maybe.Maybe").unwrap(),
1466                    },
1467                ],
1468            },
1469        );
1470
1471        let interface = Interface {
1472            module_name: ModuleName::new(PackageName::new("zelkova-core").unwrap(), "Maybe".into()),
1473            values,
1474            unions,
1475            opaque_unions: Default::default(),
1476            infixes: HashMap::new(),
1477            infix_functions: HashMap::new(),
1478            arities: HashMap::new(),
1479            classes: HashMap::new(),
1480            instances: Vec::new(),
1481            file: None,
1482            incomplete: false,
1483        };
1484
1485        ("Maybe".into(), interface)
1486    }
1487
1488    /// A module that writes no imports, in a package that provides nothing to
1489    /// import, has an empty scope.
1490    ///
1491    /// The interfaces map is empty and that is now load-bearing: `Maybe` is on the
1492    /// [default import list](crate::default_imports), so a package that
1493    /// *has* a `Maybe` puts it in scope here without a written `import` —
1494    /// `default_imports_reach_a_module_that_wrote_none` below is that case.
1495    #[test]
1496    fn new_no_imports() -> Result<(), Vec<EnvError>> {
1497        let interfaces = HashMap::new();
1498        let env = resolved(&module_name(), &interfaces, &[])?;
1499
1500        assert_eq!(env.infixes.len(), 0, "infixes={:?}", env.infixes);
1501        assert_eq!(env.types.len(), 0, "types={:?}", env.types); // qual + explicit
1502        assert_eq!(
1503            env.constructors.len(),
1504            0,
1505            "constructors:{:?}",
1506            env.constructors
1507        );
1508        assert_eq!(env.variables.len(), 0, "variables={:?}", env.variables); // qual + explicit
1509
1510        Ok(())
1511    }
1512
1513    /// `LANG-8`: a module with no `import` line at all still resolves `Maybe`,
1514    /// `Just` and `Maybe.map`, because `Maybe` is on the default import list and
1515    /// the package provides it.
1516    ///
1517    /// Asserted through the same `find_*` lookups `new_import_type_with_constructors`
1518    /// uses for a written `import Maybe exposing (Maybe(..))`, because that is the
1519    /// claim: an implicitly imported name is indistinguishable from one the file
1520    /// named. `withDefault` is the counter-assertion — the entry exposes the type
1521    /// and not the module's values, so it is reachable only qualified.
1522    ///
1523    /// Mutation-checked by dropping the `implicit` half of `new_environment`'s
1524    /// import loop: every assertion below goes red.
1525    #[test]
1526    fn default_imports_reach_a_module_that_wrote_none() -> Result<(), Vec<EnvError>> {
1527        let mut interfaces = HashMap::new();
1528        {
1529            let (name, iface) = maybe_interface();
1530            interfaces.insert(name, iface);
1531        }
1532        let env = resolved(&module_name(), &interfaces, &[])?;
1533
1534        assert!(
1535            env.find_type(&"Maybe".into()).is_some(),
1536            "type Maybe not found"
1537        );
1538        assert!(
1539            env.find_type_constructor(&"Just".into()).is_some(),
1540            "type constructor Just not found"
1541        );
1542        assert!(
1543            env.find_value(&"Maybe.withDefault".into()).is_some(),
1544            "value Maybe.withDefault not found"
1545        );
1546        assert!(
1547            env.find_value(&"withDefault".into()).is_none(),
1548            "`exposing (Maybe(..))` names the type, not the module's values"
1549        );
1550
1551        Ok(())
1552    }
1553
1554    /// The `Char` and `String` entries expose their types: a module that wrote no
1555    /// import resolves `Char` and `String` unqualified, and reaches `String.length`
1556    /// only qualified.
1557    ///
1558    /// Mutation-checked by setting both entries of `DEFAULT_IMPORTS` back to
1559    /// `Unqualified::Nothing`: `Char` is no longer found.
1560    #[test]
1561    fn char_and_string_default_imports_expose_their_types() -> Result<(), Vec<EnvError>> {
1562        // An opaque `type <module>` declared by `module`, plus a `length` for `String`.
1563        let scalar_interface = |module: &str| {
1564            let mut unions = HashMap::new();
1565            unions.insert(
1566                Name::new(module),
1567                UnionType {
1568                    span: NodeSpan::none(),
1569                    variables: vec![],
1570                    variants: vec![],
1571                },
1572            );
1573
1574            let mut values = HashMap::new();
1575            values.insert(
1576                "length".into(),
1577                ValueSignature::unconstrained(
1578                    NodeSpan::none(),
1579                    Type::Type(
1580                        QualName::parse(PackageName::core(), "Basics.Int").unwrap(),
1581                        vec![],
1582                    ),
1583                ),
1584            );
1585
1586            (
1587                Name::new(module),
1588                Interface {
1589                    module_name: ModuleName::new(
1590                        PackageName::new("zelkova-core").unwrap(),
1591                        Name::new(module),
1592                    ),
1593                    values,
1594                    unions,
1595                    opaque_unions: Default::default(),
1596                    infixes: HashMap::new(),
1597                    infix_functions: HashMap::new(),
1598                    arities: HashMap::new(),
1599                    classes: HashMap::new(),
1600                    instances: Vec::new(),
1601                    file: None,
1602                    incomplete: false,
1603                },
1604            )
1605        };
1606        let interfaces = HashMap::from([scalar_interface("Char"), scalar_interface("String")]);
1607        let env = resolved(&module_name(), &interfaces, &[])?;
1608
1609        assert!(
1610            env.find_type(&"Char".into()).is_some(),
1611            "type Char not found"
1612        );
1613        assert!(
1614            env.find_type(&"String".into()).is_some(),
1615            "type String not found"
1616        );
1617        assert!(
1618            env.find_value(&"String.length".into()).is_some(),
1619            "value String.length not found"
1620        );
1621        assert!(
1622            env.find_value(&"length".into()).is_none(),
1623            "`exposing (String)` names the type, not the module's values"
1624        );
1625
1626        Ok(())
1627    }
1628
1629    /// A module of `zelkova-core` receives none of the eight: `Basics` cannot
1630    /// implicitly import `Basics`, and `Maybe` importing `Result` importing
1631    /// `Maybe` is the cycle `dependencies` exists to reject. `maybe`'s package is
1632    /// how `new_environment` learns that — `module_name.package().is_core()` —
1633    /// rather than `new_environment` inspecting `maybe`'s own name.
1634    ///
1635    /// The five scalar names still arrive (`LANG-58`) — seeding them is a
1636    /// separate question from the eight default imports, and answered `yes`
1637    /// regardless of which module of the package this is — so `types` is not
1638    /// empty; it holds exactly the five and none of `Maybe`'s own.
1639    ///
1640    /// Mutation-checked by dropping the `is_core` guard in
1641    /// `default_imports::implicit_imports`, which puts `Maybe` in its own scope
1642    /// and turns the `variables` assertion red (`andThen`, `map` and
1643    /// `withDefault` would resolve).
1644    #[test]
1645    fn a_default_module_does_not_import_itself() -> Result<(), Vec<EnvError>> {
1646        let mut interfaces = HashMap::new();
1647        {
1648            let (name, iface) = maybe_interface();
1649            interfaces.insert(name, iface);
1650        }
1651        let maybe = ModuleName::new(PackageName::new("zelkova-core").unwrap(), "Maybe".into());
1652        let env = resolved(&maybe, &interfaces, &[])?;
1653
1654        let mut scalar_names: Vec<&str> = env.types.keys().map(Name::as_str).collect();
1655        scalar_names.sort_unstable();
1656        assert_eq!(
1657            scalar_names,
1658            vec!["Bool", "Char", "Float", "Int", "String"],
1659            "types={:?}",
1660            env.types
1661        );
1662        assert_eq!(env.variables.len(), 0, "variables={:?}", env.variables);
1663
1664        Ok(())
1665    }
1666
1667    /// `LANG-58`: a module of a package exempt from the default imports
1668    /// resolves `Int`, `Float`, `Char`, `String` and `Bool` as types bound to
1669    /// the qualified names `scalars::SCALARS` holds, with no interface for
1670    /// `Basics` (or anything else) available to consult and no `import`
1671    /// written. Only the type names arrive: no constructor, and no value.
1672    ///
1673    /// The empty `interfaces` map is load-bearing — if seeding looked `Basics`
1674    /// up to resolve these names, there would be nothing here for it to find.
1675    ///
1676    /// Mutation-checked by removing the scalar-seeding block from
1677    /// `new_environment`: every `find_type` below returns `None` instead of
1678    /// `Some`.
1679    #[test]
1680    fn scalar_names_are_seeded_without_reading_any_interface() -> Result<(), Vec<EnvError>> {
1681        let interfaces = HashMap::new();
1682        let js_basics = ModuleName::new(
1683            PackageName::new("zelkova-core").unwrap(),
1684            "Js.Basics".into(),
1685        );
1686        let env = resolved(&js_basics, &interfaces, &[])?;
1687
1688        let int = env.find_type(&"Int".into()).expect("Int should be seeded");
1689        assert_eq!(
1690            int.name,
1691            QualName::parse(PackageName::core(), "Basics.Int").unwrap()
1692        );
1693        assert_eq!(int.arity(), 0);
1694
1695        let float = env
1696            .find_type(&"Float".into())
1697            .expect("Float should be seeded");
1698        assert_eq!(
1699            float.name,
1700            QualName::parse(PackageName::core(), "Basics.Float").unwrap()
1701        );
1702
1703        let bool_ = env
1704            .find_type(&"Bool".into())
1705            .expect("Bool should be seeded");
1706        assert_eq!(
1707            bool_.name,
1708            QualName::parse(PackageName::core(), "Basics.Bool").unwrap()
1709        );
1710
1711        let char_ = env
1712            .find_type(&"Char".into())
1713            .expect("Char should be seeded");
1714        assert_eq!(
1715            char_.name,
1716            QualName::parse(PackageName::core(), "Char.Char").unwrap()
1717        );
1718
1719        let string = env
1720            .find_type(&"String".into())
1721            .expect("String should be seeded");
1722        assert_eq!(
1723            string.name,
1724            QualName::parse(PackageName::core(), "String.String").unwrap()
1725        );
1726
1727        // Type names only: no constructor and no value comes with them.
1728        assert!(
1729            env.find_type_constructor(&"True".into()).is_none(),
1730            "no constructor should be seeded for Bool"
1731        );
1732        assert!(
1733            env.find_value(&"True".into()).is_none(),
1734            "no value should be seeded for Bool"
1735        );
1736        assert_eq!(env.variables.len(), 0, "variables={:?}", env.variables);
1737        assert_eq!(
1738            env.constructors.len(),
1739            0,
1740            "constructors={:?}",
1741            env.constructors
1742        );
1743
1744        Ok(())
1745    }
1746
1747    /// A written `import` of a default module replaces the implicit one, rather
1748    /// than registering every name twice and making each *use* of one ambiguous.
1749    ///
1750    /// Mutation-checked by dropping the `written.iter().any(..)` filter in
1751    /// `default_imports::implicit_imports`: `Maybe.map` becomes
1752    /// `ValueType::Foreigns` and the assertion below goes red.
1753    #[test]
1754    fn a_written_default_import_is_not_registered_twice() -> Result<(), Vec<EnvError>> {
1755        let mut interfaces = HashMap::new();
1756        {
1757            let (name, iface) = maybe_interface();
1758            interfaces.insert(name, iface);
1759        }
1760        let imports = vec![import("Maybe".into(), None, exposing_open())];
1761        let env = resolved(&module_name(), &interfaces, &imports)?;
1762
1763        match env.find_value(&"Maybe.map".into()) {
1764            Some(ValueType::Foreign(..)) => (),
1765            other => panic!("expected one candidate for `Maybe.map`, got {:?}", other),
1766        }
1767        match env.find_value(&"map".into()) {
1768            Some(ValueType::Foreign(..)) => (),
1769            other => panic!("expected one candidate for `map`, got {:?}", other),
1770        }
1771
1772        Ok(())
1773    }
1774
1775    #[test]
1776    fn new_import_open() -> Result<(), Vec<EnvError>> {
1777        let imports = vec![import("Maybe".into(), None, exposing_open())];
1778        let mut interfaces = HashMap::new();
1779        {
1780            let (name, iface) = maybe_interface();
1781            interfaces.insert(name, iface);
1782        }
1783        let env = resolved(&module_name(), &interfaces, &imports)?;
1784
1785        // Assert we have the expected
1786        assert!(
1787            env.find_value(&"andThen".into()).is_some(),
1788            "value andThen not found"
1789        );
1790        assert!(
1791            env.find_value(&"map".into()).is_some(),
1792            "value map not found"
1793        );
1794        assert!(
1795            env.find_value(&"withDefault".into()).is_some(),
1796            "value withDefault not found"
1797        );
1798
1799        assert!(
1800            env.find_value(&"Maybe.andThen".into()).is_some(),
1801            "value Maybe.andThen not found"
1802        );
1803        assert!(
1804            env.find_value(&"Maybe.map".into()).is_some(),
1805            "value Maybe.map not found"
1806        );
1807        assert!(
1808            env.find_value(&"Maybe.withDefault".into()).is_some(),
1809            "value Maybe.withDefault not found"
1810        );
1811
1812        assert!(
1813            env.find_type(&"Maybe".into()).is_some(),
1814            "type Maybe not found"
1815        );
1816
1817        assert!(
1818            env.find_type_constructor(&"Maybe.Just".into()).is_some(),
1819            "type constructor Maybe.Just not found"
1820        );
1821        assert!(
1822            env.find_type_constructor(&"Maybe.Nothing".into()).is_some(),
1823            "type constructor Maybe.Nothing not found"
1824        );
1825        assert!(
1826            env.find_type_constructor(&"Just".into()).is_some(),
1827            "type constructor Just not found"
1828        );
1829        assert!(
1830            env.find_type_constructor(&"Nothing".into()).is_some(),
1831            "type constructor Nothing not found"
1832        );
1833
1834        // Make sure we don't have more than what is expected
1835        assert_eq!(env.infixes.len(), 0, "infixes={:?}", env.infixes);
1836        assert_eq!(env.types.len(), 1 + 1, "types={:?}", env.types); // qual + explicit
1837        assert_eq!(
1838            env.constructors.len(),
1839            4,
1840            "constructors:{:#?}",
1841            env.constructors
1842        );
1843        assert_eq!(env.variables.len(), 3 + 3, "variables={:?}", env.variables); // qual + explicit
1844
1845        Ok(())
1846    }
1847
1848    #[test]
1849    fn new_import_values() -> Result<(), Vec<EnvError>> {
1850        let imports = vec![import(
1851            "Maybe".into(),
1852            None,
1853            exposing_explicit(vec![
1854                parser::Exposed::bare(parser::ExposedKind::Upper(
1855                    "Maybe".into(),
1856                    parser::Privacy::Private,
1857                )),
1858                parser::Exposed::bare(parser::ExposedKind::Lower("andThen".into())),
1859            ]),
1860        )];
1861        let mut interfaces = HashMap::new();
1862        {
1863            let (name, iface) = maybe_interface();
1864            interfaces.insert(name, iface);
1865        }
1866        let env = resolved(&module_name(), &interfaces, &imports)?;
1867
1868        // Lookup the expected values
1869        assert!(
1870            env.find_value(&"andThen".into()).is_some(),
1871            "value andThen not found"
1872        );
1873
1874        assert!(
1875            env.find_value(&"Maybe.andThen".into()).is_some(),
1876            "value Maybe.andThen not found"
1877        );
1878        assert!(
1879            env.find_value(&"Maybe.map".into()).is_some(),
1880            "value Maybe.map not found"
1881        );
1882        assert!(
1883            env.find_value(&"Maybe.withDefault".into()).is_some(),
1884            "value Maybe.withDefault not found"
1885        );
1886
1887        assert!(
1888            env.find_type(&"Maybe.Maybe".into()).is_some(),
1889            "type Maybe.Maybe not found"
1890        );
1891        assert!(
1892            env.find_type(&"Maybe".into()).is_some(),
1893            "type Maybe not found"
1894        );
1895
1896        // Make sure we don't have more than what is expected
1897        assert_eq!(env.infixes.len(), 0, "infixes={:?}", env.infixes);
1898        assert_eq!(env.types.len(), 1 + 1, "types={:?}", env.types); // qual + explicit
1899        assert_eq!(
1900            env.constructors.len(),
1901            2,
1902            "constructors:{:?}",
1903            env.constructors
1904        );
1905        assert_eq!(
1906            env.variables.len(),
1907            3 + 1, // qual + explicit
1908            "variables={:?}",
1909            env.variables.keys()
1910        );
1911
1912        Ok(())
1913    }
1914
1915    #[test]
1916    fn new_import_type_with_constructors() -> Result<(), Vec<EnvError>> {
1917        let imports = vec![import(
1918            "Maybe".into(),
1919            None,
1920            exposing_explicit(vec![parser::Exposed::bare(parser::ExposedKind::Upper(
1921                "Maybe".into(),
1922                parser::Privacy::Public,
1923            ))]),
1924        )];
1925        let mut interfaces = HashMap::new();
1926        {
1927            let (name, iface) = maybe_interface();
1928            interfaces.insert(name, iface);
1929        }
1930        let env = resolved(&module_name(), &interfaces, &imports)?;
1931
1932        // Lookup the expected
1933        assert!(
1934            env.find_value(&"Maybe.andThen".into()).is_some(),
1935            "value Maybe.andThen not found"
1936        );
1937        assert!(
1938            env.find_value(&"Maybe.map".into()).is_some(),
1939            "value Maybe.map not found"
1940        );
1941        assert!(
1942            env.find_value(&"Maybe.withDefault".into()).is_some(),
1943            "value Maybe.withDefault not found"
1944        );
1945
1946        assert!(
1947            env.find_type(&"Maybe".into()).is_some(),
1948            "type Maybe not found"
1949        );
1950        assert!(
1951            env.find_type(&"Maybe.Maybe".into()).is_some(),
1952            "type Maybe.Maybe not found"
1953        );
1954
1955        assert!(
1956            env.find_type_constructor(&"Maybe.Just".into()).is_some(),
1957            "type constructor Maybe.Just not found"
1958        );
1959        assert!(
1960            env.find_type_constructor(&"Maybe.Nothing".into()).is_some(),
1961            "type constructor Maybe.Nothing not found"
1962        );
1963        assert!(
1964            env.find_type_constructor(&"Just".into()).is_some(),
1965            "type constructor Just not found"
1966        );
1967        assert!(
1968            env.find_type_constructor(&"Nothing".into()).is_some(),
1969            "type constructor Nothing not found"
1970        );
1971
1972        // Make sure we don't have more than what is expected
1973        assert_eq!(env.infixes.len(), 0, "infixes={:?}", env.infixes);
1974        assert_eq!(env.types.len(), 2, "types={:?}", env.types); // qual + explicit
1975        assert_eq!(
1976            env.constructors.len(),
1977            4,
1978            "constructors:{:?}",
1979            env.constructors
1980        );
1981        assert_eq!(
1982            env.variables.len(),
1983            3,
1984            "variables={:?}",
1985            env.variables.keys()
1986        ); // qual + explicit
1987
1988        Ok(())
1989    }
1990
1991    #[test]
1992    fn new_import_type_with_constructors_and_aliases() -> Result<(), Vec<EnvError>> {
1993        let imports = vec![import(
1994            "Maybe".into(),
1995            Some("M".into()),
1996            exposing_explicit(vec![parser::Exposed::bare(parser::ExposedKind::Upper(
1997                "Maybe".into(),
1998                parser::Privacy::Public,
1999            ))]),
2000        )];
2001        let mut interfaces = HashMap::new();
2002        {
2003            let (name, iface) = maybe_interface();
2004            interfaces.insert(name, iface);
2005        }
2006        let env = resolved(&module_name(), &interfaces, &imports)?;
2007
2008        // Lookup the expected
2009        assert!(
2010            env.find_value(&"M.andThen".into()).is_some(),
2011            "value M.andThen not found"
2012        );
2013        assert!(
2014            env.find_value(&"M.map".into()).is_some(),
2015            "value M.map not found"
2016        );
2017        assert!(
2018            env.find_value(&"M.withDefault".into()).is_some(),
2019            "value M.withDefault not found"
2020        );
2021
2022        assert!(
2023            env.find_type(&"Maybe".into()).is_some(),
2024            "type Maybe not found"
2025        );
2026        assert!(
2027            env.find_type(&"M.Maybe".into()).is_some(),
2028            "type M.Maybe not found"
2029        );
2030
2031        assert!(
2032            env.find_type_constructor(&"M.Just".into()).is_some(),
2033            "type constructor M.Just not found"
2034        );
2035        assert!(
2036            env.find_type_constructor(&"M.Nothing".into()).is_some(),
2037            "type constructor M.Nothing not found"
2038        );
2039
2040        assert!(
2041            env.find_type_constructor(&"Just".into()).is_some(),
2042            "type constructor Just not found"
2043        );
2044        assert!(
2045            env.find_type_constructor(&"Nothing".into()).is_some(),
2046            "type constructor Nothing not found"
2047        );
2048
2049        // Make sure we don't have more than what is expected
2050        assert_eq!(env.infixes.len(), 0, "infixes={:?}", env.infixes);
2051        assert_eq!(env.types.len(), 2, "types={:?}", env.types);
2052        assert_eq!(
2053            env.constructors.len(),
2054            4,
2055            "constructors:{:?}",
2056            env.constructors
2057        );
2058        assert_eq!(
2059            env.variables.len(),
2060            3,
2061            "variables={:?}",
2062            env.variables.keys()
2063        );
2064
2065        Ok(())
2066    }
2067
2068    // ── ERR-7: "did you mean …?" suggestions ────────────────────────────────
2069    //
2070    // These hand-build every `Import`/`Exposed` with `NodeSpan::none()` (see the
2071    // `import`/`exposing_*` helpers above), so `EnvError::labels` — which needs a
2072    // real span to attach a caret to — renders nothing for them; that path is
2073    // covered instead by `crates/zelkova-compiler/tests/canonical.rs`, which goes through the
2074    // parser and so has real spans. These assert directly on the `Option<Name>`
2075    // suggestion field each variant now carries.
2076
2077    /// A minimal interface with one infix, `maybe_interface` has none — needed to
2078    /// exercise `EnvError::InfixNotFound`'s suggestion.
2079    fn ops_interface() -> (Name, Interface) {
2080        let mut infixes = HashMap::new();
2081        infixes.insert(
2082            "plus".into(),
2083            Infix {
2084                associativity: Associativity::Left,
2085                precedence: 6,
2086                function_name: "add".into(),
2087                span: NodeSpan::none(),
2088            },
2089        );
2090
2091        let interface = Interface {
2092            module_name: ModuleName::new(PackageName::new("test-project").unwrap(), "Ops".into()),
2093            values: HashMap::new(),
2094            unions: HashMap::new(),
2095            opaque_unions: Default::default(),
2096            infixes,
2097            infix_functions: HashMap::new(),
2098            arities: HashMap::new(),
2099            classes: HashMap::new(),
2100            instances: Vec::new(),
2101            file: None,
2102            incomplete: false,
2103        };
2104
2105        ("Ops".into(), interface)
2106    }
2107
2108    /// Neutralising the `suggest` call in the `InterfaceNotFound` arm of
2109    /// `process_import` (passing `None` instead) turns this red: `suggestion`
2110    /// becomes `None` where the test expects `Some("Maybe")`.
2111    #[test]
2112    fn unknown_module_suggests_a_near_miss() {
2113        let imports = vec![import("Mabye".into(), None, exposing_open())];
2114        let mut interfaces = HashMap::new();
2115        {
2116            let (name, iface) = maybe_interface();
2117            interfaces.insert(name, iface);
2118        }
2119
2120        let errors = resolved(&module_name(), &interfaces, &imports)
2121            .expect_err("an unknown module should not resolve");
2122        assert_eq!(errors.len(), 1, "got {:?}", errors);
2123
2124        match &errors[0] {
2125            EnvError::InterfaceNotFound(name, _, suggestion) => {
2126                assert_eq!(name, &Name::from("Mabye"));
2127                assert_eq!(suggestion, &Some(Name::from("Maybe")));
2128            }
2129            other => panic!("expected InterfaceNotFound, got {:?}", other),
2130        }
2131    }
2132
2133    /// A module name resembling nothing among the known interfaces gets no
2134    /// suggestion — a bad one would send the reader to check an unrelated import.
2135    #[test]
2136    fn unrelated_unknown_module_has_no_suggestion() {
2137        let imports = vec![import("Zzzzzzzzzzzz".into(), None, exposing_open())];
2138        let mut interfaces = HashMap::new();
2139        {
2140            let (name, iface) = maybe_interface();
2141            interfaces.insert(name, iface);
2142        }
2143
2144        let errors = resolved(&module_name(), &interfaces, &imports)
2145            .expect_err("an unknown module should not resolve");
2146        assert_eq!(errors.len(), 1, "got {:?}", errors);
2147
2148        match &errors[0] {
2149            EnvError::InterfaceNotFound(_, _, suggestion) => {
2150                assert_eq!(suggestion, &None);
2151            }
2152            other => panic!("expected InterfaceNotFound, got {:?}", other),
2153        }
2154    }
2155
2156    /// A dotted module name — `Js.Basics`, `Js.Utils` and `Js.Bitwise` all exist
2157    /// in `std/core/src` today — is one identifier, not a module prefix plus a
2158    /// local part. Routing this through `suggest_name` would compare only the
2159    /// segment after the last `.` and only against candidates sharing the prefix
2160    /// exactly, so a typo anywhere else, or a dropped dot, would get nothing.
2161    /// All three of these are one edit from `Js.Basics`.
2162    #[test]
2163    fn unknown_dotted_module_suggests_on_the_whole_name() {
2164        let mut interfaces = HashMap::new();
2165        {
2166            let (_, iface) = maybe_interface();
2167            interfaces.insert("Js.Basics".into(), iface);
2168        }
2169
2170        // `Js.Basicz` shares the prefix, `Jz.Basics` does not, and `JsBasics` has
2171        // no prefix at all — the last is the likeliest typo of the three.
2172        for typo in ["Js.Basicz", "Jz.Basics", "JsBasics"] {
2173            let imports = vec![import(typo.into(), None, exposing_open())];
2174            let errors = resolved(&module_name(), &interfaces, &imports)
2175                .expect_err("an unknown module should not resolve");
2176            assert_eq!(errors.len(), 1, "got {:?}", errors);
2177
2178            match &errors[0] {
2179                EnvError::InterfaceNotFound(name, _, suggestion) => {
2180                    assert_eq!(name, &Name::from(typo));
2181                    assert_eq!(
2182                        suggestion,
2183                        &Some(Name::from("Js.Basics")),
2184                        "no suggestion for `{}`",
2185                        typo
2186                    );
2187                }
2188                other => panic!("expected InterfaceNotFound, got {:?}", other),
2189            }
2190        }
2191    }
2192
2193    /// The `Explicit` exposing arm wraps whatever `collect_accumulate` returns in
2194    /// `EnvError::Multiple`, even for a single failing entry — see
2195    /// `process_import`'s last statement — so the `UnionNotFound` this produces is
2196    /// nested one level deeper than `InterfaceNotFound`'s.
2197    ///
2198    /// Neutralising the `suggest_name` call in the `Upper(.., Public)` arm of
2199    /// `process_import` (passing `None` instead) turns this red.
2200    #[test]
2201    fn unknown_exposed_type_suggests_a_near_miss() {
2202        let imports = vec![import(
2203            "Maybe".into(),
2204            None,
2205            exposing_explicit(vec![parser::Exposed::bare(parser::ExposedKind::Upper(
2206                "Mayeb".into(),
2207                parser::Privacy::Public,
2208            ))]),
2209        )];
2210        let mut interfaces = HashMap::new();
2211        {
2212            let (name, iface) = maybe_interface();
2213            interfaces.insert(name, iface);
2214        }
2215
2216        let errors = resolved(&module_name(), &interfaces, &imports)
2217            .expect_err("an unknown exposed type should not resolve");
2218        assert_eq!(errors.len(), 1, "got {:?}", errors);
2219
2220        let inner = match &errors[0] {
2221            EnvError::Multiple(inner) => inner,
2222            other => panic!("expected Multiple, got {:?}", other),
2223        };
2224        assert_eq!(inner.len(), 1, "got {:?}", inner);
2225
2226        match &inner[0] {
2227            EnvError::UnionNotFound(name, _, suggestion) => {
2228                assert_eq!(name, &Name::from("Mayeb"));
2229                assert_eq!(suggestion, &Some(Name::from("Maybe")));
2230            }
2231            other => panic!("expected UnionNotFound, got {:?}", other),
2232        }
2233    }
2234
2235    /// A bare `Mayeb` entry is checked against the interface exactly as `Mayeb(..)`
2236    /// is: the two differ in whether the constructors come along, not in whether the
2237    /// type has to exist, so both report the same `UnionNotFound` with the same
2238    /// suggestion (`BUG-16`).
2239    ///
2240    /// Mutation-checked by restoring the arm's old body — reading the variables with
2241    /// `interface.unions.get(type_name).map(..).unwrap_or_default()` and inserting
2242    /// unconditionally: the import then succeeds and `expect_err` panics.
2243    #[test]
2244    fn unknown_opaque_exposed_type_suggests_a_near_miss() {
2245        let imports = vec![import(
2246            "Maybe".into(),
2247            None,
2248            exposing_explicit(vec![parser::Exposed::bare(parser::ExposedKind::Upper(
2249                "Mayeb".into(),
2250                parser::Privacy::Private,
2251            ))]),
2252        )];
2253        let mut interfaces = HashMap::new();
2254        {
2255            let (name, iface) = maybe_interface();
2256            interfaces.insert(name, iface);
2257        }
2258
2259        let errors = resolved(&module_name(), &interfaces, &imports)
2260            .expect_err("an unknown opaquely exposed type should not resolve");
2261        assert_eq!(errors.len(), 1, "got {:?}", errors);
2262
2263        let inner = match &errors[0] {
2264            EnvError::Multiple(inner) => inner,
2265            other => panic!("expected Multiple, got {:?}", other),
2266        };
2267        assert_eq!(inner.len(), 1, "got {:?}", inner);
2268
2269        match &inner[0] {
2270            EnvError::UnionNotFound(name, _, suggestion) => {
2271                assert_eq!(name, &Name::from("Mayeb"));
2272                assert_eq!(suggestion, &Some(Name::from("Maybe")));
2273            }
2274            other => panic!("expected UnionNotFound, got {:?}", other),
2275        }
2276    }
2277
2278    /// A `Maybe(..)` entry against an interface that lists `Maybe` in
2279    /// `opaque_unions` is `ConstructorsNotExposed`, nested in `Multiple` like the
2280    /// other entry errors, and carries the module name as the `import` wrote it.
2281    ///
2282    /// Mutation-checked by deleting the `opaque_unions.contains` early return in
2283    /// the `Upper(.., Public)` arm of `process_import`: the import then succeeds
2284    /// and `expect_err` panics.
2285    #[test]
2286    fn constructor_entry_for_an_opaque_type_is_rejected() {
2287        use zelkova_syntax::position::BytePos;
2288        let entry_span = NodeSpan::new(BytePos(20), BytePos(30));
2289        let imports = vec![import(
2290            "Maybe".into(),
2291            None,
2292            exposing_explicit(vec![parser::Exposed::new(
2293                entry_span,
2294                parser::ExposedKind::Upper("Maybe".into(), parser::Privacy::Public),
2295            )]),
2296        )];
2297        let mut interfaces = HashMap::new();
2298        {
2299            let (name, mut iface) = maybe_interface();
2300            if let Some(union) = iface.unions.get_mut(&name) {
2301                union.variants.clear();
2302            }
2303            iface.opaque_unions.insert(name.clone());
2304            interfaces.insert(name, iface);
2305        }
2306
2307        let errors = resolved(&module_name(), &interfaces, &imports)
2308            .expect_err("constructors the module does not expose should not resolve");
2309        assert_eq!(errors.len(), 1, "got {:?}", errors);
2310
2311        let inner = match &errors[0] {
2312            EnvError::Multiple(inner) => inner,
2313            other => panic!("expected Multiple, got {:?}", other),
2314        };
2315        assert_eq!(inner.len(), 1, "got {:?}", inner);
2316
2317        match &inner[0] {
2318            EnvError::ConstructorsNotExposed(name, module, span) => {
2319                assert_eq!(name, &Name::from("Maybe"));
2320                assert_eq!(module, &Name::from("Maybe"));
2321                assert_eq!(span.span(), entry_span.span());
2322            }
2323            other => panic!("expected ConstructorsNotExposed, got {:?}", other),
2324        }
2325    }
2326
2327    /// Same nesting as the type case above, for `EnvError::InfixNotFound`.
2328    ///
2329    /// Neutralising the `suggest_name` call in the `Operator` arm of
2330    /// `process_import` (passing `None` instead) turns this red.
2331    #[test]
2332    fn unknown_exposed_infix_suggests_a_near_miss() {
2333        let imports = vec![import(
2334            "Ops".into(),
2335            None,
2336            exposing_explicit(vec![parser::Exposed::bare(parser::ExposedKind::Operator(
2337                "pluss".into(),
2338            ))]),
2339        )];
2340        let mut interfaces = HashMap::new();
2341        {
2342            let (name, iface) = ops_interface();
2343            interfaces.insert(name, iface);
2344        }
2345
2346        let errors = resolved(&module_name(), &interfaces, &imports)
2347            .expect_err("an unknown exposed infix should not resolve");
2348        assert_eq!(errors.len(), 1, "got {:?}", errors);
2349
2350        let inner = match &errors[0] {
2351            EnvError::Multiple(inner) => inner,
2352            other => panic!("expected Multiple, got {:?}", other),
2353        };
2354        assert_eq!(inner.len(), 1, "got {:?}", inner);
2355
2356        match &inner[0] {
2357            EnvError::InfixNotFound(name, _, suggestion) => {
2358                assert_eq!(name, &Name::from("pluss"));
2359                assert_eq!(suggestion, &Some(Name::from("plus")));
2360            }
2361            other => panic!("expected InfixNotFound, got {:?}", other),
2362        }
2363    }
2364}