Skip to main content

Htl

Struct Htl 

Source
pub struct Htl { /* private fields */ }
Expand description

An mlua state with the Teal compiler loaded.

Implementations§

Source§

impl Htl

Source

pub fn apply_project(&self, p: &Project) -> Result<()>

Make the project’s installed deps visible to the Teal checker and to the prelude’s strict searcher (htl run / htl test without a Registry).

The directory on the path is Project::entries, where each dep is reached at its entry; the links are written first if the lockfile calls for any that are missing.

A patch_dir dependency is on the path in its own right, at Project::patch_search_dirs. The copy is committed and the manifest names it, so the two together are the whole of what a require of that dependency needs: no install, no link, no network, and nothing that has to exist outside what a clone or a tarball carries. That is the arrangement cargo vendor and Go’s vendor/ settled on — the copy plus the manifest naming it is the source of truth, and its presence is what turns the network off — and htl’s reason for it is the one #266 found: .htl/ is gitignored, so the copy cargo package verifies has the patch and the manifest and no links at all, and every require of the dependency failed there with module not found.

Those directories go on first and are therefore consulted last, after the links, the target_dir copies and the project’s own src/. A checkout that has installed resolves exactly what it resolved before — the link and the copy are the same files, and the link still answers first — so what this adds is an answer where there was none.

Except under build scratch, where this writes nothing at all — the rule and the reason are crate::cache::scratch_root’s. What it does there it does read-only: the directories go on the search path whether or not they exist (a path that resolves nothing is what a tarball with no .htl/ means, and the project’s own src/ is still there), and the dependency names still come from the lockfile.

Source§

impl Htl

Source

pub fn install_test_lib(&self) -> Result<()>

Make require("htl.test") work at runtime and its types visible to the checker.

Source§

impl Htl

Source

pub fn apply_config(&self, root: &Path, cfg: &HtlConfig) -> Result<()>

Make an htl.toml project’s dirs visible to the checker: root, root/src and [check] paths. root is the directory holding htl.toml.

Source

pub fn add_search_paths(&self, dirs: &[PathBuf]) -> Result<()>

Put dirs on the search path so they are consulted in the order given — the order search_paths documents, and the one a reader assumes from a list. add_path prepends, so adding the list front to back would leave its last entry first; this adds it back to front.

It decides one thing: which of two declarations of the same module is read. A .tl source beats a .d.tl wherever the two sit, so until neither is a source the order is invisible.

Source

pub fn contract_check( &self, file: &Path, modname: &str, type_path: &str, require_fields: &RequireFields, ) -> Result<ContractResult>

Static form of TealResolver::expect_type / require_fields for one module file: modname is what a require would say (its stem), type_path is "defs.Mod".

Source§

impl Htl

Source

pub fn new() -> Result<Self>

New state. Uses Lua::unsafe_new so stripped bytecode bundles can be loaded.

Source

pub fn with_checker(checker: &Htl) -> Result<Self>

A fresh program state that borrows checker’s compiler instead of loading its own: modules checker has already type-checked and generated are served from its store, so a run of many programs (the test runner: one state per file) checks each module once. The program state itself is as isolated as new: nothing but the checker is shared. The checker starts a new program env for this state (module-name resolution is per program).

Source

pub fn with_checker_lua(checker: &Htl, lua: Lua) -> Result<Self>

with_checker with the program state supplied.

This is the constructor for a host that decides what the program state is made of — which standard libraries it opens (Lua::unsafe_new_with), what its allocator is bounded to (Lua::set_memory_limit), what hook counts its instructions (Lua::set_global_hook) — while the checker keeps running on a state of its own, with whatever it needs. Every such limit is mlua’s and is set on lua by the host; htl adds none of its own and puts nothing in the way of them.

What htl itself needs from lua: package (the searcher and preload) and the base library’s load; debug, only for coverage_start. A state that will load bundles has to come from unsafe_new_with: mlua’s safe new_with refuses binary chunks, which is what a bundle is.

Source

pub fn preload_generated( &self, name: &str, code: &str, file: &Path, ) -> Result<()>

Put Lua this checker generated for a .tl module in front of the searcher, in a program state.

Distinct from preload, which registers a source string as a module: this loads the way the searcher would have, so the module sees the same chunk name and the same arguments as if it had been generated during the run.

