Language Imports

Imports

An import names a directory, not a file. Every .hvr file directly inside it is imported together, as one unit sharing one namespace — the same rule Go uses for packages and Python uses for packages.

Two things to choose, independently of each other:

  • Which directory — the standard library, an installed package, or a path relative to the importing file.
  • How the names arrive — behind a qualifier (the default), or bound directly.

The four forms

Every form works with every path style below.

Whole directory, qualified

The default, and always qualified. The binding name is the last path segmentimport <semiconductors/bjt>; binds bjt — so bjt.NPN reads naturally without anyone having to name it.

Qualified-by-default is deliberate. Merging into a flat namespace would mean adding a declaration to a library could break its consumers, and a reader could not tell where a name came from without checking every import in the file. Nothing an import brings in can shadow anything, so there is no collision to resolve.

Hyphens and dots become underscores, since a package directory may be called anything: import <hvr-rc>; binds hvr_rc. If the automatic name is ugly or illegal, as overrides it — and a name that could never be an identifier is an error naming the import, not a syntax error at the first use:

Whole directory, renamed

Qualifiers are per-file and one level deep. Your name for a directory is yours alone — another file importing the same directory under a different name never interacts with it — and there is no A.B.Thing, because imports are not transitive.

Specific declarations

Only the listed names become visible, under their local spelling, with no qualifier. This is the escape hatch that keeps math-dense analog bodies readable: one line at the top, and sin(x) reads exactly as it always did.

Unlike a whole-directory import, these names do land in your flat namespace, so they can collide — with each other, or with your own declarations. That is an error, and as resolves it by renaming just the one name that clashes.

A selected name may be a module or a function. If the directory declares neither, you are told what it does declare:

Note the two meanings of as, which never mix:

import <d> as N;Qualify the whole directory as N.
from <d> import X as N;Rename the one declaration X to N.

from is not a reserved word

It starts an import only when a path follows it, so from remains usable as an identifier:

That declaration is in the standard library today, and naming the terminals of a two-port from/to is natural enough that reserving the word was not worth it.

Which directory

Relative — "./quoted"

Resolved relative to the directory of the file that wrote the import — not the entry file, and not the current directory.

Angle brackets — <name/path>

The first segment is a package name.

There is no @ or other marker separating a package from the standard library, and that is the point: the standard library is an ordinary installable package. hover --setup installs it into the machine-wide project in ~/.hover, and import <math> resolves through exactly the same package table as import <hvr-rc>. It ships as one package called stdlib whose top-level directories (math, semiconductors, optoelectronics, electromechanical) each become an importable root of their own — so <math> and <stdlib/math> name the same directory and both work. See Standard library.

Resolution order:

  1. The package table built from lockfiles. What goes into it depends on where the entry file lives:
    CompilingThe table contains
    A file inside a projectThat project's hover.lock, plus the machine's standard library
    A loose .hvr file, no projectEverything installed machine-wide in ~/.hover
    Inside a project, other machine-wide packages are ignored on purpose: a project declares its dependencies, so a build must not silently pick up whatever happens to be installed on this particular machine.
  2. The bundled stdlib/, in the directory next to the hover executable — the fallback for a source tree built from the repository, where the standard library ships beside the binary.

So a project that pins a package named math overrides the standard one. That is how you pin a standard-library version (stdlib = "0.8.1" in hover.toml), and it is auditable rather than silent — everything consulted comes from a committed hover.lock.

Worth knowing: a transitive dependency named math also lands in that lockfile, and would override it project-wide. It is visible in hover.lock and hover hpm list, but it is a real reason to look at what a new dependency drags in.

An index-qualified name (myindex:vendor-parts) is only ever a package — it never falls back to the standard library, so an added index cannot satisfy an import meant for stdlib. It is also a different package from an unqualified one with the same name: the qualifier is part of the identity, which is why a third-party index can never shadow an official name.

Compiling never installs anything. Package paths resolve through hover.lock only; a build never touches the network. If a package is named in source but missing, the message depends on what you meant:

The second form is how a fresh install that never ran --setup finds out. Releases do not bundle the standard library.

See Packages & hpm for how packages get installed, and the package example for one you can build and install.

One directory, one namespace

This is what makes an import name a directory. Sibling files see each other's declarations without importing anything:

A consumer writes one import <hvr-rc>; and sees both. How the author split their package across files is not something consumers know, or should care about.

The flip side: two files in one directory may not declare the same name. There would be no way to say which one a reference meant, so it is an error with both source locations:

Importing your own directory is likewise an error, not a cycle — the names are already there:

Subdirectories are separate units. import <semiconductors>; gives you diode.hvr and nothing from semiconductors/bjt/; that needs its own import <semiconductors/bjt>;. Recursing would make one import silently pull in an entire tree.

The entry file is not merged with its siblings. You compile a file, and that file is its own scope — so a directory of independent testbenches, each declaring main, keeps working.

An imported directory must contain at least one .hvr file — an empty one is an error naming the directory that was found, which catches a misspelt path that happens to exist.

Imports are not transitive

If A imports B and B imports C, then A sees B's own declarations and not C's. This is Go's rule, and it means adding an import to a library never silently changes what its consumers can name.

The practical consequence: every directory declares what it needs. A library that calls exp names it, even if every plausible consumer already has:

Loading is still transitive — the compiler reads C because it must — but visibility is not.

Cycles are an error

Directory a importing directory b importing directory a is reported rather than silently tolerated, because there is no valid non-transitive reading of it:

Note this is a source cycle. A module instantiating itself through other modules is a separate check in the elaborator, and can happen with no imports at all.

importc is a different thing

importc is not a Hover import. It injects a #include into the generated C++, and is how extern func declarations get their definitions. The target is never parsed as Hover. Quoted headers resolve relative to the entry .hvr file's directory, and a sibling .cpp / .c / .cc / .cxx next to the header is linked automatically.

Quick reference

import <a>;Package a — installed, or from the standard library; as a.x.
import <a> as N;Same directory, as N.x.
import <a/b>;Subdirectory b, as b.x.
import <idx:a>;Package a from index idx — never stdlib.
import <stdlib/a>;The standard library's a, by its package name.
import "./b";Directory b next to this file, as b.x.
from <a> import X;Just X, unqualified.
from <a> import X as Y;Just X, called Y.
from "./b" import X, Y;Just X and Y.
importc "<cmath>";C++ #include — not a Hover import.

Instantiation of an imported module follows the usual bracket convention: angle brackets carry compile-time generic arguments, parentheses carry runtime logic arguments, square brackets bind ports to wires in the enclosing scope.

Migrating from file imports

Import paths used to name files. The compiler tells you the replacement:

Mechanically:

  1. Drop the filename: <math/math.hvr><math>.
  2. Two files from one directory collapse into one import — <optoelectronics/leds/led.hvr> plus <optoelectronics/leds/led_colors.hvr> becomes <optoelectronics/leds>.
  3. An import of a sibling file disappears entirely.
  4. A previously bare import is now qualified. Either qualify the uses (Diodesemiconductors.Diode) or name what you need (from <semiconductors> import Diode;).
  5. import <x/y.hvr> as N;import <x> as N;, and existing N.Thing references are unchanged.