omnilua 0.7.1

Every Lua, everywhere — pure-Rust Lua 5.1–5.5, suite-passing, LuaRocks-compatible, wasm-ready.
Documentation
//! Version capability matrix + typed Unsupported — issue #234.
//!
//! The matrix's authority is `ANALYSES/version_feature_matrix.tsv`, generated by
//! probing the *reference binaries* (`specs/oracle/gen_feature_matrix.sh`). This
//! test asserts `LuaVersion::supports` against that fixture for every
//! (version, feature), so the matrix can never silently drift from upstream.

use omnilua::{Feature, Lua, LuaVersion};

const FIXTURE: &str =
    include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../ANALYSES/version_feature_matrix.tsv"));

const ALL_VERSIONS: [LuaVersion; 5] = [
    LuaVersion::V51,
    LuaVersion::V52,
    LuaVersion::V53,
    LuaVersion::V54,
    LuaVersion::V55,
];

fn version_from(s: &str) -> LuaVersion {
    match s {
        "5.1" => LuaVersion::V51,
        "5.2" => LuaVersion::V52,
        "5.3" => LuaVersion::V53,
        "5.4" => LuaVersion::V54,
        "5.5" => LuaVersion::V55,
        other => panic!("unknown version in fixture: {other}"),
    }
}

fn feature_from(s: &str) -> Feature {
    match s {
        "IntegerSubtype" => Feature::IntegerSubtype,
        "EnvSandbox" => Feature::EnvSandbox,
        "FenvSandbox" => Feature::FenvSandbox,
        "GotoLabels" => Feature::GotoLabels,
        "NativeBitwise" => Feature::NativeBitwise,
        "Bit32Lib" => Feature::Bit32Lib,
        "Utf8Lib" => Feature::Utf8Lib,
        "StringPack" => Feature::StringPack,
        "CloseAttribute" => Feature::CloseAttribute,
        "ConstAttribute" => Feature::ConstAttribute,
        "CoroutineClose" => Feature::CoroutineClose,
        "WarnFunction" => Feature::WarnFunction,
        "TableLenMetamethod" => Feature::TableLenMetamethod,
        "GcIsRunning" => Feature::GcIsRunning,
        "GcParam" => Feature::GcParam,
        "GlobalKeyword" => Feature::GlobalKeyword,
        "NamedVararg" => Feature::NamedVararg,
        "TableCreate" => Feature::TableCreate,
        other => panic!("unknown feature in fixture: {other}"),
    }
}

#[test]
fn supports_matches_reference_fixture() {
    let mut rows = 0;
    for line in FIXTURE.lines() {
        if line.starts_with('#') || line.trim().is_empty() {
            continue;
        }
        let mut cols = line.split('\t');
        let version = version_from(cols.next().unwrap());
        let feature = feature_from(cols.next().unwrap());
        let expected = match cols.next().unwrap() {
            "1" => true,
            "0" => false,
            other => panic!("bad cell: {other}"),
        };
        assert_eq!(
            version.supports(feature),
            expected,
            "supports({version:?}, {feature:?}) disagrees with the reference fixture"
        );
        rows += 1;
    }
    assert_eq!(
        rows,
        ALL_VERSIONS.len() * Feature::ALL.len(),
        "fixture is missing rows"
    );
}

#[test]
fn lua_supports_matches_version_on_a_full_build() {
    // The default build compiles in every library, so the instance query and the
    // version query agree. (A `--no-default-features` build would narrow the
    // library-backed rows — exercised by the feature-gate logic, not here.)
    for v in ALL_VERSIONS {
        let lua = Lua::new_versioned(v);
        for f in Feature::ALL {
            assert_eq!(lua.supports(f), v.supports(f), "{v:?}/{f:?}");
        }
    }
}

#[test]
fn is_running_is_typed_unsupported_before_5_2() {
    let lua = Lua::new_versioned(LuaVersion::V51);
    let err = lua.gc().is_running().unwrap_err();
    assert!(err.is_unsupported(), "expected a typed Unsupported, got: {err}");
    let u = err.as_unsupported().unwrap();
    assert_eq!(u.feature, Feature::GcIsRunning);
    assert_eq!(u.version, LuaVersion::V51);
    assert!(err.to_string().contains("is not available"));
}

#[test]
fn is_running_ok_from_5_2_on() {
    for v in [
        LuaVersion::V52,
        LuaVersion::V53,
        LuaVersion::V54,
        LuaVersion::V55,
    ] {
        let lua = Lua::new_versioned(v);
        assert!(lua.gc().is_running().is_ok(), "{v:?}");
        assert!(lua.supports(Feature::GcIsRunning));
    }
}

#[test]
fn classification_is_direct_host_only_message_survives_lua_round_trip() {
    // Documents the scoping decision: an Unsupported error raised inside a Rust
    // callback and re-raised through Lua loses the typed classification (it
    // becomes a Lua error value), but the MESSAGE survives. The host pre-checks
    // with supports() rather than relying on the typed form crossing Lua.
    let lua = Lua::new_versioned(LuaVersion::V51);
    let probe = lua
        .create_function(|l, ()| {
            l.gc().is_running()?;
            Ok(())
        })
        .unwrap();
    lua.globals().set("probe", probe).unwrap();
    let msg: String = lua
        .load("local ok, e = pcall(probe); return tostring(e)")
        .eval()
        .unwrap();
    assert!(
        msg.contains("is not available"),
        "message should survive the Lua round-trip, got: {msg}"
    );
}