Without it, a require in running code asks the searcher, which checks and generates the module then and there. With it, the module is already present. The two are the same thing only if code is what this checker would generate now — the caller’s promise, and the reason anything serving this has to invalidate on the module’s own content.

Source

pub fn coverage_start(&self) -> Result<()>

Start recording which lines of which chunk run in the program state (a state made by with_checker). Lua’s line hook is per thread: code inside coroutines the program creates is not seen.

Source

pub fn coverage_stop(&self) -> Result<Vec<(String, Vec<usize>)>>

Stop recording; (chunk source, sorted executed lines) per chunk. Sources are as Lua names them: @<path> for files loaded by the searcher and the entry.

Source

pub fn executable_ranges(&self, file: &Path) -> Result<Vec<(usize, usize)>>

Statements of a .tl file as (first line, last line) ranges: what a coverage report counts as executable. A statement counts as executed when any line of its range ran (Lua attributes a multi-line statement’s instructions to several lines).

Source

pub fn coverage_spans(&self, file: &Path) -> Result<CoverageSpans>

The statement ranges of executable_ranges and the file’s named functions, from one parse: a coverage report wants both, and the second is what lets it say which function the missed statements belong to.

Source

pub fn search_path(&self) -> Result<String>

The checker’s package.path (what require inside .tl resolves through).

Source

pub fn set_search_path(&self, path: &str) -> Result<()>

Restore a checker package.path taken with search_path.

Source

pub fn from_lua(lua: Lua) -> Result<Self>

Attach the Teal compiler to an existing Lua state (the host’s own Lua).

Source

pub fn lua(&self) -> &Lua

The Lua state this Htl runs programs in.

Not always the one the checker is in: with_checker makes a fresh state for the program and leaves the prelude in the checker’s. So a value built from this state must not be handed to a function that came from the other — that is Lua instance passed Value created from a different main Lua state.

Source

pub fn check(&self, file: &Path) -> Result<CheckInfo>

Type-check one file.

Source

pub fn check_written(&self, file: &Path) -> Result<CheckInfo>

Check what is on disk right now, ignoring the store and not adding to it.

check serves a module the checker already knows from its store, and the underlying tl.check_file returns early when the environment has the file loaded. That is what makes checking a project fast, and it is wrong for a caller that has just written the file: the answer describes the version from before the write. htl fix writes and then measures, and was reverting correct fixes because of it.

Nothing is stored either, because the caller may be about to put the file back — leaving the result behind would have the store describing a file that no longer says that.

Slower than check: a cold environment re-checks the modules this file requires.

The two options it differs from check by are set in the prelude rather than in a table built here, for the reason set_deps gives: h is not always in self.lua, and a table that crossed that line would raise.

Source

pub fn gen_lua(&self, file: &Path) -> Result<(Option<String>, CheckInfo)>

Type-check and generate Lua source. None code means errors (see CheckInfo).

Source

pub fn configure_lints(&self, spec: &str) -> Result<()>

Configure lint rules: "+no-any,-shadow-local" on top of the defaults.

The spec is resolved against lint::RULES, so a name the project layer reports under is a name this takes; an unknown one is unknown lint rule: <item>.

Source

pub fn select_lints(&self, sel: &Selection) -> Result<()>

Hand the checker a selection resolved elsewhere — what a caller that also has to ask about the project-layer rules has in hand (lint::Lints), so that the file rules and the project rules of one run come from one resolution of one spec.

Two selections cross, one per producer on the Lua side: the rules lint.lua implements, which it runs from, and Teal’s warning kinds, which the prelude filters the checker’s warnings by as it collects them. Neither keeps defaults of its own.

Each side crosses as the names that are on and the names that are off, and the table is built on the other side — for the reason set_deps gives, and it applies here the harder way: h is not always in self.lua (with_checker keeps the prelude in the checker’s), and a table made here and passed there is Lua instance passed Value created from a different main Lua state. Both lists, not just the on ones, because absent and false are not the same answer to the prelude: a Teal warning kind is said unless its entry is exactly false.

Source

pub fn set_deps(&self, names: &[String]) -> Result<()>

Tell the checker which dependencies the project installed, by name.

