C+
Introduction · View as Markdown

C+ 0.0.30 — 2026-10-16

This release brings WinUI 3 as the default Windows UI and agent backend, the appenv package and a single cross-platform asset staging rule in cpc, one hybrid_web page messaging contract across all five UI backends, Liquid Glass and Material 3 UI parity, compiler soundness and borrow-checker hardening, and security audit remediations across cplus-pm, agent_mcp, and CI.

Upgrade notes

  • facet_runtime::app_data_dir() and facet_runtime::cache_dir() are removed; use appenv::app_data_dir() and appenv::cache_dir(), which answer from the first line of main on every platform without waiting for a window.
  • On Windows, facet_runtime and facet_agent now select facet_winui and agent_winui by default. facet_win32 is deprecated and remains available as an explicit opt-out.
  • scroll now takes a @view block (scroll { ... }); attaching a @view block to a leaf control reports E1010.
  • hybrid_web uses one page API on every backend: JavaScript sends messages up with window.facet.postMessage(str) (available from document start), native delivers down by firing HybridWebViewMessageReceived on window with e.detail.message, and the C+ on_message callback reads the payload through hybrid_page::message(sender). The old per-backend window.facet.onmessage / window.facet.receive hooks are gone.
  • Discarding a call's Status return value as a bare statement now warns with W0009 (let _ = call(); silences it deliberately).
  • Value-producing functions across stdlib, cpc-bindgen collection bridges, and vendored packages now return either the complete result or an empty value on allocation or read failure, never a truncated prefix.

