Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e811ef8
chore(quest): claim quest/m0/nightly-green.md
kixelated Nov 10, 2026
46f567c
fix: write a JS Math result's NaN as one NaN, free lent bytes in thei…
kixelated Nov 10, 2026
5e26123
fix(ts): format the bindgen harness's fround line
kixelated Nov 10, 2026
ec89951
Merge remote-tracking branch 'origin/main' into quest/m0/nightly-green
kixelated Nov 10, 2026
8a91504
fix(ffi): name Kotlin's write-back reader apart from the parameters, …
kixelated Nov 10, 2026
7f6b3f2
quest: finish the nightly passes
kixelated Nov 10, 2026
7c0b6f9
fix(ffi): accept an exclude of a field or variant a cfg leaves out of…
kixelated Nov 10, 2026
58bce9e
fix(ffi): keep the Android script's lending functions from R8's inlining
kixelated Nov 10, 2026
adfad41
fix(ffi): restore collect.rs's formatting
kixelated Nov 10, 2026
8b77259
fix(ffi): read a cfg-stripped member's type up to its own end, so one…
kixelated Nov 10, 2026
614b996
Merge remote-tracking branch 'origin/main' into quest/m0/nightly-green
kixelated Nov 10, 2026
28efd36
fix(ffi): take the catalog's lent Extra as a borrow in moq.cpp, as #6…
kixelated Nov 10, 2026
c18a2c4
fix(ffi): sort driver.rs's imports
kixelated Nov 10, 2026
7064cc2
fix(ffi): end a cfg-stripped member's type at the first balanced stop…
kixelated Nov 10, 2026
44ac65a
Merge remote-tracking branch 'origin/main' into quest/m0/nightly-green
kixelated Nov 10, 2026
f94e2c2
fix(ffi): name every Kotlin local apart from the function's parameter…
kixelated Nov 10, 2026
a669569
fix(ffi): hold and close a Kotlin or Dart method's receiver apart fro…
kixelated Nov 11, 2026
2ae6f63
fix(ffi): accept an exclude of a member a cfg_attr's cfg leaves out
kixelated Nov 11, 2026
ab51fa8
Merge remote-tracking branch 'origin/main' into quest/m0/nightly-green
kixelated Nov 11, 2026
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
34 changes: 34 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,37 @@ jobs:
with:
name: census
path: ts/target/census

