Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- NTNT coding guide sections are sourced from docs/AI_AGENT_GUIDE.md -->
<!-- To update NTNT coding instructions, edit AI_AGENT_GUIDE.md and copy to all agent files -->
<!-- Last synced: 2026-03-09 -->
<!-- Last synced: 2026-03-11 -->

# NTNT Language - GitHub Copilot Instructions

Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
name: Test
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
Expand All @@ -39,6 +40,10 @@ jobs:

- name: Run tests
run: cargo test --locked
env:
# macOS aarch64 has smaller default thread stack (512KB vs 8MB on Linux).
# Increase to 8MB to prevent stack overflow in recursive interpreter tests.
RUST_MIN_STACK: 8388608

lint:
name: Lint
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- NTNT coding guide sections are sourced from docs/AI_AGENT_GUIDE.md -->
<!-- To update NTNT coding instructions, edit AI_AGENT_GUIDE.md and copy to all agent files -->
<!-- Last synced: 2026-03-09 -->
<!-- Last synced: 2026-03-11 -->

# NTNT Language - Claude Code Instructions

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ntnt"
version = "0.4.0"
version = "0.4.1"
edition = "2021"
authors = ["NTNT Language Team"]
description = "NTNT (Intent) - A programming language designed for AI-driven development"
Expand Down
2 changes: 1 addition & 1 deletion docs/IAL_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Auto-generated from [ial.toml](ial.toml)** - Do not edit directly.
>
> Last updated: v0.4.0
> Last updated: v0.4.1

IAL is a term rewriting engine that translates natural language assertions into executable tests

Expand Down
2 changes: 1 addition & 1 deletion docs/RUNTIME_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Auto-generated from [runtime.toml](runtime.toml)** - Do not edit directly.
>
> Last updated: v0.4.0
> Last updated: v0.4.1

Runtime configuration, environment variables, and CLI commands for NTNT

Expand Down
2 changes: 1 addition & 1 deletion docs/STDLIB_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Auto-generated from source code doc comments** - Do not edit directly.
>
> Last updated: v0.4.0
> Last updated: v0.4.1

## Table of Contents

Expand Down
2 changes: 1 addition & 1 deletion docs/SYNTAX_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Auto-generated from [syntax.toml](syntax.toml)** - Do not edit directly.
>
> Last updated: v0.4.0
> Last updated: v0.4.1

## Table of Contents

Expand Down
55 changes: 45 additions & 10 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub enum LintMode {
Strict,
}

#[cfg_attr(test, allow(dead_code))]
fn read_type_mode_from_env() -> TypeMode {
match std::env::var("NTNT_TYPE_MODE").as_deref().unwrap_or("warn") {
"strict" => TypeMode::Strict,
Expand All @@ -55,11 +56,13 @@ fn read_type_mode_from_env() -> TypeMode {
}
}

/// Get the current runtime type mode from the `NTNT_TYPE_MODE` env var.
/// Get the current runtime type mode.
///
/// Default is [`TypeMode::Warn`]. In production builds the result is cached on
/// first call; in test builds it reads fresh from the environment every call
/// so tests can manipulate `NTNT_TYPE_MODE` with per-test isolation.
/// Default is [`TypeMode::Warn`]. In production builds the result is read from
/// `NTNT_TYPE_MODE` env var and cached via `OnceLock`. In test builds, a
/// thread-local override is used instead of env vars (since `std::env::set_var`
/// is unsafe in multi-threaded contexts on Rust 1.83+). Use
/// [`set_test_type_mode`] to override in tests.
#[cfg(not(test))]
pub fn get_type_mode() -> TypeMode {
use std::sync::OnceLock;
Expand All @@ -69,9 +72,38 @@ pub fn get_type_mode() -> TypeMode {

#[cfg(test)]
pub fn get_type_mode() -> TypeMode {
read_type_mode_from_env()
// In tests, use thread-local override only (no env var reads).
// std::env::var is unsafe to call concurrently with set_var on macOS
// (Rust 1.83+ / POSIX getenv is not thread-safe on all platforms).
TYPE_MODE_OVERRIDE.with(|cell| (*cell.borrow()).unwrap_or(TypeMode::Warn))
Comment on lines 73 to +78
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_type_mode() in test builds now uses a thread-local override and does not read NTNT_TYPE_MODE from the environment, but the surrounding documentation (above this function) still describes per-call env reads for tests. Please update the docs/comments to match the new thread-local override behavior so tests don’t attempt to rely on std::env::set_var("NTNT_TYPE_MODE", ...).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bd0742c. Doc comment now describes the thread-local override behavior and points to set_test_type_mode.

}

thread_local! {
/// Thread-local override for TypeMode in tests. Avoids `std::env::set_var`
/// which is unsafe in multi-threaded contexts (Rust 1.83+).
static TYPE_MODE_OVERRIDE: RefCell<Option<TypeMode>> = const { RefCell::new(None) };
}

/// Set a thread-local TypeMode override for the current test. Returns a guard
/// that restores `None` on drop. Use instead of `std::env::set_var("NTNT_TYPE_MODE", ...)`.
#[cfg(test)]
pub fn set_test_type_mode(mode: TypeMode) -> TestTypeModeGuard {
TYPE_MODE_OVERRIDE.with(|cell| *cell.borrow_mut() = Some(mode));
TestTypeModeGuard
}

/// RAII guard that clears the thread-local TypeMode override on drop.
#[cfg(test)]
pub struct TestTypeModeGuard;

#[cfg(test)]
impl Drop for TestTypeModeGuard {
fn drop(&mut self) {
TYPE_MODE_OVERRIDE.with(|cell| *cell.borrow_mut() = None);
}
}

#[cfg_attr(test, allow(dead_code))]
fn read_lint_mode_from_env() -> LintMode {
match std::env::var("NTNT_LINT_MODE")
.as_deref()
Expand All @@ -83,11 +115,13 @@ fn read_lint_mode_from_env() -> LintMode {
}
}

/// Get the current lint mode from the `NTNT_LINT_MODE` env var.
/// Get the current lint mode.
///
/// Default is [`LintMode::Default`]. CLI flags take precedence over this value
/// (caller is responsible for applying the override). In production builds the
/// result is cached; in test builds it reads fresh each call.
/// Default is [`LintMode::Default`]. In production builds the result is read
/// from `NTNT_LINT_MODE` env var and cached via `OnceLock`. CLI flags take
/// precedence (caller applies the override). In test builds, always returns
/// `LintMode::Default` for thread safety (no env var reads). Add a thread-local
/// override similar to `TypeMode` if lint-mode testing is needed.
#[cfg(not(test))]
pub fn get_lint_mode() -> LintMode {
use std::sync::OnceLock;
Expand All @@ -97,7 +131,8 @@ pub fn get_lint_mode() -> LintMode {

#[cfg(test)]
pub fn get_lint_mode() -> LintMode {
read_lint_mode_from_env()
// In tests, always return Default (no env var reads for thread safety).
LintMode::Default
Comment on lines 132 to +135
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_lint_mode() in test builds now always returns LintMode::Default, which diverges from the documented behavior elsewhere that test builds re-read NTNT_LINT_MODE each call. If this change is intentional for thread-safety, consider updating the surrounding docs/comments to avoid confusion, or adding a thread-local override similar to TypeMode so lint-mode behavior can still be tested deterministically.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bd0742c. Doc comment now explains that test builds return LintMode::Default for thread safety, and suggests adding a thread-local override (like TypeMode) if lint-mode testing is needed in the future.

}

use std::cell::RefCell;
Expand Down
Loading
Loading