pub struct Htl { /* private fields */ }Expand description
An mlua state with the Teal compiler loaded.
Implementations§
Source§impl Htl
impl Htl
Sourcepub fn apply_project(&self, p: &Project) -> Result<()>
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
impl Htl
Sourcepub fn install_test_lib(&self) -> Result<()>
pub fn install_test_lib(&self) -> Result<()>
Make require("htl.test") work at runtime and its types visible to the checker.
Source§impl Htl
impl Htl
Sourcepub fn apply_config(&self, root: &Path, cfg: &HtlConfig) -> Result<()>
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.
Sourcepub fn add_search_paths(&self, dirs: &[PathBuf]) -> Result<()>
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.
Sourcepub fn contract_check(
&self,
file: &Path,
modname: &str,
type_path: &str,
require_fields: &RequireFields,
) -> Result<ContractResult>
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
impl Htl
Sourcepub fn new() -> Result<Self>
pub fn new() -> Result<Self>
New state. Uses Lua::unsafe_new so stripped bytecode bundles can be loaded.
Sourcepub fn with_checker(checker: &Htl) -> Result<Self>
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).
Sourcepub fn with_checker_lua(checker: &Htl, lua: Lua) -> Result<Self>
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.
Sourcepub fn preload_generated(
&self,
name: &str,
code: &str,
file: &Path,
) -> Result<()>
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.
Sourcepub fn coverage_start(&self) -> Result<()>
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.
Sourcepub fn coverage_stop(&self) -> Result<Vec<(String, Vec<usize>)>>
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.
Sourcepub fn executable_ranges(&self, file: &Path) -> Result<Vec<(usize, usize)>>
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).
Sourcepub fn coverage_spans(&self, file: &Path) -> Result<CoverageSpans>
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.
Sourcepub fn search_path(&self) -> Result<String>
pub fn search_path(&self) -> Result<String>
The checker’s package.path (what require inside .tl resolves through).
Sourcepub fn set_search_path(&self, path: &str) -> Result<()>
pub fn set_search_path(&self, path: &str) -> Result<()>
Restore a checker package.path taken with search_path.
Sourcepub fn from_lua(lua: Lua) -> Result<Self>
pub fn from_lua(lua: Lua) -> Result<Self>
Attach the Teal compiler to an existing Lua state (the host’s own Lua).
Sourcepub fn lua(&self) -> &Lua
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.
Sourcepub fn check_written(&self, file: &Path) -> Result<CheckInfo>
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.
Sourcepub fn gen_lua(&self, file: &Path) -> Result<(Option<String>, CheckInfo)>
pub fn gen_lua(&self, file: &Path) -> Result<(Option<String>, CheckInfo)>
Type-check and generate Lua source. None code means errors (see CheckInfo).
Sourcepub fn configure_lints(&self, spec: &str) -> Result<()>
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>.
Sourcepub fn select_lints(&self, sel: &Selection) -> Result<()>
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.
Sourcepub fn set_deps(&self, names: &[String]) -> Result<()>
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.
Sourcepub fn lint_rules(&self) -> Result<Vec<String>>
pub fn lint_rules(&self) -> Result<Vec<String>>
Names of all lint rules (enabled or not), the project layer’s among them.
Sourcepub fn lua_lint_rules(&self) -> Result<Vec<String>>
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).
Sourcepub fn format_file(&self, file: &Path, indent: usize) -> Result<String>
pub fn format_file(&self, file: &Path, indent: usize) -> Result<String>
Format a .tl file (whitespace-only formatter). Returns the formatted text.
Sourcepub fn reset_search_path(&self) -> Result<()>
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.
Sourcepub fn add_layout_paths(&self, file: &Path) -> Result<()>
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).
Sourcepub fn add_path(&self, dir: &Path) -> Result<()>
pub fn add_path(&self, dir: &Path) -> Result<()>
Prepend dir/?.tl;dir/?/init.tl to package.path (Teal resolves requires through it).
Sourcepub fn install_searcher(&self) -> Result<()>
pub fn install_searcher(&self) -> Result<()>
Install the strict .tl searcher: require of a .tl with type errors fails.
Sourcepub fn preload(&self, name: &str, lua_src: &str) -> Result<()>
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).
Sourcepub fn preload_at(
&self,
name: &str,
chunk_name: &str,
lua_src: &str,
) -> Result<()>
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.
Sourcepub fn preload_bytes(&self, name: &str, bytecode: &[u8]) -> Result<()>
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.
Sourcepub fn exec_bytes(
&self,
bytecode: &[u8],
chunk_name: &str,
args: &[String],
) -> Result<()>
pub fn exec_bytes( &self, bytecode: &[u8], chunk_name: &str, args: &[String], ) -> Result<()>
Execute stripped bytecode with ... = args.
Sourcepub fn preload_value(&self, name: &str, value: impl IntoLua) -> Result<()>
pub fn preload_value(&self, name: &str, value: impl IntoLua) -> Result<()>
Register a ready-made value (typically a Rust-built table) as a module.
Sourcepub fn set_arg(&self, script: &str, args: &[String]) -> Result<()>
pub fn set_arg(&self, script: &str, args: &[String]) -> Result<()>
Set the global arg table like the lua CLI does.
Sourcepub fn strict_strings(&self) -> Result<()>
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.
Sourcepub fn exec(
&self,
lua_src: &str,
chunk_name: &str,
args: &[String],
) -> Result<()>
pub fn exec( &self, lua_src: &str, chunk_name: &str, args: &[String], ) -> Result<()>
Execute Lua source with ... = args.
Sourcepub fn run_file(&self, file: &Path, args: &[String]) -> Result<CheckInfo>
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.
Sourcepub fn compile(&self, name: &str, lua_src: &str) -> Result<Vec<u8>>
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).
Sourcepub fn compile_with(
&self,
name: &str,
lua_src: &str,
strip: bool,
) -> Result<Vec<u8>>
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).
Sourcepub fn fingerprint(&self) -> Result<Vec<u8>>
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.
Sourcepub fn lua_requires(&self, src: &str, file: &Path) -> Result<Vec<RequireSite>>
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.
Sourcepub fn resolve_module(
&self,
name: &str,
) -> Result<(Option<PathBuf>, Option<PathBuf>)>
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.
Sourcepub fn module_candidates(&self, name: &str) -> Result<Vec<ModuleCandidate>>
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.
Sourcepub fn search_path_dirs(&self) -> Result<Vec<PathBuf>>
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.
Sourcepub fn install_bundle(&self, b: &Bundle) -> Result<()>
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.
Sourcepub fn replace_bundle(&self, b: &Bundle, keep: &[&str]) -> Result<Replaced>
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.
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> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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