Skip to main content

lua_types/
version.rs

1//! `LuaVersion` — the single source of truth for which Lua language version a
2//! runtime instance speaks.
3//!
4//! This lives in `lua-types`, the lowest shared crate, so every layer above
5//! (parser, compiler, VM, stdlib, runtime) can name the version without a
6//! dependency cycle. Per the multi-version architecture decision
7//! (`specs/MULTIVERSION_ARCHITECTURE_DECISION.md` §4, §5), the version is a
8//! *backend selector* threaded from construction; it never appears in a public
9//! embedding-API type.
10
11/// The numeric model a version uses for Lua numbers.
12///
13/// This is the single sharpest behavioral axis across versions: 5.1/5.2 are
14/// float-only (one `number` type, every value an `f64`, no `math.type`), while
15/// 5.3/5.4/5.5 carry the dual integer/float subtype.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum NumberModel {
18    /// One `number` type; every numeric value is an `f64`. Lua 5.1/5.2.
19    FloatOnly,
20    /// Distinct integer (`i64`) and float (`f64`) subtypes. Lua 5.3/5.4/5.5.
21    Dual,
22}
23
24/// Which Lua language version a runtime instance speaks.
25///
26/// `Default` is [`LuaVersion::V54`] — the version this codebase currently
27/// implements end-to-end — so that `Lua::new()` and any other defaulted
28/// construction keeps the existing 5.4 behavior.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30#[non_exhaustive]
31pub enum LuaVersion {
32    /// Lua 5.1 — float-only, `fenv`-based globals. Deferred (separate core).
33    V51,
34    /// Lua 5.2 — float-only, modern `_ENV` globals. Deferred (separate core).
35    V52,
36    /// Lua 5.3 — dual subtype, modern `_ENV`. Deferred.
37    V53,
38    /// Lua 5.4 — the implemented baseline today.
39    V54,
40    /// Lua 5.5 — dual subtype, declared-globals scope model. Deferred.
41    V55,
42}
43
44impl Default for LuaVersion {
45    fn default() -> Self {
46        LuaVersion::V54
47    }
48}
49
50impl LuaVersion {
51    /// The family-level numeric model for this version.
52    pub fn number_model(self) -> NumberModel {
53        match self {
54            LuaVersion::V51 | LuaVersion::V52 => NumberModel::FloatOnly,
55            LuaVersion::V53 | LuaVersion::V54 | LuaVersion::V55 => NumberModel::Dual,
56        }
57    }
58
59    /// Whether the `__name` metafield overrides an object's type name (in
60    /// `tostring` and in type-error messages). `__name` is a 5.3 addition; 5.1
61    /// and 5.2 ignore it and always report the primitive type name.
62    pub fn honors_name_metafield(self) -> bool {
63        !matches!(self, LuaVersion::V51 | LuaVersion::V52)
64    }
65
66    /// Whether this version has a real backend. The modern family (5.3/5.4/5.5)
67    /// and 5.2 (float-only + `_ENV`) are complete. 5.1 reuses the 5.2 float-only
68    /// core plus three faithful 5.1-specific axes:
69    /// - **fenv globals** — `getfenv`/`setfenv` (the per-function environment
70    ///   model, Option B over the reused `_ENV` upvalue; `specs/followup/5.1-fenv.md`).
71    /// - **metamethod diffs** — `#t` never consults a table `__len`, no
72    ///   `__pairs`/`__ipairs`, no `__gc` on tables (userdata only).
73    /// - **roster + syntax** — `unpack` is global (no `table.unpack`/`pack`/
74    ///   `move`); `loadstring` + reader-only `load`; `table.getn`/`setn`(stub)/
75    ///   `maxn`/`foreach`/`foreachi`; `module`/`package.seeall`/`package.loaders`
76    ///   (no `package.searchers`); `string.gfind`; `math.log` 1-arg +
77    ///   `log10`/`atan2`/`pow`/`mod` (no `math.type`); `gcinfo`; `newproxy`;
78    ///   `xpcall`-no-extra-args; `coroutine.running` nil in main; no `bit32`/
79    ///   `utf8`/`rawlen`; `goto`/labels/`//`/bitwise/`<const>`/`\x`-`\z` escapes
80    ///   rejected (`goto` stays a valid identifier). See
81    ///   `specs/followup/5.1-roster-syntax.md`. Documented divergences: the
82    ///   `math.random` C-`rand()` sequence and `os.execute` raw-status byte are
83    ///   host-dependent (contract matches; exact bytes do not).
84    pub fn is_supported(self) -> bool {
85        matches!(
86            self,
87            LuaVersion::V51 | LuaVersion::V52 | LuaVersion::V53 | LuaVersion::V54 | LuaVersion::V55
88        )
89    }
90
91    /// The `_VERSION` global string for this version (e.g. `"Lua 5.4"`).
92    pub fn version_str(self) -> &'static str {
93        match self {
94            LuaVersion::V51 => "Lua 5.1",
95            LuaVersion::V52 => "Lua 5.2",
96            LuaVersion::V53 => "Lua 5.3",
97            LuaVersion::V54 => "Lua 5.4",
98            LuaVersion::V55 => "Lua 5.5",
99        }
100    }
101
102    /// The `LUAC_VERSION` byte written into a `luac`/`string.dump` header for
103    /// this version. Upstream encodes the version as `(major << 4) | minor`,
104    /// e.g. 5.4 → `0x54`.
105    pub fn luac_version_byte(self) -> u8 {
106        match self {
107            LuaVersion::V51 => 0x51,
108            LuaVersion::V52 => 0x52,
109            LuaVersion::V53 => 0x53,
110            LuaVersion::V54 => 0x54,
111            LuaVersion::V55 => 0x55,
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn default_is_v54() {
122        assert_eq!(LuaVersion::default(), LuaVersion::V54);
123    }
124
125    #[test]
126    fn number_model_split() {
127        assert_eq!(LuaVersion::V51.number_model(), NumberModel::FloatOnly);
128        assert_eq!(LuaVersion::V52.number_model(), NumberModel::FloatOnly);
129        assert_eq!(LuaVersion::V53.number_model(), NumberModel::Dual);
130        assert_eq!(LuaVersion::V54.number_model(), NumberModel::Dual);
131        assert_eq!(LuaVersion::V55.number_model(), NumberModel::Dual);
132    }
133
134    #[test]
135    fn version_str_and_byte() {
136        assert_eq!(LuaVersion::V54.version_str(), "Lua 5.4");
137        assert_eq!(LuaVersion::V54.luac_version_byte(), 0x54);
138        assert_eq!(LuaVersion::V53.version_str(), "Lua 5.3");
139        assert_eq!(LuaVersion::V53.luac_version_byte(), 0x53);
140    }
141}
142
143// ──────────────────────────────────────────────────────────────────────────
144// PORT STATUS
145//   source:        (foundation — multi-version seam, not ported from .c)
146//   target_crate:  lua-types
147//   confidence:    high
148//   todos:         0
149//   port_notes:    0
150//   unsafe_blocks: 0
151//   notes:         LuaVersion + NumberModel. Default = V54 preserves the
152//                  existing single-version behavior. V51-V55 complete; V51
153//                  reuses the 5.2 float-only core plus the fenv-globals,
154//                  metamethod-diff, and roster/syntax axes (all V51-gated).
155// ──────────────────────────────────────────────────────────────────────────