Read by the rules that are about a library the project has rather than about its own code — htlx-available, which is silent in a project without htl-x — and by nothing else. Called by Htl::apply_project with what the lockfile linked; a state nobody calls it on has none, which is the answer a run outside a project should get. The names cross as a sequence and the set is built on the other side, rather than as a table built here. h is not always in self.lua — a split state (with_checker) keeps the prelude in the checker’s — and a table made in one state and passed to a function in another is Lua instance passed Value created from a different main Lua state. A Vec is converted by the call itself, in the state the function belongs to.

Source

pub fn lint_rules(&self) -> Result<Vec<String>>

Names of all lint rules (enabled or not), the project layer’s among them.

Source

pub fn lua_lint_rules(&self) -> Result<Vec<String>>

The rules lint.lua implements, as it knows them. The registry is lint::RULES; this is the list to hold it to (tests/lint_registry.rs).

Source

pub fn format_file(&self, file: &Path, indent: usize) -> Result<String>

Format a .tl file (whitespace-only formatter). Returns the formatted text.

Source

pub fn reset_search_path(&self) -> Result<()>

Drop Lua’s default search path (cwd-relative ./?.lua etc.) so only directories passed to add_path are consulted by the checker and require.

Source

pub fn add_layout_paths(&self, file: &Path) -> Result<()>

Search paths implied by where file sits in the scaffold layout, in the order they are consulted: its own directory first, and for a file under tests/ then the project root and <root>/src (the test runner’s rule, so htl check tests sees what htl test sees).

Source

pub fn add_path(&self, dir: &Path) -> Result<()>

Prepend dir/?.tl;dir/?/init.tl to package.path (Teal resolves requires through it).

Source

pub fn install_searcher(&self) -> Result<()>

Install the strict .tl searcher: require of a .tl with type errors fails.

Source

pub fn preload(&self, name: &str, lua_src: &str) -> Result<()>

Register generated Lua source under a module name (package.preload).

The chunk is named after the .tl a require of this name would have found — foo.bar becomes @foo/bar.tl — because that name is what a run-time failure shows, and a reader who has only the output needs something to open. Use Htl::preload_at when the source sits somewhere else (@scripts/util.tl), or when there is no file at all and a bare label is the honest answer (=htl.test).

Source

pub fn preload_at( &self, name: &str, chunk_name: &str, lua_src: &str, ) -> Result<()>

Htl::preload with the chunk name spelled out, the way Htl::exec takes one. @<path> is a source location and is what a host with a file should pass; =<label> is a literal label, for a module no file backs.

Source

pub fn preload_bytes(&self, name: &str, bytecode: &[u8]) -> Result<()>

Register stripped bytecode (e.g. from include_tl_bytes!) under a module name.

A chunk name is worth less here than it is to Htl::preload, and the reason is worth knowing before reading a failure from an embedded module: a compiled chunk carries its own name, given when it was compiled, and lua_load’s name is used only for the messages loading itself produces. Stripping drops the carried name along with the line numbers, so every frame from a stripped payload reads ??: in function 'sample.greet'. Running the .tl under htl run or htl test is where those frames are; a bundle keeps them with htl build --debug.

Source

pub fn exec_bytes( &self, bytecode: &[u8], chunk_name: &str, args: &[String], ) -> Result<()>

Execute stripped bytecode with ... = args.

Source

pub fn preload_value(&self, name: &str, value: impl IntoLua) -> Result<()>

Register a ready-made value (typically a Rust-built table) as a module.

Source

pub fn set_arg(&self, script: &str, args: &[String]) -> Result<()>

Set the global arg table like the lua CLI does.

Source

pub fn strict_strings(&self) -> Result<()>

Make arithmetic on a string a run-time error instead of a conversion.

Lua 5.4 reads "10" + 1 as 11: the string library’s metatable carries __add and the other seven arithmetic metamethods, and each one converts its string operands and retries. Checked Teal never gets there: the checker refuses the expression on a string, and on an any too. It happens in what the checker did not see — the far side of a cast ((v as integer) + 1 where v came from std.json.decode or arg as "10"), a function load built from a string, Lua source a host handed to exec — and there the conversion is silent. This removes the eight from the string metatable of the program state, so the same expression fails as attempt to perform arithmetic on a string value, naming the operand.

What it does not cover, because Lua does those elsewhere: 10 .. "" (number to string under concatenation is in the VM, behind Lua’s LUA_NOCVTN2S build flag, which is the vendored Lua’s to set); "10" < "9" (a string comparison, true, and not a conversion); and tonumber / math.tointeger, which convert because they were asked to. __index stays, so s:upper() and every other string method are untouched.