Language and compiler

  • Borrow checker and soundness hardening.
    • A method returning a view derived from a parameter ties that parameter's loan for the lifetime of the view, including calls through function values and interface methods on generic receivers (fn use_it[T: Picker](p: T)).
    • take of a view type (str, T[], or a view-carrying struct/enum) is checked as a borrow rather than a consuming move, and thread::spawn_with detaches its argument's borrow ties properly.
    • Type aliases (type S = str;, type Guard = Scope;) are resolved before borrow, move, and NLL drop analysis, so aliased views, non-Copy types, and Drop guards obey the same rules as their underlying types.
    • Deferred initialization (let x: T; x = expr;) runs partial-move (E0509, E0337) and wrapper move consumption checks on the right-hand side.
    • Same-block LIFO drop ordering checks E0514 against active borrows before releasing each local's own loans; cross-statement writes and ref borrows check overlapping sub-place loans (d.field, d[i]); nested variant patterns on borrowed scrutinees enforce E0337; and Send/Sync cycle detection tags struct and enum IDs in separate namespaces.
  • Diagnostics and attributes.
    • W0009 warns when a Status return value is discarded as an expression statement.
    • W0007 catches (1 << n) as u64 where a shift happens at default i32 width before widening.
    • E0310 diagnoses cycles in type aliases cleanly instead of recursing.
    • E0510 counts a helper as a release function only when its body actually releases.
    • #target_feature("avx2,fma") and the CLI flags --target-cpu / --target-feature control target code generation per function or per build.
  • Codegen.
    • gen_drop_in_place uses field_addr on planned structs (#[align(N)], #[packed]), fixing invalid GEP emission when an owning field sits at a non-zero offset.
    • Static arrays of empty strings emit a valid constant initializer, and enums carrying inner enums size their payload slot from the inner layout.
    • An interface default method body resolves imports from its declaring module.

Toolchain, assets, and package manager

  • One project asset rule (cpc build, cpc package, --print-asset-dir). Every app build stages <root>/assets/* flat into target/assets/<target>/<mode>/ incrementally and mirrors staged assets beside a self-linked binary (tracked by .cplus-assets and refusing any asset that would overwrite the binary). cpc build --print-asset-dir stages and prints the directory for external builders (Xcode, Gradle/aapt2, Iris), and cpc package bundles from the same staged tree.
  • One [package] app_id across platforms. cpc init writes app_id into Cplus.toml and every generated platform file, and cpc build validates it (E0415 on an invalid identifier, W0008 when a platform file drifts). A refused cpc init leaves no directory behind.
  • Windows DLL and link support. A [library] cdylib on Windows links against the standard Windows system libraries, writes <name>.dll and <name>.lib, exports the C ABI symbols declared in its generated header, and omits -rpath on PE links. [link] libs expands ${VAR} environment references like search-paths and extra-objects.
  • Doctests. cpc test runs ```cplus documentation fences, skips other language fences, and fails on unclosed fences.
  • Security hardening (cplus-pm, cpc, agent_mcp, CI).
    • cplus-pm keys cache slots and tag records injectively (audit F01), refuses to write or remove unstamped directories or follow vendor/ symlinks outside the project (audit F02), and verifies that fetched package directories resolve inside the repository checkout.
    • agent_mcp places local descriptors in a verified 0700 per-user directory /tmp/cplus-<uid>/ (audit F04), rejects cross-origin, foreign-Host, and non-JSON requests on the loopback HTTP transport (audit F03), and buffers partial non-blocking socket reads per client without stalling the UI loop.
    • GitHub Actions workflows run test jobs with read-only tokens, isolate release publishing into build-free jobs with provenance attestation, and pin all actions by commit SHA (audit F05).

Facet, WinUI 3, and the agent stack

  • WinUI 3 (facet_winui, agent_winui, winui).
    • Generated WinMD bindings and a complete native WinUI 3 backend: multi-window host, custom and blended window chrome, App::menu MenuBar, modal alerts and file/folder/save pickers, TabView, tables, lists, collections, WebView2 and hybrid_web, drag-and-drop on any node, gesture and keyboard bands, focus commands, light/dark theme tracking, per-window density observers, run_on_main from background threads, and adopt_native for hosting inside an existing WinUI window.
    • agent_winui and facet_agent_winui expose every live window over the MCP and in-app agent surfaces.
  • Cross-platform UI and mobile polish.
    • One hybrid_web page bridge (facet/hybrid_page): unified window.facet.postMessage up, HybridWebViewMessageReceived custom event down, and hybrid_page::message(sender) across AppKit, UIKit, Android, GTK, and WinUI.
    • iOS 26 Liquid Glass (facet_uikit): native UITabBar with floating and sidebar glass placement, scroll panes running under glass bars, native whole-window push navigation, action sheets for choose, and blur_effect.
    • Android Material 3 & Material You (facet_android): dynamic system palette tokens, Material 3 top and bottom tab bars with indicator pills, Material bottom sheets, Material dialogs (alert, choose, prompt), per-corner radii, and content:// URI reading/writing in FacetFiles.
    • Symbols and icons: bundled Material Symbols font compiled into the binary (facet/icon_font), button(symbol:) and icon_button(symbol:), and tab bar icons across backends.
    • Text roles: named_size resolves to each platform's native typography ramp.
    • File drops and save panel: allow_file_drop, dropped_files, and drag_over on any node, plus choose_save_path.
    • Agent & Inspector: every control property is readable and writable through generated inspector descriptors (window_width / window_height included); agents can press individual tabs, pick popup items and date/time pickers, read and answer iOS/Android alerts, and are refused when targeting controls on hidden screens or behind an active sheet.

Standard library and packages

  • appenv: cross-platform app_data_dir, cache_dir, no_backup_dir, exclude_from_backup, resource_dir, asset_path, read_asset, read_asset_text, and app_id, usable from the first line of main on macOS, Linux, Windows, iOS, and Android.
  • stdlib: added stdlib/template (Template unjoined string literals), stdlib/path (join, parent, file_name, stem, extension), and process::current_id(). File::read_to_end and File::lines never return a short read as whole.
  • flex_layout: added .basis / .basis_percent, ladder-range band rules (hide_up_to, hide_from, show_up_to, show_from, add_rule_up_to, add_rule_from), bands::default_set(), BandSet::first_match, and HIG layout presets (.readable_width(), .sidebar_width(), screen filling its viewport).
  • jni: mutf8 converts JNI's modified UTF-8 at every Java string boundary in jni, android_view, http, and filepicker, so astral-plane characters (such as emoji) and embedded NULs round-trip as valid UTF-8 without tripping CheckJNI.
  • filepicker and share: FileRef reads and writes both filesystem paths and Android content:// URIs; share::file works on Android; camera adds preview(fit:).

v0.0.29 — 2026-09-24

A compiler release: a bit-reinterpret intrinsic, four ownership and checking fixes in generic and enum code, and two toolchain defects that produced wrong output or failed builds without saying why.

Upgrade notes

  • fN::from_bits(uN) and a float's .to_bits() are removed; write #bitcast::[f32](bits) and #bitcast::[u32](x) (either signedness of the integer works). Both old spellings report E0324 naming the replacement. A SIMD mask's .to_bits() is a different method and stays.
  • A generic impl body is now checked where it is written, so a template that never type-checked reports its errors (E0302, E0324, …) at its own line instead of compiling.
  • An unbounded T inside a generic body counts as owning a destructor, so E0509 and E0337 apply to it as they do to Text: struct W[T] { v: T } with fn get(this) -> T { return this.v; } is now refused. Bound it [T: Copy], or take the payload with a consuming match.

Language

  • #bitcast::[T](v) reinterprets a float's bits. A float and an integer of the same width swap types with the bits unchanged: f32↔u32/i32, f64↔u64/i64, f16↔u16/i16. #bitcast::[u32](1.0f32) is 0x3F800000, where 1.0f32 as u32 is 1. An unsuffixed literal takes the partner type, so #bitcast::[f32](0xBF800000) is -1.0 rather than an out-of-range i32. NaN payloads, infinities and -0.0 come through unchanged, and that holds in const/static position too (const INF: f32 = #bitcast::[f32](https://github.com/netdur/cplus/blob/v0.0.30/0x7F800000);): a folded float travels as an f64, which drops an f32 signalling NaN or an f16 NaN payload, so the exact pattern rides alongside it and is what gets emitted. usize/isize, int↔int and float↔float are refused (E0302): the first pair's width depends on the target, and the others are what as is for. jni's JValue::of_float / of_double, which punned through a pointer for want of this, now use it.
  • #bitcast is the only spelling. The unsigned-only fN::from_bits / .to_bits() pair it replaces is gone (see the upgrade notes); a derived Hash over float fields now bitcasts too, with the same hash values.

Toolchain

  • A generic impl's method bodies now report their errors. Sema type-checked every generic impl body against its template and then discarded what it found, on the recorded belief that the body was checked again per instantiation. Nothing checks a method body after that pass, so a template could carry an unknown method or field (a compiler panic at codegen), a wrong arity or a plain type mismatch (compiled), or return 5 in a -> T body instantiated at bool (linked, as ret i1 5). The reports now land at the template's own line, with the codes a concrete impl gets. Measured first: zero new errors across stdlib, facet and the example apps, whose generic bodies already obeyed the rules.
  • A template's T may own a destructor. Inside a generic body a type parameter answered "carries no drop", so struct W[T] { v: T } with fn get(this) -> T { return this.v; } was not a partial move — and at W[Text] the field was returned and then dropped again with the shell, a double free written in the safe subset with no raw pointer in sight. An unbounded T now counts as owning, so E0509 and E0337 apply to it as they do to Text; a [T: Copy] bound keeps the bit-copy getter, and a consuming match stays the way to take a payload out. E0509 also names the type as spelled (W[T], (T, i32)) instead of the internal W__Param_T / __Tuple[T, i32].
  • A consuming method on an enum local is a move the drop scanner sees. e.into_inner() through a take this method left e classified as never-moved, because the scanner consulted struct methods only: a debug build of the compiler aborted on the guard that exists to catch exactly that, and a release build dropped the moved-out shell again at the caller's scope exit. That second drop had been cancelling a leak on the callee's side, below, so the counts looked right. The scanner consults enum methods now.
  • A take this method on an enum drops what it owns. The enum method emitter bound this and the parameters and registered no drop for either, so a consuming method on an enum leaked its payload and every take parameter (fn both(take this, take d: H) dropped neither); the struct emitter had always dropped both. Measured with a weighted counter across twelve receiver and parameter shapes, enum and struct, method and free function: each now drops exactly what it owns, once.
  • A static with no constant form stops the build. Codegen's fallback for an initializer it could not render emitted poison, on the belief that the IR would then fail to assemble. It does not: global float poison is valid, so such a static compiled and read garbage. The fallback now emits a marker clang rejects on that line (cpc-bug:static-initializer-has-no-constant-form). Reaching it is a compiler bug; no known program does.
  • Concurrent builds no longer break each other's prebuilds. Every cpc that found a package's prebuilt slice stale rebuilt it, and each rebuild began by deleting the package's shared object directory under the others' running clangs (unable to rename temporary …). Any two builds sharing a vendor/ tree could hit it, and it failed 2–4 tests of every e2e run that started with a cold stdlib. One process now prebuilds a package under a per-package lock while the others wait and reuse its slice, printing cpc: waiting for another cpc to finish prebuilding ``.