# A scheduled run's failure opens one issue, or comments on the one still open, and a
# pass closes it, so a suite only this workflow runs cannot fail unseen. Only the
# schedule, which runs main, gets the write token: a dispatch can run any branch.
report:
name: Report
needs: [gate, ts, bench, ffi, ffi-cpp, ffi-android, ffi-bench, census]
if: ${{ !cancelled() && github.event_name == 'schedule' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
issues: write

steps:
- name: Issue
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
FAILED: ${{ contains(needs.*.result, 'failure') }}
RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
SHA: ${{ github.sha }}
run: |
title="The nightly fails"
issue=$(gh issue list --state open --search "\"$title\" in:title" --json number,title --jq ".[] | select(.title == \"$title\") | .number" | head -n 1)
if [ "$FAILED" = true ]; then
body="[This run]($RUN) failed at $SHA."
if [ -n "$issue" ]; then
gh issue comment "$issue" --body "$body"
else
gh issue create --title "$title" --body "$body"
fi
elif [ -n "$issue" ]; then
gh issue close "$issue" --comment "[This run]($RUN) passed at $SHA."
fi
29 changes: 24 additions & 5 deletions ffi/fixtures/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ impl Counter {
self.value
}

/// The value plus ten `handle`s and `h`: parameters named as a binding's receiver and
/// its held handle might be.
pub fn offset(&self, handle: u64, h: u64) -> u64 {
self.value + handle * 10 + h
}

/// The value plus `close`, consuming the counter: a parameter named as the method a
/// binding closes it with might be.
pub fn into_sum(self, close: u64) -> u64 {
self.value + close
}

pub fn name(&self) -> &str {
&self.name
}
Expand Down Expand Up @@ -1894,13 +1906,20 @@ pub fn put_number(value: u32, buf: &mut impl bytes::BufMut) {
buf.put_slice(format!("{value},").as_bytes());
}

/// Takes the first byte of `buf` as a decimal digit, as moq's decoders take from an
/// `w235` a second later: a parameter named as Kotlin's generated writer of it would be,
/// whose name the writer must take apart from it.
pub fn second_later(w235: Duration) -> Duration {
w235.saturating_add(Duration::from_secs(1))
}

/// Takes the first byte of `r` as a decimal digit, as moq's decoders take from an
/// `impl Buf`, which takes `Bytes`. It takes the byte even when it fails, and Rust writes
/// back what the call left on either path.
pub fn digit(buf: &mut impl bytes::Buf) -> Result<u8, Error> {
match buf.has_remaining() {
/// back what the call left on either path. Named `r`, as moq's `varint::decode_quic`'s
/// is, the name a binding's reader of what Rust wrote back might take.
pub fn digit(r: &mut impl bytes::Buf) -> Result<u8, Error> {
match r.has_remaining() {
false => Err(Error::Underflow { by: 1 }),
true => match buf.get_u8() {
true => match r.get_u8() {
digit @ b'0'..=b'9' => Ok(digit - b'0'),
_ => Err(Error::Overflow),
},
Expand Down
3 changes: 3 additions & 0 deletions ffi/fixtures/dep/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
pub struct Size {
pub width: u32,
pub height: u32,
/// What no build has, which the `configured_out` test excludes.
#[cfg(any())]
pub depth: u32,
}

/// An area: an object of this crate, which the api fixture's signatures take and return.
Expand Down
16 changes: 8 additions & 8 deletions ffi/moq/moq.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ int main() {
CHECK(catalog);
CHECK(!catalog->is_claimed_BinaryConfig("state"));
int read_catalog = 0;
CHECK(catalog->mutate([&](moq::hang_Catalog_Extra &lent) {
auto json = lent.to_json();
CHECK(catalog->mutate([&](moq::borrow<moq::hang_Catalog_Extra> lent) {
auto json = lent->to_json();
CHECK(json);
if (json && json.value().str().substr(0, 1) == "{") {
read_catalog++;
Expand Down Expand Up @@ -318,15 +318,15 @@ int main() {
}
auto app = moq::Json::parse(R"({"v": [1, true]})");
CHECK(app);
CHECK(catalog->mutate([&](moq::hang_Catalog_Extra &lent) {
moq::mux_catalog_hang_Extra sections = lent.ext_get();
CHECK(catalog->mutate([&](moq::borrow<moq::hang_Catalog_Extra> lent) {
moq::mux_catalog_hang_Extra sections = lent->ext_get();
CHECK(sections.set("app", std::move(*app)));
lent.ext_set(std::move(sections));
lent->ext_set(std::move(sections));
}));
CHECK(catalog->mutate([&](moq::hang_Catalog_Extra &lent) {
auto json = lent.to_json();
CHECK(catalog->mutate([&](moq::borrow<moq::hang_Catalog_Extra> lent) {
auto json = lent->to_json();
CHECK(json && json.value().str().find("\"status\"") != std::string_view::npos);
auto section = lent.ext_get().get("app");
auto section = lent->ext_get().get("app");
CHECK(section && is(section->to_text(), R"({"v":[1,true]})"));
}));
// Malformed text fails to parse, and a reserved section is moq-mux's error.
Expand Down
3 changes: 2 additions & 1 deletion ffi/src/dart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1702,7 +1702,8 @@ impl<'a> Gen<'a> {
args.push("a_.hold(this)".to_string());
let copy = matches!(ty, Type::Object(i) if abi.api.objects[i].copy);
if receiver == Receiver::Owned && !copy {
after.push("close();".to_string());
// Qualified, apart from a parameter named `close`.
after.push("this.close();".to_string());
}
}
Ty::In(inner) => {
Expand Down
111 changes: 105 additions & 6 deletions ffi/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
use std::path::{Path, PathBuf};
use std::process::ExitCode;

use rustc_ast as ast;
use rustc_data_structures::fx::FxHashSet;
use rustc_driver::{Callbacks, Compilation};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_interface::interface::Compiler;
use rustc_lexer::{FrontmatterAllowed, TokenKind};
use rustc_middle::middle::resolve::Reexport;
use rustc_middle::ty::TyCtxt;
use rustc_span::Span;
use rustc_parse::lexer::StripTokens;
use rustc_parse::parser::{AllowConstBlockItems, ForceCollect};
use rustc_span::{FileName, Span, sym};

use crate::abi::Abi;
use crate::config::{self, Config};
Expand Down Expand Up @@ -206,13 +210,105 @@ pub fn write(path: &Path, text: &str) -> Result<(), String> {
std::fs::write(path, text).map_err(|err| format!("writing {}: {err}", path.display()))
}

/// Whether `path`, from the facade's root, names an item a `cfg` leaves out of this build:
/// the config names it for the builds that have it. Its first segment no module has is
/// stripped from that module or from one the module glob-re-exports.
/// Whether the struct, enum, or union `adt` declares a field or variant `name` under a
/// `cfg` this build left it out by. rustc records only the items a `cfg` strips, not their
/// members, so this reads the declaration from its source.
fn stripped_member(tcx: TyCtxt<'_>, adt: DefId, name: &str) -> bool {
if tcx.adt_def(adt).all_fields().any(|f| f.name.as_str() == name)
|| tcx.adt_def(adt).variants().iter().any(|v| v.name.as_str() == name)
{
return false;
}
// The item from its name on, up to the `;` or closing brace that ends it: past it the
// file may close a module the item is in, which the parser would refuse unmatched. Each
// place nesting returns to the item's own level may end it, such as a const generic's
// block before its fields, so the first that parses as an item does.
let source_map = tcx.sess.source_map();
let head = tcx.def_span(adt);
let source_file = source_map.lookup_source_file(head.lo());
let Ok(source) = source_map.span_to_snippet(head.with_hi(source_file.end_position())) else {
return false;
};
let (mut depth, mut at) = (0usize, 0usize);
let mut item = None;
for token in rustc_lexer::tokenize(&source, FrontmatterAllowed::No) {
at += token.len as usize;
let end = match token.kind {
TokenKind::OpenParen | TokenKind::OpenBrace | TokenKind::OpenBracket => {
depth += 1;
false
}
TokenKind::CloseParen | TokenKind::CloseBrace | TokenKind::CloseBracket => {
// A delimiter the item never opened closes the module it is in.
let Some(inner) = depth.checked_sub(1) else { break };
depth = inner;
depth == 0 && token.kind == TokenKind::CloseBrace
}
TokenKind::Semi => depth == 0,
_ => false,
};
if end && let Some(parsed) = parse_item(tcx, &source[..at]) {
item = Some(parsed);
break;
}
}
let Some(item) = item else { return false };
// A `cfg`, or one a `cfg_attr` adds, at any depth: the attributes past its predicate.
fn adds_cfg(items: &[ast::MetaItemInner]) -> bool {
items.iter().skip(1).any(|item| {
item.has_name(sym::cfg) || (item.has_name(sym::cfg_attr) && item.meta_item_list().is_some_and(adds_cfg))
})
}
let configured = |attrs: &[ast::Attribute]| {
attrs.iter().any(|attr| {
attr.has_name(sym::cfg)
|| (attr.has_name(sym::cfg_attr) && attr.meta_item_list().is_some_and(|items| adds_cfg(&items)))
})
};
match &item.kind {
ast::ItemKind::Struct(_, _, data) | ast::ItemKind::Union(_, _, data) => {
(data.fields().iter()).any(|f| f.ident.is_some_and(|ident| ident.as_str() == name) && configured(&f.attrs))
}
ast::ItemKind::Enum(_, _, def) => {
(def.variants.iter()).any(|v| v.ident.as_str() == name && configured(&v.attrs))
}
_ => false,
}
}

/// `source` parsed as one item, or none if it is not one.
fn parse_item(tcx: TyCtxt<'_>, source: &str) -> Option<Box<ast::Item>> {
let file = FileName::anon_source_code(source);
let mut parser = match rustc_parse::new_parser_from_source_str(
&tcx.sess.psess,
file,
source.to_string(),
StripTokens::Nothing,
) {
Ok(parser) => parser,
Err(errors) => {
errors.into_iter().for_each(|e| e.cancel());
return None;
}
};
match parser.parse_item(ForceCollect::No, AllowConstBlockItems::DoesNotMatter) {
Ok(item) => item,
Err(e) => {
e.cancel();
None
}
}
}

/// Whether `path`, from the facade's root, names an item a `cfg` leaves out of this build,
/// or a field or variant of one: the config names it for the builds that have it. Its first
/// segment no module has is stripped from that module or from one the module
/// glob-re-exports.
fn configured_out(tcx: TyCtxt<'_>, path: &str) -> bool {
let path = path.strip_prefix("field@").unwrap_or(path);
let segments: Vec<&str> = path.split("::").collect();
let mut module = LOCAL_CRATE.as_def_id();
for segment in path.split("::") {
for (i, segment) in segments.iter().enumerate() {
let name = segment.split('<').next().unwrap_or(segment);
let children = match module.as_local() {
Some(local) => tcx.module_children_local(local),
Expand All @@ -224,7 +320,10 @@ fn configured_out(tcx: TyCtxt<'_>, path: &str) -> bool {
module = def_id;
continue;
}
// A member of an item, which no `cfg` this checks strips.
Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, def_id) if i + 2 == segments.len() => {
return stripped_member(tcx, def_id, segments[i + 1]);
}
// A method, or a member past one, which no `cfg` this checks strips.
_ => return false,
}
}
Expand Down
30 changes: 21 additions & 9 deletions ffi/src/kotlin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub fn generate(abi: &Abi<'_>, packages: &str, contract: u64) -> Result<Output,
names: BTreeMap::new(),
errors: Vec::new(),
fresh: 0,
params: Vec::new(),
tuples: BTreeSet::new(),
dispatchers: Vec::new(),
};
Expand Down Expand Up @@ -295,6 +296,8 @@ struct Gen<'a> {
errors: Vec<String>,
/// A counter for fresh Kotlin names.
fresh: usize,
/// The Kotlin parameters of the function being written, which a fresh name never takes.
params: Vec<String>,
/// The arities a tuple class is written for: one, and past `Triple`.
tuples: BTreeSet<usize>,
/// The Kotlin functions the glue calls, which the rules keep.
Expand All @@ -303,8 +306,13 @@ struct Gen<'a> {

impl<'a> Gen<'a> {
fn fresh(&mut self, base: &str) -> String {
self.fresh += 1;
format!("{base}{}", self.fresh)
loop {
self.fresh += 1;
let name = format!("{base}{}", self.fresh);
if !self.params.contains(&name) {
return name;
}
}
}

/// A C name without the prefix.
Expand Down Expand Up @@ -1433,6 +1441,7 @@ impl<'a> Gen<'a> {
place: Place,
) -> Option<String> {
let abi = self.abi;
self.params = locals(&function.params);
let export = abi.export(function, owner);
let c_name = export.call.name.clone();
let bare = self.bare(&c_name).to_string();
Expand Down Expand Up @@ -1478,12 +1487,14 @@ impl<'a> Gen<'a> {
n += 1;
arg.jni.push("jlong");
arg.native.push("Long".to_string());
arg.open = Some("handle.hold { h ->".to_string());
arg.exprs.push("h".to_string());
// Qualified, and named fresh, apart from the parameters in scope.
let h = self.fresh("h");
arg.open = Some(format!("this.handle.hold {{ {h} ->"));
arg.exprs.push(h);
arg.rust_args.push(format!("{p} as usize as _"));
let copy = matches!(ty, Type::Object(i) if abi.api.objects[i].copy);
if receiver == Receiver::Owned && !copy {
arg.after.push("close()".to_string());
arg.after.push("this.close()".to_string());
}
arg
}
Expand All @@ -1494,7 +1505,7 @@ impl<'a> Gen<'a> {
}
let mut kt_params = Vec::new();
let mut closures = Vec::new();
let names = locals(&function.params);
let names = self.params.clone();
// Each parameter Rust writes back, by its Kotlin name, and what it holds.
let mut written: Vec<(String, Type)> = Vec::new();
for (param, name) in function.params.iter().zip(&names) {
Expand Down Expand Up @@ -1659,16 +1670,17 @@ impl<'a> Gen<'a> {
),
);
let mut lines = Vec::new();
let r = self.fresh("r");
// What Rust wrote back comes first, read whether or not the call failed.
for (name, ty) in &written {
let read = self.read_value(ty, "r");
let read = self.read_value(ty, &r);
lines.push(format!("{name}.value = {}", to_param(ty, &read)));
}
lines.extend(self.lift_result(&ok, err.as_ref(), exception.as_deref(), "r"));
lines.extend(self.lift_result(&ok, err.as_ref(), exception.as_deref(), &r));
value = match lines.is_empty() {
true => call,
false => format!(
"Native.Reader({call}).let {{ r ->\n{}}}",
"Native.Reader({call}).let {{ {r} ->\n{}}}",
lines.iter().map(|l| format!("\t{l}\n")).collect::<String>()
),
};
Expand Down
3 changes: 3 additions & 0 deletions ffi/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@

#![feature(rustc_private)]

extern crate rustc_ast;
extern crate rustc_attr_ir;
extern crate rustc_data_structures;
extern crate rustc_driver;
extern crate rustc_errors;
extern crate rustc_hir;
extern crate rustc_infer;
extern crate rustc_interface;
extern crate rustc_lexer;
extern crate rustc_middle;
extern crate rustc_parse;
extern crate rustc_session;
extern crate rustc_span;
extern crate rustc_trait_selection;
Expand Down
Loading