Opt-in, for a host’s preload beside install_std; the CLI does not turn it on, since htl run and htl test run Teal the checker has passed. Calling it twice is the same as once. In a state that also holds the checker (the default; see with_checker for the split) the checker runs under it too, which it can: nothing in tl adds a string to a number.

Source

pub fn exec( &self, lua_src: &str, chunk_name: &str, args: &[String], ) -> Result<()>

Execute Lua source with ... = args.

Source

pub fn run_file(&self, file: &Path, args: &[String]) -> Result<CheckInfo>

Check + gen + run a .tl script. If the check fails the script is not run and the returned CheckInfo carries the errors. Runtime errors come back as Err.

Source

pub fn compile(&self, name: &str, lua_src: &str) -> Result<Vec<u8>>

Compile Lua source to stripped bytecode (Lua 5.4 format of this build).

Source

pub fn compile_with( &self, name: &str, lua_src: &str, strip: bool, ) -> Result<Vec<u8>>

Compile to bytecode; strip drops debug info (line numbers, local and upvalue names, and the chunk name: tracebacks then show the name given at load).

Source

pub fn fingerprint(&self) -> Result<Vec<u8>>

The Lua bytecode header this state produces (signature, version, format, LUAC_DATA, sizes of Instruction / Integer / Number, endianness probes): what another state must match to load this state’s bytecode. Lua’s own version byte is the same for every 5.4.x, so bundles carry this instead.

Source

pub fn lua_requires(&self, src: &str, file: &Path) -> Result<Vec<RequireSite>>

Literal requires of a plain Lua source, resolved through the checker’s path.

Source

pub fn resolve_module( &self, name: &str, ) -> Result<(Option<PathBuf>, Option<PathBuf>)>

Where require(name) resolves for the checker (.tl, .d.tl or .lua), and where a plain .lua implementation sits on the path (a .d.tl may only be typing it). Either may be None.

Source

pub fn module_candidates(&self, name: &str) -> Result<Vec<ModuleCandidate>>

Every file on the search path that could answer require(name), in the order the searchers consult them — so the first is the one resolve_module answers with, and the rest are what it hides.

The same walk declaration_sites does for the duplicate-declaration lint, over all three kinds rather than declarations alone: a searcher answers with the first hit and says nothing about the others, and which of two files is read is decided by a position nobody wrote down. contract::resolve is what turns this into a report.

Source

pub fn search_path_dirs(&self) -> Result<Vec<PathBuf>>

The directories the search path consults, in order. One entry per directory, however many package.path templates it contributes.

Source

pub fn install_bundle(&self, b: &Bundle) -> Result<()>

Install a searcher serving modules from a bundle.

Idempotent, and deliberately so: a second call installs nothing, because every name is taken by the first. Putting a newer bundle into a state that is already running is replace_bundle.

Source

pub fn replace_bundle(&self, b: &Bundle, keep: &[&str]) -> Result<Replaced>

Put a newer bundle into a state that is already running: the modules the bundle recorded under the same entry go, keep’s loaded values stay, and the host’s are untouched.

Nothing is evaluated here. A dropped name is gone from package.preload and package.loaded, so the next require of it runs the new module; a name in keep keeps the value it already evaluated to, which is how a world or a save module carries state across the swap. The entry is not re-run either — what to do with it is the host’s, and a frame loop holding a table asks for the entry again and swaps what it holds.

A reference already taken is not reached by any of this. local m = require "rules" captured by a closure that is still running keeps the old table until that closure is gone. That is Lua, and no amount of bookkeeping here changes it.

The bundle is checked before anything is dropped, so a refusal — a fingerprint that disagrees, a host module that was never registered — leaves the state as it was rather than holding neither bundle.

Source

pub fn run_bundle(&self, b: &Bundle, args: &[String]) -> Result<()>

Install the bundle and run its entry module with ... = args.

Auto Trait Implementations§

§

impl !Freeze for Htl

§

impl !RefUnwindSafe for Htl

§

impl !Send for Htl

§

impl !Sync for Htl

§

impl !UnwindSafe for Htl

§

impl Unpin for Htl

§

impl UnsafeUnpin for Htl

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> MaybeSend for T

Source§

impl<T> MaybeSync for T

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.