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}