From 9b6afcfeadc21d2283a1a8f5eb9bfdab646c9f88 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 3 Sep 2025 07:28:32 +0800 Subject: [PATCH 0001/1059] Fix indent for convert_closure_to_fn Example --- ```rust fn foo() { { let closure = |$0| match () { () => {}, }; closure(); } } ``` **Before this PR**: ```rust fn foo() { { fn closure() { match () { () => {}, } } closure(); } } ``` **After this PR**: ```rust fn foo() { { fn closure() { match () { () => {}, } } closure(); } } ``` --- .../src/handlers/convert_closure_to_fn.rs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs index 5f526ec89940..03f4e4749dbd 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs @@ -220,7 +220,7 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_>) } let body = if wrap_body_in_block { - make::block_expr([], Some(body)) + make::block_expr([], Some(body.reset_indent().indent(1.into()))) } else { ast::BlockExpr::cast(body.syntax().clone()).unwrap() }; @@ -969,6 +969,32 @@ fn closure() { } closure(); } +"#, + ); + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + { + let closure = |$0| match () { + () => {}, + }; + closure(); + } +} +"#, + r#" +fn foo() { + { + fn closure() { + match () { + () => {}, + } + } + closure(); + } +} "#, ); } From 9bc8e4eb09aab27c024b52fb461bcf3d8fa143a8 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 3 Sep 2025 22:18:01 +0800 Subject: [PATCH 0002/1059] Add applicable on LetExpr for unwrap_tuple Example --- ```rust fn main() { if $0let (foo, bar) = ("Foo", "Bar") { code(); } } ``` -> ```rust fn main() { if let foo = "Foo" && let bar = "Bar" { code(); } } ``` --- .../ide-assists/src/handlers/unwrap_tuple.rs | 64 +++++++++++++------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs index 46f3e85e1234..2345075a52be 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs @@ -1,3 +1,6 @@ +use std::iter; + +use either::Either; use syntax::{ AstNode, T, ast::{self, edit::AstNodeEdit}, @@ -24,11 +27,16 @@ // ``` pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let let_kw = ctx.find_token_syntax_at_offset(T![let])?; - let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?; - let indent_level = let_stmt.indent_level().0 as usize; - let pat = let_stmt.pat()?; - let ty = let_stmt.ty(); - let init = let_stmt.initializer()?; + let let_stmt = let_kw.parent().and_then(Either::::cast)?; + let mut indent_level = let_stmt.indent_level(); + let pat = either::for_both!(&let_stmt, it => it.pat())?; + let (ty, init, prefix, suffix) = match &let_stmt { + Either::Left(let_stmt) => (let_stmt.ty(), let_stmt.initializer()?, "", ";"), + Either::Right(let_expr) => { + indent_level = indent_level + 1; + (None, let_expr.expr()?, "&& ", "") + } + }; // This only applies for tuple patterns, types, and initializers. let tuple_pat = match pat { @@ -60,25 +68,19 @@ pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option "Unwrap tuple", let_kw.text_range(), |edit| { - let indents = " ".repeat(indent_level); + let mut decls = String::new(); // If there is an ascribed type, insert that type for each declaration, // otherwise, omit that type. - if let Some(tys) = tuple_ty { - let mut zipped_decls = String::new(); - for (pat, ty, expr) in - itertools::izip!(tuple_pat.fields(), tys.fields(), tuple_init.fields()) - { - zipped_decls.push_str(&format!("{indents}let {pat}: {ty} = {expr};\n")) - } - edit.replace(parent.text_range(), zipped_decls.trim()); - } else { - let mut zipped_decls = String::new(); - for (pat, expr) in itertools::izip!(tuple_pat.fields(), tuple_init.fields()) { - zipped_decls.push_str(&format!("{indents}let {pat} = {expr};\n")); - } - edit.replace(parent.text_range(), zipped_decls.trim()); + let tys = + tuple_ty.into_iter().flat_map(|it| it.fields().map(Some)).chain(iter::repeat(None)); + for (pat, ty, expr) in itertools::izip!(tuple_pat.fields(), tys, tuple_init.fields()) { + let ty = ty.map_or_else(String::new, |ty| format!(": {ty}")); + decls.push_str(&format!("{prefix}let {pat}{ty} = {expr}{suffix}\n{indent_level}")) } + + let s = decls.trim(); + edit.replace(parent.text_range(), s.strip_prefix(prefix).unwrap_or(s)); }, ) } @@ -123,6 +125,28 @@ fn main() { ); } + #[test] + fn unwrap_tuples_in_let_expr() { + check_assist( + unwrap_tuple, + r#" +fn main() { + if $0let (foo, bar) = ("Foo", "Bar") { + code(); + } +} +"#, + r#" +fn main() { + if let foo = "Foo" + && let bar = "Bar" { + code(); + } +} +"#, + ); + } + #[test] fn unwrap_tuple_with_types() { check_assist( From 293e8d33a2b6279d1a82fad44a4321a77705fd9f Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 5 Sep 2025 15:56:48 +0800 Subject: [PATCH 0003/1059] Fix not applicable on empty struct for no_such_field Example --- ```rust fn main() { Foo { bar$0: false }; } struct Foo {} ``` -> ```rust fn main() { Foo { bar: false }; } struct Foo { bar: bool, } ``` --- .../src/handlers/no_such_field.rs | 62 +++++++++++++++++-- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs index 0edab5e0b3b1..9ae99faa00a4 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs @@ -97,24 +97,36 @@ fn missing_record_expr_field_fixes( make::ty(&new_field_type.display_source_code(sema.db, module.into(), true).ok()?), ); - let last_field = record_fields.fields().last()?; - let last_field_syntax = last_field.syntax(); - let indent = IndentLevel::from_node(last_field_syntax); + let (indent, offset, postfix, needs_comma) = + if let Some(last_field) = record_fields.fields().last() { + let indent = IndentLevel::from_node(last_field.syntax()); + let offset = last_field.syntax().text_range().end(); + let needs_comma = !last_field.to_string().ends_with(','); + (indent, offset, String::new(), needs_comma) + } else { + let indent = IndentLevel::from_node(record_fields.syntax()); + let offset = record_fields.l_curly_token()?.text_range().end(); + let postfix = if record_fields.syntax().text().contains_char('\n') { + ",".into() + } else { + format!(",\n{indent}") + }; + (indent + 1, offset, postfix, false) + }; let mut new_field = new_field.to_string(); if usage_file_id != def_file_id { new_field = format!("pub(crate) {new_field}"); } - new_field = format!("\n{indent}{new_field}"); + new_field = format!("\n{indent}{new_field}{postfix}"); - let needs_comma = !last_field_syntax.to_string().ends_with(','); if needs_comma { new_field = format!(",{new_field}"); } let source_change = SourceChange::from_text_edit( def_file_id.file_id(sema.db), - TextEdit::insert(last_field_syntax.text_range().end(), new_field), + TextEdit::insert(offset, new_field), ); return Some(vec![fix( @@ -333,6 +345,44 @@ struct Foo { ) } + #[test] + fn test_add_field_from_usage_with_empty_struct() { + check_fix( + r" +fn main() { + Foo { bar$0: false }; +} +struct Foo {} +", + r" +fn main() { + Foo { bar: false }; +} +struct Foo { + bar: bool, +} +", + ); + + check_fix( + r" +fn main() { + Foo { bar$0: false }; +} +struct Foo { +} +", + r" +fn main() { + Foo { bar: false }; +} +struct Foo { + bar: bool, +} +", + ); + } + #[test] fn test_add_field_in_other_file_from_usage() { check_fix( From 04b232a2576eaebd233815a6a670f3ae800d9827 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 7 Sep 2025 16:13:49 +0800 Subject: [PATCH 0004/1059] Add wrap multiple attr for wrap_unwrap_cfg_attr Example --- ```rust pub struct Test { $0#[foo] #[bar]$0 test: u32, } ``` -> ```rust pub struct Test { #[cfg_attr($0, foo, bar)] test: u32, } ``` --- .../src/handlers/wrap_unwrap_cfg_attr.rs | 138 +++++++++++++----- 1 file changed, 101 insertions(+), 37 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs index 7d5740b748be..36df4af31d5e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use syntax::{ NodeOrToken, SyntaxToken, T, TextRange, algo, - ast::{self, AstNode, make, syntax_factory::SyntaxFactory}, + ast::{self, AstNode, edit::AstNodeEdit, make, syntax_factory::SyntaxFactory}, }; use crate::{AssistContext, AssistId, Assists}; @@ -27,7 +27,7 @@ enum WrapUnwrapOption { WrapDerive { derive: TextRange, attr: ast::Attr }, - WrapAttr(ast::Attr), + WrapAttr(Vec), } /// Attempts to get the derive attribute from a derive attribute list @@ -102,9 +102,9 @@ fn attempt_get_derive(attr: ast::Attr, ident: SyntaxToken) -> WrapUnwrapOption { if ident.parent().and_then(ast::TokenTree::cast).is_none() || !attr.simple_name().map(|v| v.eq("derive")).unwrap_or_default() { - WrapUnwrapOption::WrapAttr(attr) + WrapUnwrapOption::WrapAttr(vec![attr]) } else { - attempt_attr().unwrap_or(WrapUnwrapOption::WrapAttr(attr)) + attempt_attr().unwrap_or_else(|| WrapUnwrapOption::WrapAttr(vec![attr])) } } pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { @@ -118,13 +118,27 @@ pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) - Some(attempt_get_derive(attr, ident)) } - (Some(attr), _) => Some(WrapUnwrapOption::WrapAttr(attr)), + (Some(attr), _) => Some(WrapUnwrapOption::WrapAttr(vec![attr])), _ => None, } } else { let covering_element = ctx.covering_element(); match covering_element { - NodeOrToken::Node(node) => ast::Attr::cast(node).map(WrapUnwrapOption::WrapAttr), + NodeOrToken::Node(node) => { + if let Some(attr) = ast::Attr::cast(node.clone()) { + Some(WrapUnwrapOption::WrapAttr(vec![attr])) + } else { + let attrs = node + .children() + .filter(|it| it.text_range().intersect(ctx.selection_trimmed()).is_some()) + .map(ast::Attr::cast) + .collect::>>()?; + if attrs.is_empty() { + return None; + } + Some(WrapUnwrapOption::WrapAttr(attrs)) + } + } NodeOrToken::Token(ident) if ident.kind() == syntax::T![ident] => { let attr = ident.parent_ancestors().find_map(ast::Attr::cast)?; Some(attempt_get_derive(attr, ident)) @@ -133,10 +147,12 @@ pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) - } }?; match option { - WrapUnwrapOption::WrapAttr(attr) if attr.simple_name().as_deref() == Some("cfg_attr") => { - unwrap_cfg_attr(acc, attr) - } - WrapUnwrapOption::WrapAttr(attr) => wrap_cfg_attr(acc, ctx, attr), + WrapUnwrapOption::WrapAttr(attrs) => match &attrs[..] { + [attr] if attr.simple_name().as_deref() == Some("cfg_attr") => { + unwrap_cfg_attr(acc, attrs.into_iter().next().unwrap()) + } + _ => wrap_cfg_attrs(acc, ctx, attrs), + }, WrapUnwrapOption::WrapDerive { derive, attr } => wrap_derive(acc, ctx, attr, derive), } } @@ -220,40 +236,51 @@ fn wrap_derive( ); Some(()) } -fn wrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>, attr: ast::Attr) -> Option<()> { - let range = attr.syntax().text_range(); - let path = attr.path()?; +fn wrap_cfg_attrs(acc: &mut Assists, ctx: &AssistContext<'_>, attrs: Vec) -> Option<()> { + let (first_attr, last_attr) = (attrs.first()?, attrs.last()?); + let range = first_attr.syntax().text_range().cover(last_attr.syntax().text_range()); + let path_attrs = + attrs.iter().map(|attr| Some((attr.path()?, attr.clone()))).collect::>>()?; let handle_source_change = |edit: &mut SourceChangeBuilder| { let make = SyntaxFactory::with_mappings(); - let mut editor = edit.make_editor(attr.syntax()); - let mut raw_tokens = - vec![NodeOrToken::Token(make.token(T![,])), NodeOrToken::Token(make.whitespace(" "))]; - path.syntax().descendants_with_tokens().for_each(|it| { - if let NodeOrToken::Token(token) = it { - raw_tokens.push(NodeOrToken::Token(token)); - } - }); - if let Some(meta) = attr.meta() { - if let (Some(eq), Some(expr)) = (meta.eq_token(), meta.expr()) { - raw_tokens.push(NodeOrToken::Token(make.whitespace(" "))); - raw_tokens.push(NodeOrToken::Token(eq)); - raw_tokens.push(NodeOrToken::Token(make.whitespace(" "))); + let mut editor = edit.make_editor(first_attr.syntax()); + let mut raw_tokens = vec![]; + for (path, attr) in path_attrs { + raw_tokens.extend([ + NodeOrToken::Token(make.token(T![,])), + NodeOrToken::Token(make.whitespace(" ")), + ]); + path.syntax().descendants_with_tokens().for_each(|it| { + if let NodeOrToken::Token(token) = it { + raw_tokens.push(NodeOrToken::Token(token)); + } + }); + if let Some(meta) = attr.meta() { + if let (Some(eq), Some(expr)) = (meta.eq_token(), meta.expr()) { + raw_tokens.push(NodeOrToken::Token(make.whitespace(" "))); + raw_tokens.push(NodeOrToken::Token(eq)); + raw_tokens.push(NodeOrToken::Token(make.whitespace(" "))); - expr.syntax().descendants_with_tokens().for_each(|it| { - if let NodeOrToken::Token(token) = it { - raw_tokens.push(NodeOrToken::Token(token)); - } - }); - } else if let Some(tt) = meta.token_tree() { - raw_tokens.extend(tt.token_trees_and_tokens()); + expr.syntax().descendants_with_tokens().for_each(|it| { + if let NodeOrToken::Token(token) = it { + raw_tokens.push(NodeOrToken::Token(token)); + } + }); + } else if let Some(tt) = meta.token_tree() { + raw_tokens.extend(tt.token_trees_and_tokens()); + } } } let meta = make.meta_token_tree(make.ident_path("cfg_attr"), make.token_tree(T!['('], raw_tokens)); - let cfg_attr = - if attr.excl_token().is_some() { make.attr_inner(meta) } else { make.attr_outer(meta) }; + let cfg_attr = if first_attr.excl_token().is_some() { + make.attr_inner(meta) + } else { + make.attr_outer(meta) + }; - editor.replace(attr.syntax(), cfg_attr.syntax()); + let syntax_range = first_attr.syntax().clone().into()..=last_attr.syntax().clone().into(); + editor.replace_all(syntax_range, vec![cfg_attr.syntax().clone().into()]); if let Some(snippet_cap) = ctx.config.snippet_cap && let Some(first_meta) = @@ -332,7 +359,8 @@ fn unwrap_cfg_attr(acc: &mut Assists, attr: ast::Attr) -> Option<()> { return None; } let handle_source_change = |f: &mut SourceChangeBuilder| { - let inner_attrs = inner_attrs.iter().map(|it| it.to_string()).join("\n"); + let inner_attrs = + inner_attrs.iter().map(|it| it.to_string()).join(&format!("\n{}", attr.indent_level())); f.replace(range, inner_attrs); }; acc.add( @@ -414,6 +442,42 @@ pub struct Test { } "#, ); + check_assist( + wrap_unwrap_cfg_attr, + r#" + pub struct Test { + #[other_attr] + $0#[foo] + #[bar]$0 + #[other_attr] + test: u32, + } + "#, + r#" + pub struct Test { + #[other_attr] + #[cfg_attr($0, foo, bar)] + #[other_attr] + test: u32, + } + "#, + ); + check_assist( + wrap_unwrap_cfg_attr, + r#" + pub struct Test { + #[cfg_attr(debug_assertions$0, foo, bar)] + test: u32, + } + "#, + r#" + pub struct Test { + #[foo] + #[bar] + test: u32, + } + "#, + ); } #[test] fn to_from_eq_attr() { From 2b3e3190a22650fe0874e482afb3de1ab36d678b Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 8 Sep 2025 14:09:48 +0800 Subject: [PATCH 0005/1059] Add nested lifetime support for add_lifetime_to_type Changes: - Add nested lifetime support - Add explicit infer lifetime support - Change assist type to `quickfix` Example --- ```rust struct Foo { a: &$0i32, b: &'_ i32, c: (&i32, Bar<'_>), } ``` **Before this PR**: ```rust struct Foo<'a> { a: &'a i32, b: &'_ i32, c: (&i32, Bar<'_>), } ``` **After this PR**: ```rust struct Foo<'a> { a: &'a i32, b: &'a i32, c: (&'a i32, Bar<'a>), } ``` --- .../src/handlers/add_lifetime_to_type.rs | 115 +++++++++++------- 1 file changed, 68 insertions(+), 47 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs index 27dbdcf2c4d5..265ee3d2d4e7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs @@ -1,4 +1,7 @@ -use syntax::ast::{self, AstNode, HasGenericParams, HasName}; +use syntax::{ + SyntaxKind, SyntaxNode, SyntaxToken, + ast::{self, AstNode, HasGenericParams, HasName}, +}; use crate::{AssistContext, AssistId, Assists}; @@ -21,7 +24,7 @@ // ``` pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let ref_type_focused = ctx.find_node_at_offset::()?; - if ref_type_focused.lifetime().is_some() { + if ref_type_focused.lifetime().is_some_and(|lifetime| lifetime.text() != "'_") { return None; } @@ -34,10 +37,10 @@ pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_>) - return None; } - let ref_types = fetch_borrowed_types(&node)?; + let changes = fetch_borrowed_types(&node)?; let target = node.syntax().text_range(); - acc.add(AssistId::generate("add_lifetime_to_type"), "Add lifetime", target, |builder| { + acc.add(AssistId::quick_fix("add_lifetime_to_type"), "Add lifetime", target, |builder| { match node.generic_param_list() { Some(gen_param) => { if let Some(left_angle) = gen_param.l_angle_token() { @@ -51,16 +54,21 @@ pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_>) - } } - for ref_type in ref_types { - if let Some(amp_token) = ref_type.amp_token() { - builder.insert(amp_token.text_range().end(), "'a "); + for change in changes { + match change { + Change::Replace(it) => { + builder.replace(it.text_range(), "'a"); + } + Change::Insert(it) => { + builder.insert(it.text_range().end(), "'a "); + } } } }) } -fn fetch_borrowed_types(node: &ast::Adt) -> Option> { - let ref_types: Vec = match node { +fn fetch_borrowed_types(node: &ast::Adt) -> Option> { + let ref_types: Vec<_> = match node { ast::Adt::Enum(enum_) => { let variant_list = enum_.variant_list()?; variant_list @@ -79,55 +87,50 @@ fn fetch_borrowed_types(node: &ast::Adt) -> Option> { } ast::Adt::Union(un) => { let record_field_list = un.record_field_list()?; - record_field_list - .fields() - .filter_map(|r_field| { - if let ast::Type::RefType(ref_type) = r_field.ty()? - && ref_type.lifetime().is_none() - { - return Some(ref_type); - } - - None - }) - .collect() + find_ref_types_from_field_list(&record_field_list.into())? } }; if ref_types.is_empty() { None } else { Some(ref_types) } } -fn find_ref_types_from_field_list(field_list: &ast::FieldList) -> Option> { - let ref_types: Vec = match field_list { - ast::FieldList::RecordFieldList(record_list) => record_list - .fields() - .filter_map(|f| { - if let ast::Type::RefType(ref_type) = f.ty()? - && ref_type.lifetime().is_none() - { - return Some(ref_type); - } - - None - }) - .collect(), - ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list - .fields() - .filter_map(|f| { - if let ast::Type::RefType(ref_type) = f.ty()? - && ref_type.lifetime().is_none() - { - return Some(ref_type); - } - - None - }) - .collect(), +fn find_ref_types_from_field_list(field_list: &ast::FieldList) -> Option> { + let ref_types: Vec<_> = match field_list { + ast::FieldList::RecordFieldList(record_list) => { + record_list.fields().flat_map(|f| infer_lifetimes(f.syntax())).collect() + } + ast::FieldList::TupleFieldList(tuple_field_list) => { + tuple_field_list.fields().flat_map(|f| infer_lifetimes(f.syntax())).collect() + } }; if ref_types.is_empty() { None } else { Some(ref_types) } } +enum Change { + Replace(SyntaxToken), + Insert(SyntaxToken), +} + +fn infer_lifetimes(node: &SyntaxNode) -> Vec { + node.children() + .filter(|it| !matches!(it.kind(), SyntaxKind::FN_PTR_TYPE | SyntaxKind::TYPE_BOUND_LIST)) + .flat_map(|it| { + infer_lifetimes(&it) + .into_iter() + .chain(ast::Lifetime::cast(it.clone()).and_then(|lt| { + lt.lifetime_ident_token().filter(|lt| lt.text() == "'_").map(Change::Replace) + })) + .chain( + ast::RefType::cast(it) + .filter(|ty| ty.lifetime().is_none()) + .and_then(|ty| ty.amp_token()) + .map(Change::Insert), + ) + }) + .collect() +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -164,6 +167,24 @@ fn add_lifetime_to_struct() { check_assist_not_applicable(add_lifetime_to_type, r#"struct Foo { a: &'a$0 i32 }"#); } + #[test] + fn add_lifetime_to_nested_types() { + check_assist( + add_lifetime_to_type, + r#"struct Foo { a: &$0i32, b: &(&i32, fn(&str) -> &str) }"#, + r#"struct Foo<'a> { a: &'a i32, b: &'a (&'a i32, fn(&str) -> &str) }"#, + ); + } + + #[test] + fn add_lifetime_to_explicit_infer_lifetime() { + check_assist( + add_lifetime_to_type, + r#"struct Foo { a: &'_ $0i32, b: &'_ (&'_ i32, fn(&str) -> &str) }"#, + r#"struct Foo<'a> { a: &'a i32, b: &'a (&'a i32, fn(&str) -> &str) }"#, + ); + } + #[test] fn add_lifetime_to_enum() { check_assist( From 5cd99fb215e58a881900e1c25af15e0bd7707023 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 29 Sep 2025 18:12:38 +0800 Subject: [PATCH 0006/1059] Add fixes for non_exhaustive_let diagnostic Example --- ```rust fn foo() { let None$0 = Some(5); } ``` -> ```rust fn foo() { let None = Some(5) else { return }; } ``` --- .../src/handlers/non_exhaustive_let.rs | 185 +++++++++++++++++- 1 file changed, 178 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs index c86ecd2f03b9..346a59ed528d 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs @@ -1,4 +1,11 @@ -use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; +use either::Either; +use hir::Semantics; +use ide_db::text_edit::TextEdit; +use ide_db::ty_filter::TryEnum; +use ide_db::{RootDatabase, source_change::SourceChange}; +use syntax::{AstNode, ast}; + +use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, fix}; // Diagnostic: non-exhaustive-let // @@ -15,11 +22,68 @@ pub(crate) fn non_exhaustive_let( d.pat.map(Into::into), ) .stable() + .with_fixes(fixes(&ctx.sema, d)) +} +fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::NonExhaustiveLet) -> Option> { + let root = sema.parse_or_expand(d.pat.file_id); + let pat = d.pat.value.to_node(&root); + let let_stmt = ast::LetStmt::cast(pat.syntax().parent()?)?; + let early_node = let_stmt.syntax().ancestors().find_map(AstNode::cast)?; + let early_text = early_text(sema, &early_node); + + if let_stmt.let_else().is_some() { + return None; + } + + let file_id = d.pat.file_id.file_id()?.file_id(sema.db); + let semicolon = if let_stmt.semicolon_token().is_none() { ";" } else { "" }; + let else_block = format!(" else {{ {early_text} }}{semicolon}"); + let insert_offset = let_stmt + .semicolon_token() + .map(|it| it.text_range().start()) + .unwrap_or_else(|| let_stmt.syntax().text_range().end()); + + let source_change = + SourceChange::from_text_edit(file_id, TextEdit::insert(insert_offset, else_block)); + let target = sema.original_range(let_stmt.syntax()).range; + Some(vec![fix("add_let_else_block", "Add let-else block", source_change, target)]) +} + +fn early_text( + sema: &Semantics<'_, RootDatabase>, + early_node: &Either>, +) -> &'static str { + match early_node { + Either::Left(_any_loop) => "continue", + Either::Right(Either::Left(fn_)) => sema + .to_def(fn_) + .map(|fn_def| fn_def.ret_type(sema.db)) + .map(|ty| return_text(&ty, sema)) + .unwrap_or("return"), + Either::Right(Either::Right(closure)) => closure + .body() + .and_then(|expr| sema.type_of_expr(&expr)) + .map(|ty| return_text(&ty.adjusted(), sema)) + .unwrap_or("return"), + } +} + +fn return_text(ty: &hir::Type<'_>, sema: &Semantics<'_, RootDatabase>) -> &'static str { + if ty.is_unit() { + "return" + } else if let Some(try_enum) = TryEnum::from_ty(sema, ty) { + match try_enum { + TryEnum::Option => "return None", + TryEnum::Result => "return Err($0)", + } + } else { + "return $0" + } } #[cfg(test)] mod tests { - use crate::tests::check_diagnostics; + use crate::tests::{check_diagnostics, check_fix}; #[test] fn option_nonexhaustive() { @@ -28,7 +92,7 @@ fn option_nonexhaustive() { //- minicore: option fn main() { let None = Some(5); - //^^^^ error: non-exhaustive pattern: `Some(_)` not covered + //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered } "#, ); @@ -54,7 +118,7 @@ fn option_nonexhaustive_inside_blocks() { fn main() { '_a: { let None = Some(5); - //^^^^ error: non-exhaustive pattern: `Some(_)` not covered + //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered } } "#, @@ -66,7 +130,7 @@ fn main() { fn main() { let _ = async { let None = Some(5); - //^^^^ error: non-exhaustive pattern: `Some(_)` not covered + //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered }; } "#, @@ -78,7 +142,7 @@ fn main() { fn main() { unsafe { let None = Some(5); - //^^^^ error: non-exhaustive pattern: `Some(_)` not covered + //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered } } "#, @@ -101,7 +165,7 @@ fn test(x: Result) { //- minicore: result fn test(x: Result) { let Ok(_y) = x; - //^^^^^^ error: non-exhaustive pattern: `Err(_)` not covered + //^^^^^^ 💡 error: non-exhaustive pattern: `Err(_)` not covered } "#, ); @@ -132,6 +196,113 @@ fn foo(v: Enum<()>) { ); } + #[test] + fn fix_return_in_loop() { + check_fix( + r#" +//- minicore: option +fn foo() { + while cond { + let None$0 = Some(5); + } +} +"#, + r#" +fn foo() { + while cond { + let None = Some(5) else { continue }; + } +} +"#, + ); + } + + #[test] + fn fix_return_in_fn() { + check_fix( + r#" +//- minicore: option +fn foo() { + let None$0 = Some(5); +} +"#, + r#" +fn foo() { + let None = Some(5) else { return }; +} +"#, + ); + } + + #[test] + fn fix_return_in_incomplete_let() { + check_fix( + r#" +//- minicore: option +fn foo() { + let None$0 = Some(5) +} +"#, + r#" +fn foo() { + let None = Some(5) else { return }; +} +"#, + ); + } + + #[test] + fn fix_return_in_closure() { + check_fix( + r#" +//- minicore: option +fn foo() -> Option<()> { + let _f = || { + let None$0 = Some(5); + }; +} +"#, + r#" +fn foo() -> Option<()> { + let _f = || { + let None = Some(5) else { return }; + }; +} +"#, + ); + } + + #[test] + fn fix_return_try_in_fn() { + check_fix( + r#" +//- minicore: option +fn foo() -> Option<()> { + let None$0 = Some(5); +} +"#, + r#" +fn foo() -> Option<()> { + let None = Some(5) else { return None }; +} +"#, + ); + + check_fix( + r#" +//- minicore: option, result +fn foo() -> Result<(), i32> { + let None$0 = Some(5); +} +"#, + r#" +fn foo() -> Result<(), i32> { + let None = Some(5) else { return Err($0) }; +} +"#, + ); + } + #[test] fn regression_20259() { check_diagnostics( From c55a2249cd3014c3f14a782c292adf3bee32f7f9 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 26 Oct 2025 17:16:47 +0800 Subject: [PATCH 0007/1059] Support unmap macro expansion --- .../src/handlers/non_exhaustive_let.rs | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs index 346a59ed528d..bc10e82854f5 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs @@ -24,24 +24,30 @@ pub(crate) fn non_exhaustive_let( .stable() .with_fixes(fixes(&ctx.sema, d)) } + fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::NonExhaustiveLet) -> Option> { let root = sema.parse_or_expand(d.pat.file_id); let pat = d.pat.value.to_node(&root); let let_stmt = ast::LetStmt::cast(pat.syntax().parent()?)?; - let early_node = let_stmt.syntax().ancestors().find_map(AstNode::cast)?; + let early_node = + sema.ancestors_with_macros(let_stmt.syntax().clone()).find_map(AstNode::cast)?; let early_text = early_text(sema, &early_node); if let_stmt.let_else().is_some() { return None; } - - let file_id = d.pat.file_id.file_id()?.file_id(sema.db); + let hir::FileRangeWrapper { file_id, range } = sema.original_range_opt(let_stmt.syntax())?; + let insert_offset = if let Some(semicolon) = let_stmt.semicolon_token() + && let Some(token) = sema.parse(file_id).syntax().token_at_offset(range.end()).left_biased() + && token.kind() == semicolon.kind() + { + token.text_range().start() + } else { + range.end() + }; let semicolon = if let_stmt.semicolon_token().is_none() { ";" } else { "" }; let else_block = format!(" else {{ {early_text} }}{semicolon}"); - let insert_offset = let_stmt - .semicolon_token() - .map(|it| it.text_range().start()) - .unwrap_or_else(|| let_stmt.syntax().text_range().end()); + let file_id = file_id.file_id(sema.db); let source_change = SourceChange::from_text_edit(file_id, TextEdit::insert(insert_offset, else_block)); @@ -234,6 +240,29 @@ fn foo() { ); } + #[test] + fn fix_return_in_macro_expanded() { + check_fix( + r#" +//- minicore: option +macro_rules! identity { ($($t:tt)*) => { $($t)* }; } +fn foo() { + identity! { + let None$0 = Some(5); + } +} +"#, + r#" +macro_rules! identity { ($($t:tt)*) => { $($t)* }; } +fn foo() { + identity! { + let None = Some(5) else { return }; + } +} +"#, + ); + } + #[test] fn fix_return_in_incomplete_let() { check_fix( From c1b51dd002ee13514355987ce77fa600584eb3a5 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Wed, 5 Nov 2025 18:49:44 -0800 Subject: [PATCH 0008/1059] In `Option::get_or_insert_with()`, forget the `None` instead of dropping it. This allows eliminating the `T: [const] Destruct` bounds and avoids generating an implicit `drop_in_place::>()` that will never do anything. Ideally, the compiler would prove that that drop is not necessary itself, but it currently doesn't, even with `const_precise_live_drops` enabled. --- library/core/src/option.rs | 21 ++++++++++++++++++--- library/coretests/tests/option.rs | 24 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/library/core/src/option.rs b/library/core/src/option.rs index e3c4758bc6af..df92fb3955a9 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -1776,7 +1776,7 @@ pub fn get_or_insert(&mut self, value: T) -> &mut T { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn get_or_insert_default(&mut self) -> &mut T where - T: [const] Default + [const] Destruct, + T: [const] Default, { self.get_or_insert_with(T::default) } @@ -1804,10 +1804,25 @@ pub const fn get_or_insert_default(&mut self) -> &mut T pub const fn get_or_insert_with(&mut self, f: F) -> &mut T where F: [const] FnOnce() -> T + [const] Destruct, - T: [const] Destruct, { if let None = self { - *self = Some(f()); + // The effect of the following statement is identical to + // *self = Some(f()); + // except that it does not drop the old value of `*self`. This is not a leak, because + // we just checked that the old value is `None`, which contains no fields to drop. + // This implementation strategy + // + // * avoids needing a `T: [const] Destruct` bound, to the benefit of `const` callers, + // * and avoids possibly compiling needless drop code (as would sometimes happen in the + // previous implementation), to the benefit of non-`const` callers. + // + // FIXME(const-hack): It would be nice if this weird trick were made obsolete + // (though that is likely to be hard/wontfix). + // + // It could also be expressed as `unsafe { core::ptr::write(self, Some(f())) }`, but + // no reason is currently known to use additional unsafe code here. + + mem::forget(mem::replace(self, Some(f()))); } // SAFETY: a `None` variant for `self` would have been replaced by a `Some` diff --git a/library/coretests/tests/option.rs b/library/coretests/tests/option.rs index fc0f82ad6bb3..3df7afb4f3be 100644 --- a/library/coretests/tests/option.rs +++ b/library/coretests/tests/option.rs @@ -495,6 +495,30 @@ const fn option_const_mut() { */ } +/// Test that `Option::get_or_insert_default` is usable in const contexts, including with types that +/// do not satisfy `T: const Destruct`. +#[test] +fn const_get_or_insert_default() { + const OPT_DEFAULT: Option> = { + let mut x = None; + x.get_or_insert_default(); + x + }; + assert!(OPT_DEFAULT.is_some()); +} + +/// Test that `Option::get_or_insert_with` is usable in const contexts, including with types that +/// do not satisfy `T: const Destruct`. +#[test] +fn const_get_or_insert_with() { + const OPT_WITH: Option> = { + let mut x = None; + x.get_or_insert_with(Vec::new); + x + }; + assert!(OPT_WITH.is_some()); +} + #[test] fn test_unwrap_drop() { struct Dtor<'a> { From 440cb96934cbc71daee8b6c0cde9271090ed3bde Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 7 Nov 2025 21:55:50 +0800 Subject: [PATCH 0009/1059] Support WhileExpr and ForExpr for add_label_to_loop Example --- ```rust fn main() { for$0 _ in 0..5 { break; continue; } } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn main() { 'l: for _ in 0..5 { break 'l; continue 'l; } } ``` --- .../src/handlers/add_label_to_loop.rs | 81 ++++++++++++++++++- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs index b84ad24cfcef..ece927fce2cd 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs @@ -35,9 +35,9 @@ // } // ``` pub(crate) fn add_label_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let loop_kw = ctx.find_token_syntax_at_offset(T![loop])?; - let loop_expr = loop_kw.parent().and_then(ast::LoopExpr::cast)?; - if loop_expr.label().is_some() { + let loop_expr = ctx.find_node_at_offset::()?; + let loop_kw = loop_token(&loop_expr)?; + if loop_expr.label().is_some() || !loop_kw.text_range().contains_inclusive(ctx.offset()) { return None; } @@ -80,6 +80,14 @@ pub(crate) fn add_label_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) -> O ) } +fn loop_token(loop_expr: &ast::AnyHasLoopBody) -> Option { + loop_expr + .syntax() + .children_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| matches!(it.kind(), T![for] | T![loop] | T![while])) +} + fn insert_label_after_token( editor: &mut SyntaxEditor, make: &SyntaxFactory, @@ -123,6 +131,48 @@ fn main() { ); } + #[test] + fn add_label_to_while_expr() { + check_assist( + add_label_to_loop, + r#" +fn main() { + while$0 true { + break; + continue; + } +}"#, + r#" +fn main() { + ${1:'l}: while true { + break ${2:'l}; + continue ${0:'l}; + } +}"#, + ); + } + + #[test] + fn add_label_to_for_expr() { + check_assist( + add_label_to_loop, + r#" +fn main() { + for$0 _ in 0..5 { + break; + continue; + } +}"#, + r#" +fn main() { + ${1:'l}: for _ in 0..5 { + break ${2:'l}; + continue ${0:'l}; + } +}"#, + ); + } + #[test] fn add_label_to_outer_loop() { check_assist( @@ -191,6 +241,31 @@ fn main() { break 'l; continue 'l; } +}"#, + ); + } + + #[test] + fn do_not_add_label_if_outside_keyword() { + check_assist_not_applicable( + add_label_to_loop, + r#" +fn main() { + 'l: loop {$0 + break 'l; + continue 'l; + } +}"#, + ); + + check_assist_not_applicable( + add_label_to_loop, + r#" +fn main() { + 'l: while true {$0 + break 'l; + continue 'l; + } }"#, ); } From f4334bb19c847eb9e37d42a20c153ea81e74ee80 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 23 Dec 2025 16:49:57 +0800 Subject: [PATCH 0010/1059] stdx indent and dedent utils --- .../rust-analyzer/crates/stdx/src/lib.rs | 57 +++++++++++++++++-- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/stdx/src/lib.rs b/src/tools/rust-analyzer/crates/stdx/src/lib.rs index 5fa007416371..bda933bb4032 100644 --- a/src/tools/rust-analyzer/crates/stdx/src/lib.rs +++ b/src/tools/rust-analyzer/crates/stdx/src/lib.rs @@ -207,12 +207,7 @@ pub fn trim_indent(mut text: &str) -> String { if text.starts_with('\n') { text = &text[1..]; } - let indent = text - .lines() - .filter(|it| !it.trim().is_empty()) - .map(|it| it.len() - it.trim_start().len()) - .min() - .unwrap_or(0); + let indent = indent_of(text); text.split_inclusive('\n') .map( |line| { @@ -222,6 +217,25 @@ pub fn trim_indent(mut text: &str) -> String { .collect() } +#[must_use] +fn indent_of(text: &str) -> usize { + text.lines() + .filter(|it| !it.trim().is_empty()) + .map(|it| it.len() - it.trim_start().len()) + .min() + .unwrap_or(0) +} + +#[must_use] +pub fn dedent_by(spaces: usize, text: &str) -> String { + text.split_inclusive('\n') + .map(|line| { + let trimmed = line.trim_start_matches(' '); + if line.len() - trimmed.len() <= spaces { trimmed } else { &line[spaces..] } + }) + .collect() +} + pub fn equal_range_by(slice: &[T], mut key: F) -> ops::Range where F: FnMut(&T) -> Ordering, @@ -352,6 +366,37 @@ fn main() { ); } + #[test] + fn test_dedent() { + assert_eq!(dedent_by(0, ""), ""); + assert_eq!(dedent_by(1, ""), ""); + assert_eq!(dedent_by(2, ""), ""); + assert_eq!(dedent_by(0, "foo"), "foo"); + assert_eq!(dedent_by(2, "foo"), "foo"); + assert_eq!(dedent_by(2, " foo"), "foo"); + assert_eq!(dedent_by(2, " foo"), " foo"); + assert_eq!(dedent_by(2, " foo\nbar"), " foo\nbar"); + assert_eq!(dedent_by(2, "foo\n bar"), "foo\n bar"); + assert_eq!(dedent_by(2, "foo\n\n bar"), "foo\n\n bar"); + assert_eq!(dedent_by(2, "foo\n.\n bar"), "foo\n.\n bar"); + assert_eq!(dedent_by(2, "foo\n .\n bar"), "foo\n.\n bar"); + assert_eq!(dedent_by(2, "foo\n .\n bar"), "foo\n .\n bar"); + } + + #[test] + fn test_indent_of() { + assert_eq!(indent_of(""), 0); + assert_eq!(indent_of(" "), 0); + assert_eq!(indent_of(" x"), 1); + assert_eq!(indent_of(" x\n"), 1); + assert_eq!(indent_of(" x\ny"), 0); + assert_eq!(indent_of(" x\n y"), 1); + assert_eq!(indent_of(" x\n y"), 1); + assert_eq!(indent_of(" x\n y"), 2); + assert_eq!(indent_of(" x\n y\n"), 2); + assert_eq!(indent_of(" x\n\n y\n"), 2); + } + #[test] fn test_replace() { #[track_caller] From ad4e1c2fb27802f54806286d4fc486c5d11392fa Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 23 Dec 2025 16:50:25 +0800 Subject: [PATCH 0011/1059] Fix postfix completion indentation compensation Editor adds the current indentation to the content of the code snippet This PR dedentation is used to offset the editor snippet indentation Example --- ```rust fn foo(x: Option, y: Option) { let _f = || { x .and(y) .map(|it| it+2) .$0 }; } ``` **Before this PR** ```rust fn foo(x: Option, y: Option) { let _f = || { let $0 = x .and(y) .map(|it| it+2); }; } ``` **After this PR** ```rust fn foo(x: Option, y: Option) { let _f = || { let $0 = x .and(y) .map(|it| it+2); }; } ``` --- .../ide-completion/src/completions/postfix.rs | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index 7f67ef848ece..250e1472e9a3 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -355,12 +355,20 @@ fn get_receiver_text( range.range = TextRange::at(range.range.start(), range.range.len() - TextSize::of('.')) } let file_text = sema.db.file_text(range.file_id.file_id(sema.db)); - let mut text = file_text.text(sema.db)[range.range].to_owned(); + let text = file_text.text(sema.db); + let indent_spaces = indent_of_tail_line(&text[TextRange::up_to(range.range.start())]); + let mut text = stdx::dedent_by(indent_spaces, &text[range.range]); // The receiver texts should be interpreted as-is, as they are expected to be // normal Rust expressions. escape_snippet_bits(&mut text); - text + return text; + + fn indent_of_tail_line(text: &str) -> usize { + let tail_line = text.rsplit_once('\n').map_or(text, |(_, s)| s); + let trimmed = tail_line.trim_start_matches(' '); + tail_line.len() - trimmed.len() + } } /// Escapes `\` and `$` so that they don't get interpreted as snippet-specific constructs. @@ -977,9 +985,9 @@ fn main() { fn main() { ControlFlow::Break(match true { - true => "\${1:placeholder}", - false => "\\\$", - }) + true => "\${1:placeholder}", + false => "\\\$", +}) } "#, ); @@ -1219,4 +1227,31 @@ fn foo() { "#, ); } + + #[test] + fn snippet_dedent() { + check_edit( + "let", + r#" +//- minicore: option +fn foo(x: Option, y: Option) { + let _f = || { + x + .and(y) + .map(|it| it+2) + .$0 + }; +} +"#, + r#" +fn foo(x: Option, y: Option) { + let _f = || { + let $0 = x + .and(y) + .map(|it| it+2); + }; +} +"#, + ); + } } From 430468fdbfb5d3972aafe9bcd196b5a7fc484159 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 25 Dec 2025 15:06:45 +0800 Subject: [PATCH 0012/1059] Fix tuple struct pat expected type Example --- ```rust struct Foo(Option); fn foo(x: Foo) -> Foo { match x { Foo($0) => () } } ``` **Before this PR** ```rust ty: Foo, name: ? ``` **After this PR** ```rust ty: Option, name: ? ``` --- .../ide-completion/src/context/analysis.rs | 10 +++++ .../ide-completion/src/context/tests.rs | 44 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index ce26d3806cc4..0528a94dac65 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -761,6 +761,16 @@ fn expected_type_and_name<'db>( let ty = sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original); (ty, None) }, + ast::TupleStructPat(it) => { + let fields = it.path().and_then(|path| match sema.resolve_path(&path)? { + hir::PathResolution::Def(hir::ModuleDef::Adt(adt)) => Some(adt.as_struct()?.fields(sema.db)), + hir::PathResolution::Def(hir::ModuleDef::Variant(variant)) => Some(variant.fields(sema.db)), + _ => None, + }); + let nr = it.fields().take_while(|it| it.syntax().text_range().end() <= token.text_range().start()).count(); + let ty = fields.and_then(|fields| Some(fields.get(nr)?.ty(sema.db).to_type(sema.db))); + (ty, None) + }, ast::Fn(it) => { cov_mark::hit!(expected_type_fn_ret_with_leading_char); cov_mark::hit!(expected_type_fn_ret_without_leading_char); diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs index 09a9b6f112f0..3169a16e2e74 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs @@ -287,6 +287,50 @@ fn foo() -> Foo { ); } +#[test] +fn expected_type_tuple_struct_pat() { + check_expected_type_and_name( + r#" +//- minicore: option +struct Foo(Option); +fn foo(x: Foo) -> Foo { + match x { Foo($0) => () } +} +"#, + expect![[r#"ty: Option, name: ?"#]], + ); + + check_expected_type_and_name( + r#" +struct Foo(i32, u32, f32); +fn foo(x: Foo) -> Foo { + match x { Foo($0) => () } +} +"#, + expect![[r#"ty: i32, name: ?"#]], + ); + + check_expected_type_and_name( + r#" +struct Foo(i32, u32, f32); +fn foo(x: Foo) -> Foo { + match x { Foo(num,$0) => () } +} +"#, + expect![[r#"ty: u32, name: ?"#]], + ); + + check_expected_type_and_name( + r#" +struct Foo(i32, u32, f32); +fn foo(x: Foo) -> Foo { + match x { Foo(num,$0,float) => () } +} +"#, + expect![[r#"ty: u32, name: ?"#]], + ); +} + #[test] fn expected_type_if_let_without_leading_char() { cov_mark::check!(expected_type_if_let_without_leading_char); From e04b950e1231b33d7dc951494b66f4315192bc81 Mon Sep 17 00:00:00 2001 From: dfireBird Date: Sat, 3 Jan 2026 10:37:57 +0530 Subject: [PATCH 0013/1059] change test_name placeholder to executable_arg fix replacing target on lib target kind --- .../crates/rust-analyzer/src/config.rs | 12 +-- .../crates/rust-analyzer/src/target_spec.rs | 85 +++++++++++++------ .../docs/book/src/configuration_generated.md | 12 +-- .../rust-analyzer/editors/code/package.json | 6 +- 4 files changed, 76 insertions(+), 39 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index e39569e108de..444bbd1b2683 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -909,18 +909,18 @@ pub enum MaxSubstitutionLength { /// Override the command used for bench runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically + /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically /// replace the package name, target option (such as `--bin` or `--example`), the target name and - /// the test name (name of test function or test mod path). + /// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). runnables_bench_overrideCommand: Option> = None, /// Command to be executed instead of 'cargo' for runnables. runnables_command: Option = None, /// Override the command used for bench runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically + /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically /// replace the package name, target option (such as `--bin` or `--example`), the target name and - /// the test name (name of test function or test mod path). + /// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). runnables_doctest_overrideCommand: Option> = None, /// Additional arguments to be passed to cargo for runnables such as /// tests or binaries. For example, it may be `--release`. @@ -938,9 +938,9 @@ pub enum MaxSubstitutionLength { /// Override the command used for test runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically + /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically /// replace the package name, target option (such as `--bin` or `--example`), the target name and - /// the test name (name of test function or test mod path). + /// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). runnables_test_overrideCommand: Option> = None, /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index e0f95a7830ea..6d9adc2d8d8a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -119,38 +119,21 @@ pub(crate) fn runnable_args( let extra_test_binary_args = config.extra_test_binary_args; let mut cargo_args = Vec::new(); - let mut executable_args = Vec::new(); + let executable_args = Self::executable_args_for(kind, extra_test_binary_args); match kind { - RunnableKind::Test { test_id, attr } => { + RunnableKind::Test { .. } => { cargo_args.push(config.test_command); - executable_args.push(test_id.to_string()); - if let TestId::Path(_) = test_id { - executable_args.push("--exact".to_owned()); - } - executable_args.extend(extra_test_binary_args); - if attr.ignore { - executable_args.push("--ignored".to_owned()); - } } - RunnableKind::TestMod { path } => { + RunnableKind::TestMod { .. } => { cargo_args.push(config.test_command); - executable_args.push(path.clone()); - executable_args.extend(extra_test_binary_args); } - RunnableKind::Bench { test_id } => { + RunnableKind::Bench { .. } => { cargo_args.push(config.bench_command); - executable_args.push(test_id.to_string()); - if let TestId::Path(_) = test_id { - executable_args.push("--exact".to_owned()); - } - executable_args.extend(extra_test_binary_args); } - RunnableKind::DocTest { test_id } => { + RunnableKind::DocTest { .. } => { cargo_args.push("test".to_owned()); cargo_args.push("--doc".to_owned()); - executable_args.push(test_id.to_string()); - executable_args.extend(extra_test_binary_args); } RunnableKind::Bin => { let subcommand = match spec { @@ -243,16 +226,70 @@ pub(crate) fn override_command( TargetKind::BuildScript | TargetKind::Other => "", }; + let target = |kind, target| match kind { + TargetKind::Bin | TargetKind::Test | TargetKind::Bench | TargetKind::Example => target, + _ => "", + }; + let replace_placeholders = |arg: String| match &spec { Some(spec) => arg .replace("${package}", &spec.package) .replace("${target_arg}", target_arg(spec.target_kind)) - .replace("${target}", &spec.target) + .replace("${target}", target(spec.target_kind, &spec.target)) .replace("${test_name}", &test_name), _ => arg, }; - args.map(|args| args.into_iter().map(replace_placeholders).collect()) + let extra_test_binary_args = config.extra_test_binary_args; + let executable_args = Self::executable_args_for(kind, extra_test_binary_args); + + args.map(|mut args| { + let exec_args_idx = args.iter().position(|a| a == "${executable_args}"); + + if let Some(idx) = exec_args_idx { + args.splice(idx..idx + 1, executable_args); + } + + args.into_iter().map(replace_placeholders).filter(|a| !a.trim().is_empty()).collect() + }) + } + + fn executable_args_for( + kind: &RunnableKind, + extra_test_binary_args: impl IntoIterator, + ) -> Vec { + let mut executable_args = Vec::new(); + + match kind { + RunnableKind::Test { test_id, attr } => { + executable_args.push(test_id.to_string()); + if let TestId::Path(_) = test_id { + executable_args.push("--exact".to_owned()); + } + executable_args.extend(extra_test_binary_args); + if attr.ignore { + executable_args.push("--ignored".to_owned()); + } + } + RunnableKind::TestMod { path } => { + executable_args.push(path.clone()); + executable_args.extend(extra_test_binary_args); + } + RunnableKind::Bench { test_id } => { + executable_args.push(test_id.to_string()); + if let TestId::Path(_) = test_id { + executable_args.push("--exact".to_owned()); + } + executable_args.extend(extra_test_binary_args); + } + RunnableKind::DocTest { test_id } => { + executable_args.push(test_id.to_string()); + executable_args.extend(extra_test_binary_args); + } + RunnableKind::Bin => {} + } + + executable_args } pub(crate) fn push_to(self, buf: &mut Vec, kind: &RunnableKind) { diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md index 58b636334527..811f995493a6 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -1362,9 +1362,9 @@ Default: `null` Override the command used for bench runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically +Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically replace the package name, target option (such as `--bin` or `--example`), the target name and -the test name (name of test function or test mod path). +the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). ## rust-analyzer.runnables.command {#runnables.command} @@ -1381,9 +1381,9 @@ Default: `null` Override the command used for bench runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically +Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically replace the package name, target option (such as `--bin` or `--example`), the target name and -the test name (name of test function or test mod path). +the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). ## rust-analyzer.runnables.extraArgs {#runnables.extraArgs} @@ -1426,9 +1426,9 @@ Default: `null` Override the command used for test runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically +Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically replace the package name, target option (such as `--bin` or `--example`), the target name and -the test name (name of test function or test mod path). +the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). ## rust-analyzer.rustc.source {#rustc.source} diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 2157cbd48653..498077ddb51a 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -2840,7 +2840,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.bench.overrideCommand": { - "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).", + "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", "default": null, "type": [ "null", @@ -2869,7 +2869,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.doctest.overrideCommand": { - "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).", + "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", "default": null, "type": [ "null", @@ -2923,7 +2923,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.test.overrideCommand": { - "markdownDescription": "Override the command used for test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).", + "markdownDescription": "Override the command used for test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", "default": null, "type": [ "null", From b21e115e2cc6193838cdc499e127debcffe4d62a Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 14:04:32 +1100 Subject: [PATCH 0014/1059] Add a test for parsing BuildData and RunnableData json --- .../crates/project-model/src/tests.rs | 6 ++++ .../test_data/labeled-project.json | 31 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json diff --git a/src/tools/rust-analyzer/crates/project-model/src/tests.rs b/src/tools/rust-analyzer/crates/project-model/src/tests.rs index a03ed562e1be..395cea6f76e6 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/tests.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/tests.rs @@ -192,6 +192,12 @@ fn rust_project_hello_world_project_model() { ); } +#[test] +fn rust_project_labeled_project_model() { + // This just needs to parse. + _ = load_rust_project("labeled-project.json"); +} + #[test] fn rust_project_cfg_groups() { let (crate_graph, _proc_macros) = load_rust_project("cfg-groups.json"); diff --git a/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json b/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json new file mode 100644 index 000000000000..50f66739cfdf --- /dev/null +++ b/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json @@ -0,0 +1,31 @@ +{ + "sysroot_src": null, + "crates": [ + { + "display_name": "hello_world", + "root_module": "$ROOT$src/lib.rs", + "edition": "2018", + "deps": [], + "is_workspace_member": true, + "build": { + "label": "//:hello_world", + "build_file": "$ROOT$BUILD", + "target_kind": "bin" + } + } + ], + "runnables": [ + { + "kind": "run", + "program": "bazel", + "args": ["run", "{label}"], + "cwd": "$ROOT$" + }, + { + "kind": "flycheck", + "program": "$ROOT$custom-flychecker.sh", + "args": ["{label}"], + "cwd": "$ROOT$" + } + ] +} From 557df27ade4759f3e7d0c5c8ea8aed6cdf75a597 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 10:07:36 +1100 Subject: [PATCH 0015/1059] Privatise some fields and structs in project_json These are not used outside of the project-model crate, ie. instantly converted to other structures. --- .../crates/project-model/src/project_json.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs index cbbb95cc468f..cd13a208a4a4 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs @@ -460,23 +460,23 @@ enum EditionData { } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] -pub struct BuildData { +struct BuildData { label: String, build_file: Utf8PathBuf, target_kind: TargetKindData, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct RunnableData { - pub program: String, - pub args: Vec, - pub cwd: Utf8PathBuf, - pub kind: RunnableKindData, +struct RunnableData { + program: String, + args: Vec, + cwd: Utf8PathBuf, + kind: RunnableKindData, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -pub enum RunnableKindData { +enum RunnableKindData { Flycheck, Check, Run, @@ -490,7 +490,7 @@ pub enum RunnableKindData { #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -pub enum TargetKindData { +enum TargetKindData { Bin, /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...). Lib, From b801b88405cf5911ad8196fc2ed67233d4bb2b51 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 10:06:47 +1100 Subject: [PATCH 0016/1059] Add unknown runnable kind for forwards compatibility --- .../crates/project-model/src/project_json.rs | 9 +++++++++ .../crates/project-model/test_data/labeled-project.json | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs index 6938010cbd70..cbbb95cc468f 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs @@ -368,6 +368,9 @@ pub enum RunnableKind { /// Template for checking a target, emitting rustc JSON diagnostics. /// May include {label} which will get the label from the `build` section of a crate. Flycheck, + + /// For forwards-compatibility, i.e. old rust-analyzer binary with newer workspace discovery tools + Unknown, } #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] @@ -478,6 +481,11 @@ pub enum RunnableKindData { Check, Run, TestOne, + + /// For forwards-compatibility, i.e. old rust-analyzer binary with newer workspace discovery tools + #[allow(unused)] + #[serde(other)] + Unknown, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] @@ -546,6 +554,7 @@ fn from(data: RunnableKindData) -> Self { RunnableKindData::Run => RunnableKind::Run, RunnableKindData::TestOne => RunnableKind::TestOne, RunnableKindData::Flycheck => RunnableKind::Flycheck, + RunnableKindData::Unknown => RunnableKind::Unknown, } } } diff --git a/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json b/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json index 50f66739cfdf..5c0e1f33979e 100644 --- a/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json +++ b/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json @@ -26,6 +26,12 @@ "program": "$ROOT$custom-flychecker.sh", "args": ["{label}"], "cwd": "$ROOT$" + }, + { + "kind": "we-ignore-unknown-runnable-kinds-for-forwards-compatibility", + "program": "abc", + "args": ["{label}"], + "cwd": "$ROOT$" } ] } From f8455e26bdbd27dd4f970e39d87aaa1d84b3ae87 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 10:07:36 +1100 Subject: [PATCH 0017/1059] A comment to help ProjectJsonData stay backwards-compatible Makes clear to future editors of this code that they should add #[serde(default)] to new fields. --- .../rust-analyzer/crates/project-model/src/project_json.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs index cd13a208a4a4..e3a4feec8d92 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs @@ -383,6 +383,8 @@ pub struct ProjectJsonData { crates: Vec, #[serde(default)] runnables: Vec, + // + // New fields should be Option or #[serde(default)]. This applies to most of this datastructure. } #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Default)] From ff365fd4a4eb9b7c7a59c514b240912ed710d3dd Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 11:07:21 +1100 Subject: [PATCH 0018/1059] Refactor the runnable_args implementation to be more compact I'm about to complete the match statement here with more of the same. Once you write the exact same code 5 times, it's time for a helper function. --- .../crates/rust-analyzer/src/target_spec.rs | 64 ++++++++----------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index b8d9acc02a32..c87eefc6955a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -6,7 +6,7 @@ use cfg::{CfgAtom, CfgExpr}; use hir::sym; use ide::{Cancellable, Crate, FileId, RunnableKind, TestId}; -use project_model::project_json::Runnable; +use project_model::project_json::{self, Runnable}; use project_model::{CargoFeatures, ManifestPath, TargetKind}; use rustc_hash::FxHashSet; use triomphe::Arc; @@ -72,44 +72,36 @@ pub(crate) struct ProjectJsonTargetSpec { } impl ProjectJsonTargetSpec { + fn find_replace_runnable( + &self, + kind: project_json::RunnableKind, + replacer: &dyn Fn(&Self, &str) -> String, + ) -> Option { + for runnable in &self.shell_runnables { + if runnable.kind == kind { + let mut runnable = runnable.clone(); + + let replaced_args: Vec<_> = + runnable.args.iter().map(|arg| replacer(self, arg)).collect(); + runnable.args = replaced_args; + + return Some(runnable); + } + } + + None + } + pub(crate) fn runnable_args(&self, kind: &RunnableKind) -> Option { match kind { - RunnableKind::Bin => { - for runnable in &self.shell_runnables { - if matches!(runnable.kind, project_model::project_json::RunnableKind::Run) { - let mut runnable = runnable.clone(); - - let replaced_args: Vec<_> = runnable - .args - .iter() - .map(|arg| arg.replace("{label}", &self.label)) - .collect(); - runnable.args = replaced_args; - - return Some(runnable); - } - } - - None - } + RunnableKind::Bin => self + .find_replace_runnable(project_json::RunnableKind::Run, &|this, arg| { + arg.replace("{label}", &this.label) + }), RunnableKind::Test { test_id, .. } => { - for runnable in &self.shell_runnables { - if matches!(runnable.kind, project_model::project_json::RunnableKind::TestOne) { - let mut runnable = runnable.clone(); - - let replaced_args: Vec<_> = runnable - .args - .iter() - .map(|arg| arg.replace("{test_id}", &test_id.to_string())) - .map(|arg| arg.replace("{label}", &self.label)) - .collect(); - runnable.args = replaced_args; - - return Some(runnable); - } - } - - None + self.find_replace_runnable(project_json::RunnableKind::Run, &|this, arg| { + arg.replace("{label}", &this.label).replace("{test_id}", &test_id.to_string()) + }) } RunnableKind::TestMod { .. } => None, RunnableKind::Bench { .. } => None, From 91819d05f63488c0db7df48fa8212b6eccb4e843 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 10:34:16 +1100 Subject: [PATCH 0019/1059] Restore a deleted comment that was quite informative Was deleted when this code was moved in PR 18043. --- src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index 62a3b3a17bdf..ba817609d3ff 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -1180,6 +1180,8 @@ fn handle_flycheck_msg(&mut self, message: FlycheckMessage, cargo_finished: &mut } => self.diagnostics.clear_check_older_than_for_package(id, package_id, generation), FlycheckMessage::Progress { id, progress } => { let format_with_id = |user_facing_command: String| { + // When we're running multiple flychecks, we have to include a disambiguator in + // the title, or the editor complains. Note that this is a user-facing string. if self.flycheck.len() == 1 { user_facing_command } else { From f8d046d87deb9fd77fc35e42506091aee34cb8da Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 11:12:20 +1100 Subject: [PATCH 0020/1059] Support more runnable kinds for project json Allows project JSON users to run a whole module of tests, benchmarks, doctests. --- .../crates/project-model/src/project_json.rs | 21 +++++++++++++++++++ .../crates/rust-analyzer/src/target_spec.rs | 17 ++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs index cbbb95cc468f..201c59039bef 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs @@ -365,6 +365,21 @@ pub enum RunnableKind { /// May include {test_id} which will get the test clicked on by the user. TestOne, + /// Run tests matching a pattern (in RA, usually a path::to::module::of::tests) + /// May include {label} which will get the label from the `build` section of a crate. + /// May include {test_pattern} which will get the test module clicked on by the user. + TestMod, + + /// Run a single doctest + /// May include {label} which will get the label from the `build` section of a crate. + /// May include {test_id} which will get the doctest clicked on by the user. + DocTestOne, + + /// Run a single benchmark + /// May include {label} which will get the label from the `build` section of a crate. + /// May include {bench_id} which will get the benchmark clicked on by the user. + BenchOne, + /// Template for checking a target, emitting rustc JSON diagnostics. /// May include {label} which will get the label from the `build` section of a crate. Flycheck, @@ -481,6 +496,9 @@ pub enum RunnableKindData { Check, Run, TestOne, + TestMod, + DocTestOne, + BenchOne, /// For forwards-compatibility, i.e. old rust-analyzer binary with newer workspace discovery tools #[allow(unused)] @@ -553,6 +571,9 @@ fn from(data: RunnableKindData) -> Self { RunnableKindData::Check => RunnableKind::Check, RunnableKindData::Run => RunnableKind::Run, RunnableKindData::TestOne => RunnableKind::TestOne, + RunnableKindData::TestMod => RunnableKind::TestMod, + RunnableKindData::DocTestOne => RunnableKind::DocTestOne, + RunnableKindData::BenchOne => RunnableKind::BenchOne, RunnableKindData::Flycheck => RunnableKind::Flycheck, RunnableKindData::Unknown => RunnableKind::Unknown, } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index c87eefc6955a..b96786cae935 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -103,9 +103,20 @@ pub(crate) fn runnable_args(&self, kind: &RunnableKind) -> Option { arg.replace("{label}", &this.label).replace("{test_id}", &test_id.to_string()) }) } - RunnableKind::TestMod { .. } => None, - RunnableKind::Bench { .. } => None, - RunnableKind::DocTest { .. } => None, + RunnableKind::TestMod { path } => self + .find_replace_runnable(project_json::RunnableKind::TestMod, &|this, arg| { + arg.replace("{label}", &this.label).replace("{test_pattern}", path) + }), + RunnableKind::Bench { test_id } => { + self.find_replace_runnable(project_json::RunnableKind::BenchOne, &|this, arg| { + arg.replace("{label}", &this.label).replace("{bench_id}", &test_id.to_string()) + }) + } + RunnableKind::DocTest { test_id } => { + self.find_replace_runnable(project_json::RunnableKind::DocTestOne, &|this, arg| { + arg.replace("{label}", &this.label).replace("{test_id}", &test_id.to_string()) + }) + } } } } From 8ec0c96d8c8cc83552a65413cb8d8ea647dcd25b Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 29 Aug 2025 18:20:41 +0800 Subject: [PATCH 0021/1059] Add partial selection for merge_imports Example --- **Input**: ```rust use std::fmt::Error; $0use std::fmt::Display; use std::fmt::Debug; use std::fmt::Write; use$0 std::fmt::Result; ``` **Before this PR**: ```rust use std::fmt::Error; use std::fmt::{Debug, Display, Write}; use std::fmt::Result; ``` **After this PR**: ```rust use std::fmt::Error; use std::fmt::{Debug, Display, Result, Write}; ``` --- .../ide-assists/src/handlers/merge_imports.rs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs index 9ba73d23dd24..42bc05811fd1 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs @@ -49,8 +49,9 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio SyntaxElement::Node(n) => n, SyntaxElement::Token(t) => t.parent()?, }; - let mut selected_nodes = - parent_node.children().filter(|it| selection_range.contains_range(it.text_range())); + let mut selected_nodes = parent_node.children().filter(|it| { + selection_range.intersect(it.text_range()).is_some_and(|it| !it.is_empty()) + }); let first_selected = selected_nodes.next()?; let edits = match_ast! { @@ -677,6 +678,25 @@ fn merge_selection_uses() { ); } + #[test] + fn merge_partial_selection_uses() { + cov_mark::check!(merge_with_selected_use_item_neighbors); + check_assist( + merge_imports, + r" +use std::fmt::Error; +$0use std::fmt::Display; +use std::fmt::Debug; +use std::fmt::Write; +use$0 std::fmt::Result; +", + r" +use std::fmt::Error; +use std::fmt::{Debug, Display, Result, Write}; +", + ); + } + #[test] fn merge_selection_use_trees() { cov_mark::check!(merge_with_selected_use_tree_neighbors); From 529c83d0cd4fa4ee2f002b322257cd5e16959a0d Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 15 Jan 2026 16:55:20 +0800 Subject: [PATCH 0022/1059] Add applicable on let-else branch for unwrap_block Example --- ```rust fn main() { let Some(2) = None else {$0 return; }; } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn main() { return; } ``` --- .../ide-assists/src/handlers/unwrap_block.rs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs index e4f5e3523bd2..e029d7884fd5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs @@ -45,6 +45,7 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option ast::LoopExpr(it) => it.syntax().clone(), ast::WhileExpr(it) => it.syntax().clone(), ast::MatchArm(it) => it.parent_match().syntax().clone(), + ast::LetElse(it) => it.syntax().parent()?, ast::LetStmt(it) => { replacement = wrap_let(&it, replacement); prefer_container = Some(it.syntax().clone()); @@ -556,6 +557,40 @@ fn main() { ); } + #[test] + fn simple_let_else() { + check_assist( + unwrap_block, + r#" +fn main() { + let Some(2) = None else {$0 + return; + }; +} +"#, + r#" +fn main() { + return; +} +"#, + ); + check_assist( + unwrap_block, + r#" +fn main() { + let Some(2) = None else {$0 + return + }; +} +"#, + r#" +fn main() { + return +} +"#, + ); + } + #[test] fn unwrap_match_arm() { check_assist( From 438b12617774fa989673968aad51e5eae03d0333 Mon Sep 17 00:00:00 2001 From: binarycat Date: Fri, 12 Dec 2025 14:38:01 -0600 Subject: [PATCH 0023/1059] rustdoc: don't give depreciation notes special handling --- src/librustdoc/html/markdown.rs | 12 +++++++----- src/librustdoc/html/markdown/tests.rs | 2 +- src/librustdoc/html/static/css/rustdoc.css | 1 - tests/rustdoc-html/deprecated.rs | 5 +++++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index c472c20a7dc7..7b3198a194c4 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -109,13 +109,15 @@ pub(crate) struct MarkdownWithToc<'a> { pub(crate) edition: Edition, pub(crate) playground: &'a Option, } -/// A tuple struct like `Markdown` that renders the markdown escaping HTML tags + +/// A struct like `Markdown` that renders the markdown escaping HTML tags /// and includes no paragraph tags. pub(crate) struct MarkdownItemInfo<'a> { pub(crate) content: &'a str, pub(crate) links: &'a [RenderedLink], pub(crate) ids: &'a mut IdMap, } + /// A tuple struct like `Markdown` that renders only the first paragraph. pub(crate) struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [RenderedLink]); @@ -1497,10 +1499,10 @@ pub(crate) fn write_into(self, mut f: impl fmt::Write) -> fmt::Result { let p = SpannedLinkReplacer::new(p, links); let p = footnotes::Footnotes::new(p, existing_footnotes); let p = TableWrapper::new(p.map(|(ev, _)| ev)); - let p = p.filter(|event| { - !matches!(event, Event::Start(Tag::Paragraph) | Event::End(TagEnd::Paragraph)) - }); - html::write_html_fmt(&mut f, p) + // in legacy wrap mode, strip

elements to avoid them inserting newlines + html::write_html_fmt(&mut f, p)?; + + Ok(()) }) } } diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs index 1c99ccc5228b..3655a52deee6 100644 --- a/src/librustdoc/html/markdown/tests.rs +++ b/src/librustdoc/html/markdown/tests.rs @@ -472,7 +472,7 @@ fn t(input: &str, expect: &str) { let mut idmap = IdMap::new(); let mut output = String::new(); MarkdownItemInfo::new(input, &[], &mut idmap).write_into(&mut output).unwrap(); - assert_eq!(output, expect, "original: {}", input); + assert_eq!(output, format!("

{}

\n", expect), "original: {}", input); } t("`Struct<'a, T>`", "Struct<'a, T>"); diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index b770a0e2a0e4..8410e8c793e1 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1585,7 +1585,6 @@ so that we can apply CSS-filters to change the arrow color in themes */ color: var(--main-color); background-color: var(--stab-background-color); width: fit-content; - white-space: pre-wrap; border-radius: 3px; display: inline; vertical-align: baseline; diff --git a/tests/rustdoc-html/deprecated.rs b/tests/rustdoc-html/deprecated.rs index a84657a3df5a..c7db816cffcf 100644 --- a/tests/rustdoc-html/deprecated.rs +++ b/tests/rustdoc-html/deprecated.rs @@ -30,3 +30,8 @@ // 'Deprecated: shorthand reason: code$' #[deprecated = "shorthand reason: `code`"] pub struct X; + +//@ matches deprecated/struct.Y.html '//*[@class="stab deprecated"]//p[1]' 'multiple' +//@ matches deprecated/struct.Y.html '//*[@class="stab deprecated"]//p[2]' 'paragraphs' +#[deprecated = "multiple\n\nparagraphs"] +pub struct Y; From e7d56c6d01a11cddc94d2f12edeb381bf4f0f579 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 14 Jan 2026 19:03:30 -0500 Subject: [PATCH 0024/1059] Add functions to `GrowableBitSet`. --- compiler/rustc_index/src/bit_set.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/compiler/rustc_index/src/bit_set.rs b/compiler/rustc_index/src/bit_set.rs index a9bdf597e128..2a850898b2d6 100644 --- a/compiler/rustc_index/src/bit_set.rs +++ b/compiler/rustc_index/src/bit_set.rs @@ -1336,6 +1336,12 @@ pub fn insert(&mut self, elem: T) -> bool { self.bit_set.insert(elem) } + #[inline] + pub fn insert_range(&mut self, elems: Range) { + self.ensure(elems.end.index()); + self.bit_set.insert_range(elems); + } + /// Returns `true` if the set has changed. #[inline] pub fn remove(&mut self, elem: T) -> bool { @@ -1343,6 +1349,16 @@ pub fn remove(&mut self, elem: T) -> bool { self.bit_set.remove(elem) } + #[inline] + pub fn clear(&mut self) { + self.bit_set.clear(); + } + + #[inline] + pub fn count(&self) -> usize { + self.bit_set.count() + } + #[inline] pub fn is_empty(&self) -> bool { self.bit_set.is_empty() @@ -1354,6 +1370,14 @@ pub fn contains(&self, elem: T) -> bool { self.bit_set.words.get(word_index).is_some_and(|word| (word & mask) != 0) } + #[inline] + pub fn contains_any(&self, elems: Range) -> bool { + elems.start.index() < self.bit_set.domain_size + && self + .bit_set + .contains_any(elems.start..T::new(elems.end.index().min(self.bit_set.domain_size))) + } + #[inline] pub fn iter(&self) -> BitIter<'_, T> { self.bit_set.iter() From f1d5fdefa8e8d7a2bacb640888bc6f4e4d1b6b6d Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 23 Jan 2026 23:18:36 -0600 Subject: [PATCH 0025/1059] Fix Hexagon ABI calling convention for small aggregates Small structs (<= 64 bits) were being passed with their fields split into separate arguments instead of being packed into register-sized chunks. This caused ABI mismatches. The fix properly casts small aggregates to consecutive register-sized chunks using Uniform::consecutive(), matching the Hexagon C ABI where small structs are packed into R1:0 register pair. This fixes tests like extern-pass-TwoU16s.rs and extern-pass-TwoU8s.rs. --- compiler/rustc_target/src/callconv/hexagon.rs | 60 +++++++++++++++---- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_target/src/callconv/hexagon.rs b/compiler/rustc_target/src/callconv/hexagon.rs index e08e6daa7405..e07aa32251aa 100644 --- a/compiler/rustc_target/src/callconv/hexagon.rs +++ b/compiler/rustc_target/src/callconv/hexagon.rs @@ -1,36 +1,74 @@ -use rustc_abi::TyAbiInterface; +use rustc_abi::{HasDataLayout, TyAbiInterface}; -use crate::callconv::{ArgAbi, FnAbi}; +use crate::callconv::{ArgAbi, FnAbi, Reg, Uniform}; -fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { - if ret.layout.is_aggregate() && ret.layout.size.bits() > 64 { - ret.make_indirect(); - } else { - ret.extend_integer_width_to(32); +fn classify_ret<'a, Ty, C>(_cx: &C, ret: &mut ArgAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, + C: HasDataLayout, +{ + if !ret.layout.is_sized() { + return; } + if !ret.layout.is_aggregate() { + ret.extend_integer_width_to(32); + return; + } + + let size = ret.layout.size; + let bits = size.bits(); + + // Aggregates larger than 64 bits are returned indirectly + if bits > 64 { + ret.make_indirect(); + return; + } + + // Small aggregates are returned in registers + // Cast to appropriate register type to ensure proper ABI + let align = ret.layout.align.bytes(); + ret.cast_to(Uniform::consecutive(if align <= 4 { Reg::i32() } else { Reg::i64() }, size)); } fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) where Ty: TyAbiInterface<'a, C> + Copy, + C: HasDataLayout, { + if !arg.layout.is_sized() { + return; + } if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { arg.make_indirect(); return; } - if arg.layout.is_aggregate() && arg.layout.size.bits() > 64 { - arg.make_indirect(); - } else { + if !arg.layout.is_aggregate() { arg.extend_integer_width_to(32); + return; } + + let size = arg.layout.size; + let bits = size.bits(); + + // Aggregates larger than 64 bits are passed indirectly + if bits > 64 { + arg.make_indirect(); + return; + } + + // Small aggregates are passed in registers + // Cast to consecutive register-sized chunks to match the C ABI + let align = arg.layout.align.bytes(); + arg.cast_to(Uniform::consecutive(if align <= 4 { Reg::i32() } else { Reg::i64() }, size)); } pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) where Ty: TyAbiInterface<'a, C> + Copy, + C: HasDataLayout, { if !fn_abi.ret.is_ignore() { - classify_ret(&mut fn_abi.ret); + classify_ret(cx, &mut fn_abi.ret); } for arg in fn_abi.args.iter_mut() { From b1925a9791a3097065af228f9b109430b92d75de Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sat, 24 Jan 2026 13:46:00 -0600 Subject: [PATCH 0026/1059] Fix Hexagon ABI calling convention for aggregates Correct the handling of aggregate types in extern "C" functions to match the Hexagon ABI specification: - Aggregates up to 32 bits: passed/returned in a single register (R0) - Aggregates 33-64 bits: passed/returned in a register pair (R1:R0) - Aggregates > 64 bits: passed on stack via byval, returned via sret This fixes all tests/ui/abi/extern/ tests for Hexagon, including: - extern-pass-TwoU8s, extern-pass-TwoU16s, extern-pass-TwoU32s - extern-pass-TwoU64s, extern-pass-FiveU16s - extern-return-TwoU8s, extern-return-TwoU16s, extern-return-TwoU32s - extern-return-TwoU64s, extern-return-FiveU16s --- compiler/rustc_target/src/callconv/hexagon.rs | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_target/src/callconv/hexagon.rs b/compiler/rustc_target/src/callconv/hexagon.rs index e07aa32251aa..1e0f1f769c3b 100644 --- a/compiler/rustc_target/src/callconv/hexagon.rs +++ b/compiler/rustc_target/src/callconv/hexagon.rs @@ -10,24 +10,25 @@ fn classify_ret<'a, Ty, C>(_cx: &C, ret: &mut ArgAbi<'a, Ty>) if !ret.layout.is_sized() { return; } + if !ret.layout.is_aggregate() { ret.extend_integer_width_to(32); return; } + // Per the Hexagon ABI: + // - Aggregates up to 32 bits are returned in R0 + // - Aggregates 33-64 bits are returned in R1:R0 + // - Aggregates > 64 bits are returned indirectly via hidden first argument let size = ret.layout.size; let bits = size.bits(); - - // Aggregates larger than 64 bits are returned indirectly - if bits > 64 { + if bits <= 32 { + ret.cast_to(Uniform::new(Reg::i32(), size)); + } else if bits <= 64 { + ret.cast_to(Uniform::new(Reg::i64(), size)); + } else { ret.make_indirect(); - return; } - - // Small aggregates are returned in registers - // Cast to appropriate register type to ensure proper ABI - let align = ret.layout.align.bytes(); - ret.cast_to(Uniform::consecutive(if align <= 4 { Reg::i32() } else { Reg::i64() }, size)); } fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) @@ -42,24 +43,25 @@ fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) arg.make_indirect(); return; } + if !arg.layout.is_aggregate() { arg.extend_integer_width_to(32); return; } + // Per the Hexagon ABI: + // - Aggregates up to 32 bits are passed in a single register + // - Aggregates 33-64 bits are passed in a register pair + // - Aggregates > 64 bits are passed on the stack let size = arg.layout.size; let bits = size.bits(); - - // Aggregates larger than 64 bits are passed indirectly - if bits > 64 { - arg.make_indirect(); - return; + if bits <= 32 { + arg.cast_to(Uniform::new(Reg::i32(), size)); + } else if bits <= 64 { + arg.cast_to(Uniform::new(Reg::i64(), size)); + } else { + arg.pass_by_stack_offset(None); } - - // Small aggregates are passed in registers - // Cast to consecutive register-sized chunks to match the C ABI - let align = arg.layout.align.bytes(); - arg.cast_to(Uniform::consecutive(if align <= 4 { Reg::i32() } else { Reg::i64() }, size)); } pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) From 57650eeb9dd4d4af3cf265c360864716b2badc4f Mon Sep 17 00:00:00 2001 From: Lieselotte <52315535+she3py@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:24:16 +0100 Subject: [PATCH 0027/1059] fix: don't suggest replacing `env!("CARGO_BIN_NAME")` with itself --- compiler/rustc_builtin_macros/src/env.rs | 46 ++++++++++++------- compiler/rustc_builtin_macros/src/errors.rs | 1 + .../env-cargo-var-typo-issue-148439.rs | 10 +++- .../env-cargo-var-typo-issue-148439.stderr | 26 +++++++---- .../env-macro/env-not-defined-default.stderr | 1 + 5 files changed, 58 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/env.rs b/compiler/rustc_builtin_macros/src/env.rs index af78db156a22..12f59467ffd1 100644 --- a/compiler/rustc_builtin_macros/src/env.rs +++ b/compiler/rustc_builtin_macros/src/env.rs @@ -140,18 +140,21 @@ pub(crate) fn expand_env<'cx>( unreachable!("`expr_to_string` ensures this is a string lit") }; + let var = var.as_str(); let guar = match err { VarError::NotPresent => { if let Some(msg_from_user) = custom_msg { cx.dcx() .emit_err(errors::EnvNotDefinedWithUserMessage { span, msg_from_user }) - } else if let Some(suggested_var) = find_similar_cargo_var(var.as_str()) { + } else if let Some(suggested_var) = find_similar_cargo_var(var) + && suggested_var != var + { cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVarTypo { span, var: *symbol, suggested_var: Symbol::intern(suggested_var), }) - } else if is_cargo_env_var(var.as_str()) { + } else if is_cargo_env_var(var) { cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVar { span, var: *symbol, @@ -177,7 +180,7 @@ pub(crate) fn expand_env<'cx>( ExpandResult::Ready(MacEager::expr(e)) } -/// Returns `true` if an environment variable from `env!` is one used by Cargo. +/// Returns `true` if an environment variable from `env!` could be one used by Cargo. fn is_cargo_env_var(var: &str) -> bool { var.starts_with("CARGO_") || var.starts_with("DEP_") @@ -187,25 +190,28 @@ fn is_cargo_env_var(var: &str) -> bool { const KNOWN_CARGO_VARS: &[&str] = &[ // List of known Cargo environment variables that are set for crates (not build scripts, OUT_DIR etc). // See: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates + // tidy-alphabetical-start + "CARGO_BIN_NAME", + "CARGO_CRATE_NAME", + "CARGO_MANIFEST_DIR", + "CARGO_MANIFEST_PATH", + "CARGO_PKG_AUTHORS", + "CARGO_PKG_DESCRIPTION", + "CARGO_PKG_HOMEPAGE", + "CARGO_PKG_LICENSE", + "CARGO_PKG_LICENSE_FILE", + "CARGO_PKG_NAME", + "CARGO_PKG_README", + "CARGO_PKG_REPOSITORY", + "CARGO_PKG_RUST_VERSION", "CARGO_PKG_VERSION", "CARGO_PKG_VERSION_MAJOR", "CARGO_PKG_VERSION_MINOR", "CARGO_PKG_VERSION_PATCH", "CARGO_PKG_VERSION_PRE", - "CARGO_PKG_AUTHORS", - "CARGO_PKG_NAME", - "CARGO_PKG_DESCRIPTION", - "CARGO_PKG_HOMEPAGE", - "CARGO_PKG_REPOSITORY", - "CARGO_PKG_LICENSE", - "CARGO_PKG_LICENSE_FILE", - "CARGO_PKG_RUST_VERSION", - "CARGO_PKG_README", - "CARGO_MANIFEST_DIR", - "CARGO_MANIFEST_PATH", - "CARGO_CRATE_NAME", - "CARGO_BIN_NAME", "CARGO_PRIMARY_PACKAGE", + "CARGO_TARGET_TMPDIR", + // tidy-alphabetical-end ]; fn find_similar_cargo_var(var: &str) -> Option<&'static str> { @@ -219,7 +225,13 @@ fn find_similar_cargo_var(var: &str) -> Option<&'static str> { let mut best_distance = usize::MAX; for &known_var in KNOWN_CARGO_VARS { - if let Some(distance) = edit_distance(var, known_var, max_dist) { + if let Some(mut distance) = edit_distance(var, known_var, max_dist) { + // assume `PACKAGE` to equals `PKG` + // (otherwise, `d("CARGO_PACKAGE_NAME", "CARGO_PKG_NAME") == d("CARGO_PACKAGE_NAME", "CARGO_CRATE_NAME") == 4`) + if var.contains("PACKAGE") && known_var.contains("PKG") { + distance = distance.saturating_sub(const { "PACKAGE".len() - "PKG".len() }) // == d("PACKAGE", "PKG") + } + if distance < best_distance { best_distance = distance; best_match = Some(known_var); diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs index d3f7e1c5d8e3..e57d3f621f69 100644 --- a/compiler/rustc_builtin_macros/src/errors.rs +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -544,6 +544,7 @@ fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> { #[derive(Diagnostic)] pub(crate) enum EnvNotDefined<'a> { #[diag("environment variable `{$var}` not defined at compile time")] + #[help("`{$var}` may not be available for the current Cargo target")] #[help( "Cargo sets build script variables at run time. Use `std::env::var({$var_expr})` instead" )] diff --git a/tests/ui/env-macro/env-cargo-var-typo-issue-148439.rs b/tests/ui/env-macro/env-cargo-var-typo-issue-148439.rs index f859decd09ec..f72b43bb2543 100644 --- a/tests/ui/env-macro/env-cargo-var-typo-issue-148439.rs +++ b/tests/ui/env-macro/env-cargo-var-typo-issue-148439.rs @@ -1,4 +1,5 @@ //@ edition: 2021 +//@ compile-flags: --crate-type=lib // Regression test for issue #148439 // Ensure that when using misspelled Cargo environment variables in env!(), @@ -44,7 +45,14 @@ fn test_cargo_unknown_var() { // Cargo-prefixed but not similar to any known variable let _ = env!("CARGO_SOMETHING_TOTALLY_UNKNOWN"); //~^ ERROR environment variable `CARGO_SOMETHING_TOTALLY_UNKNOWN` not defined at compile time + //~| HELP `CARGO_SOMETHING_TOTALLY_UNKNOWN` may not be available for the current Cargo target //~| HELP Cargo sets build script variables at run time. Use `std::env::var("CARGO_SOMETHING_TOTALLY_UNKNOWN")` instead } -fn main() {} +fn test_cargo_conditional_var() { + // Only set for binairies + let _ = env!("CARGO_BIN_NAME"); + //~^ ERROR environment variable `CARGO_BIN_NAME` not defined at compile time + //~| HELP `CARGO_BIN_NAME` may not be available for the current Cargo target + //~| HELP Cargo sets build script variables at run time. Use `std::env::var("CARGO_BIN_NAME")` instead +} diff --git a/tests/ui/env-macro/env-cargo-var-typo-issue-148439.stderr b/tests/ui/env-macro/env-cargo-var-typo-issue-148439.stderr index e16c4d9a1f4c..50a4cf1a616b 100644 --- a/tests/ui/env-macro/env-cargo-var-typo-issue-148439.stderr +++ b/tests/ui/env-macro/env-cargo-var-typo-issue-148439.stderr @@ -1,5 +1,5 @@ error: environment variable `CARGO_PACKAGE_VERSION` not defined at compile time - --> $DIR/env-cargo-var-typo-issue-148439.rs:7:13 + --> $DIR/env-cargo-var-typo-issue-148439.rs:8:13 | LL | let _ = env!("CARGO_PACKAGE_VERSION"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -7,7 +7,7 @@ LL | let _ = env!("CARGO_PACKAGE_VERSION"); = help: there is a similar Cargo environment variable: `CARGO_PKG_VERSION` error: environment variable `CARGO_PACKAGE_NAME` not defined at compile time - --> $DIR/env-cargo-var-typo-issue-148439.rs:13:13 + --> $DIR/env-cargo-var-typo-issue-148439.rs:14:13 | LL | let _ = env!("CARGO_PACKAGE_NAME"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -15,7 +15,7 @@ LL | let _ = env!("CARGO_PACKAGE_NAME"); = help: there is a similar Cargo environment variable: `CARGO_PKG_NAME` error: environment variable `CARGO_PACKAGE_AUTHORS` not defined at compile time - --> $DIR/env-cargo-var-typo-issue-148439.rs:19:13 + --> $DIR/env-cargo-var-typo-issue-148439.rs:20:13 | LL | let _ = env!("CARGO_PACKAGE_AUTHORS"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -23,7 +23,7 @@ LL | let _ = env!("CARGO_PACKAGE_AUTHORS"); = help: there is a similar Cargo environment variable: `CARGO_PKG_AUTHORS` error: environment variable `CARGO_MANIFEST_DIRECTORY` not defined at compile time - --> $DIR/env-cargo-var-typo-issue-148439.rs:25:13 + --> $DIR/env-cargo-var-typo-issue-148439.rs:26:13 | LL | let _ = env!("CARGO_MANIFEST_DIRECTORY"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -31,7 +31,7 @@ LL | let _ = env!("CARGO_MANIFEST_DIRECTORY"); = help: there is a similar Cargo environment variable: `CARGO_MANIFEST_DIR` error: environment variable `CARGO_PKG_VERSIO` not defined at compile time - --> $DIR/env-cargo-var-typo-issue-148439.rs:31:13 + --> $DIR/env-cargo-var-typo-issue-148439.rs:32:13 | LL | let _ = env!("CARGO_PKG_VERSIO"); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -39,7 +39,7 @@ LL | let _ = env!("CARGO_PKG_VERSIO"); = help: there is a similar Cargo environment variable: `CARGO_PKG_VERSION` error: environment variable `MY_CUSTOM_VAR` not defined at compile time - --> $DIR/env-cargo-var-typo-issue-148439.rs:38:13 + --> $DIR/env-cargo-var-typo-issue-148439.rs:39:13 | LL | let _ = env!("MY_CUSTOM_VAR"); | ^^^^^^^^^^^^^^^^^^^^^ @@ -47,12 +47,22 @@ LL | let _ = env!("MY_CUSTOM_VAR"); = help: use `std::env::var("MY_CUSTOM_VAR")` to read the variable at run time error: environment variable `CARGO_SOMETHING_TOTALLY_UNKNOWN` not defined at compile time - --> $DIR/env-cargo-var-typo-issue-148439.rs:45:13 + --> $DIR/env-cargo-var-typo-issue-148439.rs:46:13 | LL | let _ = env!("CARGO_SOMETHING_TOTALLY_UNKNOWN"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = help: `CARGO_SOMETHING_TOTALLY_UNKNOWN` may not be available for the current Cargo target = help: Cargo sets build script variables at run time. Use `std::env::var("CARGO_SOMETHING_TOTALLY_UNKNOWN")` instead -error: aborting due to 7 previous errors +error: environment variable `CARGO_BIN_NAME` not defined at compile time + --> $DIR/env-cargo-var-typo-issue-148439.rs:54:13 + | +LL | let _ = env!("CARGO_BIN_NAME"); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: `CARGO_BIN_NAME` may not be available for the current Cargo target + = help: Cargo sets build script variables at run time. Use `std::env::var("CARGO_BIN_NAME")` instead + +error: aborting due to 8 previous errors diff --git a/tests/ui/env-macro/env-not-defined-default.stderr b/tests/ui/env-macro/env-not-defined-default.stderr index 77ba00e45c1c..8340bb0e8fba 100644 --- a/tests/ui/env-macro/env-not-defined-default.stderr +++ b/tests/ui/env-macro/env-not-defined-default.stderr @@ -4,6 +4,7 @@ error: environment variable `CARGO__HOPEFULLY_NOT_DEFINED__` not defined at comp LL | env!("CARGO__HOPEFULLY_NOT_DEFINED__"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = help: `CARGO__HOPEFULLY_NOT_DEFINED__` may not be available for the current Cargo target = help: Cargo sets build script variables at run time. Use `std::env::var("CARGO__HOPEFULLY_NOT_DEFINED__")` instead error: aborting due to 1 previous error From 0d2a5d24d3ca2416eebb7000608a1ce176025302 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 7 Feb 2026 05:24:36 -0600 Subject: [PATCH 0028/1059] symcheck: Switch to getopts for argument parsing It would be nice to support a few more flags here, but the current implementation is a bit limited. Switch to `getopts` which should make things a bit easier in the future. --- library/compiler-builtins/Cargo.toml | 1 + library/compiler-builtins/ci/run.sh | 16 ++-- .../crates/symbol-check/Cargo.toml | 1 + .../crates/symbol-check/src/main.rs | 80 ++++++++++++------- .../crates/symbol-check/tests/all.rs | 6 +- 5 files changed, 64 insertions(+), 40 deletions(-) diff --git a/library/compiler-builtins/Cargo.toml b/library/compiler-builtins/Cargo.toml index 26f67e02fc52..3dff23d28577 100644 --- a/library/compiler-builtins/Cargo.toml +++ b/library/compiler-builtins/Cargo.toml @@ -37,6 +37,7 @@ assert_cmd = "2.1.2" cc = "1.2.55" compiler_builtins = { path = "builtins-shim", default-features = false } criterion = { version = "0.6.0", default-features = false, features = ["cargo_bench_support"] } +getopts = "0.2.24" getrandom = "0.3.4" gmp-mpfr-sys = { version = "1.6.8", default-features = false } gungraun = "0.17.0" diff --git a/library/compiler-builtins/ci/run.sh b/library/compiler-builtins/ci/run.sh index 12b3f37889c9..04e44e1cb859 100755 --- a/library/compiler-builtins/ci/run.sh +++ b/library/compiler-builtins/ci/run.sh @@ -48,21 +48,21 @@ fi # build with the arguments we provide it, then validates the built artifacts. SYMCHECK_TEST_TARGET="$target" cargo test -p symbol-check --release symcheck=(cargo run -p symbol-check --release) -symcheck+=(-- build-and-check) +symcheck+=(-- --build-and-check --target "$target") -"${symcheck[@]}" "$target" -- -p compiler_builtins -"${symcheck[@]}" "$target" -- -p compiler_builtins --release -"${symcheck[@]}" "$target" -- -p compiler_builtins --features c -"${symcheck[@]}" "$target" -- -p compiler_builtins --features c --release -"${symcheck[@]}" "$target" -- -p compiler_builtins --features no-asm -"${symcheck[@]}" "$target" -- -p compiler_builtins --features no-asm --release +"${symcheck[@]}" -- -p compiler_builtins +"${symcheck[@]}" -- -p compiler_builtins --release +"${symcheck[@]}" -- -p compiler_builtins --features c +"${symcheck[@]}" -- -p compiler_builtins --features c --release +"${symcheck[@]}" -- -p compiler_builtins --features no-asm +"${symcheck[@]}" -- -p compiler_builtins --features no-asm --release run_intrinsics_test() { build_args=(--verbose --manifest-path builtins-test-intrinsics/Cargo.toml) build_args+=("$@") # symcheck also checks the results of builtins-test-intrinsics - "${symcheck[@]}" "$target" -- "${build_args[@]}" + "${symcheck[@]}" -- "${build_args[@]}" # FIXME: we get access violations on Windows, our entrypoint may need to # be tweaked. diff --git a/library/compiler-builtins/crates/symbol-check/Cargo.toml b/library/compiler-builtins/crates/symbol-check/Cargo.toml index 5bc13d337c27..6d13f9488800 100644 --- a/library/compiler-builtins/crates/symbol-check/Cargo.toml +++ b/library/compiler-builtins/crates/symbol-check/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" publish = false [dependencies] +getopts.workspace = true object.workspace = true regex.workspace = true serde_json.workspace = true diff --git a/library/compiler-builtins/crates/symbol-check/src/main.rs b/library/compiler-builtins/crates/symbol-check/src/main.rs index 733d9f4e8bef..85aa7118aac2 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -8,7 +8,7 @@ use std::fs; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; +use std::process::{Command, Stdio, exit}; use std::sync::LazyLock; use object::read::archive::ArchiveFile; @@ -24,38 +24,60 @@ const USAGE: &str = "Usage: - symbol-check build-and-check [TARGET] -- CARGO_BUILD_ARGS ... - -Cargo will get invoked with `CARGO_ARGS` and the specified target. All output -`compiler_builtins*.rlib` files will be checked. - -If TARGET is not specified, the host target is used. - - check PATHS ... - -Run the same checks on the given set of paths, without invoking Cargo. Paths -may be either archives or object files. + symbol-check --build-and-check [--target TARGET] -- CARGO_BUILD_ARGS ... + symbol-check --check PATHS ...\ "; fn main() { - // Create a `&str` vec so we can match on it. - let args = std::env::args().collect::>(); - let args_ref = args.iter().map(String::as_str).collect::>(); + let mut opts = getopts::Options::new(); - match &args_ref[1..] { - ["build-and-check", target, "--", args @ ..] if !args.is_empty() => { - run_build_and_check(target, args); - } - ["build-and-check", "--", args @ ..] if !args.is_empty() => { - run_build_and_check(env!("HOST"), args); - } - ["check", paths @ ..] if !paths.is_empty() => { - check_paths(paths); - } - _ => { - println!("{USAGE}"); - std::process::exit(1); + // Ideally these would be subcommands but that isn't supported. + opts.optflag("h", "help", "Print this help message"); + opts.optflag( + "", + "build-and-check", + "Cargo will get invoked with `CARGO_BUILD_ARGS` and the specified target. All output \ + `compiler_builtins*.rlib` files will be checked.", + ); + opts.optopt( + "", + "target", + "Set the target for build-and-check. Falls back to the host target otherwise.", + "TARGET", + ); + opts.optflag( + "", + "check", + "Run checks on the given set of paths, without invoking Cargo. Paths \ + may be either archives or object files.", + ); + + let print_usage_and_exit = |code: i32| -> ! { + eprintln!("{}", opts.usage(USAGE)); + exit(code); + }; + + let m = opts.parse(std::env::args().skip(1)).unwrap_or_else(|e| { + eprintln!("{e}"); + print_usage_and_exit(1); + }); + + if m.opt_present("help") { + print_usage_and_exit(0); + } + + let free_args = m.free.iter().map(String::as_str).collect::>(); + + if m.opt_present("build-and-check") { + let target = m.opt_str("target").unwrap_or(env!("HOST").to_string()); + run_build_and_check(&target, &free_args); + } else if m.opt_present("check") { + if free_args.is_empty() { + print_usage_and_exit(1); } + check_paths(&free_args); + } else { + print_usage_and_exit(1); } } @@ -65,7 +87,7 @@ fn run_build_and_check(target: &str, args: &[&str]) { for arg in args { assert!( !arg.contains("--target"), - "target must be passed positionally. {USAGE}" + "target must be passed to symbol-check" ); } diff --git a/library/compiler-builtins/crates/symbol-check/tests/all.rs b/library/compiler-builtins/crates/symbol-check/tests/all.rs index 400469a49e2a..34a2dd3c399e 100644 --- a/library/compiler-builtins/crates/symbol-check/tests/all.rs +++ b/library/compiler-builtins/crates/symbol-check/tests/all.rs @@ -51,7 +51,7 @@ fn test_duplicates() { let status = ar.arg(&dup_out).status().unwrap(); assert!(status.success()); - let assert = cargo_bin_cmd!().arg("check").arg(&lib_out).assert(); + let assert = cargo_bin_cmd!().arg("--check").arg(&lib_out).assert(); assert .failure() .stderr_contains("duplicate symbols") @@ -65,7 +65,7 @@ fn test_core_symbols() { let dir = tempdir().unwrap(); let lib_out = dir.path().join("libfoo.rlib"); rustc_build(&input_dir().join("core_symbols.rs"), &lib_out, |cmd| cmd); - let assert = cargo_bin_cmd!().arg("check").arg(&lib_out).assert(); + let assert = cargo_bin_cmd!().arg("--check").arg(&lib_out).assert(); assert .failure() .stderr_contains("found 1 undefined symbols from core") @@ -77,7 +77,7 @@ fn test_good() { let dir = tempdir().unwrap(); let lib_out = dir.path().join("libfoo.rlib"); rustc_build(&input_dir().join("good.rs"), &lib_out, |cmd| cmd); - let assert = cargo_bin_cmd!().arg("check").arg(&lib_out).assert(); + let assert = cargo_bin_cmd!().arg("--check").arg(&lib_out).assert(); assert.success(); } From 1da633ca4bcc0bb5ffb2a758ff6b9cb6bc78454f Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 13:16:50 -0600 Subject: [PATCH 0029/1059] symcheck: Always check args, simplify running functions --- .../crates/symbol-check/src/main.rs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/library/compiler-builtins/crates/symbol-check/src/main.rs b/library/compiler-builtins/crates/symbol-check/src/main.rs index 85aa7118aac2..2520073de099 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -67,10 +67,17 @@ fn main() { } let free_args = m.free.iter().map(String::as_str).collect::>(); + for arg in &free_args { + assert!( + !arg.contains("--target"), + "target must be passed to symbol-check" + ); + } if m.opt_present("build-and-check") { let target = m.opt_str("target").unwrap_or(env!("HOST").to_string()); - run_build_and_check(&target, &free_args); + let paths = exec_cargo_with_args(&target, &free_args); + check_paths(&paths); } else if m.opt_present("check") { if free_args.is_empty() { print_usage_and_exit(1); @@ -81,20 +88,6 @@ fn main() { } } -fn run_build_and_check(target: &str, args: &[&str]) { - // Make sure `--target` isn't passed to avoid confusion (since it should be - // proivded only once, positionally). - for arg in args { - assert!( - !arg.contains("--target"), - "target must be passed to symbol-check" - ); - } - - let paths = exec_cargo_with_args(target, args); - check_paths(&paths); -} - fn check_paths>(paths: &[P]) { for path in paths { let path = path.as_ref(); From 7d5135c25b4be6355de910dbe352b18fb435d713 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 13:16:50 -0600 Subject: [PATCH 0030/1059] symcheck: Clean up test setup that requires environment --- .../crates/symbol-check/tests/all.rs | 103 +++++++++++------- 1 file changed, 63 insertions(+), 40 deletions(-) diff --git a/library/compiler-builtins/crates/symbol-check/tests/all.rs b/library/compiler-builtins/crates/symbol-check/tests/all.rs index 34a2dd3c399e..f65038c62da6 100644 --- a/library/compiler-builtins/crates/symbol-check/tests/all.rs +++ b/library/compiler-builtins/crates/symbol-check/tests/all.rs @@ -2,7 +2,6 @@ use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; -use std::sync::LazyLock; use assert_cmd::assert::Assert; use assert_cmd::cargo::cargo_bin_cmd; @@ -13,6 +12,7 @@ trait AssertExt { } impl AssertExt for Assert { + #[track_caller] fn stderr_contains(self, s: &str) -> Self { let out = String::from_utf8_lossy(&self.get_output().stderr); assert!(out.contains(s), "looking for: `{s}`\nout:\n```\n{out}\n```"); @@ -22,18 +22,19 @@ fn stderr_contains(self, s: &str) -> Self { #[test] fn test_duplicates() { + let t = TestTarget::from_env(); let dir = tempdir().unwrap(); let dup_out = dir.path().join("dup.o"); let lib_out = dir.path().join("libfoo.rlib"); // For the "bad" file, we need duplicate symbols from different object files in the archive. Do // this reliably by building an archive and a separate object file then merging them. - rustc_build(&input_dir().join("duplicates.rs"), &lib_out, |cmd| cmd); - rustc_build(&input_dir().join("duplicates.rs"), &dup_out, |cmd| { + t.rustc_build(&input_dir().join("duplicates.rs"), &lib_out, |cmd| cmd); + t.rustc_build(&input_dir().join("duplicates.rs"), &dup_out, |cmd| { cmd.arg("--emit=obj") }); - let mut ar = cc_build().get_archiver(); + let mut ar = t.cc_build().get_archiver(); if ar.get_program().to_string_lossy().contains("lib.exe") { let mut out_arg = OsString::from("-out:"); @@ -48,10 +49,10 @@ fn test_duplicates() { .stderr(Stdio::null()) .arg(&lib_out); } - let status = ar.arg(&dup_out).status().unwrap(); - assert!(status.success()); - let assert = cargo_bin_cmd!().arg("--check").arg(&lib_out).assert(); + run(ar.arg(&dup_out)); + + let assert = t.symcheck_exe().arg(&lib_out).assert(); assert .failure() .stderr_contains("duplicate symbols") @@ -62,10 +63,11 @@ fn test_duplicates() { #[test] fn test_core_symbols() { + let t = TestTarget::from_env(); let dir = tempdir().unwrap(); let lib_out = dir.path().join("libfoo.rlib"); - rustc_build(&input_dir().join("core_symbols.rs"), &lib_out, |cmd| cmd); - let assert = cargo_bin_cmd!().arg("--check").arg(&lib_out).assert(); + t.rustc_build(&input_dir().join("core_symbols.rs"), &lib_out, |cmd| cmd); + let assert = t.symcheck_exe().arg(&lib_out).assert(); assert .failure() .stderr_contains("found 1 undefined symbols from core") @@ -73,40 +75,24 @@ fn test_core_symbols() { } #[test] -fn test_good() { +fn test_good_lib() { + let t = TestTarget::from_env(); let dir = tempdir().unwrap(); let lib_out = dir.path().join("libfoo.rlib"); - rustc_build(&input_dir().join("good.rs"), &lib_out, |cmd| cmd); - let assert = cargo_bin_cmd!().arg("--check").arg(&lib_out).assert(); + t.rustc_build(&input_dir().join("good.rs"), &lib_out, |cmd| cmd); + let assert = t.symcheck_exe().arg(&lib_out).assert(); assert.success(); } -/// Build i -> o with optional additional configuration. -fn rustc_build(i: &Path, o: &Path, mut f: impl FnMut(&mut Command) -> &mut Command) { - let mut cmd = Command::new("rustc"); - cmd.arg(i) - .arg("--target") - .arg(target()) - .arg("--crate-type=lib") - .arg("-o") - .arg(o); - f(&mut cmd); - let status = cmd.status().unwrap(); - assert!(status.success()); +/// Since symcheck is a hostprog, the target we want to build and test symcheck for may not be the +/// same as the host target. +struct TestTarget { + triple: String, } -/// Configure `cc` with the host and target. -fn cc_build() -> cc::Build { - let mut b = cc::Build::new(); - b.host(env!("HOST")).target(&target()); - b -} - -/// Symcheck runs on the host but we want to verify that we find issues on all targets, so -/// the cross target may be specified. -fn target() -> String { - static TARGET: LazyLock = LazyLock::new(|| { - let target = match env::var("SYMCHECK_TEST_TARGET") { +impl TestTarget { + fn from_env() -> Self { + let triple = match env::var("SYMCHECK_TEST_TARGET") { Ok(t) => t, // Require on CI so we don't accidentally always test the native target _ if env::var("CI").is_ok() => panic!("SYMCHECK_TEST_TARGET must be set in CI"), @@ -114,13 +100,50 @@ fn target() -> String { Err(_) => env!("HOST").to_string(), }; - println!("using target {target}"); - target - }); + println!("using target {triple}"); + Self { triple } + } - TARGET.clone() + /// Build i -> o with optional additional configuration. + fn rustc_build(&self, i: &Path, o: &Path, mut f: impl FnMut(&mut Command) -> &mut Command) { + let mut cmd = Command::new("rustc"); + cmd.arg(i) + .arg("--target") + .arg(&self.triple) + .arg("--crate-type=lib") + .arg("-o") + .arg(o); + f(&mut cmd); + run(&mut cmd); + } + + /// Configure `cc` with the host and target. + fn cc_build(&self) -> cc::Build { + let mut b = cc::Build::new(); + b.host(env!("HOST")) + .target(&self.triple) + .opt_level(0) + .cargo_debug(true) + .cargo_metadata(false); + b + } + + fn symcheck_exe(&self) -> assert_cmd::Command { + let mut cmd = cargo_bin_cmd!(); + cmd.arg("--check"); + cmd + } } fn input_dir() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/input") } + +#[track_caller] +fn run(cmd: &mut Command) { + eprintln!("+ {cmd:?}"); + let out = cmd.output().unwrap(); + println!("{}", String::from_utf8_lossy(&out.stdout)); + eprintln!("{}", String::from_utf8_lossy(&out.stderr)); + assert!(out.status.success(), "{:?}", out.status); +} From 44b0bd98fd0be6bd0dd5dcea63620c0a41dda8ba Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 13:16:50 -0600 Subject: [PATCH 0031/1059] symcheck: Don't check for empty symbol tables with PE binaries PE executables don't seem to have anything in the symbol table. Don't assert that we find any symbols in this case, which allows using symcheck for executable binaries. --- .../crates/symbol-check/src/main.rs | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/library/compiler-builtins/crates/symbol-check/src/main.rs b/library/compiler-builtins/crates/symbol-check/src/main.rs index 2520073de099..683bba285cde 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -13,8 +13,8 @@ use object::read::archive::ArchiveFile; use object::{ - File as ObjFile, Object, ObjectSection, ObjectSymbol, Result as ObjResult, Symbol, SymbolKind, - SymbolScope, + BinaryFormat, File as ObjFile, Object, ObjectSection, ObjectSymbol, Result as ObjResult, + Symbol, SymbolKind, SymbolScope, }; use regex::Regex; use serde_json::Value; @@ -259,7 +259,9 @@ fn verify_no_duplicates(archive: &BinFile) { found_any = true; }); - assert!(found_any, "no symbols found"); + if archive.has_symbol_tables() { + assert!(found_any, "no symbols found"); + } if !dups.is_empty() { let count = dups.iter().map(|x| &x.name).collect::>().len(); @@ -300,7 +302,9 @@ fn verify_core_symbols(archive: &BinFile) { } }); - assert!(has_symbols, "no symbols found"); + if archive.has_symbol_tables() { + assert!(has_symbols, "no symbols found"); + } // Discard any symbols that are defined somewhere in the archive undefined.retain(|sym| !defined.contains(&sym.name)); @@ -376,4 +380,23 @@ fn for_each_symbol(&self, mut f: impl FnMut(Symbol, &ObjFile, &str)) { obj.symbols().for_each(|sym| f(sym, &obj, obj_path)); }); } + + /// PE executable files don't have the same kind of symbol tables. This isn't a perfectly + /// accurate check, but at least tells us whether we can skip erroring if we don't find any + /// symbols. + fn has_symbol_tables(&self) -> bool { + let mut empty = true; + let mut ret = false; + + self.for_each_object(|obj, _obj_path| { + if !matches!(obj.format(), BinaryFormat::Pe) { + // Any non-PE objects should have symbol tables. + ret = true; + } + empty = false; + }); + + // If empty, assume there should be tables. + empty || ret + } } From 15d02fc0d71f86fe09a882ed16a541b3f4de4ca0 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 13:16:50 -0600 Subject: [PATCH 0032/1059] symcheck: Check for binaries requiring a writeable + executable stack Implement the following logic: * For elf executable binaries, check for PT_GNU_STACK with PF_X. This combination tells the kernel to make the stack executable. * For elf intermediate objects, check for `.note.GNU-stack` with SHF_EXECINSTR. This combination in an object file tells the linker to give the final binary PT_GNU_STACK PF_X. * For elf intermediate objects with no `.note.GNU-stack`, assume the legacy behavior that assumes an executable stack is required. * For non-elf binaries, don't check anything. In a follow up it may be possible to check for `MH_ALLOW_STACK_EXECUTION` on Mach-O binaries, but it doesn't seem possible to get the latest compiler to emit this. This appears to match what is done by `scanelf` to emit `!WX` [1], which seems to be the tool used to create the output in the issue. The ld manpage [2] also has some useful notes about these flags, as does the presentation at [3]. Closes: https://github.com/rust-lang/compiler-builtins/issues/183 [1]: https://github.com/gentoo/pax-utils/blob/9ef54b472e42ba2c5479fbd86b8be2275724b064/scanelf.c [2]: https://man7.org/linux/man-pages/man1/ld.1.html [3]: https://www.ndss-symposium.org/wp-content/uploads/6D-s0924-ye.pdf --- library/compiler-builtins/ci/run.sh | 3 + .../crates/symbol-check/src/main.rs | 194 +++++++++++++++++- .../crates/symbol-check/tests/all.rs | 179 +++++++++++++++- .../symbol-check/tests/input/good_bin.c | 3 + .../tests/input/{good.rs => good_lib.rs} | 0 .../tests/input/has_exe_gnu_stack_section.c | 16 ++ .../tests/input/missing_gnu_stack_section.S | 19 ++ 7 files changed, 406 insertions(+), 8 deletions(-) create mode 100644 library/compiler-builtins/crates/symbol-check/tests/input/good_bin.c rename library/compiler-builtins/crates/symbol-check/tests/input/{good.rs => good_lib.rs} (100%) create mode 100644 library/compiler-builtins/crates/symbol-check/tests/input/has_exe_gnu_stack_section.c create mode 100644 library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S diff --git a/library/compiler-builtins/ci/run.sh b/library/compiler-builtins/ci/run.sh index 04e44e1cb859..b9a21d555c9e 100755 --- a/library/compiler-builtins/ci/run.sh +++ b/library/compiler-builtins/ci/run.sh @@ -50,6 +50,9 @@ SYMCHECK_TEST_TARGET="$target" cargo test -p symbol-check --release symcheck=(cargo run -p symbol-check --release) symcheck+=(-- --build-and-check --target "$target") +# Executable section checks are meaningless on no-std targets +[[ "$target" == *"-none"* ]] && symcheck+=(--no-os) + "${symcheck[@]}" -- -p compiler_builtins "${symcheck[@]}" -- -p compiler_builtins --release "${symcheck[@]}" -- -p compiler_builtins --features c diff --git a/library/compiler-builtins/crates/symbol-check/src/main.rs b/library/compiler-builtins/crates/symbol-check/src/main.rs index 683bba285cde..e15522d223d8 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -5,26 +5,27 @@ //! actual target is cross compiled. use std::collections::{BTreeMap, BTreeSet, HashSet}; -use std::fs; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio, exit}; use std::sync::LazyLock; +use std::{env, fs}; use object::read::archive::ArchiveFile; use object::{ - BinaryFormat, File as ObjFile, Object, ObjectSection, ObjectSymbol, Result as ObjResult, - Symbol, SymbolKind, SymbolScope, + Architecture, BinaryFormat, Endianness, File as ObjFile, Object, ObjectSection, ObjectSymbol, + Result as ObjResult, SectionFlags, Symbol, SymbolKind, SymbolScope, U32, elf, }; use regex::Regex; use serde_json::Value; const CHECK_LIBRARIES: &[&str] = &["compiler_builtins", "builtins_test_intrinsics"]; const CHECK_EXTENSIONS: &[Option<&str>] = &[Some("rlib"), Some("a"), Some("exe"), None]; +const GNU_STACK: &str = ".note.GNU-stack"; const USAGE: &str = "Usage: - symbol-check --build-and-check [--target TARGET] -- CARGO_BUILD_ARGS ... + symbol-check --build-and-check [--target TARGET] [--no-os] -- CARGO_BUILD_ARGS ... symbol-check --check PATHS ...\ "; @@ -51,6 +52,12 @@ fn main() { "Run checks on the given set of paths, without invoking Cargo. Paths \ may be either archives or object files.", ); + opts.optflag( + "", + "no-os", + "The binaries will not be checked for executable stacks. Used for embedded targets which \ + don't set `.note.GNU-stack` since there is no protection.", + ); let print_usage_and_exit = |code: i32| -> ! { eprintln!("{}", opts.usage(USAGE)); @@ -66,6 +73,7 @@ fn main() { print_usage_and_exit(0); } + let no_os_target = m.opt_present("no-os"); let free_args = m.free.iter().map(String::as_str).collect::>(); for arg in &free_args { assert!( @@ -77,18 +85,18 @@ fn main() { if m.opt_present("build-and-check") { let target = m.opt_str("target").unwrap_or(env!("HOST").to_string()); let paths = exec_cargo_with_args(&target, &free_args); - check_paths(&paths); + check_paths(&paths, no_os_target); } else if m.opt_present("check") { if free_args.is_empty() { print_usage_and_exit(1); } - check_paths(&free_args); + check_paths(&free_args, no_os_target); } else { print_usage_and_exit(1); } } -fn check_paths>(paths: &[P]) { +fn check_paths>(paths: &[P], no_os_target: bool) { for path in paths { let path = path.as_ref(); println!("Checking {}", path.display()); @@ -96,6 +104,7 @@ fn check_paths>(paths: &[P]) { verify_no_duplicates(&archive); verify_core_symbols(&archive); + verify_no_exec_stack(&archive, no_os_target); } } @@ -320,6 +329,177 @@ fn verify_core_symbols(archive: &BinFile) { println!(" success: no undefined references to core found"); } +/// Reasons a binary is considered to have an executable stack. +enum ExeStack { + MissingGnuStackSec, + ExeGnuStackSec, + ExePtGnuStack, +} + +/// Ensure that the object/archive will not require an executable stack. +fn verify_no_exec_stack(archive: &BinFile, no_os_target: bool) { + if no_os_target { + // We don't really have a good way of knowing whether or not an elf file is for a + // no-os environment so we rely on a CLI arg (note.GNU-stack doesn't get emitted if + // there is no OS to protect the stack). + println!(" skipping check for writeable+executable stack on no-os target"); + return; + } + + let mut problem_objfiles = Vec::new(); + + archive.for_each_object(|obj, obj_path| match check_obj_exe_stack(&obj) { + Ok(()) => (), + Err(exe) => problem_objfiles.push((obj_path.to_owned(), exe)), + }); + + if problem_objfiles.is_empty() { + println!(" success: no writeable+executable stack indicators found"); + return; + } + + eprintln!("the following object files require an executable stack:"); + + for (obj, exe) in problem_objfiles { + let reason = match exe { + ExeStack::MissingGnuStackSec => "no .note.GNU-stack section", + ExeStack::ExeGnuStackSec => ".note.GNU-stack section marked SHF_EXECINSTR", + ExeStack::ExePtGnuStack => "PT_GNU_STACK program header marked PF_X", + }; + eprintln!(" {obj} ({reason})"); + } + + exit(1); +} + +/// `Err` if the section/flag combination indicates that the object file should be linked with an +/// executable stack. +fn check_obj_exe_stack(obj: &ObjFile) -> Result<(), ExeStack> { + match obj.format() { + BinaryFormat::Elf => check_elf_exe_stack(obj), + // Technically has the `MH_ALLOW_STACK_EXECUTION` flag but I can't get the compiler to + // emit it (`-allow_stack_execute` doesn't seem to work in recent versions). + BinaryFormat::MachO => Ok(()), + // Can't find much information about Windows stack executability. + BinaryFormat::Coff | BinaryFormat::Pe => Ok(()), + // Also not sure about wasm. + BinaryFormat::Wasm => Ok(()), + BinaryFormat::Xcoff | _ => { + unimplemented!("binary format {:?} is not supported", obj.format()) + } + } +} + +/// Check for an executable stack in elf binaries. +/// +/// If the `PT_GNU_STACK` header on a binary is present and marked executable, the binary will +/// have an executable stack (RWE rather than the desired RW). If any object file has the right +/// `.note.GNU-stack` logic, the final binary will get `PT_GNU_STACK`. +/// +/// Individual object file logic is as follows, paraphrased from [1]: +/// +/// - A `.note.GNU-stack` section with the exe flag means this needs an executable stack +/// - A `.note.GNU-stack` section without the exe flag means there is no executable stack needed +/// - Without the section, behavior is target-specific. Historically it usually means an executable +/// stack is required. +/// +/// Per [2], it is now deprecated behavior for a missing `.note.GNU-stack` section to imply an +/// executable stack. However, we shouldn't assume that tooling has caught up to this. +/// +/// [1]: https://www.man7.org/linux/man-pages/man1/ld.1.html +/// [2]: https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=0d38576a34ec64a1b4500c9277a8e9d0f07e6774> +fn check_elf_exe_stack(obj: &ObjFile) -> Result<(), ExeStack> { + let end = obj.endianness(); + + // Check for PT_GNU_STACK marked executable + let mut is_obj_exe = false; + let mut found_gnu_stack = false; + let mut check_ph = |p_type: U32, p_flags: U32| { + let ty = p_type.get(end); + let flags = p_flags.get(end); + + // Presence of PT_INTERP indicates that this is an executable rather than a standalone + // object file. + if ty == elf::PT_INTERP { + is_obj_exe = true; + } + + if ty == elf::PT_GNU_STACK { + assert!(!found_gnu_stack, "multiple PT_GNU_STACK sections"); + found_gnu_stack = true; + if flags & elf::PF_X != 0 { + return Err(ExeStack::ExePtGnuStack); + } + } + + Ok(()) + }; + + match obj { + ObjFile::Elf32(f) => { + for ph in f.elf_program_headers() { + check_ph(ph.p_type, ph.p_flags)?; + } + } + ObjFile::Elf64(f) => { + for ph in f.elf_program_headers() { + check_ph(ph.p_type, ph.p_flags)?; + } + } + _ => panic!("should only be called with elf objects"), + } + + if is_obj_exe { + return Ok(()); + } + + // The remaining are checks for individual object files, which wind up controlling PT_GNU_STACK + // in the final binary. + let mut gnu_stack_exe = None; + let mut has_exe_sections = false; + for sec in obj.sections() { + let SectionFlags::Elf { sh_flags } = sec.flags() else { + unreachable!("only elf files are being checked"); + }; + + let is_sec_exe = sh_flags & u64::from(elf::SHF_EXECINSTR) != 0; + + // If the magic section is present, its exe bit tells us whether or not the object + // file requires an executable stack. + if sec.name().unwrap_or_default() == GNU_STACK { + assert!(gnu_stack_exe.is_none(), "multiple {GNU_STACK} sections"); + if is_sec_exe { + gnu_stack_exe = Some(Err(ExeStack::ExeGnuStackSec)); + } else { + gnu_stack_exe = Some(Ok(())); + } + } + + // Otherwise, just keep track of whether or not we have exeuctable sections + has_exe_sections |= is_sec_exe; + } + + // GNU_STACK sets the executability if specified. + if let Some(exe) = gnu_stack_exe { + return exe; + } + + // Ignore object files that have no executable sections, like rmeta. + if !has_exe_sections { + return Ok(()); + } + + // If there is no `.note.GNU-stack` and no executable sections, behavior differs by platform. + match obj.architecture() { + // PPC64 doesn't set `.note.GNU-stack` since GNU nested functions don't need a trampoline, + // . From experimentation, it seems + // like this only applies to big endian. + Architecture::PowerPc64 if obj.endianness() == Endianness::Big => Ok(()), + + _ => Err(ExeStack::MissingGnuStackSec), + } +} + /// Thin wrapper for owning data used by `object`. struct BinFile { path: PathBuf, diff --git a/library/compiler-builtins/crates/symbol-check/tests/all.rs b/library/compiler-builtins/crates/symbol-check/tests/all.rs index f65038c62da6..6dc34c3e3dba 100644 --- a/library/compiler-builtins/crates/symbol-check/tests/all.rs +++ b/library/compiler-builtins/crates/symbol-check/tests/all.rs @@ -5,6 +5,7 @@ use assert_cmd::assert::Assert; use assert_cmd::cargo::cargo_bin_cmd; +use object::BinaryFormat; use tempfile::tempdir; trait AssertExt { @@ -74,16 +75,134 @@ fn test_core_symbols() { .stderr_contains("from_utf8"); } +mod exe_stack { + use super::*; + + /// Check with an object that has no `.note.GNU-stack` section, indicating platform-default stack + /// writeability (usually enabled). + #[test] + fn test_missing_gnu_stack_section() { + let t = TestTarget::from_env(); + if t.is_msvc() { + // Can't easily build asm via cc with cl.exe / masm.exe + eprintln!("assembly on windows, skipping"); + return; + } + + let dir = tempdir().unwrap(); + let src = input_dir().join("missing_gnu_stack_section.S"); + + let objs = t.cc_build().file(src).out_dir(&dir).compile_intermediates(); + let [obj] = objs.as_slice() else { panic!() }; + + let assert = t.symcheck_exe().arg(obj).assert(); + + if t.is_ppc64be() || t.no_os() || t.binary_obj_format() != BinaryFormat::Elf { + // Ppc64be doesn't emit `.note.GNU-stack`, not relevant without an OS, and non-elf + // targets don't use `.note.GNU-stack`. + assert.success(); + return; + } + + assert + .failure() + .stderr_contains("the following object files require an executable stack") + .stderr_contains("missing_gnu_stack_section.o (no .note.GNU-stack section)"); + } + + /// Check with an object that has a `.note.GNU-stack` section with the executable flag set. + #[test] + fn test_exe_gnu_stack_section() { + let t = TestTarget::from_env(); + let mut build = t.cc_build(); + if !build.get_compiler().is_like_gnu() || t.is_windows() { + eprintln!("unsupported compiler for nested functions, skipping"); + return; + } + + let dir = tempdir().unwrap(); + let objs = build + .file(input_dir().join("has_exe_gnu_stack_section.c")) + .out_dir(&dir) + .compile_intermediates(); + let [obj] = objs.as_slice() else { panic!() }; + + let assert = t.symcheck_exe().arg(obj).assert(); + + if t.is_ppc64be() || t.no_os() { + // Ppc64be doesn't emit `.note.GNU-stack`, not relevant without an OS. + assert.success(); + return; + } + + assert + .failure() + .stderr_contains("the following object files require an executable stack") + .stderr_contains( + "has_exe_gnu_stack_section.o (.note.GNU-stack section marked SHF_EXECINSTR)", + ); + } + + /// Check a final binary with `PT_GNU_STACK`. + #[test] + fn test_execstack_bin() { + let t = TestTarget::from_env(); + if t.binary_obj_format() != BinaryFormat::Elf || !t.supports_executables() { + // Mac's Clang rejects `-z execstack`. `-allow_stack_execute` should work per the ld + // manpage, at least on x86, but it doesn't seem to., not relevant without an OS. + eprintln!("non-elf or no-executable target, skipping"); + return; + } + + let dir = tempdir().unwrap(); + let out = dir.path().join("execstack.out"); + + let mut cmd = t.cc_build().get_compiler().to_command(); + t.set_bin_out_path(&mut cmd, &out); + + run(cmd + .arg("-z") + .arg("execstack") + .arg(input_dir().join("good_bin.c"))); + + let assert = t.symcheck_exe().arg(&out).assert(); + assert + .failure() + .stderr_contains("the following object files require an executable stack") + .stderr_contains("execstack.out (PT_GNU_STACK program header marked PF_X)"); + } +} + #[test] fn test_good_lib() { let t = TestTarget::from_env(); let dir = tempdir().unwrap(); let lib_out = dir.path().join("libfoo.rlib"); - t.rustc_build(&input_dir().join("good.rs"), &lib_out, |cmd| cmd); + t.rustc_build(&input_dir().join("good_lib.rs"), &lib_out, |cmd| cmd); let assert = t.symcheck_exe().arg(&lib_out).assert(); assert.success(); } +#[test] +fn test_good_bin() { + let t = TestTarget::from_env(); + // Nothing to test if we can't build a binary. + if !t.supports_executables() { + eprintln!("no-exe target, skipping"); + return; + } + + let dir = tempdir().unwrap(); + let out = dir.path().join("good_bin.out"); + + let mut cmd = t.cc_build().get_compiler().to_command(); + t.set_bin_out_path(&mut cmd, &out); + run(cmd.arg(input_dir().join("good_bin.c"))); + + let assert = t.symcheck_exe().arg(&out).assert(); + assert.success(); +} + /// Since symcheck is a hostprog, the target we want to build and test symcheck for may not be the /// same as the host target. struct TestTarget { @@ -131,8 +250,66 @@ fn cc_build(&self) -> cc::Build { fn symcheck_exe(&self) -> assert_cmd::Command { let mut cmd = cargo_bin_cmd!(); cmd.arg("--check"); + if self.no_os() { + cmd.arg("--no-os"); + } cmd } + + /// MSVC requires different flags for setting output path, account for that here. + fn set_bin_out_path<'a>(&self, cmd: &'a mut Command, out: &Path) -> &'a mut Command { + if self.cc_build().get_compiler().is_like_msvc() { + let mut exe_arg = OsString::from("/Fe"); + let mut obj_arg = OsString::from("/Fo"); + exe_arg.push(out); + obj_arg.push(out.with_extension("o")); + cmd.arg(exe_arg).arg(obj_arg) + } else { + cmd.arg("-o").arg(out) + } + } + + /// Based on `rustc_target`. + fn binary_obj_format(&self) -> BinaryFormat { + let t = &self.triple; + if t.contains("-windows-") || t.contains("-cygwin") { + // Coff for libraries, PE for executables. + BinaryFormat::Coff + } else if t.starts_with("wasm") { + BinaryFormat::Wasm + } else if t.contains("-aix") { + BinaryFormat::Xcoff + } else if t.contains("-apple-") { + BinaryFormat::MachO + } else { + BinaryFormat::Elf + } + } + + fn is_windows(&self) -> bool { + self.triple.contains("-windows-") + } + + fn is_msvc(&self) -> bool { + self.triple.contains("-windows-msvc") + } + + fn is_ppc64be(&self) -> bool { + self.triple.starts_with("powerpc64-") + } + + /// True if the target needs `--no-os` passed to symcheck. + fn no_os(&self) -> bool { + self.triple.contains("-none") + } + + /// True if the target supports (easily) building to a final executable. + fn supports_executables(&self) -> bool { + // Technically i686-pc-windows-gnu should work but it has nontrivial setup in CI. + !(self.no_os() + || self.triple == "wasm32-unknown-unknown" + || self.triple == "i686-pc-windows-gnu") + } } fn input_dir() -> PathBuf { diff --git a/library/compiler-builtins/crates/symbol-check/tests/input/good_bin.c b/library/compiler-builtins/crates/symbol-check/tests/input/good_bin.c new file mode 100644 index 000000000000..dba75b58af1a --- /dev/null +++ b/library/compiler-builtins/crates/symbol-check/tests/input/good_bin.c @@ -0,0 +1,3 @@ +/* empty main used to test binaries with compiler options */ + +int main() {} diff --git a/library/compiler-builtins/crates/symbol-check/tests/input/good.rs b/library/compiler-builtins/crates/symbol-check/tests/input/good_lib.rs similarity index 100% rename from library/compiler-builtins/crates/symbol-check/tests/input/good.rs rename to library/compiler-builtins/crates/symbol-check/tests/input/good_lib.rs diff --git a/library/compiler-builtins/crates/symbol-check/tests/input/has_exe_gnu_stack_section.c b/library/compiler-builtins/crates/symbol-check/tests/input/has_exe_gnu_stack_section.c new file mode 100644 index 000000000000..d4be217b5a06 --- /dev/null +++ b/library/compiler-builtins/crates/symbol-check/tests/input/has_exe_gnu_stack_section.c @@ -0,0 +1,16 @@ +/* A file that requires an executable stack and thus will have a + * `.note.GNU-stack` section with the executable bit set. + * + * GNU nested functions are the only way I could find to force an explicitly + * executable stack. Supported by GCC only, not Clang. + */ + +void intermediate(void (*)(int, int), int); + +void hack(int *array, int size) { + void store (int index, int value) { + array[index] = value; + } + + intermediate(store, size); +} diff --git a/library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S b/library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S new file mode 100644 index 000000000000..09bb761c40ba --- /dev/null +++ b/library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S @@ -0,0 +1,19 @@ +/* Create an object file with no `.note.GNU-stack` section. + * + * Assembly files do not get that section, meaning platform-default stack + * executability is implied (usually yes on Linux). + */ + +.global func + +#ifdef __wasm__ +.functype func () -> () +.type func, @function +#endif + +func: + nop + +#ifdef __wasm__ + end_function +#endif From 200968c81db4920f6d2f1546c476a00ab6ea98af Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 20:55:17 -0600 Subject: [PATCH 0033/1059] ci: Pin Miri to the 2026-02-11 nightly CI is failing with: error: failed to run custom build command for `quote v1.0.44` Caused by: process didn't exit successfully: `/home/runner/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo-miri runner /home/runner/work/compiler-builtins/compiler-builtins/target/miri/debug/build/quote-32cc00414fa6f72d/build-script-build` (exit status: 1) --- stderr fatal error: file "/home/runner/work/compiler-builtins/compiler-builtins/target/miri/debug/build/quote-32cc00414fa6f72d/build-script-build" contains outdated or invalid JSON; try `cargo clean` Link: https://rust-lang.zulipchat.com/#narrow/channel/269128-miri/topic/build-script-build.20contains.20outdated.20or.20invalid.20JSON/with/573426109 --- library/compiler-builtins/.github/workflows/main.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/compiler-builtins/.github/workflows/main.yaml b/library/compiler-builtins/.github/workflows/main.yaml index 3fed58f2a207..a62f847082ae 100644 --- a/library/compiler-builtins/.github/workflows/main.yaml +++ b/library/compiler-builtins/.github/workflows/main.yaml @@ -303,7 +303,9 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Rust (rustup) - run: rustup update nightly --no-self-update && rustup default nightly + # FIXME(ci): not working in the 2026-02-11 nightly + # https://rust-lang.zulipchat.com/#narrow/channel/269128-miri/topic/build-script-build.20contains.20outdated.20or.20invalid.20JSON/with/573426109 + run: rustup update nightly-2026-02-10 --no-self-update && rustup default nightly-2026-02-10 shell: bash - run: rustup component add miri - run: cargo miri setup From 8f0208ecb2365135ae3f8a8f67544c43002ae8f4 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 02:58:18 +0000 Subject: [PATCH 0034/1059] Revert "ci: Temporarily disable native PPC and s390x jobs" These were disabled due to permission issues, which should now be resolved. This reverts commit 28e0556a7bca6ca828186d9de74d30c7cb45db5e. --- .../compiler-builtins/.github/workflows/main.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/library/compiler-builtins/.github/workflows/main.yaml b/library/compiler-builtins/.github/workflows/main.yaml index a62f847082ae..163b64616b0b 100644 --- a/library/compiler-builtins/.github/workflows/main.yaml +++ b/library/compiler-builtins/.github/workflows/main.yaml @@ -70,14 +70,14 @@ jobs: os: ubuntu-24.04 - target: powerpc64le-unknown-linux-gnu os: ubuntu-24.04 - # - target: powerpc64le-unknown-linux-gnu - # os: ubuntu-24.04-ppc64le - # # FIXME(rust#151807): remove once PPC builds work again. - # channel: nightly-2026-01-23 + - target: powerpc64le-unknown-linux-gnu + os: ubuntu-24.04-ppc64le + # FIXME(rust#151807): remove once PPC builds work again. + channel: nightly-2026-01-23 - target: riscv64gc-unknown-linux-gnu os: ubuntu-24.04 - # - target: s390x-unknown-linux-gnu - # os: ubuntu-24.04-s390x + - target: s390x-unknown-linux-gnu + os: ubuntu-24.04-s390x - target: thumbv6m-none-eabi os: ubuntu-24.04 - target: thumbv7em-none-eabi From 804a1f87cfcce416cdfd10830e500ce5f50b4997 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 03:59:46 +0000 Subject: [PATCH 0035/1059] Revert "ci: Pin rustc on the native PowerPC job" The nightly has the LLVM bump that resolves the relevant issue. This reverts commit 99d1fc7752b0e09cff5729cd4f3783d23c23cb66. Link: https://github.com/rust-lang/rust/pull/152428 --- library/compiler-builtins/.github/workflows/main.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/compiler-builtins/.github/workflows/main.yaml b/library/compiler-builtins/.github/workflows/main.yaml index 163b64616b0b..0375d1eb29de 100644 --- a/library/compiler-builtins/.github/workflows/main.yaml +++ b/library/compiler-builtins/.github/workflows/main.yaml @@ -72,8 +72,6 @@ jobs: os: ubuntu-24.04 - target: powerpc64le-unknown-linux-gnu os: ubuntu-24.04-ppc64le - # FIXME(rust#151807): remove once PPC builds work again. - channel: nightly-2026-01-23 - target: riscv64gc-unknown-linux-gnu os: ubuntu-24.04 - target: s390x-unknown-linux-gnu From f2bec36b92b9e3ba64d8b5b6ad390e534af64f24 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 20 Apr 2025 04:21:18 +0000 Subject: [PATCH 0036/1059] libm: Convert `frexp` and `ilogb` to a generic implementations --- .../etc/function-definitions.json | 10 ++++-- .../compiler-builtins/libm/src/math/frexp.rs | 30 +++++++--------- .../compiler-builtins/libm/src/math/frexpf.rs | 22 ------------ .../libm/src/math/generic/frexp.rs | 24 +++++++++++++ .../libm/src/math/generic/ilogb.rs | 35 +++++++++++++++++++ .../libm/src/math/generic/mod.rs | 4 +++ .../compiler-builtins/libm/src/math/ilogb.rs | 35 ++++--------------- .../compiler-builtins/libm/src/math/ilogbf.rs | 28 --------------- .../compiler-builtins/libm/src/math/mod.rs | 8 ++--- 9 files changed, 91 insertions(+), 105 deletions(-) delete mode 100644 library/compiler-builtins/libm/src/math/frexpf.rs create mode 100644 library/compiler-builtins/libm/src/math/generic/frexp.rs create mode 100644 library/compiler-builtins/libm/src/math/generic/ilogb.rs delete mode 100644 library/compiler-builtins/libm/src/math/ilogbf.rs diff --git a/library/compiler-builtins/etc/function-definitions.json b/library/compiler-builtins/etc/function-definitions.json index 4f796905b754..105d0266fc60 100644 --- a/library/compiler-builtins/etc/function-definitions.json +++ b/library/compiler-builtins/etc/function-definitions.json @@ -560,13 +560,15 @@ }, "frexp": { "sources": [ - "libm/src/math/frexp.rs" + "libm/src/math/frexp.rs", + "libm/src/math/generic/frexp.rs" ], "type": "f64" }, "frexpf": { "sources": [ - "libm/src/math/frexpf.rs" + "libm/src/math/frexp.rs", + "libm/src/math/generic/frexp.rs" ], "type": "f32" }, @@ -584,13 +586,15 @@ }, "ilogb": { "sources": [ + "libm/src/math/generic/ilogb.rs", "libm/src/math/ilogb.rs" ], "type": "f64" }, "ilogbf": { "sources": [ - "libm/src/math/ilogbf.rs" + "libm/src/math/generic/ilogb.rs", + "libm/src/math/ilogb.rs" ], "type": "f32" }, diff --git a/library/compiler-builtins/libm/src/math/frexp.rs b/library/compiler-builtins/libm/src/math/frexp.rs index 932111eebc95..8281ed27bd5d 100644 --- a/library/compiler-builtins/libm/src/math/frexp.rs +++ b/library/compiler-builtins/libm/src/math/frexp.rs @@ -1,21 +1,15 @@ +/// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2. +/// +/// That is, `x * 2^p` will represent the input value. +#[cfg_attr(assert_no_panic, no_panic::no_panic)] +pub fn frexpf(x: f32) -> (f32, i32) { + super::generic::frexp(x) +} + +/// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2. +/// +/// That is, `x * 2^p` will represent the input value. #[cfg_attr(assert_no_panic, no_panic::no_panic)] pub fn frexp(x: f64) -> (f64, i32) { - let mut y = x.to_bits(); - let ee = ((y >> 52) & 0x7ff) as i32; - - if ee == 0 { - if x != 0.0 { - let x1p64 = f64::from_bits(0x43f0000000000000); - let (x, e) = frexp(x * x1p64); - return (x, e - 64); - } - return (x, 0); - } else if ee == 0x7ff { - return (x, 0); - } - - let e = ee - 0x3fe; - y &= 0x800fffffffffffff; - y |= 0x3fe0000000000000; - return (f64::from_bits(y), e); + super::generic::frexp(x) } diff --git a/library/compiler-builtins/libm/src/math/frexpf.rs b/library/compiler-builtins/libm/src/math/frexpf.rs deleted file mode 100644 index 904bf14f7b8e..000000000000 --- a/library/compiler-builtins/libm/src/math/frexpf.rs +++ /dev/null @@ -1,22 +0,0 @@ -#[cfg_attr(assert_no_panic, no_panic::no_panic)] -pub fn frexpf(x: f32) -> (f32, i32) { - let mut y = x.to_bits(); - let ee: i32 = ((y >> 23) & 0xff) as i32; - - if ee == 0 { - if x != 0.0 { - let x1p64 = f32::from_bits(0x5f800000); - let (x, e) = frexpf(x * x1p64); - return (x, e - 64); - } else { - return (x, 0); - } - } else if ee == 0xff { - return (x, 0); - } - - let e = ee - 0x7e; - y &= 0x807fffff; - y |= 0x3f000000; - (f32::from_bits(y), e) -} diff --git a/library/compiler-builtins/libm/src/math/generic/frexp.rs b/library/compiler-builtins/libm/src/math/generic/frexp.rs new file mode 100644 index 000000000000..d5c2758a4b04 --- /dev/null +++ b/library/compiler-builtins/libm/src/math/generic/frexp.rs @@ -0,0 +1,24 @@ +use super::super::{CastFrom, Float, MinInt}; + +#[inline] +pub fn frexp(x: F) -> (F, i32) { + let mut ix = x.to_bits(); + let ee = x.ex() as i32; + + if ee == 0 { + if x != F::ZERO { + // normalize via multiplication; 1p64 for `f64` + let magic = F::from_parts(false, F::EXP_BIAS + F::BITS, F::Int::ZERO); + let (x, e) = frexp(x * magic); + return (x, e - F::BITS as i32); + } + return (x, 0); + } else if ee == F::EXP_SAT as i32 { + return (x, 0); + } + + let e = ee - (F::EXP_BIAS as i32 - 1); + ix &= F::SIGN_MASK | F::SIG_MASK; + ix |= F::Int::cast_from(F::EXP_BIAS - 1) << F::SIG_BITS; + (F::from_bits(ix), e) +} diff --git a/library/compiler-builtins/libm/src/math/generic/ilogb.rs b/library/compiler-builtins/libm/src/math/generic/ilogb.rs new file mode 100644 index 000000000000..02a7d6b30b10 --- /dev/null +++ b/library/compiler-builtins/libm/src/math/generic/ilogb.rs @@ -0,0 +1,35 @@ +use super::super::{Float, MinInt}; + +const FP_ILOGBNAN: i32 = i32::MIN; +const FP_ILOGB0: i32 = FP_ILOGBNAN; + +#[inline] +pub fn ilogb(x: F) -> i32 { + let zero = F::Int::ZERO; + let mut i = x.to_bits(); + let e = x.ex() as i32; + + if e == 0 { + i <<= F::EXP_BITS + 1; + if i == F::Int::ZERO { + force_eval!(0.0 / 0.0); + return FP_ILOGB0; + } + /* subnormal x */ + let mut e = -(F::EXP_BIAS as i32); + while i >> (F::BITS - 1) == zero { + e -= 1; + i <<= 1; + } + e + } else if e == F::EXP_SAT as i32 { + force_eval!(0.0 / 0.0); + if i << (F::EXP_BITS + 1) != zero { + FP_ILOGBNAN + } else { + i32::MAX + } + } else { + e - F::EXP_BIAS as i32 + } +} diff --git a/library/compiler-builtins/libm/src/math/generic/mod.rs b/library/compiler-builtins/libm/src/math/generic/mod.rs index 9d497a03f544..114fcddf516e 100644 --- a/library/compiler-builtins/libm/src/math/generic/mod.rs +++ b/library/compiler-builtins/libm/src/math/generic/mod.rs @@ -15,6 +15,8 @@ mod fminimum; mod fminimum_num; mod fmod; +mod frexp; +mod ilogb; mod rint; mod round; mod scalbn; @@ -35,6 +37,8 @@ pub use fminimum::fminimum; pub use fminimum_num::fminimum_num; pub use fmod::fmod; +pub use frexp::frexp; +pub use ilogb::ilogb; pub use rint::rint_round; pub use round::round; pub use scalbn::scalbn; diff --git a/library/compiler-builtins/libm/src/math/ilogb.rs b/library/compiler-builtins/libm/src/math/ilogb.rs index ef774f6ad3a6..13bef5f8c617 100644 --- a/library/compiler-builtins/libm/src/math/ilogb.rs +++ b/library/compiler-builtins/libm/src/math/ilogb.rs @@ -1,32 +1,11 @@ -const FP_ILOGBNAN: i32 = -1 - 0x7fffffff; -const FP_ILOGB0: i32 = FP_ILOGBNAN; +/// Extract the binary exponent of `x`. +#[cfg_attr(assert_no_panic, no_panic::no_panic)] +pub fn ilogbf(x: f32) -> i32 { + super::generic::ilogb(x) +} +/// Extract the binary exponent of `x`. #[cfg_attr(assert_no_panic, no_panic::no_panic)] pub fn ilogb(x: f64) -> i32 { - let mut i: u64 = x.to_bits(); - let e = ((i >> 52) & 0x7ff) as i32; - - if e == 0 { - i <<= 12; - if i == 0 { - force_eval!(0.0 / 0.0); - return FP_ILOGB0; - } - /* subnormal x */ - let mut e = -0x3ff; - while (i >> 63) == 0 { - e -= 1; - i <<= 1; - } - e - } else if e == 0x7ff { - force_eval!(0.0 / 0.0); - if (i << 12) != 0 { - FP_ILOGBNAN - } else { - i32::MAX - } - } else { - e - 0x3ff - } + super::generic::ilogb(x) } diff --git a/library/compiler-builtins/libm/src/math/ilogbf.rs b/library/compiler-builtins/libm/src/math/ilogbf.rs deleted file mode 100644 index 5b0cb46ec558..000000000000 --- a/library/compiler-builtins/libm/src/math/ilogbf.rs +++ /dev/null @@ -1,28 +0,0 @@ -const FP_ILOGBNAN: i32 = -1 - 0x7fffffff; -const FP_ILOGB0: i32 = FP_ILOGBNAN; - -#[cfg_attr(assert_no_panic, no_panic::no_panic)] -pub fn ilogbf(x: f32) -> i32 { - let mut i = x.to_bits(); - let e = ((i >> 23) & 0xff) as i32; - - if e == 0 { - i <<= 9; - if i == 0 { - force_eval!(0.0 / 0.0); - return FP_ILOGB0; - } - /* subnormal x */ - let mut e = -0x7f; - while (i >> 31) == 0 { - e -= 1; - i <<= 1; - } - e - } else if e == 0xff { - force_eval!(0.0 / 0.0); - if (i << 9) != 0 { FP_ILOGBNAN } else { i32::MAX } - } else { - e - 0x7f - } -} diff --git a/library/compiler-builtins/libm/src/math/mod.rs b/library/compiler-builtins/libm/src/math/mod.rs index 8eecfe5667d1..c1ea60e368d9 100644 --- a/library/compiler-builtins/libm/src/math/mod.rs +++ b/library/compiler-builtins/libm/src/math/mod.rs @@ -166,11 +166,9 @@ macro_rules! div { mod fminimum_fmaximum_num; mod fmod; mod frexp; -mod frexpf; mod hypot; mod hypotf; mod ilogb; -mod ilogbf; mod j0; mod j0f; mod j1; @@ -260,12 +258,10 @@ macro_rules! div { pub use self::fminimum_fmaximum::{fmaximum, fmaximumf, fminimum, fminimumf}; pub use self::fminimum_fmaximum_num::{fmaximum_num, fmaximum_numf, fminimum_num, fminimum_numf}; pub use self::fmod::{fmod, fmodf}; -pub use self::frexp::frexp; -pub use self::frexpf::frexpf; +pub use self::frexp::{frexp, frexpf}; pub use self::hypot::hypot; pub use self::hypotf::hypotf; -pub use self::ilogb::ilogb; -pub use self::ilogbf::ilogbf; +pub use self::ilogb::{ilogb, ilogbf}; pub use self::j0::{j0, y0}; pub use self::j0f::{j0f, y0f}; pub use self::j1::{j1, y1}; From 212652c5d4abf5c92c4a654ed9bad1e4dbff809b Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 20 Apr 2025 04:21:18 +0000 Subject: [PATCH 0037/1059] libm: Add `frexpf128`, `ilogbf16`, and `ilogbf128` The `frexpf16` signature is added (to make traits easier) but left unimplemented, since it will need a slightly different algorithm. --- .../crates/libm-macros/src/shared.rs | 36 +++++++++++ .../etc/function-definitions.json | 21 ++++++ .../compiler-builtins/etc/function-list.txt | 3 + .../libm-test/benches/icount.rs | 3 + .../libm-test/src/generate/case_list.rs | 19 +++++- .../libm-test/src/mpfloat.rs | 64 ++++++++++++------- .../libm-test/src/test_traits.rs | 12 ++++ .../compiler-builtins/libm/src/libm_helper.rs | 4 ++ .../compiler-builtins/libm/src/math/frexp.rs | 20 ++++++ .../compiler-builtins/libm/src/math/ilogb.rs | 14 ++++ .../compiler-builtins/libm/src/math/mod.rs | 6 ++ 11 files changed, 177 insertions(+), 25 deletions(-) diff --git a/library/compiler-builtins/crates/libm-macros/src/shared.rs b/library/compiler-builtins/crates/libm-macros/src/shared.rs index 1cefe4e8c7ed..5a5eca6f685d 100644 --- a/library/compiler-builtins/crates/libm-macros/src/shared.rs +++ b/library/compiler-builtins/crates/libm-macros/src/shared.rs @@ -279,6 +279,17 @@ struct NestedOp { fn_list: &["fmaf128"], public: true, }, + NestedOp { + // `(f16) -> i32` + float_ty: FloatTy::F16, + rust_sig: Signature { + args: &[Ty::F16], + returns: &[Ty::I32], + }, + c_sig: None, + fn_list: &["ilogbf16"], + public: true, + }, NestedOp { // `(f32) -> i32` float_ty: FloatTy::F32, @@ -301,6 +312,17 @@ struct NestedOp { fn_list: &["ilogb"], public: true, }, + NestedOp { + // `(f128) -> i32` + float_ty: FloatTy::F128, + rust_sig: Signature { + args: &[Ty::F128], + returns: &[Ty::I32], + }, + c_sig: None, + fn_list: &["ilogbf128"], + public: true, + }, NestedOp { // `(i32, f32) -> f32` float_ty: FloatTy::F32, @@ -423,6 +445,20 @@ struct NestedOp { fn_list: &["frexp", "lgamma_r"], public: true, }, + NestedOp { + // `(f128, &mut c_int) -> f128` as `(f128) -> (f128, i32)` + float_ty: FloatTy::F128, + rust_sig: Signature { + args: &[Ty::F128], + returns: &[Ty::F128, Ty::I32], + }, + c_sig: Some(Signature { + args: &[Ty::F128, Ty::MutCInt], + returns: &[Ty::F128], + }), + fn_list: &["frexpf128"], + public: true, + }, NestedOp { // `(f32, f32, &mut c_int) -> f32` as `(f32, f32) -> (f32, i32)` float_ty: FloatTy::F32, diff --git a/library/compiler-builtins/etc/function-definitions.json b/library/compiler-builtins/etc/function-definitions.json index 105d0266fc60..06eb1ad5e2e7 100644 --- a/library/compiler-builtins/etc/function-definitions.json +++ b/library/compiler-builtins/etc/function-definitions.json @@ -572,6 +572,13 @@ ], "type": "f32" }, + "frexpf128": { + "sources": [ + "libm/src/math/frexp.rs", + "libm/src/math/generic/frexp.rs" + ], + "type": "f128" + }, "hypot": { "sources": [ "libm/src/math/hypot.rs" @@ -598,6 +605,20 @@ ], "type": "f32" }, + "ilogbf128": { + "sources": [ + "libm/src/math/generic/ilogb.rs", + "libm/src/math/ilogb.rs" + ], + "type": "f128" + }, + "ilogbf16": { + "sources": [ + "libm/src/math/generic/ilogb.rs", + "libm/src/math/ilogb.rs" + ], + "type": "f16" + }, "j0": { "sources": [ "libm/src/math/j0.rs" diff --git a/library/compiler-builtins/etc/function-list.txt b/library/compiler-builtins/etc/function-list.txt index 1f226c8c0ff3..45a39e3eee8d 100644 --- a/library/compiler-builtins/etc/function-list.txt +++ b/library/compiler-builtins/etc/function-list.txt @@ -84,10 +84,13 @@ fmodf128 fmodf16 frexp frexpf +frexpf128 hypot hypotf ilogb ilogbf +ilogbf128 +ilogbf16 j0 j0f j1 diff --git a/library/compiler-builtins/libm-test/benches/icount.rs b/library/compiler-builtins/libm-test/benches/icount.rs index fb856d9be451..be5178e205a2 100644 --- a/library/compiler-builtins/libm-test/benches/icount.rs +++ b/library/compiler-builtins/libm-test/benches/icount.rs @@ -331,10 +331,13 @@ fn icount_bench_print_hf128(x: f128) -> String { icount_bench_fmodf16_group, icount_bench_fmodf_group, icount_bench_frexp_group, + icount_bench_frexpf128_group, icount_bench_frexpf_group, icount_bench_hypot_group, icount_bench_hypotf_group, icount_bench_ilogb_group, + icount_bench_ilogbf128_group, + icount_bench_ilogbf16_group, icount_bench_ilogbf_group, icount_bench_j0_group, icount_bench_j0f_group, diff --git a/library/compiler-builtins/libm-test/src/generate/case_list.rs b/library/compiler-builtins/libm-test/src/generate/case_list.rs index 43b28722f2dd..958e03ab67f0 100644 --- a/library/compiler-builtins/libm-test/src/generate/case_list.rs +++ b/library/compiler-builtins/libm-test/src/generate/case_list.rs @@ -460,11 +460,16 @@ fn fmodf16_cases() -> Vec> { vec![] } +fn frexpf_cases() -> Vec> { + vec![] +} + fn frexp_cases() -> Vec> { vec![] } -fn frexpf_cases() -> Vec> { +#[cfg(f128_enabled)] +fn frexpf128_cases() -> Vec> { vec![] } @@ -476,7 +481,8 @@ fn hypotf_cases() -> Vec> { vec![] } -fn ilogb_cases() -> Vec> { +#[cfg(f16_enabled)] +fn ilogbf16_cases() -> Vec> { vec![] } @@ -484,6 +490,15 @@ fn ilogbf_cases() -> Vec> { vec![] } +fn ilogb_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn ilogbf128_cases() -> Vec> { + vec![] +} + fn j0_cases() -> Vec> { vec![] } diff --git a/library/compiler-builtins/libm-test/src/mpfloat.rs b/library/compiler-builtins/libm-test/src/mpfloat.rs index 85f0a4da4a6e..3237a232f7dc 100644 --- a/library/compiler-builtins/libm-test/src/mpfloat.rs +++ b/library/compiler-builtins/libm-test/src/mpfloat.rs @@ -162,8 +162,11 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { fmodf16, frexp, frexpf, + frexpf128, ilogb, ilogbf, + ilogbf128, + ilogbf16, jn, jnf, ldexp, @@ -338,29 +341,6 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { } } - impl MpOp for crate::op::[]::Routine { - type MpTy = MpFloat; - - fn new_mp() -> Self::MpTy { - new_mpfloat::() - } - - fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { - this.assign(input.0); - - // `get_exp` follows `frexp` for `0.5 <= |m| < 1.0`. Adjust the exponent by - // one to scale the significand to `1.0 <= |m| < 2.0`. - this.get_exp().map(|v| v - 1).unwrap_or_else(|| { - if this.is_infinite() { - i32::MAX - } else { - // Zero or NaN - i32::MIN - } - }) - } - } - impl MpOp for crate::op::[]::Routine { type MpTy = MpFloat; @@ -505,6 +485,29 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { } } + impl MpOp for crate::op::[]::Routine { + type MpTy = MpFloat; + + fn new_mp() -> Self::MpTy { + new_mpfloat::() + } + + fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { + this.assign(input.0); + + // `get_exp` follows `frexp` for `0.5 <= |m| < 1.0`. Adjust the exponent by + // one to scale the significand to `1.0 <= |m| < 2.0`. + this.get_exp().map(|v| v - 1).unwrap_or_else(|| { + if this.is_infinite() { + i32::MAX + } else { + // Zero or NaN + i32::MIN + } + }) + } + } + // `ldexp` and `scalbn` are the same for binary floating point, so just forward all // methods. impl MpOp for crate::op::[]::Routine { @@ -546,6 +549,21 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { #[cfg(f128_enabled)] impl_op_for_ty_all!(f128, "f128"); +#[cfg(f128_enabled)] +impl MpOp for crate::op::frexpf128::Routine { + type MpTy = MpFloat; + + fn new_mp() -> Self::MpTy { + new_mpfloat::() + } + + fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { + this.assign(input.0); + let exp = this.frexp_mut(); + (prep_retval::(this, Ordering::Equal), exp) + } +} + // `lgamma_r` is not a simple suffix so we can't use the above macro. impl MpOp for crate::op::lgamma_r::Routine { type MpTy = MpFloat; diff --git a/library/compiler-builtins/libm-test/src/test_traits.rs b/library/compiler-builtins/libm-test/src/test_traits.rs index 278274d917b3..8b21e2af9a04 100644 --- a/library/compiler-builtins/libm-test/src/test_traits.rs +++ b/library/compiler-builtins/libm-test/src/test_traits.rs @@ -456,3 +456,15 @@ fn validate<'a>( (f32, f32); (f64, f64); ); + +#[cfg(f16_enabled)] +impl_tuples!( + (f16, i32); + (f16, f16); +); + +#[cfg(f128_enabled)] +impl_tuples!( + (f128, i32); + (f128, f128); +); diff --git a/library/compiler-builtins/libm/src/libm_helper.rs b/library/compiler-builtins/libm/src/libm_helper.rs index 0bb669398657..1e6e0beb3e3e 100644 --- a/library/compiler-builtins/libm/src/libm_helper.rs +++ b/library/compiler-builtins/libm/src/libm_helper.rs @@ -202,6 +202,8 @@ pub fn $func($($arg: $arg_typ),*) -> ($($ret_typ),*) { (fn fminimum(x: f16, y: f16) -> (f16); => fminimumf16); (fn fminimum_num(x: f16, y: f16) -> (f16); => fminimum_numf16); (fn fmod(x: f16, y: f16) -> (f16); => fmodf16); + (fn frexp(x: f16) -> (f16, i32); => frexpf16); + (fn ilogb(x: f16) -> (i32); => ilogbf16); (fn ldexp(x: f16, n: i32) -> (f16); => ldexpf16); (fn rint(x: f16) -> (f16); => rintf16); (fn round(x: f16) -> (f16); => roundf16); @@ -231,6 +233,8 @@ pub fn $func($($arg: $arg_typ),*) -> ($($ret_typ),*) { (fn fminimum(x: f128, y: f128) -> (f128); => fminimumf128); (fn fminimum_num(x: f128, y: f128) -> (f128); => fminimum_numf128); (fn fmod(x: f128, y: f128) -> (f128); => fmodf128); + (fn frexp(x: f128) -> (f128, i32); => frexpf128); + (fn ilogb(x: f128) -> (i32); => ilogbf128); (fn ldexp(x: f128, n: i32) -> (f128); => ldexpf128); (fn rint(x: f128) -> (f128); => rintf128); (fn round(x: f128) -> (f128); => roundf128); diff --git a/library/compiler-builtins/libm/src/math/frexp.rs b/library/compiler-builtins/libm/src/math/frexp.rs index 8281ed27bd5d..f2f545fb65e4 100644 --- a/library/compiler-builtins/libm/src/math/frexp.rs +++ b/library/compiler-builtins/libm/src/math/frexp.rs @@ -1,3 +1,14 @@ +/// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2. +/// +/// That is, `x * 2^p` will represent the input value. +// Placeholder so we can have `frexpf16` in the `Float` trait. +#[allow(unused)] +#[cfg(f16_enabled)] +#[cfg_attr(assert_no_panic, no_panic::no_panic)] +pub(crate) fn frexpf16(x: f16) -> (f16, i32) { + unimplemented!() +} + /// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2. /// /// That is, `x * 2^p` will represent the input value. @@ -13,3 +24,12 @@ pub fn frexpf(x: f32) -> (f32, i32) { pub fn frexp(x: f64) -> (f64, i32) { super::generic::frexp(x) } + +/// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2. +/// +/// That is, `x * 2^p` will represent the input value. +#[cfg(f128_enabled)] +#[cfg_attr(assert_no_panic, no_panic::no_panic)] +pub fn frexpf128(x: f128) -> (f128, i32) { + super::generic::frexp(x) +} diff --git a/library/compiler-builtins/libm/src/math/ilogb.rs b/library/compiler-builtins/libm/src/math/ilogb.rs index 13bef5f8c617..4c3f0a6aa045 100644 --- a/library/compiler-builtins/libm/src/math/ilogb.rs +++ b/library/compiler-builtins/libm/src/math/ilogb.rs @@ -1,3 +1,10 @@ +/// Extract the binary exponent of `x`. +#[cfg(f16_enabled)] +#[cfg_attr(assert_no_panic, no_panic::no_panic)] +pub fn ilogbf16(x: f16) -> i32 { + super::generic::ilogb(x) +} + /// Extract the binary exponent of `x`. #[cfg_attr(assert_no_panic, no_panic::no_panic)] pub fn ilogbf(x: f32) -> i32 { @@ -9,3 +16,10 @@ pub fn ilogbf(x: f32) -> i32 { pub fn ilogb(x: f64) -> i32 { super::generic::ilogb(x) } + +/// Extract the binary exponent of `x`. +#[cfg(f128_enabled)] +#[cfg_attr(assert_no_panic, no_panic::no_panic)] +pub fn ilogbf128(x: f128) -> i32 { + super::generic::ilogb(x) +} diff --git a/library/compiler-builtins/libm/src/math/mod.rs b/library/compiler-builtins/libm/src/math/mod.rs index c1ea60e368d9..0484fe6a3765 100644 --- a/library/compiler-builtins/libm/src/math/mod.rs +++ b/library/compiler-builtins/libm/src/math/mod.rs @@ -322,6 +322,7 @@ macro_rules! div { pub use self::fminimum_fmaximum::{fmaximumf16, fminimumf16}; pub use self::fminimum_fmaximum_num::{fmaximum_numf16, fminimum_numf16}; pub use self::fmod::fmodf16; + pub use self::ilogb::ilogbf16; pub use self::ldexp::ldexpf16; pub use self::rint::rintf16; pub use self::round::roundf16; @@ -333,6 +334,9 @@ macro_rules! div { #[allow(unused_imports)] pub(crate) use self::fma::fmaf16; + + #[allow(unused_imports)] + pub(crate) use self::frexp::frexpf16; } } @@ -349,6 +353,8 @@ macro_rules! div { pub use self::fminimum_fmaximum::{fmaximumf128, fminimumf128}; pub use self::fminimum_fmaximum_num::{fmaximum_numf128, fminimum_numf128}; pub use self::fmod::fmodf128; + pub use self::frexp::frexpf128; + pub use self::ilogb::ilogbf128; pub use self::ldexp::ldexpf128; pub use self::rint::rintf128; pub use self::round::roundf128; From 0a4613c2978d428ac2ef99d6937a3414cc3c8864 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 22:05:07 -0600 Subject: [PATCH 0038/1059] util: `f16` now has parsing in the standard library, use it --- library/compiler-builtins/crates/util/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/compiler-builtins/crates/util/src/main.rs b/library/compiler-builtins/crates/util/src/main.rs index 5972181531b2..34e28d7b9759 100644 --- a/library/compiler-builtins/crates/util/src/main.rs +++ b/library/compiler-builtins/crates/util/src/main.rs @@ -232,11 +232,11 @@ fn parse(_input: &[&str]) -> Self { }; } +#[cfg(f16_enabled)] +impl_parse_tuple!(f16); impl_parse_tuple!(f32); impl_parse_tuple!(f64); -#[cfg(f16_enabled)] -impl_parse_tuple_via_rug!(f16); #[cfg(f128_enabled)] impl_parse_tuple_via_rug!(f128); From 6aa1e6932d15709a1fe67df045c7ff6115f6dc9c Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 19:56:08 -0600 Subject: [PATCH 0039/1059] util: Add support for decomposing floats --- library/compiler-builtins/Cargo.toml | 1 + .../compiler-builtins/crates/util/Cargo.toml | 1 + .../compiler-builtins/crates/util/src/main.rs | 70 ++++++++++++++++++- .../libm-test/src/test_traits.rs | 2 +- 4 files changed, 71 insertions(+), 3 deletions(-) diff --git a/library/compiler-builtins/Cargo.toml b/library/compiler-builtins/Cargo.toml index 3dff23d28577..785df6a20015 100644 --- a/library/compiler-builtins/Cargo.toml +++ b/library/compiler-builtins/Cargo.toml @@ -35,6 +35,7 @@ exclude = [ anyhow = "1.0.101" assert_cmd = "2.1.2" cc = "1.2.55" +cfg-if = "1.0.4" compiler_builtins = { path = "builtins-shim", default-features = false } criterion = { version = "0.6.0", default-features = false, features = ["cargo_bench_support"] } getopts = "0.2.24" diff --git a/library/compiler-builtins/crates/util/Cargo.toml b/library/compiler-builtins/crates/util/Cargo.toml index c56e2cc12ea5..b0a365e4735b 100644 --- a/library/compiler-builtins/crates/util/Cargo.toml +++ b/library/compiler-builtins/crates/util/Cargo.toml @@ -6,6 +6,7 @@ publish = false license = "MIT OR Apache-2.0" [dependencies] +cfg-if.workspace = true libm.workspace = true libm-macros.workspace = true libm-test.workspace = true diff --git a/library/compiler-builtins/crates/util/src/main.rs b/library/compiler-builtins/crates/util/src/main.rs index 34e28d7b9759..498162158426 100644 --- a/library/compiler-builtins/crates/util/src/main.rs +++ b/library/compiler-builtins/crates/util/src/main.rs @@ -8,10 +8,11 @@ use std::num::ParseIntError; use std::str::FromStr; -use libm::support::{Hexf, hf32, hf64}; +use cfg_if::cfg_if; +use libm::support::{Float, Hexf, hf32, hf64}; #[cfg(feature = "build-mpfr")] use libm_test::mpfloat::MpOp; -use libm_test::{MathOp, TupleCall}; +use libm_test::{Hex, MathOp, TupleCall}; #[cfg(feature = "build-mpfr")] use rug::az::{self, Az}; @@ -26,6 +27,10 @@ running routines with a debugger, or quickly checking input. Examples: * eval musl sinf 1.234 # print the results of musl sinf(1.234f32) * eval mpfr pow 1.234 2.432 # print the results of mpfr pow(1.234, 2.432) + + print inputs... + For each input, print it in different formats with various floating + point properties (normal, infinite, etc). "; fn main() { @@ -34,6 +39,7 @@ fn main() { match &str_args.as_slice()[1..] { ["eval", basis, op, inputs @ ..] => do_eval(basis, op, inputs), + ["print" | "p", inputs @ ..] => do_classify(inputs), _ => { println!("{USAGE}\nunrecognized input `{str_args:?}`"); std::process::exit(1); @@ -106,6 +112,66 @@ fn do_eval(basis: &str, op: &str, inputs: &[&str]) { panic!("no operation matching {op}"); } +/// Print basic float information to stdout. +fn do_classify(inputs: &[&str]) { + for s in inputs { + if let Some(s) = s.strip_suffix("f16") { + cfg_if! { + if #[cfg(f16_enabled)] { + let s = s.trim_end_matches("_"); + let x: f16 = parse(&[s], 0); + classify_print(x); + continue; + } else { + panic!("parsing this type requires f16 support: `{s}`"); + } + } + }; + if let Some(s) = s.strip_suffix("f32") { + let s = s.trim_end_matches("_"); + let x: f32 = parse(&[s], 0); + classify_print(x); + continue; + } else if let Some(s) = s.strip_suffix("f64") { + let s = s.trim_end_matches("_"); + let x: f64 = parse(&[s], 0); + classify_print(x); + continue; + } + if let Some(s) = s.strip_suffix("f128") { + cfg_if! { + if #[cfg(all(f128_enabled, feature = "build-mpfr"))] { + let s = s.trim_end_matches("_"); + let x: f128 = parse_rug(&[s], 0); + classify_print(x); + continue; + } else { + panic!("parsing this type requires f128 support and \ + the `build-mpfr` feature: `{s}`"); + } + } + }; + panic!("float type must be specified with a `f*` suffix: `{s}`"); + } +} + +fn classify_print(x: F) +where + F: Float, + F::Int: Hex, +{ + println!("{x:?}"); + println!(" hex: {}", Hexf(x)); + println!(" bits: {}", x.to_bits().hex()); + println!(" nan: {}", x.is_nan()); + println!(" inf: {}", x.is_infinite()); + println!(" normal: {}", !x.is_subnormal()); + println!(" pos: {}", x.is_sign_positive()); + println!(" exp: {} {}", x.ex(), x.ex().hex()); + println!(" exp unbiased: {}", x.exp_unbiased()); + println!(" frac: {} {}", x.frac(), x.frac().hex()); +} + /// Parse a tuple from a space-delimited string. trait ParseTuple { fn parse(input: &[&str]) -> Self; diff --git a/library/compiler-builtins/libm-test/src/test_traits.rs b/library/compiler-builtins/libm-test/src/test_traits.rs index 8b21e2af9a04..f8621a3734a4 100644 --- a/library/compiler-builtins/libm-test/src/test_traits.rs +++ b/library/compiler-builtins/libm-test/src/test_traits.rs @@ -260,7 +260,7 @@ fn validate_int(actual: I, expected: I, input: Input, ctx: &CheckCtx) Ok(()) } -impl_int!(u32, i32, u64, i64); +impl_int!(u16, i16, u32, i32, u64, i64, u128, i128); /* trait implementations for floats */ From c1653840a62f6a03ec01db3763cbe3a30c0e2548 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 22:06:49 -0600 Subject: [PATCH 0040/1059] util: Add an alias for the eval subcommand --- library/compiler-builtins/crates/util/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/compiler-builtins/crates/util/src/main.rs b/library/compiler-builtins/crates/util/src/main.rs index 498162158426..70aa613f18d0 100644 --- a/library/compiler-builtins/crates/util/src/main.rs +++ b/library/compiler-builtins/crates/util/src/main.rs @@ -23,12 +23,14 @@ SUBCOMMAND: eval inputs... + x inputs... Evaulate the expression with a given basis. This can be useful for running routines with a debugger, or quickly checking input. Examples: * eval musl sinf 1.234 # print the results of musl sinf(1.234f32) * eval mpfr pow 1.234 2.432 # print the results of mpfr pow(1.234, 2.432) print inputs... + p inputs... For each input, print it in different formats with various floating point properties (normal, infinite, etc). "; @@ -38,7 +40,7 @@ fn main() { let str_args = args.iter().map(|s| s.as_str()).collect::>(); match &str_args.as_slice()[1..] { - ["eval", basis, op, inputs @ ..] => do_eval(basis, op, inputs), + ["eval" | "x", basis, op, inputs @ ..] => do_eval(basis, op, inputs), ["print" | "p", inputs @ ..] => do_classify(inputs), _ => { println!("{USAGE}\nunrecognized input `{str_args:?}`"); From e7e9546ef6c746055d7b62ccb4b667bab19e925f Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 00:41:46 -0600 Subject: [PATCH 0041/1059] libm: Print `Hexf` with `0x` and zero padding --- library/compiler-builtins/libm/src/math/support/hex_float.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/compiler-builtins/libm/src/math/support/hex_float.rs b/library/compiler-builtins/libm/src/math/support/hex_float.rs index 2f9369e50441..4495e3c18801 100644 --- a/library/compiler-builtins/libm/src/math/support/hex_float.rs +++ b/library/compiler-builtins/libm/src/math/support/hex_float.rs @@ -419,7 +419,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let _ = f; unimplemented!() } else { - fmt::LowerHex::fmt(&self.0, f) + write!(f, "{:#010x}", self.0) } } } From 4401daa4ff79b1665a95b5546b96356ed8be9a9f Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 00:41:46 -0600 Subject: [PATCH 0042/1059] libm: Switch to a non-recursive algorithm for subnormals This will support `frexpf16` and should allow `generic::frexp` to be inlined into the non-generic `frexp*` functions. Additionally remove an unneeded `as` cast. --- .../libm/src/math/generic/frexp.rs | 18 ++++++++++-------- .../libm/src/math/support/float_traits.rs | 6 +----- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/generic/frexp.rs b/library/compiler-builtins/libm/src/math/generic/frexp.rs index d5c2758a4b04..cbf5b100ba0f 100644 --- a/library/compiler-builtins/libm/src/math/generic/frexp.rs +++ b/library/compiler-builtins/libm/src/math/generic/frexp.rs @@ -1,19 +1,21 @@ -use super::super::{CastFrom, Float, MinInt}; +use super::super::{CastFrom, Float}; #[inline] pub fn frexp(x: F) -> (F, i32) { let mut ix = x.to_bits(); - let ee = x.ex() as i32; + let mut ee = x.ex() as i32; if ee == 0 { - if x != F::ZERO { - // normalize via multiplication; 1p64 for `f64` - let magic = F::from_parts(false, F::EXP_BIAS + F::BITS, F::Int::ZERO); - let (x, e) = frexp(x * magic); - return (x, e - F::BITS as i32); + if x == F::ZERO { + return (x, 0); } - return (x, 0); + + // Subnormals, needs to be normalized first + ix &= F::SIG_MASK; + (ee, ix) = F::normalize(ix); + ix |= x.to_bits() & F::SIGN_MASK; } else if ee == F::EXP_SAT as i32 { + // inf or NaN return (x, 0); } diff --git a/library/compiler-builtins/libm/src/math/support/float_traits.rs b/library/compiler-builtins/libm/src/math/support/float_traits.rs index 60c8bfca5165..9f2ce532ed17 100644 --- a/library/compiler-builtins/libm/src/math/support/float_traits.rs +++ b/library/compiler-builtins/libm/src/math/support/float_traits.rs @@ -176,7 +176,6 @@ fn from_parts(negative: bool, exponent: u32, significand: Self::Int) -> Self { fn fma(self, y: Self, z: Self) -> Self; /// Returns (normalized exponent, normalized significand) - #[allow(dead_code)] fn normalize(significand: Self::Int) -> (i32, Self::Int); /// Returns a number that represents the sign of self. @@ -295,10 +294,7 @@ fn fma(self, y: Self, z: Self) -> Self { } fn normalize(significand: Self::Int) -> (i32, Self::Int) { let shift = significand.leading_zeros().wrapping_sub(Self::EXP_BITS); - ( - 1i32.wrapping_sub(shift as i32), - significand << shift as Self::Int, - ) + (1i32.wrapping_sub(shift as i32), significand << shift) } } }; From 67ab9a5568209e381b166edd34fbff5c1a26f000 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 00:41:46 -0600 Subject: [PATCH 0043/1059] libm: Add `frexpf16` The new algorithm now correctly supports the type. --- .../crates/libm-macros/src/shared.rs | 14 ++++++ .../etc/function-definitions.json | 7 +++ .../compiler-builtins/etc/function-list.txt | 1 + .../libm-test/benches/icount.rs | 1 + .../libm-test/src/generate/case_list.rs | 5 +++ .../libm-test/src/mpfloat.rs | 44 +++++++------------ .../compiler-builtins/libm/src/math/frexp.rs | 5 +-- .../compiler-builtins/libm/src/math/mod.rs | 4 +- 8 files changed, 46 insertions(+), 35 deletions(-) diff --git a/library/compiler-builtins/crates/libm-macros/src/shared.rs b/library/compiler-builtins/crates/libm-macros/src/shared.rs index 5a5eca6f685d..ee1feed7c35e 100644 --- a/library/compiler-builtins/crates/libm-macros/src/shared.rs +++ b/library/compiler-builtins/crates/libm-macros/src/shared.rs @@ -417,6 +417,20 @@ struct NestedOp { fn_list: &["modf"], public: true, }, + NestedOp { + // `(f16, &mut c_int) -> f16` as `(f16) -> (f16, i32)` + float_ty: FloatTy::F16, + rust_sig: Signature { + args: &[Ty::F16], + returns: &[Ty::F16, Ty::I32], + }, + c_sig: Some(Signature { + args: &[Ty::F16, Ty::MutCInt], + returns: &[Ty::F16], + }), + fn_list: &["frexpf16"], + public: true, + }, NestedOp { // `(f32, &mut c_int) -> f32` as `(f32) -> (f32, i32)` float_ty: FloatTy::F32, diff --git a/library/compiler-builtins/etc/function-definitions.json b/library/compiler-builtins/etc/function-definitions.json index 06eb1ad5e2e7..6bd395a84b66 100644 --- a/library/compiler-builtins/etc/function-definitions.json +++ b/library/compiler-builtins/etc/function-definitions.json @@ -579,6 +579,13 @@ ], "type": "f128" }, + "frexpf16": { + "sources": [ + "libm/src/math/frexp.rs", + "libm/src/math/generic/frexp.rs" + ], + "type": "f16" + }, "hypot": { "sources": [ "libm/src/math/hypot.rs" diff --git a/library/compiler-builtins/etc/function-list.txt b/library/compiler-builtins/etc/function-list.txt index 45a39e3eee8d..f7a694d10f95 100644 --- a/library/compiler-builtins/etc/function-list.txt +++ b/library/compiler-builtins/etc/function-list.txt @@ -85,6 +85,7 @@ fmodf16 frexp frexpf frexpf128 +frexpf16 hypot hypotf ilogb diff --git a/library/compiler-builtins/libm-test/benches/icount.rs b/library/compiler-builtins/libm-test/benches/icount.rs index be5178e205a2..617e9fb7ad21 100644 --- a/library/compiler-builtins/libm-test/benches/icount.rs +++ b/library/compiler-builtins/libm-test/benches/icount.rs @@ -332,6 +332,7 @@ fn icount_bench_print_hf128(x: f128) -> String { icount_bench_fmodf_group, icount_bench_frexp_group, icount_bench_frexpf128_group, + icount_bench_frexpf16_group, icount_bench_frexpf_group, icount_bench_hypot_group, icount_bench_hypotf_group, diff --git a/library/compiler-builtins/libm-test/src/generate/case_list.rs b/library/compiler-builtins/libm-test/src/generate/case_list.rs index 958e03ab67f0..66d7f6a282f6 100644 --- a/library/compiler-builtins/libm-test/src/generate/case_list.rs +++ b/library/compiler-builtins/libm-test/src/generate/case_list.rs @@ -460,6 +460,11 @@ fn fmodf16_cases() -> Vec> { vec![] } +#[cfg(f16_enabled)] +fn frexpf16_cases() -> Vec> { + vec![] +} + fn frexpf_cases() -> Vec> { vec![] } diff --git a/library/compiler-builtins/libm-test/src/mpfloat.rs b/library/compiler-builtins/libm-test/src/mpfloat.rs index 3237a232f7dc..91130f892b8a 100644 --- a/library/compiler-builtins/libm-test/src/mpfloat.rs +++ b/library/compiler-builtins/libm-test/src/mpfloat.rs @@ -163,6 +163,7 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { frexp, frexpf, frexpf128, + frexpf16, ilogb, ilogbf, ilogbf128, @@ -327,20 +328,6 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { } } - impl MpOp for crate::op::[]::Routine { - type MpTy = MpFloat; - - fn new_mp() -> Self::MpTy { - new_mpfloat::() - } - - fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { - this.assign(input.0); - let exp = this.frexp_mut(); - (prep_retval::(this, Ordering::Equal), exp) - } - } - impl MpOp for crate::op::[]::Routine { type MpTy = MpFloat; @@ -485,6 +472,20 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { } } + impl MpOp for crate::op::[]::Routine { + type MpTy = MpFloat; + + fn new_mp() -> Self::MpTy { + new_mpfloat::() + } + + fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { + this.assign(input.0); + let exp = this.frexp_mut(); + (prep_retval::(this, Ordering::Equal), exp) + } + } + impl MpOp for crate::op::[]::Routine { type MpTy = MpFloat; @@ -549,21 +550,6 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { #[cfg(f128_enabled)] impl_op_for_ty_all!(f128, "f128"); -#[cfg(f128_enabled)] -impl MpOp for crate::op::frexpf128::Routine { - type MpTy = MpFloat; - - fn new_mp() -> Self::MpTy { - new_mpfloat::() - } - - fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { - this.assign(input.0); - let exp = this.frexp_mut(); - (prep_retval::(this, Ordering::Equal), exp) - } -} - // `lgamma_r` is not a simple suffix so we can't use the above macro. impl MpOp for crate::op::lgamma_r::Routine { type MpTy = MpFloat; diff --git a/library/compiler-builtins/libm/src/math/frexp.rs b/library/compiler-builtins/libm/src/math/frexp.rs index f2f545fb65e4..af38915a3ac6 100644 --- a/library/compiler-builtins/libm/src/math/frexp.rs +++ b/library/compiler-builtins/libm/src/math/frexp.rs @@ -2,11 +2,10 @@ /// /// That is, `x * 2^p` will represent the input value. // Placeholder so we can have `frexpf16` in the `Float` trait. -#[allow(unused)] #[cfg(f16_enabled)] #[cfg_attr(assert_no_panic, no_panic::no_panic)] -pub(crate) fn frexpf16(x: f16) -> (f16, i32) { - unimplemented!() +pub fn frexpf16(x: f16) -> (f16, i32) { + super::generic::frexp(x) } /// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2. diff --git a/library/compiler-builtins/libm/src/math/mod.rs b/library/compiler-builtins/libm/src/math/mod.rs index 0484fe6a3765..b34ab8465349 100644 --- a/library/compiler-builtins/libm/src/math/mod.rs +++ b/library/compiler-builtins/libm/src/math/mod.rs @@ -322,6 +322,7 @@ macro_rules! div { pub use self::fminimum_fmaximum::{fmaximumf16, fminimumf16}; pub use self::fminimum_fmaximum_num::{fmaximum_numf16, fminimum_numf16}; pub use self::fmod::fmodf16; + pub use self::frexp::frexpf16; pub use self::ilogb::ilogbf16; pub use self::ldexp::ldexpf16; pub use self::rint::rintf16; @@ -334,9 +335,6 @@ macro_rules! div { #[allow(unused_imports)] pub(crate) use self::fma::fmaf16; - - #[allow(unused_imports)] - pub(crate) use self::frexp::frexpf16; } } From ef8c95cde1a83a12b7e34a71138729e624da86e0 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 12 Feb 2026 17:35:57 +0300 Subject: [PATCH 0044/1059] privacy: Fix type privacy holes when it doesn't cause too much breakage --- compiler/rustc_privacy/src/lib.rs | 5 +- tests/ui/privacy/private-in-public-warn.rs | 12 +-- .../ui/privacy/private-in-public-warn.stderr | 78 +++++++------------ 3 files changed, 39 insertions(+), 56 deletions(-) diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index 9a952bb72195..a34fcc4f68c9 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -1590,13 +1590,14 @@ fn check_assoc_item( let mut check = self.check(item.def_id.expect_local(), vis, effective_vis); let is_assoc_ty = item.is_type(); - check.hard_error = is_assoc_ty && !item.is_impl_trait_in_trait(); + check.hard_error = is_assoc_ty; check.generics().predicates(); if assoc_has_type_of(self.tcx, item) { - check.hard_error = check.hard_error && item.defaultness(self.tcx).has_value(); check.ty(); } if is_assoc_ty && item.container == AssocContainer::Trait { + // FIXME: too much breakage from reporting hard errors here, better wait for a fix + // from proper associated type normalization. check.hard_error = false; check.bounds(); } diff --git a/tests/ui/privacy/private-in-public-warn.rs b/tests/ui/privacy/private-in-public-warn.rs index 6a0ac2b9ade7..299fe9a75d4c 100644 --- a/tests/ui/privacy/private-in-public-warn.rs +++ b/tests/ui/privacy/private-in-public-warn.rs @@ -49,10 +49,10 @@ pub trait Tr3 { fn f(arg: T) {} //~^ ERROR trait `traits::PrivTr` is more private than the item `traits::Tr3::f` fn g() -> impl PrivTr; - //~^ ERROR trait `traits::PrivTr` is more private than the item `traits::Tr3::g::{anon_assoc#0}` + //~^ ERROR private trait `traits::PrivTr` in public interface //~| ERROR trait `traits::PrivTr` is more private than the item `traits::Tr3::g::{anon_assoc#0}` fn h() -> impl PrivTr {} - //~^ ERROR trait `traits::PrivTr` is more private than the item `traits::Tr3::h::{anon_assoc#0}` + //~^ ERROR private trait `traits::PrivTr` in public interface //~| ERROR trait `traits::PrivTr` is more private than the item `traits::Tr3::h::{anon_assoc#0}` } impl Pub {} //~ ERROR trait `traits::PrivTr` is more private than the item `traits::Pub` @@ -93,13 +93,13 @@ pub trait Tr4: PubTr> {} //~ ERROR type `generics::Priv` is more priva pub trait Tr5 { fn required() -> impl PrivTr>; - //~^ ERROR trait `generics::PrivTr>` is more private than the item `Tr5::required::{anon_assoc#0}` - //~| ERROR type `generics::Priv<()>` is more private than the item `Tr5::required::{anon_assoc#0}` + //~^ ERROR private trait `generics::PrivTr>` in public interface + //~| ERROR private type `generics::Priv<()>` in public interface //~| ERROR trait `generics::PrivTr>` is more private than the item `Tr5::required::{anon_assoc#0}` //~| ERROR type `generics::Priv<()>` is more private than the item `Tr5::required::{anon_assoc#0}` fn provided() -> impl PrivTr> {} - //~^ ERROR trait `generics::PrivTr>` is more private than the item `Tr5::provided::{anon_assoc#0}` - //~| ERROR type `generics::Priv<()>` is more private than the item `Tr5::provided::{anon_assoc#0}` + //~^ ERROR private trait `generics::PrivTr>` in public interface + //~| ERROR private type `generics::Priv<()>` in public interface //~| ERROR trait `generics::PrivTr>` is more private than the item `Tr5::provided::{anon_assoc#0}` //~| ERROR type `generics::Priv<()>` is more private than the item `Tr5::provided::{anon_assoc#0}` } diff --git a/tests/ui/privacy/private-in-public-warn.stderr b/tests/ui/privacy/private-in-public-warn.stderr index 1fa415e27c16..64be22b4cdf9 100644 --- a/tests/ui/privacy/private-in-public-warn.stderr +++ b/tests/ui/privacy/private-in-public-warn.stderr @@ -194,17 +194,14 @@ note: but trait `traits::PrivTr` is only usable at visibility `pub(self)` LL | trait PrivTr {} | ^^^^^^^^^^^^ -error: trait `traits::PrivTr` is more private than the item `traits::Tr3::g::{anon_assoc#0}` +error[E0446]: private trait `traits::PrivTr` in public interface --> $DIR/private-in-public-warn.rs:51:19 | -LL | fn g() -> impl PrivTr; - | ^^^^^^^^^^^ opaque type `traits::Tr3::g::{anon_assoc#0}` is reachable at visibility `pub(crate)` - | -note: but trait `traits::PrivTr` is only usable at visibility `pub(self)` - --> $DIR/private-in-public-warn.rs:37:5 - | LL | trait PrivTr {} - | ^^^^^^^^^^^^ + | ------------ `traits::PrivTr` declared as private +... +LL | fn g() -> impl PrivTr; + | ^^^^^^^^^^^ can't leak private trait error: trait `traits::PrivTr` is more private than the item `traits::Tr3::g::{anon_assoc#0}` --> $DIR/private-in-public-warn.rs:51:19 @@ -218,17 +215,14 @@ note: but trait `traits::PrivTr` is only usable at visibility `pub(self)` LL | trait PrivTr {} | ^^^^^^^^^^^^ -error: trait `traits::PrivTr` is more private than the item `traits::Tr3::h::{anon_assoc#0}` +error[E0446]: private trait `traits::PrivTr` in public interface --> $DIR/private-in-public-warn.rs:54:19 | -LL | fn h() -> impl PrivTr {} - | ^^^^^^^^^^^ opaque type `traits::Tr3::h::{anon_assoc#0}` is reachable at visibility `pub(crate)` - | -note: but trait `traits::PrivTr` is only usable at visibility `pub(self)` - --> $DIR/private-in-public-warn.rs:37:5 - | LL | trait PrivTr {} - | ^^^^^^^^^^^^ + | ------------ `traits::PrivTr` declared as private +... +LL | fn h() -> impl PrivTr {} + | ^^^^^^^^^^^ can't leak private trait error: trait `traits::PrivTr` is more private than the item `traits::Tr3::h::{anon_assoc#0}` --> $DIR/private-in-public-warn.rs:54:19 @@ -350,29 +344,23 @@ note: but type `generics::Priv` is only usable at visibility `pub(self)` LL | struct Priv(T); | ^^^^^^^^^^^^^^^^^^^ -error: trait `generics::PrivTr>` is more private than the item `Tr5::required::{anon_assoc#0}` +error[E0446]: private trait `generics::PrivTr>` in public interface --> $DIR/private-in-public-warn.rs:95:26 | -LL | fn required() -> impl PrivTr>; - | ^^^^^^^^^^^^^^^^^^^^^ opaque type `Tr5::required::{anon_assoc#0}` is reachable at visibility `pub(crate)` - | -note: but trait `generics::PrivTr>` is only usable at visibility `pub(self)` - --> $DIR/private-in-public-warn.rs:84:5 - | LL | trait PrivTr {} - | ^^^^^^^^^^^^^^^ + | --------------- `generics::PrivTr>` declared as private +... +LL | fn required() -> impl PrivTr>; + | ^^^^^^^^^^^^^^^^^^^^^ can't leak private trait -error: type `generics::Priv<()>` is more private than the item `Tr5::required::{anon_assoc#0}` +error[E0446]: private type `generics::Priv<()>` in public interface --> $DIR/private-in-public-warn.rs:95:26 | -LL | fn required() -> impl PrivTr>; - | ^^^^^^^^^^^^^^^^^^^^^ opaque type `Tr5::required::{anon_assoc#0}` is reachable at visibility `pub(crate)` - | -note: but type `generics::Priv<()>` is only usable at visibility `pub(self)` - --> $DIR/private-in-public-warn.rs:82:5 - | LL | struct Priv(T); - | ^^^^^^^^^^^^^^^^^^^ + | ------------------- `generics::Priv<()>` declared as private +... +LL | fn required() -> impl PrivTr>; + | ^^^^^^^^^^^^^^^^^^^^^ can't leak private type error: trait `generics::PrivTr>` is more private than the item `Tr5::required::{anon_assoc#0}` --> $DIR/private-in-public-warn.rs:95:26 @@ -398,29 +386,23 @@ note: but type `generics::Priv<()>` is only usable at visibility `pub(self)` LL | struct Priv(T); | ^^^^^^^^^^^^^^^^^^^ -error: trait `generics::PrivTr>` is more private than the item `Tr5::provided::{anon_assoc#0}` +error[E0446]: private trait `generics::PrivTr>` in public interface --> $DIR/private-in-public-warn.rs:100:26 | -LL | fn provided() -> impl PrivTr> {} - | ^^^^^^^^^^^^^^^^^^^^^ opaque type `Tr5::provided::{anon_assoc#0}` is reachable at visibility `pub(crate)` - | -note: but trait `generics::PrivTr>` is only usable at visibility `pub(self)` - --> $DIR/private-in-public-warn.rs:84:5 - | LL | trait PrivTr {} - | ^^^^^^^^^^^^^^^ + | --------------- `generics::PrivTr>` declared as private +... +LL | fn provided() -> impl PrivTr> {} + | ^^^^^^^^^^^^^^^^^^^^^ can't leak private trait -error: type `generics::Priv<()>` is more private than the item `Tr5::provided::{anon_assoc#0}` +error[E0446]: private type `generics::Priv<()>` in public interface --> $DIR/private-in-public-warn.rs:100:26 | -LL | fn provided() -> impl PrivTr> {} - | ^^^^^^^^^^^^^^^^^^^^^ opaque type `Tr5::provided::{anon_assoc#0}` is reachable at visibility `pub(crate)` - | -note: but type `generics::Priv<()>` is only usable at visibility `pub(self)` - --> $DIR/private-in-public-warn.rs:82:5 - | LL | struct Priv(T); - | ^^^^^^^^^^^^^^^^^^^ + | ------------------- `generics::Priv<()>` declared as private +... +LL | fn provided() -> impl PrivTr> {} + | ^^^^^^^^^^^^^^^^^^^^^ can't leak private type error: trait `generics::PrivTr>` is more private than the item `Tr5::provided::{anon_assoc#0}` --> $DIR/private-in-public-warn.rs:100:26 From e68fc280c21da48381fa99f1dc0962625841a60c Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 06:17:04 -0600 Subject: [PATCH 0045/1059] ci: Increase the benchmark rustc version to 2026-02-10 This includes the most recent LLVM bump. --- library/compiler-builtins/.github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/compiler-builtins/.github/workflows/main.yaml b/library/compiler-builtins/.github/workflows/main.yaml index 0375d1eb29de..1a69f7484d88 100644 --- a/library/compiler-builtins/.github/workflows/main.yaml +++ b/library/compiler-builtins/.github/workflows/main.yaml @@ -13,7 +13,7 @@ env: RUSTDOCFLAGS: -Dwarnings RUSTFLAGS: -Dwarnings RUST_BACKTRACE: full - BENCHMARK_RUSTC: nightly-2025-12-01 # Pin the toolchain for reproducable results + BENCHMARK_RUSTC: nightly-2026-02-10 # Pin the toolchain for reproducable results jobs: # Determine which tests should be run based on changed files. From 0b0d40d5f3ab401604d138fdf2cc4785a3ecc6bc Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 7 Feb 2026 07:06:05 -0600 Subject: [PATCH 0046/1059] libm: Improve debug output for `Status` --- .../libm/src/math/support/env.rs | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/support/env.rs b/library/compiler-builtins/libm/src/math/support/env.rs index 53ae32f658db..0f89799ed918 100644 --- a/library/compiler-builtins/libm/src/math/support/env.rs +++ b/library/compiler-builtins/libm/src/math/support/env.rs @@ -49,10 +49,14 @@ pub enum Round { } /// IEEE 754 exception status flags. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct Status(u8); impl Status { + /* Note that if we ever store/load this to/from floating point control status registers, it + * may be worth making these values platform-dependent to line up with register layout + * to avoid bit swapping. For the time being, this isn't a concern. */ + /// Default status indicating no errors. pub const OK: Self = Self(0); @@ -74,7 +78,7 @@ impl Status { /// result is -inf. /// `x / y` when `x != 0.0` and `y == 0.0`, #[cfg_attr(not(feature = "unstable-public-internals"), allow(dead_code))] - pub const DIVIDE_BY_ZERO: Self = Self(1 << 2); + pub const DIVIDE_BY_ZERO: Self = Self(1 << 1); /// The result exceeds the maximum finite value. /// @@ -82,14 +86,14 @@ impl Status { /// on the intermediate result. `Zero` rounds to the signed maximum finite. `Positive` and /// `Negative` round to signed maximum finite in one direction, signed infinity in the other. #[cfg_attr(not(feature = "unstable-public-internals"), allow(dead_code))] - pub const OVERFLOW: Self = Self(1 << 3); + pub const OVERFLOW: Self = Self(1 << 2); /// The result is subnormal and lost precision. - pub const UNDERFLOW: Self = Self(1 << 4); + pub const UNDERFLOW: Self = Self(1 << 3); /// The finite-precision result does not match that of infinite precision, and the reason /// is not represented by one of the other flags. - pub const INEXACT: Self = Self(1 << 5); + pub const INEXACT: Self = Self(1 << 4); /// True if `UNDERFLOW` is set. #[cfg_attr(not(feature = "unstable-public-internals"), allow(dead_code))] @@ -128,3 +132,38 @@ pub(crate) const fn with(self, rhs: Self) -> Self { Self(self.0 | rhs.0) } } + +#[cfg(any(test, feature = "unstable-public-internals"))] +impl core::fmt::Debug for Status { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // shift -> flag map + let names = &[ + "INVALID", + "DIVIDE_BY_ZERO", + "OVERFLOW", + "UNDERFLOW", + "INEXACT", + ]; + + write!(f, "Status(")?; + let mut any = false; + for shift in 0..u8::BITS { + if self.0 & (1 << shift) != 0 { + if any { + write!(f, " | ")?; + } + match names.get(shift as usize) { + Some(name) => write!(f, "{name}")?, + None => write!(f, "UNKNOWN(1 << {shift})")?, + } + any = true; + } + } + + if !any { + write!(f, "OK")?; + } + write!(f, ")")?; + Ok(()) + } +} From 70c6d5a4476db95a94e417694f3535388ef613ed Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 7 Feb 2026 07:06:05 -0600 Subject: [PATCH 0047/1059] libm: Fix `_status` status outputs on null-mantissa inputs Values like 0.5 that have an exponent but not a mantissa are currently being reported as OK rather than INEXACT for ceil, floor, and trunc. Fix this and clean up the test cases. This tries to keep the algorithm diff as simple as possible, which introduces some small performance regressions. This is improved in a future commit. --- .../libm/src/math/generic/ceil.rs | 146 +++++++--------- .../libm/src/math/generic/floor.rs | 129 +++++++------- .../libm/src/math/generic/trunc.rs | 159 ++++++++---------- 3 files changed, 194 insertions(+), 240 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/generic/ceil.rs b/library/compiler-builtins/libm/src/math/generic/ceil.rs index 1072ba7c29b6..5584f6503ef5 100644 --- a/library/compiler-builtins/libm/src/math/generic/ceil.rs +++ b/library/compiler-builtins/libm/src/math/generic/ceil.rs @@ -46,7 +46,7 @@ pub fn ceil_status(x: F) -> FpResult { F::from_bits(ix) } else { // |x| < 1.0, raise an inexact exception since truncation will happen (unless x == 0). - if ix & F::SIG_MASK == F::Int::ZERO { + if ix & !F::SIGN_MASK == F::Int::ZERO { status = Status::OK; } else { status = Status::INEXACT; @@ -72,103 +72,83 @@ mod tests { use super::*; use crate::support::Hexf; - /// Test against https://en.cppreference.com/w/cpp/numeric/math/ceil - fn spec_test(cases: &[(F, F, Status)]) { - let roundtrip = [ - F::ZERO, - F::ONE, - F::NEG_ONE, - F::NEG_ZERO, - F::INFINITY, - F::NEG_INFINITY, - ]; - - for x in roundtrip { - let FpResult { val, status } = ceil_status(x); - assert_biteq!(val, x, "{}", Hexf(x)); - assert_eq!(status, Status::OK, "{}", Hexf(x)); - } - - for &(x, res, res_stat) in cases { - let FpResult { val, status } = ceil_status(x); - assert_biteq!(val, res, "{}", Hexf(x)); - assert_eq!(status, res_stat, "{}", Hexf(x)); - } + macro_rules! cases { + ($f:ty) => { + [ + // roundtrip + (0.0, 0.0, Status::OK), + (-0.0, -0.0, Status::OK), + (1.0, 1.0, Status::OK), + (-1.0, -1.0, Status::OK), + (<$f>::INFINITY, <$f>::INFINITY, Status::OK), + (<$f>::NEG_INFINITY, <$f>::NEG_INFINITY, Status::OK), + // with rounding + (0.1, 1.0, Status::INEXACT), + (-0.1, -0.0, Status::INEXACT), + (0.5, 1.0, Status::INEXACT), + (-0.5, -0.0, Status::INEXACT), + (0.9, 1.0, Status::INEXACT), + (-0.9, -0.0, Status::INEXACT), + (1.1, 2.0, Status::INEXACT), + (-1.1, -1.0, Status::INEXACT), + (1.5, 2.0, Status::INEXACT), + (-1.5, -1.0, Status::INEXACT), + (1.9, 2.0, Status::INEXACT), + (-1.9, -1.0, Status::INEXACT), + ] + }; } - /* Skipping f16 / f128 "sanity_check"s due to rejected literal lexing at MSRV */ + #[track_caller] + fn check(cases: &[(F, F, Status)]) { + for &(x, exp_res, exp_stat) in cases { + let FpResult { val, status } = ceil_status(x); + assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x)); + assert_eq!( + status, + exp_stat, + "{x:?} {} -> {exp_res:?} {}", + Hexf(x), + Hexf(exp_res) + ); + } + } #[test] #[cfg(f16_enabled)] - fn spec_tests_f16() { - let cases = [ - (0.1, 1.0, Status::INEXACT), - (-0.1, -0.0, Status::INEXACT), - (0.9, 1.0, Status::INEXACT), - (-0.9, -0.0, Status::INEXACT), - (1.1, 2.0, Status::INEXACT), - (-1.1, -1.0, Status::INEXACT), - (1.9, 2.0, Status::INEXACT), - (-1.9, -1.0, Status::INEXACT), - ]; - spec_test::(&cases); + fn check_f16() { + check::(&cases!(f16)); + check::(&[ + (hf16!("0x1p10"), hf16!("0x1p10"), Status::OK), + (hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK), + ]); } #[test] - fn sanity_check_f32() { - assert_eq!(ceil(1.1f32), 2.0); - assert_eq!(ceil(2.9f32), 3.0); + fn check_f32() { + check::(&cases!(f32)); + check::(&[ + (hf32!("0x1p23"), hf32!("0x1p23"), Status::OK), + (hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK), + ]); } #[test] - fn spec_tests_f32() { - let cases = [ - (0.1, 1.0, Status::INEXACT), - (-0.1, -0.0, Status::INEXACT), - (0.9, 1.0, Status::INEXACT), - (-0.9, -0.0, Status::INEXACT), - (1.1, 2.0, Status::INEXACT), - (-1.1, -1.0, Status::INEXACT), - (1.9, 2.0, Status::INEXACT), - (-1.9, -1.0, Status::INEXACT), - ]; - spec_test::(&cases); - } - - #[test] - fn sanity_check_f64() { - assert_eq!(ceil(1.1f64), 2.0); - assert_eq!(ceil(2.9f64), 3.0); - } - - #[test] - fn spec_tests_f64() { - let cases = [ - (0.1, 1.0, Status::INEXACT), - (-0.1, -0.0, Status::INEXACT), - (0.9, 1.0, Status::INEXACT), - (-0.9, -0.0, Status::INEXACT), - (1.1, 2.0, Status::INEXACT), - (-1.1, -1.0, Status::INEXACT), - (1.9, 2.0, Status::INEXACT), - (-1.9, -1.0, Status::INEXACT), - ]; - spec_test::(&cases); + fn check_f64() { + check::(&cases!(f64)); + check::(&[ + (hf64!("0x1p52"), hf64!("0x1p52"), Status::OK), + (hf64!("-0x1p52"), hf64!("-0x1p52"), Status::OK), + ]); } #[test] #[cfg(f128_enabled)] fn spec_tests_f128() { - let cases = [ - (0.1, 1.0, Status::INEXACT), - (-0.1, -0.0, Status::INEXACT), - (0.9, 1.0, Status::INEXACT), - (-0.9, -0.0, Status::INEXACT), - (1.1, 2.0, Status::INEXACT), - (-1.1, -1.0, Status::INEXACT), - (1.9, 2.0, Status::INEXACT), - (-1.9, -1.0, Status::INEXACT), - ]; - spec_test::(&cases); + check::(&cases!(f128)); + check::(&[ + (hf128!("0x1p112"), hf128!("0x1p112"), Status::OK), + (hf128!("-0x1p112"), hf128!("-0x1p112"), Status::OK), + ]); } } diff --git a/library/compiler-builtins/libm/src/math/generic/floor.rs b/library/compiler-builtins/libm/src/math/generic/floor.rs index e6dfd8866a42..c763a3cc265e 100644 --- a/library/compiler-builtins/libm/src/math/generic/floor.rs +++ b/library/compiler-builtins/libm/src/math/generic/floor.rs @@ -46,7 +46,7 @@ pub fn floor_status(x: F) -> FpResult { F::from_bits(ix) } else { // |x| < 1.0, raise an inexact exception since truncation will happen. - if ix & F::SIG_MASK == F::Int::ZERO { + if ix & !F::SIGN_MASK == F::Int::ZERO { status = Status::OK; } else { status = Status::INEXACT; @@ -72,86 +72,83 @@ mod tests { use super::*; use crate::support::Hexf; - /// Test against https://en.cppreference.com/w/cpp/numeric/math/floor - fn spec_test(cases: &[(F, F, Status)]) { - let roundtrip = [ - F::ZERO, - F::ONE, - F::NEG_ONE, - F::NEG_ZERO, - F::INFINITY, - F::NEG_INFINITY, - ]; - - for x in roundtrip { - let FpResult { val, status } = floor_status(x); - assert_biteq!(val, x, "{}", Hexf(x)); - assert_eq!(status, Status::OK, "{}", Hexf(x)); - } - - for &(x, res, res_stat) in cases { - let FpResult { val, status } = floor_status(x); - assert_biteq!(val, res, "{}", Hexf(x)); - assert_eq!(status, res_stat, "{}", Hexf(x)); - } + macro_rules! cases { + ($f:ty) => { + [ + // roundtrip + (0.0, 0.0, Status::OK), + (-0.0, -0.0, Status::OK), + (1.0, 1.0, Status::OK), + (-1.0, -1.0, Status::OK), + (<$f>::INFINITY, <$f>::INFINITY, Status::OK), + (<$f>::NEG_INFINITY, <$f>::NEG_INFINITY, Status::OK), + // with rounding + (0.1, 0.0, Status::INEXACT), + (-0.1, -1.0, Status::INEXACT), + (0.5, 0.0, Status::INEXACT), + (-0.5, -1.0, Status::INEXACT), + (0.9, 0.0, Status::INEXACT), + (-0.9, -1.0, Status::INEXACT), + (1.1, 1.0, Status::INEXACT), + (-1.1, -2.0, Status::INEXACT), + (1.5, 1.0, Status::INEXACT), + (-1.5, -2.0, Status::INEXACT), + (1.9, 1.0, Status::INEXACT), + (-1.9, -2.0, Status::INEXACT), + ] + }; } - /* Skipping f16 / f128 "sanity_check"s and spec cases due to rejected literal lexing at MSRV */ + #[track_caller] + fn check(cases: &[(F, F, Status)]) { + for &(x, exp_res, exp_stat) in cases { + let FpResult { val, status } = floor_status(x); + assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x)); + assert_eq!( + status, + exp_stat, + "{x:?} {} -> {exp_res:?} {}", + Hexf(x), + Hexf(exp_res) + ); + } + } #[test] #[cfg(f16_enabled)] - fn spec_tests_f16() { - let cases = []; - spec_test::(&cases); + fn check_f16() { + check::(&cases!(f16)); + check::(&[ + (hf16!("0x1p10"), hf16!("0x1p10"), Status::OK), + (hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK), + ]); } #[test] - fn sanity_check_f32() { - assert_eq!(floor(0.5f32), 0.0); - assert_eq!(floor(1.1f32), 1.0); - assert_eq!(floor(2.9f32), 2.0); + fn check_f32() { + check::(&cases!(f32)); + check::(&[ + (hf32!("0x1p23"), hf32!("0x1p23"), Status::OK), + (hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK), + ]); } #[test] - fn spec_tests_f32() { - let cases = [ - (0.1, 0.0, Status::INEXACT), - (-0.1, -1.0, Status::INEXACT), - (0.9, 0.0, Status::INEXACT), - (-0.9, -1.0, Status::INEXACT), - (1.1, 1.0, Status::INEXACT), - (-1.1, -2.0, Status::INEXACT), - (1.9, 1.0, Status::INEXACT), - (-1.9, -2.0, Status::INEXACT), - ]; - spec_test::(&cases); - } - - #[test] - fn sanity_check_f64() { - assert_eq!(floor(1.1f64), 1.0); - assert_eq!(floor(2.9f64), 2.0); - } - - #[test] - fn spec_tests_f64() { - let cases = [ - (0.1, 0.0, Status::INEXACT), - (-0.1, -1.0, Status::INEXACT), - (0.9, 0.0, Status::INEXACT), - (-0.9, -1.0, Status::INEXACT), - (1.1, 1.0, Status::INEXACT), - (-1.1, -2.0, Status::INEXACT), - (1.9, 1.0, Status::INEXACT), - (-1.9, -2.0, Status::INEXACT), - ]; - spec_test::(&cases); + fn check_f64() { + check::(&cases!(f64)); + check::(&[ + (hf64!("0x1p52"), hf64!("0x1p52"), Status::OK), + (hf64!("-0x1p52"), hf64!("-0x1p52"), Status::OK), + ]); } #[test] #[cfg(f128_enabled)] fn spec_tests_f128() { - let cases = []; - spec_test::(&cases); + check::(&cases!(f128)); + check::(&[ + (hf128!("0x1p112"), hf128!("0x1p112"), Status::OK), + (hf128!("-0x1p112"), hf128!("-0x1p112"), Status::OK), + ]); } } diff --git a/library/compiler-builtins/libm/src/math/generic/trunc.rs b/library/compiler-builtins/libm/src/math/generic/trunc.rs index d5b444d15dfc..686436c43b95 100644 --- a/library/compiler-builtins/libm/src/math/generic/trunc.rs +++ b/library/compiler-builtins/libm/src/math/generic/trunc.rs @@ -13,35 +13,29 @@ pub fn trunc_status(x: F) -> FpResult { let mut xi: F::Int = x.to_bits(); let e: i32 = x.exp_unbiased(); - // C1: The represented value has no fractional part, so no truncation is needed + // The represented value has no fractional part, so no truncation is needed if e >= F::SIG_BITS as i32 { return FpResult::ok(x); } let mask = if e < 0 { - // C2: If the exponent is negative, the result will be zero so we mask out everything + // If the exponent is negative, the result will be zero so we mask out everything // except the sign. F::SIGN_MASK } else { - // C3: Otherwise, we mask out the last `e` bits of the significand. + // Otherwise, we mask out the last `e` bits of the significand. !(F::SIG_MASK >> e.unsigned()) }; - // C4: If the to-be-masked-out portion is already zero, we have an exact result + // If the to-be-masked-out portion is already zero, we have an exact result if (xi & !mask) == IntTy::::ZERO { return FpResult::ok(x); } - // C5: Otherwise the result is inexact and we will truncate. Raise `FE_INEXACT`, mask the + // Otherwise the result is inexact and we will truncate. Raise `FE_INEXACT`, mask the // result, and return. - - let status = if xi & F::SIG_MASK == F::Int::ZERO { - Status::OK - } else { - Status::INEXACT - }; xi &= mask; - FpResult::new(F::from_bits(xi), status) + FpResult::new(F::from_bits(xi), Status::INEXACT) } #[cfg(test)] @@ -49,100 +43,83 @@ mod tests { use super::*; use crate::support::Hexf; - fn spec_test(cases: &[(F, F, Status)]) { - let roundtrip = [ - F::ZERO, - F::ONE, - F::NEG_ONE, - F::NEG_ZERO, - F::INFINITY, - F::NEG_INFINITY, - ]; - - for x in roundtrip { - let FpResult { val, status } = trunc_status(x); - assert_biteq!(val, x, "{}", Hexf(x)); - assert_eq!(status, Status::OK, "{}", Hexf(x)); - } - - for &(x, res, res_stat) in cases { - let FpResult { val, status } = trunc_status(x); - assert_biteq!(val, res, "{}", Hexf(x)); - assert_eq!(status, res_stat, "{}", Hexf(x)); - } + macro_rules! cases { + ($f:ty) => { + [ + // roundtrip + (0.0, 0.0, Status::OK), + (-0.0, -0.0, Status::OK), + (1.0, 1.0, Status::OK), + (-1.0, -1.0, Status::OK), + (<$f>::INFINITY, <$f>::INFINITY, Status::OK), + (<$f>::NEG_INFINITY, <$f>::NEG_INFINITY, Status::OK), + // with rounding + (0.1, 0.0, Status::INEXACT), + (-0.1, -0.0, Status::INEXACT), + (0.5, 0.0, Status::INEXACT), + (-0.5, -0.0, Status::INEXACT), + (0.9, 0.0, Status::INEXACT), + (-0.9, -0.0, Status::INEXACT), + (1.1, 1.0, Status::INEXACT), + (-1.1, -1.0, Status::INEXACT), + (1.5, 1.0, Status::INEXACT), + (-1.5, -1.0, Status::INEXACT), + (1.9, 1.0, Status::INEXACT), + (-1.9, -1.0, Status::INEXACT), + ] + }; } - /* Skipping f16 / f128 "sanity_check"s and spec cases due to rejected literal lexing at MSRV */ + #[track_caller] + fn check(cases: &[(F, F, Status)]) { + for &(x, exp_res, exp_stat) in cases { + let FpResult { val, status } = trunc_status(x); + assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x)); + assert_eq!( + status, + exp_stat, + "{x:?} {} -> {exp_res:?} {}", + Hexf(x), + Hexf(exp_res) + ); + } + } #[test] #[cfg(f16_enabled)] - fn spec_tests_f16() { - let cases = []; - spec_test::(&cases); + fn check_f16() { + check::(&cases!(f16)); + check::(&[ + (hf16!("0x1p10"), hf16!("0x1p10"), Status::OK), + (hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK), + ]); } #[test] - fn sanity_check_f32() { - assert_eq!(trunc(0.5f32), 0.0); - assert_eq!(trunc(1.1f32), 1.0); - assert_eq!(trunc(2.9f32), 2.0); + fn check_f32() { + check::(&cases!(f32)); + check::(&[ + (hf32!("0x1p23"), hf32!("0x1p23"), Status::OK), + (hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK), + ]); } #[test] - fn spec_tests_f32() { - let cases = [ - (0.1, 0.0, Status::INEXACT), - (-0.1, -0.0, Status::INEXACT), - (0.9, 0.0, Status::INEXACT), - (-0.9, -0.0, Status::INEXACT), - (1.1, 1.0, Status::INEXACT), - (-1.1, -1.0, Status::INEXACT), - (1.9, 1.0, Status::INEXACT), - (-1.9, -1.0, Status::INEXACT), - ]; - spec_test::(&cases); - - assert_biteq!(trunc(1.1f32), 1.0); - assert_biteq!(trunc(1.1f64), 1.0); - - // C1 - assert_biteq!(trunc(hf32!("0x1p23")), hf32!("0x1p23")); - assert_biteq!(trunc(hf64!("0x1p52")), hf64!("0x1p52")); - assert_biteq!(trunc(hf32!("-0x1p23")), hf32!("-0x1p23")); - assert_biteq!(trunc(hf64!("-0x1p52")), hf64!("-0x1p52")); - - // C2 - assert_biteq!(trunc(hf32!("0x1p-1")), 0.0); - assert_biteq!(trunc(hf64!("0x1p-1")), 0.0); - assert_biteq!(trunc(hf32!("-0x1p-1")), -0.0); - assert_biteq!(trunc(hf64!("-0x1p-1")), -0.0); - } - - #[test] - fn sanity_check_f64() { - assert_eq!(trunc(1.1f64), 1.0); - assert_eq!(trunc(2.9f64), 2.0); - } - - #[test] - fn spec_tests_f64() { - let cases = [ - (0.1, 0.0, Status::INEXACT), - (-0.1, -0.0, Status::INEXACT), - (0.9, 0.0, Status::INEXACT), - (-0.9, -0.0, Status::INEXACT), - (1.1, 1.0, Status::INEXACT), - (-1.1, -1.0, Status::INEXACT), - (1.9, 1.0, Status::INEXACT), - (-1.9, -1.0, Status::INEXACT), - ]; - spec_test::(&cases); + fn check_f64() { + check::(&cases!(f64)); + check::(&[ + (hf64!("0x1p52"), hf64!("0x1p52"), Status::OK), + (hf64!("-0x1p52"), hf64!("-0x1p52"), Status::OK), + ]); } #[test] #[cfg(f128_enabled)] fn spec_tests_f128() { - let cases = []; - spec_test::(&cases); + check::(&cases!(f128)); + check::(&[ + (hf128!("0x1p112"), hf128!("0x1p112"), Status::OK), + (hf128!("-0x1p112"), hf128!("-0x1p112"), Status::OK), + ]); } } From 8c26adcab0d57791c2af752fd1c0973860bc38f5 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 7 Feb 2026 09:26:48 -0600 Subject: [PATCH 0048/1059] libm: Add an optimization for `floor` ceil seems to optimize better, but this gets closer to the pre-fix codegen. --- .../libm/src/math/generic/floor.rs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/generic/floor.rs b/library/compiler-builtins/libm/src/math/generic/floor.rs index c763a3cc265e..7045229c0c75 100644 --- a/library/compiler-builtins/libm/src/math/generic/floor.rs +++ b/library/compiler-builtins/libm/src/math/generic/floor.rs @@ -26,7 +26,6 @@ pub fn floor_status(x: F) -> FpResult { return FpResult::ok(x); } - let status; let res = if e >= 0 { // |x| >= 1.0 let m = F::SIG_MASK >> e.unsigned(); @@ -35,9 +34,6 @@ pub fn floor_status(x: F) -> FpResult { return FpResult::ok(x); } - // Otherwise, raise an inexact exception. - status = Status::INEXACT; - if x.is_sign_negative() { ix += m; } @@ -45,26 +41,22 @@ pub fn floor_status(x: F) -> FpResult { ix &= !m; F::from_bits(ix) } else { - // |x| < 1.0, raise an inexact exception since truncation will happen. - if ix & !F::SIGN_MASK == F::Int::ZERO { - status = Status::OK; - } else { - status = Status::INEXACT; + // |x| < 1.0, zero or inexact with truncation + + if (ix & !F::SIGN_MASK) == F::Int::ZERO { + return FpResult::ok(x); } if x.is_sign_positive() { // 0.0 <= x < 1.0; rounding down goes toward +0.0. F::ZERO - } else if ix << 1 != zero { + } else { // -1.0 < x < 0.0; rounding down goes toward -1.0. F::NEG_ONE - } else { - // -0.0 remains unchanged - x } }; - FpResult::new(res, status) + FpResult::new(res, Status::INEXACT) } #[cfg(test)] From dffcc200c07f756612b2ab20ac0acf96f7ca3d19 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 02:23:56 -0600 Subject: [PATCH 0049/1059] libm: Add an optimization for `trunc` Suggested-by: Juho Kahala <57393910+quaternic@users.noreply.github.com> --- .../libm/src/math/generic/trunc.rs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/generic/trunc.rs b/library/compiler-builtins/libm/src/math/generic/trunc.rs index 686436c43b95..7f18eb42e884 100644 --- a/library/compiler-builtins/libm/src/math/generic/trunc.rs +++ b/library/compiler-builtins/libm/src/math/generic/trunc.rs @@ -10,7 +10,7 @@ pub fn trunc(x: F) -> F { #[inline] pub fn trunc_status(x: F) -> FpResult { - let mut xi: F::Int = x.to_bits(); + let xi: F::Int = x.to_bits(); let e: i32 = x.exp_unbiased(); // The represented value has no fractional part, so no truncation is needed @@ -18,24 +18,26 @@ pub fn trunc_status(x: F) -> FpResult { return FpResult::ok(x); } - let mask = if e < 0 { - // If the exponent is negative, the result will be zero so we mask out everything + let clear_mask = if e < 0 { + // If the exponent is negative, the result will be zero so we clear everything // except the sign. - F::SIGN_MASK + !F::SIGN_MASK } else { - // Otherwise, we mask out the last `e` bits of the significand. - !(F::SIG_MASK >> e.unsigned()) + // Otherwise, we keep `e` fractional bits and clear the rest. + F::SIG_MASK >> e.unsigned() }; - // If the to-be-masked-out portion is already zero, we have an exact result - if (xi & !mask) == IntTy::::ZERO { - return FpResult::ok(x); - } + let cleared = xi & clear_mask; + let status = if cleared == IntTy::::ZERO { + // If the to-be-zeroed portion is already zero, we have an exact result. + Status::OK + } else { + // Otherwise the result is inexact and we will truncate, so indicate `FE_INEXACT`. + Status::INEXACT + }; - // Otherwise the result is inexact and we will truncate. Raise `FE_INEXACT`, mask the - // result, and return. - xi &= mask; - FpResult::new(F::from_bits(xi), Status::INEXACT) + // Now zero the bits we need to truncate and return. + FpResult::new(F::from_bits(xi ^ cleared), status) } #[cfg(test)] From 8fd2474e2deb9ec1e44c7f8166c20cb3f129e1f5 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 7 Feb 2026 04:11:18 -0600 Subject: [PATCH 0050/1059] meta: Upgrade all dependencies to the latest incompatible versions Most of the semver-breaking changes here relate to the 0.10 release of `rand`. Changelogs: * https://github.com/criterion-rs/criterion.rs/releases/tag/criterion-v0.8.0 * https://github.com/gimli-rs/object/blob/master/CHANGELOG.md#0380 * https://github.com/rust-random/rand/blob/master/CHANGELOG.md#0100---2026-02-08 --- library/compiler-builtins/Cargo.toml | 22 +++++++++---------- .../builtins-test/src/lib.rs | 2 +- .../libm-test/src/generate/random.rs | 2 +- .../compiler-builtins/libm-test/tests/u256.rs | 2 +- library/compiler-builtins/libm/Cargo.toml | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/library/compiler-builtins/Cargo.toml b/library/compiler-builtins/Cargo.toml index 785df6a20015..26a056f43496 100644 --- a/library/compiler-builtins/Cargo.toml +++ b/library/compiler-builtins/Cargo.toml @@ -34,14 +34,14 @@ exclude = [ [workspace.dependencies] anyhow = "1.0.101" assert_cmd = "2.1.2" -cc = "1.2.55" +cc = "1.2.56" cfg-if = "1.0.4" compiler_builtins = { path = "builtins-shim", default-features = false } -criterion = { version = "0.6.0", default-features = false, features = ["cargo_bench_support"] } +criterion = { version = "0.8.2", default-features = false, features = ["cargo_bench_support"] } getopts = "0.2.24" -getrandom = "0.3.4" +getrandom = "0.4.1" gmp-mpfr-sys = { version = "1.6.8", default-features = false } -gungraun = "0.17.0" +gungraun = "0.17.2" heck = "0.5.0" indicatif = { version = "0.18.3", default-features = false } libm = { path = "libm", default-features = false } @@ -49,22 +49,22 @@ libm-macros = { path = "crates/libm-macros" } libm-test = { path = "libm-test", default-features = false } libtest-mimic = "0.8.1" musl-math-sys = { path = "crates/musl-math-sys" } -no-panic = "0.1.35" -object = { version = "0.37.3", features = ["wasm"] } +no-panic = "0.1.36" +object = { version = "0.38.1", features = ["wasm"] } panic-handler = { path = "crates/panic-handler" } paste = "1.0.15" proc-macro2 = "1.0.106" quote = "1.0.44" -rand = "0.9.2" -rand_chacha = "0.9.0" -rand_xoshiro = "0.7" +rand = "0.10.0" +rand_chacha = "0.10.0" +rand_xoshiro = "0.8" rayon = "1.11.0" regex = "1.12.3" rug = { version = "1.28.1", default-features = false, features = ["float", "integer", "std"] } rustc_apfloat = "0.2.3" serde_json = "1.0.149" -syn = "2.0.114" -tempfile = "3.24.0" +syn = "2.0.115" +tempfile = "3.25.0" [profile.release] panic = "abort" diff --git a/library/compiler-builtins/builtins-test/src/lib.rs b/library/compiler-builtins/builtins-test/src/lib.rs index f1673133be27..b9ad649f88dd 100644 --- a/library/compiler-builtins/builtins-test/src/lib.rs +++ b/library/compiler-builtins/builtins-test/src/lib.rs @@ -22,7 +22,7 @@ use compiler_builtins::float::Float; use compiler_builtins::int::{Int, MinInt}; use rand_xoshiro::Xoshiro128StarStar; -use rand_xoshiro::rand_core::{RngCore, SeedableRng}; +use rand_xoshiro::rand_core::{Rng, SeedableRng}; /// Sets the number of fuzz iterations run for most tests. In practice, the vast majority of bugs /// are caught by the edge case testers. Most of the remaining bugs triggered by more complex diff --git a/library/compiler-builtins/libm-test/src/generate/random.rs b/library/compiler-builtins/libm-test/src/generate/random.rs index 4ee88946d8ea..09a3766c6678 100644 --- a/library/compiler-builtins/libm-test/src/generate/random.rs +++ b/library/compiler-builtins/libm-test/src/generate/random.rs @@ -5,7 +5,7 @@ use libm::support::Float; use rand::distr::{Alphanumeric, StandardUniform}; use rand::prelude::Distribution; -use rand::{Rng, SeedableRng}; +use rand::{RngExt, SeedableRng}; use rand_chacha::ChaCha8Rng; use super::KnownSize; diff --git a/library/compiler-builtins/libm-test/tests/u256.rs b/library/compiler-builtins/libm-test/tests/u256.rs index d1c5cfbcc586..e697945f4797 100644 --- a/library/compiler-builtins/libm-test/tests/u256.rs +++ b/library/compiler-builtins/libm-test/tests/u256.rs @@ -10,7 +10,7 @@ use libm_test::bigint_fuzz_iteration_count; use libm_test::generate::random::SEED; -use rand::{Rng, SeedableRng}; +use rand::{RngExt, SeedableRng}; use rand_chacha::ChaCha8Rng; use rug::Assign; use rug::integer::Order; diff --git a/library/compiler-builtins/libm/Cargo.toml b/library/compiler-builtins/libm/Cargo.toml index 98202d1977dc..28e594dca1f9 100644 --- a/library/compiler-builtins/libm/Cargo.toml +++ b/library/compiler-builtins/libm/Cargo.toml @@ -17,7 +17,7 @@ rust-version = "1.67" [dev-dependencies] # FIXME(msrv): switch to `no-panic.workspace` when possible -no-panic = "0.1.35" +no-panic = "0.1.36" [features] default = ["arch"] From 706a3ab68e65c007ba236a6f39189dd9b218834b Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 13 Feb 2026 07:46:29 -0600 Subject: [PATCH 0051/1059] meta: Move the Criterion dependency behind a `walltime` feature Currently Criterion is always built with the test crates, but it doesn't really need to be. It wasn't a problem before but did start showing up with the latest version bump since it added a C dependency, and for the Miri CI runs we don't have the C toolchains. To resolve this and speed up compilation time, move criterion behind a new feature `walltime`. I'd rather have named this `runtime` but don't want to confuse my future self with "what runtime?". --- .../.github/workflows/main.yaml | 2 +- .../builtins-test/Cargo.toml | 19 +++++++++++++++---- .../{bench-runtime.sh => bench-walltime.sh} | 2 +- .../compiler-builtins/libm-test/Cargo.toml | 13 +++++++++---- 4 files changed, 26 insertions(+), 10 deletions(-) rename library/compiler-builtins/ci/{bench-runtime.sh => bench-walltime.sh} (77%) diff --git a/library/compiler-builtins/.github/workflows/main.yaml b/library/compiler-builtins/.github/workflows/main.yaml index 1a69f7484d88..261b1619f1c5 100644 --- a/library/compiler-builtins/.github/workflows/main.yaml +++ b/library/compiler-builtins/.github/workflows/main.yaml @@ -287,7 +287,7 @@ jobs: path: ${{ env.BASELINE_NAME }}.tar.xz - name: Run wall time benchmarks - run: ./ci/bench-runtime.sh + run: ./ci/bench-walltime.sh - name: Print test logs if available if: always() diff --git a/library/compiler-builtins/builtins-test/Cargo.toml b/library/compiler-builtins/builtins-test/Cargo.toml index 9395ab1a985e..b2313bb9d140 100644 --- a/library/compiler-builtins/builtins-test/Cargo.toml +++ b/library/compiler-builtins/builtins-test/Cargo.toml @@ -16,11 +16,11 @@ rand_xoshiro.workspace = true # To compare float builtins against rustc_apfloat.workspace = true -# Really a dev dependency, but dev dependencies can't be optional +# Really dev dependencies, but those can't be optional +criterion = { workspace = true, optional = true } gungraun = { workspace = true, optional = true } [dev-dependencies] -criterion.workspace = true paste.workspace = true [target.'cfg(all(target_arch = "arm", not(any(target_env = "gnu", target_env = "musl")), target_os = "linux"))'.dev-dependencies] @@ -46,8 +46,10 @@ no-sys-f16 = ["no-sys-f16-f64-convert"] # Enable icount benchmarks (requires gungraun-runner and valgrind locally) icount = ["dep:gungraun"] -# Enable report generation without bringing in more dependencies by default -benchmarking-reports = ["criterion/plotters", "criterion/html_reports"] +# Config for wall time benchmarks. Plotters and html_reports bring in extra +# deps so are off by default for CI. +benchmarking-reports = ["walltime", "criterion/plotters", "criterion/html_reports"] +walltime = ["dep:criterion"] # NOTE: benchmarks must be run with `--no-default-features` or with # `-p builtins-test`, otherwise the default `compiler-builtins` feature @@ -57,38 +59,47 @@ benchmarking-reports = ["criterion/plotters", "criterion/html_reports"] [[bench]] name = "float_add" harness = false +required-features = ["walltime"] [[bench]] name = "float_sub" harness = false +required-features = ["walltime"] [[bench]] name = "float_mul" harness = false +required-features = ["walltime"] [[bench]] name = "float_div" harness = false +required-features = ["walltime"] [[bench]] name = "float_cmp" harness = false +required-features = ["walltime"] [[bench]] name = "float_conv" harness = false +required-features = ["walltime"] [[bench]] name = "float_extend" harness = false +required-features = ["walltime"] [[bench]] name = "float_trunc" harness = false +required-features = ["walltime"] [[bench]] name = "float_pow" harness = false +required-features = ["walltime"] [[bench]] name = "mem_icount" diff --git a/library/compiler-builtins/ci/bench-runtime.sh b/library/compiler-builtins/ci/bench-walltime.sh similarity index 77% rename from library/compiler-builtins/ci/bench-runtime.sh rename to library/compiler-builtins/ci/bench-walltime.sh index d272cf33463e..0393d02dfc45 100755 --- a/library/compiler-builtins/ci/bench-runtime.sh +++ b/library/compiler-builtins/ci/bench-walltime.sh @@ -6,4 +6,4 @@ export LIBM_SEED=benchesbenchesbenchesbencheswoo! cargo bench --package libm-test \ --no-default-features \ - --features short-benchmarks,build-musl,libm/force-soft-floats + --features walltime,short-benchmarks,build-musl,libm/force-soft-floats diff --git a/library/compiler-builtins/libm-test/Cargo.toml b/library/compiler-builtins/libm-test/Cargo.toml index 4f65504bd584..eb382ad87f5f 100644 --- a/library/compiler-builtins/libm-test/Cargo.toml +++ b/library/compiler-builtins/libm-test/Cargo.toml @@ -9,7 +9,6 @@ license = "MIT OR Apache-2.0" anyhow.workspace = true # This is not directly used but is required so we can enable `gmp-mpfr-sys/force-cross`. gmp-mpfr-sys = { workspace = true, optional = true } -gungraun = { workspace = true, optional = true } indicatif.workspace = true libm = { workspace = true, default-features = true, features = ["unstable-public-internals"] } libm-macros.workspace = true @@ -20,6 +19,10 @@ rand_chacha.workspace = true rayon.workspace = true rug = { workspace = true, optional = true } +# Really dev dependencies, but those can't be optional +criterion = { workspace = true, optional = true } +gungraun = { workspace = true, optional = true } + [target.'cfg(target_family = "wasm")'.dependencies] getrandom = { workspace = true, features = ["wasm_js"] } @@ -27,7 +30,6 @@ getrandom = { workspace = true, features = ["wasm_js"] } rand = { workspace = true, optional = true } [dev-dependencies] -criterion.workspace = true libtest-mimic.workspace = true [features] @@ -43,8 +45,10 @@ build-mpfr = ["dep:rug", "dep:gmp-mpfr-sys"] # Build our own musl for testing and benchmarks build-musl = ["dep:musl-math-sys"] -# Enable report generation without bringing in more dependencies by default -benchmarking-reports = ["criterion/plotters", "criterion/html_reports"] +# Config for wall time benchmarks. Plotters and html_reports bring in extra +# deps so are off by default for CI. +benchmarking-reports = ["walltime", "criterion/plotters", "criterion/html_reports"] +walltime = ["dep:criterion"] # Enable icount benchmarks (requires gungraun-runner and valgrind locally) icount = ["dep:gungraun"] @@ -60,6 +64,7 @@ required-features = ["icount"] [[bench]] name = "random" harness = false +required-features = ["walltime"] [[test]] # No harness so that we can skip tests at runtime based on env. Prefixed with From 873e96b6752a323f149495abc2479d3d70df611a Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 13 Feb 2026 08:34:31 -0600 Subject: [PATCH 0052/1059] ci: Rewrap the command in miri.sh --- library/compiler-builtins/ci/miri.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/compiler-builtins/ci/miri.sh b/library/compiler-builtins/ci/miri.sh index 7b0ea44c690f..aae474d88463 100755 --- a/library/compiler-builtins/ci/miri.sh +++ b/library/compiler-builtins/ci/miri.sh @@ -14,5 +14,9 @@ targets=( ) for target in "${targets[@]}"; do # Only run the `mem` tests to avoid this taking too long. - cargo miri test --manifest-path builtins-test/Cargo.toml --features no-asm --target "$target" -- mem + cargo miri test \ + --manifest-path builtins-test/Cargo.toml \ + --features no-asm \ + --target "$target" \ + -- mem done From 87166b545e435a1bffac968a023260d3795e532c Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Fri, 13 Feb 2026 22:59:28 -0500 Subject: [PATCH 0053/1059] libm: Reenable should_panic tests on ppc64le The tests pass successfully for me on powerpc64le-unknown-linux-gnu with nightly 1.95. --- library/compiler-builtins/libm/src/math/support/big/tests.rs | 2 -- library/compiler-builtins/libm/src/math/support/hex_float.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/support/big/tests.rs b/library/compiler-builtins/libm/src/math/support/big/tests.rs index 0c32f445c136..2eafed50a275 100644 --- a/library/compiler-builtins/libm/src/math/support/big/tests.rs +++ b/library/compiler-builtins/libm/src/math/support/big/tests.rs @@ -261,8 +261,6 @@ fn shr_u256() { #[test] #[should_panic] #[cfg(debug_assertions)] -// FIXME(ppc): ppc64le seems to have issues with `should_panic` tests. -#[cfg(not(all(target_arch = "powerpc64", target_endian = "little")))] fn shr_u256_overflow() { // Like regular shr, panic on overflow with debug assertions let _ = u256::MAX >> 256; diff --git a/library/compiler-builtins/libm/src/math/support/hex_float.rs b/library/compiler-builtins/libm/src/math/support/hex_float.rs index 4495e3c18801..81f6b86686c4 100644 --- a/library/compiler-builtins/libm/src/math/support/hex_float.rs +++ b/library/compiler-builtins/libm/src/math/support/hex_float.rs @@ -846,8 +846,6 @@ fn test_macros() { } #[cfg(test)] -// FIXME(ppc): something with `should_panic` tests cause a SIGILL with ppc64le -#[cfg(not(all(target_arch = "powerpc64", target_endian = "little")))] mod tests_panicking { extern crate std; use super::*; From 7d9b4120739fa80e1fa04b2448b631148d37810d Mon Sep 17 00:00:00 2001 From: Aelin Date: Sat, 14 Feb 2026 21:57:28 +0100 Subject: [PATCH 0054/1059] libm: Reenable sincosf tests on ppc64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was missed in 6e91b0346c3d (“Revert "Disable broken powerpc64 test due to https://github.com/rust-lang/rust/issues/88520"”). --- library/compiler-builtins/libm/src/math/sincosf.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/sincosf.rs b/library/compiler-builtins/libm/src/math/sincosf.rs index c4beb5267f28..1d15abe54306 100644 --- a/library/compiler-builtins/libm/src/math/sincosf.rs +++ b/library/compiler-builtins/libm/src/math/sincosf.rs @@ -122,8 +122,6 @@ pub fn sincosf(x: f32) -> (f32, f32) { } } -// PowerPC tests are failing on LLVM 13: https://github.com/rust-lang/rust/issues/88520 -#[cfg(not(target_arch = "powerpc64"))] #[cfg(test)] mod tests { use super::sincosf; From 49e594e11af4ada0d3b44f7414c5f8b515de7474 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 16 Feb 2026 01:18:47 -0600 Subject: [PATCH 0055/1059] ci: Allow specifying extra extensive tests to run For cases where we would like to run tests that aren't automatically detected, allow the following syntax in PR descriptions: ci: extra-extensive=copysignf,sqrtf16 --- library/compiler-builtins/ci/ci-util.py | 64 +++++++++++++++++++++---- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/library/compiler-builtins/ci/ci-util.py b/library/compiler-builtins/ci/ci-util.py index ef9ce455178e..1fd8e72077ff 100755 --- a/library/compiler-builtins/ci/ci-util.py +++ b/library/compiler-builtins/ci/ci-util.py @@ -11,7 +11,7 @@ import pprint import re import subprocess as sp import sys -from dataclasses import dataclass +from dataclasses import dataclass, field from functools import cache from glob import glob from inspect import cleandoc @@ -19,8 +19,7 @@ from os import getenv from pathlib import Path from typing import TypedDict, Self -USAGE = cleandoc( - """ +USAGE = cleandoc(""" usage: ./ci/ci-util.py [flags] @@ -44,8 +43,7 @@ USAGE = cleandoc( Exit with success if the pull request contains a line starting with `ci: allow-regressions`, indicating that regressions in benchmarks should be accepted. Otherwise, exit 1. - """ -) + """) REPO_ROOT = Path(__file__).parent.parent GIT = ["git", "-C", REPO_ROOT] @@ -84,6 +82,8 @@ class PrCfg: allow_regressions: bool = False # Don't run extensive tests skip_extensive: bool = False + # Add these extensive tests to the list + extra_extensive: list[str] = field(default_factory=list) # Allow running a large number of extensive tests. If not set, this script # will error out if a threshold is exceeded in order to avoid accidentally @@ -101,11 +101,17 @@ class PrCfg: DIR_SKIP_EXTENSIVE: str = "skip-extensive" DIR_ALLOW_MANY_EXTENSIVE: str = "allow-many-extensive" DIR_TEST_LIBM: str = "test-libm" + DIR_EXTRA_EXTENSIVE: str = "extra-extensive" def __init__(self, body: str): - directives = re.finditer(r"^\s*ci:\s*(?P\S*)", body, re.MULTILINE) + directives = re.finditer( + r"^\s*ci:\s*(?P[^\s=]*)(?:\s*=\s*(?P.*))?", + body, + re.MULTILINE, + ) for dir in directives: name = dir.group("dir_name") + args = dir.group("args") if name == self.DIR_ALLOW_REGRESSIONS: self.allow_regressions = True elif name == self.DIR_SKIP_EXTENSIVE: @@ -114,10 +120,17 @@ class PrCfg: self.allow_many_extensive = True elif name == self.DIR_TEST_LIBM: self.always_test_libm = True + elif name == self.DIR_EXTRA_EXTENSIVE: + self.extra_extensive = [x.strip() for x in args.split(",")] + args = None else: eprint(f"Found unexpected directive `{name}`") exit(1) + if args is not None: + eprint("Found arguments where not expected") + exit(1) + pprint.pp(self) @@ -276,29 +289,35 @@ class Context: skip_tests = False error_on_many_tests = False + extra_tests = {} pr = PrInfo.from_env() if pr is not None: skip_tests = pr.cfg.skip_extensive error_on_many_tests = not pr.cfg.allow_many_extensive + for fn_name in pr.cfg.extra_extensive: + extra_tests.setdefault(base_name(fn_name)[1], []).append(fn_name) if skip_tests: eprint("Skipping all extensive tests") changed = self.changed_routines() + eprint(f"Changed: {changed}") + matrix = [] total_to_test = 0 # Figure out which extensive tests need to run for ty in TYPES: ty_changed = changed.get(ty, []) - ty_to_test = [] if skip_tests else ty_changed + ty_to_test = [] if skip_tests else ty_changed.copy() + ty_to_test.extend(extra_tests.get(ty, [])) total_to_test += len(ty_to_test) item = { "ty": ty, - "changed": ",".join(ty_changed), - "to_test": ",".join(ty_to_test), + "changed": ",".join(sorted(set(ty_changed))), + "to_test": ",".join(sorted(set(ty_to_test))), } matrix.append(item) @@ -319,6 +338,33 @@ class Context: exit(1) +def base_name(name: str) -> tuple[str, str]: + """Return the basename and type from a full function name. Keep in sync with Rust's + `fn base_name`. + """ + known_mappings = [ + ("erff", ("erf", "f32")), + ("erf", ("erf", "f64")), + ("modff", ("modf", "f32")), + ("modf", ("modf", "f64")), + ("lgammaf_r", ("lgamma_r", "f32")), + ("lgamma_r", ("lgamma_r", "f64")), + ] + + found = next((base for (full, base) in known_mappings if full == name), None) + if found is not None: + return found + + if name.endswith("f"): + return (name.rstrip("f"), "f32") + elif name.endswith("f16"): + return (name.rstrip("f16"), "f16") + elif name.endswith("f128"): + return (name.rstrip("f128"), "f128") + + return (name, "f64") + + def locate_baseline(flags: list[str]) -> None: """Find the most recent baseline from CI, download it if specified. From 9be3b21559a5de22ee3920dc91811d4f1c5ac65c Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 16 Feb 2026 02:35:29 -0600 Subject: [PATCH 0056/1059] meta: Add a root Cargo.lock file In order to improve upon some inconsistencies seen across CI runs and locally, add a Cargo.lock file for the main workspace. --- library/compiler-builtins/.gitignore | 6 +- library/compiler-builtins/Cargo.lock | 1644 ++++++++++++++++++++++++++ 2 files changed, 1649 insertions(+), 1 deletion(-) create mode 100644 library/compiler-builtins/Cargo.lock diff --git a/library/compiler-builtins/.gitignore b/library/compiler-builtins/.gitignore index abe346659d4c..1137a19cf093 100644 --- a/library/compiler-builtins/.gitignore +++ b/library/compiler-builtins/.gitignore @@ -1,7 +1,11 @@ # Rust files -Cargo.lock target +# We have a couple of potential Cargo.lock files, but we only the root +# workspace should have dependencies other than (optional) `cc`. +Cargo.lock +!/Cargo.lock + # Sources for external files compiler-rt *.tar.gz diff --git a/library/compiler-builtins/Cargo.lock b/library/compiler-builtins/Cargo.lock new file mode 100644 index 000000000000..4bbdf5f4ae20 --- /dev/null +++ b/library/compiler-builtins/Cargo.lock @@ -0,0 +1,1644 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] +name = "assert_cmd" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514" +dependencies = [ + "anstyle", + "bstr", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "az" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "builtins-test" +version = "0.1.0" +dependencies = [ + "compiler_builtins", + "criterion", + "gungraun", + "paste", + "rand_xoshiro", + "rustc_apfloat", + "test", + "utest-cortex-m-qemu", + "utest-macros", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "compiler_builtins" +version = "0.1.160" +dependencies = [ + "cc", +] + +[[package]] +name = "console" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.61.2", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "page_size", + "plotters", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "rand_core", + "wasip2", + "wasip3", + "wasm-bindgen", +] + +[[package]] +name = "gmp-mpfr-sys" +version = "1.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60f8970a75c006bb2f8ae79c6768a116dd215fa8346a87aed99bf9d82ca43394" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "gungraun" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c1bbe46f51c63bc08a1fac0ee0c530a77c961613a86ecf828ab1b0ffc6687a" +dependencies = [ + "bincode", + "derive_more", + "gungraun-macros", + "gungraun-runner", +] + +[[package]] +name = "gungraun-macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdccd089c36fb2ee66ef0eb7b1baa3ce7e7878a8eae682d9c8c368869ff6eca1" +dependencies = [ + "derive_more", + "proc-macro-error2", + "proc-macro2", + "quote", + "rustc_version", + "serde", + "serde_json", + "syn", +] + +[[package]] +name = "gungraun-runner" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6da6487203fa53ae6b1c8fead642fe79a3199464b0dd1337635594d675a9ac05" +dependencies = [ + "serde", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indicatif" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" +dependencies = [ + "console", + "portable-atomic", + "unit-prefix", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "libm" +version = "0.2.16" +dependencies = [ + "no-panic", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libm-macros" +version = "0.1.0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libm-test" +version = "0.1.0" +dependencies = [ + "anyhow", + "criterion", + "getrandom", + "gmp-mpfr-sys", + "gungraun", + "indicatif", + "libm 0.2.16", + "libm-macros", + "libtest-mimic", + "musl-math-sys", + "paste", + "rand", + "rand_chacha", + "rayon", + "rug", +] + +[[package]] +name = "libtest-mimic" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5297962ef19edda4ce33aaa484386e0a5b3d7f2f4e037cbeee00503ef6b29d33" +dependencies = [ + "anstream", + "anstyle", + "clap", + "escape8259", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "musl-math-sys" +version = "0.1.0" +dependencies = [ + "cc", + "libm 0.2.16", +] + +[[package]] +name = "no-panic" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f967505aabc8af5752d098c34146544a43684817cdba8f9725b292530cabbf53" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271638cd5fa9cca89c4c304675ca658efc4e64a66c716b7cfe1afb4b9611dbbc" +dependencies = [ + "flate2", + "memchr", + "ruzstd", + "wasmparser 0.243.0", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "panic-handler" +version = "0.1.0" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "rand_xoshiro" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f0b2cc7bfeef8f0320ca45f88b00157a03c67137022d59393614352d6bf4312" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "rug" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de190ec858987c79cad4da30e19e546139b3339331282832af004d0ea7829639" +dependencies = [ + "az", + "gmp-mpfr-sys", + "libc", + "libm 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc_apfloat" +version = "0.2.3+llvm-462a31f5a5ab" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486c2179b4796f65bfe2ee33679acf0927ac83ecf583ad6c91c3b4570911b9ad" +dependencies = [ + "bitflags", + "smallvec", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ruzstd" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "sc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010e18bd3bfd1d45a7e666b236c78720df0d9a7698ebaa9c1c559961eb60a38b" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "symbol-check" +version = "0.1.0" +dependencies = [ + "assert_cmd", + "cc", + "getopts", + "object", + "regex", + "serde_json", + "tempfile", +] + +[[package]] +name = "syn" +version = "2.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "test" +version = "0.1.0" +source = "git+https://github.com/japaric/utest#e32073e2b078e3bee46001c13ae4c1acf368d762" + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + +[[package]] +name = "utest-cortex-m-qemu" +version = "0.1.0" +source = "git+https://github.com/japaric/utest#e32073e2b078e3bee46001c13ae4c1acf368d762" +dependencies = [ + "sc", +] + +[[package]] +name = "utest-macros" +version = "0.1.0" +source = "git+https://github.com/japaric/utest#e32073e2b078e3bee46001c13ae4c1acf368d762" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "util" +version = "0.1.0" +dependencies = [ + "cfg-if", + "libm 0.2.16", + "libm-macros", + "libm-test", + "musl-math-sys", + "rug", +] + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasmparser" +version = "0.243.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser 0.244.0", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.244.0", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" From e12d83967e0537f4066f18a485d61e044f0314b6 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 16 Feb 2026 03:02:14 -0600 Subject: [PATCH 0057/1059] ci: Resolve an issue calculating workflow variables PRs are now getting the following: Traceback (most recent call last): File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 510, in main() File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 496, in main ctx.emit_workflow_output() File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 294, in emit_workflow_output pr = PrInfo.from_env() ^^^^^^^^^^^^^^^^^ File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 152, in from_env return cls.from_pr(pr_env) ^^^^^^^^^^^^^^^^^^^ File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 174, in from_pr return cls(**json.loads(pr_info), cfg=PrCfg(pr_json["body"])) ^^^^^^^^^^^^^^^^^^^^^^ File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 134, in __init__ pprint.pp(self) File "/usr/lib/python3.12/pprint.py", line 66, in pp pprint(object, *args, sort_dicts=sort_dicts, **kwargs) ... AttributeError: 'PrCfg' object has no attribute 'extra_extensive'. Did you mean: 'skip_extensive'? Resolve this by using `__post_init__` rather than `__init__`. Fixes: bba024d20464 ("ci: Allow specifying extra extensive tests to run") --- library/compiler-builtins/ci/ci-util.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/library/compiler-builtins/ci/ci-util.py b/library/compiler-builtins/ci/ci-util.py index 1fd8e72077ff..392f83c219e7 100755 --- a/library/compiler-builtins/ci/ci-util.py +++ b/library/compiler-builtins/ci/ci-util.py @@ -71,19 +71,21 @@ def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) -@dataclass(init=False) +@dataclass(kw_only=True) class PrCfg: """Directives that we allow in the commit body to control test behavior. These are of the form `ci: foo`, at the start of a line. """ + # The PR body + body: str # Skip regression checks (must be at the start of a line). allow_regressions: bool = False # Don't run extensive tests skip_extensive: bool = False # Add these extensive tests to the list - extra_extensive: list[str] = field(default_factory=list) + extra_extensive: list[str] = field(default_factory=list, init=False) # Allow running a large number of extensive tests. If not set, this script # will error out if a threshold is exceeded in order to avoid accidentally @@ -103,10 +105,10 @@ class PrCfg: DIR_TEST_LIBM: str = "test-libm" DIR_EXTRA_EXTENSIVE: str = "extra-extensive" - def __init__(self, body: str): + def __post_init__(self): directives = re.finditer( r"^\s*ci:\s*(?P[^\s=]*)(?:\s*=\s*(?P.*))?", - body, + self.body, re.MULTILINE, ) for dir in directives: @@ -131,7 +133,7 @@ class PrCfg: eprint("Found arguments where not expected") exit(1) - pprint.pp(self) + eprint(pprint.pformat(self)) @dataclass @@ -171,7 +173,7 @@ class PrInfo: ) pr_json = json.loads(pr_info) eprint("PR info:", json.dumps(pr_json, indent=4)) - return cls(**json.loads(pr_info), cfg=PrCfg(pr_json["body"])) + return cls(**pr_json, cfg=PrCfg(body=pr_json["body"])) class FunctionDef(TypedDict): From 1542d6405c144697cd9d5f50c5db55c0931651af Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 16 Feb 2026 03:40:01 -0600 Subject: [PATCH 0058/1059] test: Enable the `wasmbind` feature on indicatif for wasm As of indicatif 0.18.4, this option must be enabled in able to build. --- library/compiler-builtins/Cargo.lock | 11 +++++++++++ library/compiler-builtins/libm-test/Cargo.toml | 1 + 2 files changed, 12 insertions(+) diff --git a/library/compiler-builtins/Cargo.lock b/library/compiler-builtins/Cargo.lock index 4bbdf5f4ae20..7a3e9a38430b 100644 --- a/library/compiler-builtins/Cargo.lock +++ b/library/compiler-builtins/Cargo.lock @@ -590,6 +590,7 @@ dependencies = [ "console", "portable-atomic", "unit-prefix", + "web-time", ] [[package]] @@ -1409,6 +1410,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/library/compiler-builtins/libm-test/Cargo.toml b/library/compiler-builtins/libm-test/Cargo.toml index eb382ad87f5f..8a8c2b0a2ce0 100644 --- a/library/compiler-builtins/libm-test/Cargo.toml +++ b/library/compiler-builtins/libm-test/Cargo.toml @@ -25,6 +25,7 @@ gungraun = { workspace = true, optional = true } [target.'cfg(target_family = "wasm")'.dependencies] getrandom = { workspace = true, features = ["wasm_js"] } +indicatif = { workspace = true, features = ["wasmbind"] } [build-dependencies] rand = { workspace = true, optional = true } From 65443dc7074c66e53ea16a02665eee00aeb9acb3 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 16 Feb 2026 22:10:26 +1100 Subject: [PATCH 0059/1059] Unalign `PackedFingerprint` on all hosts, not just x86 and x86-64 --- .../rustc_data_structures/src/fingerprint.rs | 34 ++++++++++--------- .../rustc_middle/src/dep_graph/dep_node.rs | 3 -- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_data_structures/src/fingerprint.rs b/compiler/rustc_data_structures/src/fingerprint.rs index c7c0d0ab0725..06ab95d65b6a 100644 --- a/compiler/rustc_data_structures/src/fingerprint.rs +++ b/compiler/rustc_data_structures/src/fingerprint.rs @@ -181,22 +181,24 @@ fn decode(d: &mut D) -> Self { } } -// `PackedFingerprint` wraps a `Fingerprint`. Its purpose is to, on certain -// architectures, behave like a `Fingerprint` without alignment requirements. -// This behavior is only enabled on x86 and x86_64, where the impact of -// unaligned accesses is tolerable in small doses. -// -// This may be preferable to use in large collections of structs containing -// fingerprints, as it can reduce memory consumption by preventing the padding -// that the more strictly-aligned `Fingerprint` can introduce. An application of -// this is in the query dependency graph, which contains a large collection of -// `DepNode`s. As of this writing, the size of a `DepNode` decreases by ~30% -// (from 24 bytes to 17) by using the packed representation here, which -// noticeably decreases total memory usage when compiling large crates. -// -// The wrapped `Fingerprint` is private to reduce the chance of a client -// invoking undefined behavior by taking a reference to the packed field. -#[cfg_attr(any(target_arch = "x86", target_arch = "x86_64"), repr(packed))] +/// `PackedFingerprint` wraps a `Fingerprint`. +/// Its purpose is to behave like a `Fingerprint` without alignment requirements. +/// +/// This may be preferable to use in large collections of structs containing +/// fingerprints, as it can reduce memory consumption by preventing the padding +/// that the more strictly-aligned `Fingerprint` can introduce. An application of +/// this is in the query dependency graph, which contains a large collection of +/// `DepNode`s. As of this writing, the size of a `DepNode` decreases by 25% +/// (from 24 bytes to 18) by using the packed representation here, which +/// noticeably decreases total memory usage when compiling large crates. +/// +/// (Unalignment was previously restricted to `x86` and `x86_64` hosts, but is +/// now enabled by default for all host architectures, in the hope that the +/// memory and cache savings should outweigh any unaligned access penalty.) +/// +/// The wrapped `Fingerprint` is private to reduce the chance of a client +/// invoking undefined behavior by taking a reference to the packed field. +#[repr(packed)] #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash)] pub struct PackedFingerprint(Fingerprint); diff --git a/compiler/rustc_middle/src/dep_graph/dep_node.rs b/compiler/rustc_middle/src/dep_graph/dep_node.rs index 909638b85906..78ab157487a6 100644 --- a/compiler/rustc_middle/src/dep_graph/dep_node.rs +++ b/compiler/rustc_middle/src/dep_graph/dep_node.rs @@ -506,9 +506,6 @@ mod size_asserts { use super::*; // tidy-alphabetical-start static_assert_size!(DepKind, 2); - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] static_assert_size!(DepNode, 18); - #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] - static_assert_size!(DepNode, 24); // tidy-alphabetical-end } From 04334f05157ce6f0d7898de357d9ddd37769f6ef Mon Sep 17 00:00:00 2001 From: Juho Kahala <57393910+quaternic@users.noreply.github.com> Date: Mon, 16 Feb 2026 04:36:15 +0200 Subject: [PATCH 0060/1059] fix testing at lgammaf overflow threshold The current implementation of lgammaf evaluates to f32::MAX at the first input that would overflow if correctly rounded. This is still well within allowed error, so special case it. --- library/compiler-builtins/libm-test/src/precision.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/compiler-builtins/libm-test/src/precision.rs b/library/compiler-builtins/libm-test/src/precision.rs index 5d52da168fe7..8c01c9e3d491 100644 --- a/library/compiler-builtins/libm-test/src/precision.rs +++ b/library/compiler-builtins/libm-test/src/precision.rs @@ -222,6 +222,15 @@ fn check_float(input: (f32,), actual: F, expected: F, ctx: &CheckCtx) return XFAIL_NOCHECK; } + // the testing infrastructure doesn't account for allowed ulp in the case of overflow + if matches!(ctx.base_name, BaseName::Lgamma | BaseName::LgammaR) + && input.0 == 4.0850034e36 + && expected.is_infinite() + && actual == F::MAX + { + return XFAIL_NOCHECK; + } + // FIXME(correctness): lgammaf has high relative inaccuracy near its zeroes if matches!(ctx.base_name, BaseName::Lgamma | BaseName::LgammaR) && input.0 > -13.0625 From 84cfd9a557167fe453f7f6474dbea02b76f46b1b Mon Sep 17 00:00:00 2001 From: Hans Wennborg Date: Tue, 3 Feb 2026 17:02:22 +0100 Subject: [PATCH 0061/1059] Check for symbols with default visibility in symbol-check to ensure that compiler-builtins doesn't expose any symbols with default visibility. --- .../crates/symbol-check/src/main.rs | 43 +++++++++++++++++-- .../crates/symbol-check/tests/all.rs | 26 +++++++++-- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/library/compiler-builtins/crates/symbol-check/src/main.rs b/library/compiler-builtins/crates/symbol-check/src/main.rs index e15522d223d8..135019e5f729 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -58,6 +58,7 @@ fn main() { "The binaries will not be checked for executable stacks. Used for embedded targets which \ don't set `.note.GNU-stack` since there is no protection.", ); + opts.optflag("", "no-visibility", "Don't check visibility."); let print_usage_and_exit = |code: i32| -> ! { eprintln!("{}", opts.usage(USAGE)); @@ -74,6 +75,7 @@ fn main() { } let no_os_target = m.opt_present("no-os"); + let check_visibility = !m.opt_present("no-visibility"); let free_args = m.free.iter().map(String::as_str).collect::>(); for arg in &free_args { assert!( @@ -85,18 +87,18 @@ fn main() { if m.opt_present("build-and-check") { let target = m.opt_str("target").unwrap_or(env!("HOST").to_string()); let paths = exec_cargo_with_args(&target, &free_args); - check_paths(&paths, no_os_target); + check_paths(&paths, no_os_target, check_visibility); } else if m.opt_present("check") { if free_args.is_empty() { print_usage_and_exit(1); } - check_paths(&free_args, no_os_target); + check_paths(&free_args, no_os_target, check_visibility); } else { print_usage_and_exit(1); } } -fn check_paths>(paths: &[P], no_os_target: bool) { +fn check_paths>(paths: &[P], no_os_target: bool, check_visibility: bool) { for path in paths { let path = path.as_ref(); println!("Checking {}", path.display()); @@ -105,6 +107,9 @@ fn check_paths>(paths: &[P], no_os_target: bool) { verify_no_duplicates(&archive); verify_core_symbols(&archive); verify_no_exec_stack(&archive, no_os_target); + if check_visibility { + verify_hidden_visibility(&archive); + } } } @@ -329,6 +334,38 @@ fn verify_core_symbols(archive: &BinFile) { println!(" success: no undefined references to core found"); } +/// Check for symbols with default visibility. +fn verify_hidden_visibility(archive: &BinFile) { + let mut visible = Vec::new(); + let mut found_any = false; + + archive.for_each_symbol(|symbol, obj, member| { + // Only check defined globals. + if !symbol.is_global() || symbol.is_undefined() { + return; + } + + let sym = SymInfo::new(&symbol, obj, member); + if sym.scope == SymbolScope::Dynamic { + visible.push(sym); + } + + found_any = true + }); + + if archive.has_symbol_tables() { + assert!(found_any, "no symbols found"); + } + + if !visible.is_empty() { + visible.sort_unstable_by(|a, b| a.name.cmp(&b.name)); + let num = visible.len(); + panic!("found {num:#?} visible symbols: {visible:#?}"); + } + + println!(" success: no visible symbols found"); +} + /// Reasons a binary is considered to have an executable stack. enum ExeStack { MissingGnuStackSec, diff --git a/library/compiler-builtins/crates/symbol-check/tests/all.rs b/library/compiler-builtins/crates/symbol-check/tests/all.rs index 6dc34c3e3dba..cfc8641aca82 100644 --- a/library/compiler-builtins/crates/symbol-check/tests/all.rs +++ b/library/compiler-builtins/crates/symbol-check/tests/all.rs @@ -75,6 +75,20 @@ fn test_core_symbols() { .stderr_contains("from_utf8"); } +#[test] +fn test_visible_symbols() { + let t = TestTarget::from_env(); + if t.is_windows() { + eprintln!("windows does not have visibility, skipping"); + return; + } + let dir = tempdir().unwrap(); + let lib_out = dir.path().join("libfoo.rlib"); + t.rustc_build(&input_dir().join("good_lib.rs"), &lib_out, |cmd| cmd); + let assert = t.symcheck_exe().arg(&lib_out).assert(); + assert.failure().stderr_contains("found 1 visible symbols"); // good is visible. +} + mod exe_stack { use super::*; @@ -95,7 +109,7 @@ fn test_missing_gnu_stack_section() { let objs = t.cc_build().file(src).out_dir(&dir).compile_intermediates(); let [obj] = objs.as_slice() else { panic!() }; - let assert = t.symcheck_exe().arg(obj).assert(); + let assert = t.symcheck_exe().arg(obj).arg("--no-visibility").assert(); if t.is_ppc64be() || t.no_os() || t.binary_obj_format() != BinaryFormat::Elf { // Ppc64be doesn't emit `.note.GNU-stack`, not relevant without an OS, and non-elf @@ -127,7 +141,7 @@ fn test_exe_gnu_stack_section() { .compile_intermediates(); let [obj] = objs.as_slice() else { panic!() }; - let assert = t.symcheck_exe().arg(obj).assert(); + let assert = t.symcheck_exe().arg(obj).arg("--no-visibility").assert(); if t.is_ppc64be() || t.no_os() { // Ppc64be doesn't emit `.note.GNU-stack`, not relevant without an OS. @@ -179,7 +193,11 @@ fn test_good_lib() { let dir = tempdir().unwrap(); let lib_out = dir.path().join("libfoo.rlib"); t.rustc_build(&input_dir().join("good_lib.rs"), &lib_out, |cmd| cmd); - let assert = t.symcheck_exe().arg(&lib_out).assert(); + let assert = t + .symcheck_exe() + .arg(&lib_out) + .arg("--no-visibility") + .assert(); assert.success(); } @@ -199,7 +217,7 @@ fn test_good_bin() { t.set_bin_out_path(&mut cmd, &out); run(cmd.arg(input_dir().join("good_bin.c"))); - let assert = t.symcheck_exe().arg(&out).assert(); + let assert = t.symcheck_exe().arg(&out).arg("--no-visibility").assert(); assert.success(); } From 980b31646f38b123ba3667607508bf52b0845e85 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Wed, 15 Oct 2025 23:08:15 +0000 Subject: [PATCH 0062/1059] Remove ShallowInitBox. --- src/base.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/base.rs b/src/base.rs index 1a916c876824..e4865ece63b6 100644 --- a/src/base.rs +++ b/src/base.rs @@ -902,7 +902,6 @@ fn is_wide_ptr<'tcx>(fx: &FunctionCx<'_, '_, 'tcx>, ty: Ty<'tcx>) -> bool { lval.write_cvalue_transmute(fx, operand); } Rvalue::CopyForDeref(_) => bug!("`CopyForDeref` in codegen"), - Rvalue::ShallowInitBox(..) => bug!("`ShallowInitBox` in codegen"), } } StatementKind::StorageLive(_) From 9ddb7fc60a596492db05b3ed846c0dc5f1ebbf23 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:02:27 +0000 Subject: [PATCH 0063/1059] Merge commit 'abdb98ad4b47117ee3be17b1e43fab34f18f5805' into sync_cg_clif-2026-02-18 --- .github/scripts/free-disk-space.sh | 259 ++++++++++++++++++ .github/workflows/abi-cafe.yml | 6 + .github/workflows/audit.yml | 2 +- Cargo.lock | 108 ++++---- Cargo.toml | 24 +- build_system/build_backend.rs | 2 + build_system/build_sysroot.rs | 9 +- config.txt | 2 +- ...esn-t-get-emitted-unless-VaList-is-a.patch | 25 ++ rust-toolchain.toml | 2 +- scripts/jit-helpers.py | 52 ++++ scripts/setup_rust_fork.sh | 44 ++- scripts/test_rustc_tests.sh | 38 +-- src/abi/mod.rs | 6 +- 14 files changed, 472 insertions(+), 107 deletions(-) create mode 100755 .github/scripts/free-disk-space.sh create mode 100644 patches/0028-stdlib-Ensure-va_end-doesn-t-get-emitted-unless-VaList-is-a.patch create mode 100644 scripts/jit-helpers.py diff --git a/.github/scripts/free-disk-space.sh b/.github/scripts/free-disk-space.sh new file mode 100755 index 000000000000..06afdaad619f --- /dev/null +++ b/.github/scripts/free-disk-space.sh @@ -0,0 +1,259 @@ +#!/bin/bash +# Ported from rust-lang/rust commit d29e4783dff30f9526eeba3929ebfe86c00c9dad in src/ci/scripts/free-disk-space-linux.sh +set -euo pipefail + +# Script inspired by https://github.com/jlumbroso/free-disk-space +isX86() { + local arch + arch=$(uname -m) + if [ "$arch" = "x86_64" ]; then + return 0 + else + return 1 + fi +} + +# In aws codebuild, the variable RUNNER_ENVIRONMENT is "self-hosted". +isGitHubRunner() { + # `:-` means "use the value of RUNNER_ENVIRONMENT if it exists, otherwise use an empty string". + if [[ "${RUNNER_ENVIRONMENT:-}" == "github-hosted" ]]; then + return 0 + else + return 1 + fi +} + +# print a line of the specified character +printSeparationLine() { + for ((i = 0; i < 80; i++)); do + printf "%s" "$1" + done + printf "\n" +} + +# REF: https://stackoverflow.com/a/450821/408734 +getAvailableSpace() { + df -a | awk 'NR > 1 {avail+=$4} END {print avail}' +} + +# REF: https://unix.stackexchange.com/a/44087/60849 +formatByteCount() { + numfmt --to=iec-i --suffix=B --padding=7 "${1}000" +} + +# macro to output saved space +printSavedSpace() { + # Disk space before the operation + local before=${1} + local title=${2:-} + + local after + after=$(getAvailableSpace) + local saved=$((after - before)) + + if [ "$saved" -lt 0 ]; then + echo "::warning::Saved space is negative: $saved. Using '0' as saved space." + saved=0 + fi + + echo "" + printSeparationLine "*" + if [ -n "${title}" ]; then + echo "=> ${title}: Saved $(formatByteCount "$saved")" + else + echo "=> Saved $(formatByteCount "$saved")" + fi + printSeparationLine "*" + echo "" +} + +# macro to print output of df with caption +printDF() { + local caption=${1} + + printSeparationLine "=" + echo "${caption}" + echo "" + echo "$ df -h" + echo "" + df -h + printSeparationLine "=" +} + +removeUnusedFilesAndDirs() { + local to_remove=( + "/usr/share/java" + ) + + if isGitHubRunner; then + to_remove+=( + "/usr/local/aws-sam-cli" + "/usr/local/doc/cmake" + "/usr/local/julia"* + "/usr/local/lib/android" + "/usr/local/share/chromedriver-"* + "/usr/local/share/chromium" + "/usr/local/share/cmake-"* + "/usr/local/share/edge_driver" + "/usr/local/share/emacs" + "/usr/local/share/gecko_driver" + "/usr/local/share/icons" + "/usr/local/share/powershell" + "/usr/local/share/vcpkg" + "/usr/local/share/vim" + "/usr/share/apache-maven-"* + "/usr/share/gradle-"* + "/usr/share/kotlinc" + "/usr/share/miniconda" + "/usr/share/php" + "/usr/share/ri" + "/usr/share/swift" + + # binaries + "/usr/local/bin/azcopy" + "/usr/local/bin/bicep" + "/usr/local/bin/ccmake" + "/usr/local/bin/cmake-"* + "/usr/local/bin/cmake" + "/usr/local/bin/cpack" + "/usr/local/bin/ctest" + "/usr/local/bin/helm" + "/usr/local/bin/kind" + "/usr/local/bin/kustomize" + "/usr/local/bin/minikube" + "/usr/local/bin/packer" + "/usr/local/bin/phpunit" + "/usr/local/bin/pulumi-"* + "/usr/local/bin/pulumi" + "/usr/local/bin/stack" + + # Haskell runtime + "/usr/local/.ghcup" + + # Azure + "/opt/az" + "/usr/share/az_"* + ) + + if [ -n "${AGENT_TOOLSDIRECTORY:-}" ]; then + # Environment variable set by GitHub Actions + to_remove+=( + "${AGENT_TOOLSDIRECTORY}" + ) + else + echo "::warning::AGENT_TOOLSDIRECTORY is not set. Skipping removal." + fi + else + # Remove folders and files present in AWS CodeBuild + to_remove+=( + # binaries + "/usr/local/bin/ecs-cli" + "/usr/local/bin/eksctl" + "/usr/local/bin/kubectl" + + "${HOME}/.gradle" + "${HOME}/.dotnet" + "${HOME}/.goenv" + "${HOME}/.phpenv" + + ) + fi + + for element in "${to_remove[@]}"; do + if [ ! -e "$element" ]; then + # The file or directory doesn't exist. + # Maybe it was removed in a newer version of the runner or it's not present in a + # specific architecture (e.g. ARM). + echo "::warning::Directory or file $element does not exist, skipping." + fi + done + + # Remove all files and directories at once to save time. + sudo rm -rf "${to_remove[@]}" +} + +execAndMeasureSpaceChange() { + local operation=${1} # Function to execute + local title=${2} + + local before + before=$(getAvailableSpace) + $operation + + printSavedSpace "$before" "$title" +} + +# REF: https://github.com/apache/flink/blob/master/tools/azure-pipelines/free_disk_space.sh +cleanPackages() { + local packages=( + '^aspnetcore-.*' + '^dotnet-.*' + '^llvm-.*' + '^mongodb-.*' + 'firefox' + 'libgl1-mesa-dri' + 'mono-devel' + 'php.*' + ) + + if isGitHubRunner; then + packages+=( + azure-cli + ) + + if isX86; then + packages+=( + 'google-chrome-stable' + 'google-cloud-cli' + 'google-cloud-sdk' + 'powershell' + ) + fi + else + packages+=( + 'google-chrome-stable' + ) + fi + + WAIT_DPKG_LOCK="-o DPkg::Lock::Timeout=60" + sudo apt-get ${WAIT_DPKG_LOCK} -qq remove -y --fix-missing "${packages[@]}" + + sudo apt-get ${WAIT_DPKG_LOCK} autoremove -y \ + || echo "::warning::The command [sudo apt-get autoremove -y] failed" + sudo apt-get ${WAIT_DPKG_LOCK} clean \ + || echo "::warning::The command [sudo apt-get clean] failed" +} + +# They aren't present in ubuntu 24 runners. +cleanDocker() { + echo "=> Removing the following docker images:" + sudo docker image ls + echo "=> Removing docker images..." + sudo docker image prune --all --force || true +} + +# Remove Swap storage +cleanSwap() { + sudo swapoff -a || true + sudo rm -rf /mnt/swapfile || true + free -h +} + +# Display initial disk space stats +AVAILABLE_INITIAL=$(getAvailableSpace) + +printDF "BEFORE CLEAN-UP:" +echo "" +execAndMeasureSpaceChange cleanPackages "Unused packages" +execAndMeasureSpaceChange cleanDocker "Docker images" +execAndMeasureSpaceChange cleanSwap "Swap storage" +execAndMeasureSpaceChange removeUnusedFilesAndDirs "Unused files and directories" + +# Output saved space statistic +echo "" +printDF "AFTER CLEAN-UP:" + +echo "" +echo "" + +printSavedSpace "$AVAILABLE_INITIAL" "Total saved" diff --git a/.github/workflows/abi-cafe.yml b/.github/workflows/abi-cafe.yml index 170c7126c296..3367562f2683 100644 --- a/.github/workflows/abi-cafe.yml +++ b/.github/workflows/abi-cafe.yml @@ -49,6 +49,12 @@ jobs: if: matrix.os == 'ubuntu-latest' run: cat /proc/cpuinfo + - name: Free disk space + if: runner.os == 'Linux' + env: + RUNNER_ENVIRONMENT: github-hosted + run: .github/scripts/free-disk-space.sh + - name: Cache cargo target dir uses: actions/cache@v4 with: diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 274b9504beb0..95a4dcd3266d 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -13,6 +13,6 @@ jobs: - uses: actions/checkout@v4 - run: | sed -i 's/components.*/components = []/' rust-toolchain.toml - - uses: rustsec/audit-check@v1.4.1 + - uses: rustsec/audit-check@v2.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index 3d13b5540e19..afc1d0d0ab95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" dependencies = [ "allocator-api2", ] @@ -43,42 +43,42 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cranelift-assembler-x64" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd963a645179fa33834ba61fa63353998543b07f877e208da9eb47d4a70d1e7" +checksum = "0377b13bf002a0774fcccac4f1102a10f04893d24060cf4b7350c87e4cbb647c" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6d5739c9dc6b5553ca758d78d87d127dd19f397f776efecf817b8ba8d0bb01" +checksum = "cfa027979140d023b25bf7509fb7ede3a54c3d3871fb5ead4673c4b633f671a2" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff402c11bb1c9652b67a3e885e84b1b8d00c13472c8fd85211e06a41a63c3e03" +checksum = "618e4da87d9179a70b3c2f664451ca8898987aa6eb9f487d16988588b5d8cc40" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "769a0d88c2f5539e9c5536a93a7bf164b0dc68d91e3d00723e5b4ffc1440afdc" +checksum = "db53764b5dad233b37b8f5dc54d3caa9900c54579195e00f17ea21f03f71aaa7" [[package]] name = "cranelift-codegen" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4351f721fb3b26add1c180f0a75c7474bab2f903c8b777c6ca65238ded59a78" +checksum = "4ae927f1d8c0abddaa863acd201471d56e7fc6c3925104f4861ed4dc3e28b421" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -102,9 +102,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61f86c0ba5b96713643f4dd0de0df12844de9c7bb137d6829b174b706939aa74" +checksum = "d3fcf1e3e6757834bd2584f4cbff023fcc198e9279dcb5d684b4bb27a9b19f54" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -114,33 +114,33 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f08605eee8d51fd976a970bd5b16c9529b51b624f8af68f80649ffb172eb85a4" +checksum = "205dcb9e6ccf9d368b7466be675ff6ee54a63e36da6fe20e72d45169cf6fd254" [[package]] name = "cranelift-control" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "623aab0a09e40f0cf0b5d35eb7832bae4c4f13e3768228e051a6c1a60e88ef5f" +checksum = "108eca9fcfe86026054f931eceaf57b722c1b97464bf8265323a9b5877238817" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0f066e07e3bcbe38884cc5c94c32c7a90267d69df80f187d9dfe421adaa7c4" +checksum = "a0d96496910065d3165f84ff8e1e393916f4c086f88ac8e1b407678bc78735aa" dependencies = [ "cranelift-bitset", ] [[package]] name = "cranelift-frontend" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40865b02a0e52ca8e580ad64feef530cb1d05f6bb4972b4eef05e3eaeae81701" +checksum = "e303983ad7e23c850f24d9c41fc3cb346e1b930f066d3966545e4c98dac5c9fb" dependencies = [ "cranelift-codegen", "log", @@ -150,15 +150,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "104b3c117ae513e9af1d90679842101193a5ccb96ac9f997966d85ea25be2852" +checksum = "24b0cf8d867d891245836cac7abafb0a5b0ea040a019d720702b3b8bcba40bfa" [[package]] name = "cranelift-jit" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa5f855cfb8e4253ed2d0dfc1a0b6ebe4912e67aa8b7ee14026ff55ca17f1fe" +checksum = "dcf1e35da6eca2448395f483eb172ce71dd7842f7dc96f44bb8923beafe43c6d" dependencies = [ "anyhow", "cranelift-codegen", @@ -176,9 +176,9 @@ dependencies = [ [[package]] name = "cranelift-module" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d01806b191b59f4fc4680293dd5f554caf2de5b62f95eff5beef7acb46c29c" +checksum = "792ba2a54100e34f8a36e3e329a5207cafd1f0918a031d34695db73c163fdcc7" dependencies = [ "anyhow", "cranelift-codegen", @@ -187,9 +187,9 @@ dependencies = [ [[package]] name = "cranelift-native" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c54e0a358bc05b48f2032e1c320e7f468da068604f2869b77052eab68eb0fe" +checksum = "e24b641e315443e27807b69c440fe766737d7e718c68beb665a2d69259c77bf3" dependencies = [ "cranelift-codegen", "libc", @@ -198,9 +198,9 @@ dependencies = [ [[package]] name = "cranelift-object" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d17e0216be5daabab616647c1918e06dae0708474ba5f7b7762ac24ea5eb126" +checksum = "ecba1f219a201cf946150538e631defd620c5051b62c52ecb89a0004bab263d4" dependencies = [ "anyhow", "cranelift-codegen", @@ -213,9 +213,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.127.0" +version = "0.128.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6f4b039f453b66c75e9f7886e5a2af96276e151f44dc19b24b58f9a0c98009" +checksum = "a4e378a54e7168a689486d67ee1f818b7e5356e54ae51a1d7a53f4f13f7f8b7a" [[package]] name = "crc32fast" @@ -257,9 +257,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", ] @@ -282,9 +282,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libloading" @@ -298,9 +298,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "log" @@ -337,27 +337,27 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] [[package]] name = "regalloc2" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e249c660440317032a71ddac302f25f1d5dff387667bcc3978d1f77aa31ac34" +checksum = "08effbc1fa53aaebff69521a5c05640523fab037b34a4a2c109506bc938246fa" dependencies = [ "allocator-api2", "bumpalo", @@ -446,9 +446,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -457,9 +457,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" +checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" [[package]] name = "unicode-ident" @@ -469,9 +469,9 @@ checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "40.0.0" +version = "41.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0858b470463f3e7c73acd6049046049e64be17b98901c2db5047450cf83df1fe" +checksum = "bada5ca1cc47df7d14100e2254e187c2486b426df813cea2dd2553a7469f7674" dependencies = [ "anyhow", "cfg-if", @@ -481,9 +481,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-math" -version = "40.0.0" +version = "41.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222e1a590ece4e898f20af1e541b61d2cb803f2557e7eaff23e6c1db5434454a" +checksum = "cf6f615d528eda9adc6eefb062135f831b5215c348f4c3ec3e143690c730605b" dependencies = [ "libm", ] diff --git a/Cargo.toml b/Cargo.toml index ee4bde477c47..a7b4664282ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,12 @@ crate-type = ["dylib"] [dependencies] # These have to be in sync with each other -cranelift-codegen = { version = "0.127.0", default-features = false, features = ["std", "timing", "unwind", "all-native-arch"] } -cranelift-frontend = { version = "0.127.0" } -cranelift-module = { version = "0.127.0" } -cranelift-native = { version = "0.127.0" } -cranelift-jit = { version = "0.127.0", optional = true } -cranelift-object = { version = "0.127.0" } +cranelift-codegen = { version = "0.128.3", default-features = false, features = ["std", "timing", "unwind", "all-native-arch"] } +cranelift-frontend = { version = "0.128.3" } +cranelift-module = { version = "0.128.3" } +cranelift-native = { version = "0.128.3" } +cranelift-jit = { version = "0.128.3", optional = true } +cranelift-object = { version = "0.128.3" } target-lexicon = "0.13" gimli = { version = "0.32", default-features = false, features = ["write"] } object = { version = "0.37.3", default-features = false, features = ["std", "read_core", "write", "archive", "coff", "elf", "macho", "pe"] } @@ -24,12 +24,12 @@ smallvec = "1.8.1" [patch.crates-io] # Uncomment to use an unreleased version of cranelift -#cranelift-codegen = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-40.0.0" } -#cranelift-frontend = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-40.0.0" } -#cranelift-module = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-40.0.0" } -#cranelift-native = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-40.0.0" } -#cranelift-jit = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-40.0.0" } -#cranelift-object = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-40.0.0" } +#cranelift-codegen = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-41.0.0" } +#cranelift-frontend = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-41.0.0" } +#cranelift-module = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-41.0.0" } +#cranelift-native = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-41.0.0" } +#cranelift-jit = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-41.0.0" } +#cranelift-object = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-41.0.0" } # Uncomment to use local checkout of cranelift #cranelift-codegen = { path = "../wasmtime/cranelift/codegen" } diff --git a/build_system/build_backend.rs b/build_system/build_backend.rs index c0a8cc95614f..6b14727cd153 100644 --- a/build_system/build_backend.rs +++ b/build_system/build_backend.rs @@ -43,6 +43,8 @@ pub(crate) fn build_backend( cmd.arg("--release"); + cmd.arg("-Zno-embed-metadata"); + eprintln!("[BUILD] rustc_codegen_cranelift"); crate::utils::spawn_and_wait(cmd); diff --git a/build_system/build_sysroot.rs b/build_system/build_sysroot.rs index 7b4c604580c1..5205ec1e8aaa 100644 --- a/build_system/build_sysroot.rs +++ b/build_system/build_sysroot.rs @@ -235,17 +235,14 @@ fn build_clif_sysroot_for_triple( if let Some(prefix) = env::var_os("CG_CLIF_STDLIB_REMAP_PATH_PREFIX") { rustflags.push("--remap-path-prefix".to_owned()); - rustflags.push(format!( - "{}={}", - STDLIB_SRC.to_path(dirs).to_str().unwrap(), - prefix.to_str().unwrap() - )); + rustflags.push(format!("library/={}/library", prefix.to_str().unwrap())); } compiler.rustflags.extend(rustflags); let mut build_cmd = STANDARD_LIBRARY.build(&compiler, dirs); build_cmd.arg("--release"); build_cmd.arg("--features").arg("backtrace panic-unwind"); build_cmd.arg(format!("-Zroot-dir={}", STDLIB_SRC.to_path(dirs).display())); + build_cmd.arg("-Zno-embed-metadata"); build_cmd.env("CARGO_PROFILE_RELEASE_DEBUG", "true"); build_cmd.env("__CARGO_DEFAULT_LIB_METADATA", "cg_clif"); if compiler.triple.contains("apple") { @@ -260,7 +257,7 @@ fn build_clif_sysroot_for_triple( for entry in fs::read_dir(build_dir.join("deps")).unwrap() { let entry = entry.unwrap(); if let Some(ext) = entry.path().extension() { - if ext == "rmeta" || ext == "d" || ext == "dSYM" || ext == "clif" { + if ext == "d" || ext == "dSYM" || ext == "clif" { continue; } } else { diff --git a/config.txt b/config.txt index 85748a4f8a78..72631355733c 100644 --- a/config.txt +++ b/config.txt @@ -20,7 +20,7 @@ aot.mini_core_hello_world testsuite.base_sysroot aot.arbitrary_self_types_pointers_and_wrappers -jit.std_example +#jit.std_example # FIXME(#1619) broken for some reason aot.std_example aot.dst_field_align aot.subslice-patterns-const-eval diff --git a/patches/0028-stdlib-Ensure-va_end-doesn-t-get-emitted-unless-VaList-is-a.patch b/patches/0028-stdlib-Ensure-va_end-doesn-t-get-emitted-unless-VaList-is-a.patch new file mode 100644 index 000000000000..2aa93164674f --- /dev/null +++ b/patches/0028-stdlib-Ensure-va_end-doesn-t-get-emitted-unless-VaList-is-a.patch @@ -0,0 +1,25 @@ +From 116abc64add4d617104993a7a3011f20bcf31ef2 Mon Sep 17 00:00:00 2001 +From: bjorn3 <17426603+bjorn3@users.noreply.github.com> +Date: Mon, 26 Jan 2026 16:20:58 +0000 +Subject: [PATCH] Ensure va_end doesn't get emitted unless VaList is actually + used + +--- + library/core/src/ffi/va_list.rs | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs +index d0f1553..75129af 100644 +--- a/library/core/src/ffi/va_list.rs ++++ b/library/core/src/ffi/va_list.rs +@@ -217,6 +217,7 @@ impl Clone for VaList<'_> { + + #[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")] + impl<'f> const Drop for VaList<'f> { ++ #[inline] + fn drop(&mut self) { + // SAFETY: this variable argument list is being dropped, so won't be read from again. + unsafe { va_end(self) } +-- +2.43.0 + diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b157c5879ba7..fe967c84352c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2025-12-23" +channel = "nightly-2026-02-18" components = ["rust-src", "rustc-dev", "llvm-tools", "rustfmt"] profile = "minimal" diff --git a/scripts/jit-helpers.py b/scripts/jit-helpers.py new file mode 100644 index 000000000000..1128521c0dfb --- /dev/null +++ b/scripts/jit-helpers.py @@ -0,0 +1,52 @@ +import gdb + +def jitmap_raw(): + pid = gdb.selected_inferior().pid + jitmap_file = open("/tmp/perf-%d.map" % (pid,), "r") + jitmap = jitmap_file.read() + jitmap_file.close() + return jitmap + +def jit_functions(): + jitmap = jitmap_raw() + + functions = [] + for line in jitmap.strip().split("\n"): + [addr, size, name] = line.split(" ") + functions.append((int(addr, 16), int(size, 16), name)) + + return functions + +class JitDecorator(gdb.FrameDecorator.FrameDecorator): + def __init__(self, fobj, name): + super(JitDecorator, self).__init__(fobj) + self.name = name + + def function(self): + return self.name + +class JitFilter: + """ + A backtrace filter which reads perf map files produced by cranelift-jit. + """ + + def __init__(self): + self.name = 'JitFilter' + self.enabled = True + self.priority = 0 + + gdb.current_progspace().frame_filters[self.name] = self + + # FIXME add an actual unwinder or somehow register JITed .eh_frame with gdb to avoid relying on + # gdb unwinder heuristics. + def filter(self, frame_iter): + for frame in frame_iter: + frame_addr = frame.inferior_frame().pc() + for (addr, size, name) in jit_functions(): + if frame_addr >= addr and frame_addr < addr + size: + yield JitDecorator(frame, name) + break + else: + yield frame + +JitFilter() diff --git a/scripts/setup_rust_fork.sh b/scripts/setup_rust_fork.sh index c16cb4e538fe..bb9f69b5c974 100644 --- a/scripts/setup_rust_fork.sh +++ b/scripts/setup_rust_fork.sh @@ -49,25 +49,45 @@ std-features = ["panic-unwind"] EOF cat <( - ); +@@ -2249,7 +2249,7 @@ pub fn parse_download_ci_llvm<'a>( } -- #[cfg(not(test))] + #[cfg(not(test))] - if b && dwn_ctx.is_running_on_ci && CiEnv::is_rust_lang_managed_ci_job() { -- // On rust-lang CI, we must always rebuild LLVM if there were any modifications to it -- panic!( -- "\`llvm.download-ci-llvm\` cannot be set to \`true\` on CI. Use \`if-unchanged\` instead." -- ); -- } -- - // If download-ci-llvm=true we also want to check that CI llvm is available - b && llvm::is_ci_llvm_available_for_target(&dwn_ctx.host_target, asserts) - } ++ if false && dwn_ctx.is_running_on_ci && CiEnv::is_rust_lang_managed_ci_job() { + // On rust-lang CI, we must always rebuild LLVM if there were any modifications to it + panic!( + "\`llvm.download-ci-llvm\` cannot be set to \`true\` on CI. Use \`if-unchanged\` instead." +diff --git a/src/build_helper/src/git.rs b/src/build_helper/src/git.rs +index 330fb465de..a4593ed96f 100644 +--- a/src/build_helper/src/git.rs ++++ b/src/build_helper/src/git.rs +@@ -218,7 +218,7 @@ pub fn get_closest_upstream_commit( + config: &GitConfig<'_>, + env: CiEnv, + ) -> Result, String> { +- let base = match env { ++ let base = match CiEnv::None { + CiEnv::None => "HEAD", + CiEnv::GitHubActions => { + // On CI, we should always have a non-upstream merge commit at the tip, EOF popd diff --git a/scripts/test_rustc_tests.sh b/scripts/test_rustc_tests.sh index b25269d1430a..4cad18f2a94f 100755 --- a/scripts/test_rustc_tests.sh +++ b/scripts/test_rustc_tests.sh @@ -10,11 +10,6 @@ pushd rust command -v rg >/dev/null 2>&1 || cargo install ripgrep -rm -r tests/ui/{lto/,linkage*} || true -for test in $(rg --files-with-matches "lto" tests/{codegen-units,ui,incremental}); do - rm $test -done - # should-fail tests don't work when compiletest is compiled with panic=abort for test in $(rg --files-with-matches "//@ should-fail" tests/{codegen-units,ui,incremental}); do rm $test @@ -38,6 +33,7 @@ rm tests/ui/simd/intrinsic/generic-arithmetic-pass.rs # unimplemented simd_funne rm -r tests/ui/scalable-vectors # scalable vectors are unsupported # exotic linkages +rm -r tests/ui/linkage* rm tests/incremental/hashes/function_interfaces.rs rm tests/incremental/hashes/statics.rs rm -r tests/run-make/naked-symbol-visibility @@ -45,11 +41,13 @@ rm -r tests/run-make/naked-symbol-visibility # variadic arguments rm tests/ui/abi/mir/mir_codegen_calls_variadic.rs # requires float varargs rm tests/ui/c-variadic/naked.rs # same +rm tests/ui/consts/const-eval/c-variadic.rs # same rm tests/ui/abi/variadic-ffi.rs # requires callee side vararg support rm -r tests/run-make/c-link-to-rust-va-list-fn # requires callee side vararg support rm tests/ui/c-variadic/valid.rs # same rm tests/ui/c-variadic/trait-method.rs # same rm tests/ui/c-variadic/inherent-method.rs # same +rm tests/ui/c-variadic/copy.rs # same rm tests/ui/sanitizer/kcfi-c-variadic.rs # same rm tests/ui/c-variadic/same-program-multiple-abis-x86_64.rs # variadics for calling conventions other than C unsupported rm tests/ui/delegation/fn-header.rs @@ -79,6 +77,10 @@ rm -r tests/ui/eii # EII not yet implemented rm -r tests/run-make/forced-unwind-terminate-pof # forced unwinding doesn't take precedence # requires LTO +rm -r tests/ui/lto +for test in $(rg --files-with-matches "lto" tests/{codegen-units,ui,incremental}); do + rm $test +done rm -r tests/run-make/cdylib rm -r tests/run-make/codegen-options-parsing rm -r tests/run-make/lto-* @@ -126,6 +128,14 @@ rm -r tests/run-make/notify-all-emit-artifacts rm -r tests/run-make/reset-codegen-1 rm -r tests/run-make/inline-always-many-cgu rm -r tests/run-make/intrinsic-unreachable +rm -r tests/run-make/artifact-incr-cache +rm -r tests/run-make/artifact-incr-cache-no-obj +rm -r tests/run-make/emit +rm -r tests/run-make/llvm-outputs +rm -r tests/run-make/panic-impl-transitive +rm -r tests/ui/debuginfo/debuginfo-emit-llvm-ir-and-split-debuginfo.rs +rm -r tests/ui/statics/issue-91050-1.rs +rm -r tests/ui/statics/issue-91050-2.rs # giving different but possibly correct results # ============================================= @@ -134,6 +144,7 @@ rm tests/ui/mir/mir_raw_fat_ptr.rs # same rm tests/ui/consts/issue-33537.rs # same rm tests/ui/consts/const-mut-refs-crate.rs # same rm tests/ui/abi/large-byval-align.rs # exceeds implementation limit of Cranelift +rm -r tests/run-make/short-ice # ICE backtrace begin/end marker mismatch # doesn't work due to the way the rustc test suite is invoked. # should work when using ./x.py test the way it is intended @@ -147,20 +158,15 @@ rm -r tests/run-make-cargo/panic-immediate-abort-codegen # same rm -r tests/run-make/missing-unstable-trait-bound # This disables support for unstable features, but running cg_clif needs some unstable features rm -r tests/run-make/const-trait-stable-toolchain # same rm -r tests/run-make/print-request-help-stable-unstable # same +rm -r tests/run-make/issue-149402-suggest-unresolve # same rm -r tests/run-make/incr-add-rust-src-component rm tests/ui/errors/remap-path-prefix-sysroot.rs # different sysroot source path rm -r tests/run-make/export/extern-opt # something about rustc version mismatches rm -r tests/run-make/export # same rm -r tests/ui/compiletest-self-test/compile-flags-incremental.rs # needs compiletest compiled with panic=unwind -rm tests/ui/async-await/in-trait/dont-project-to-specializable-projection.rs # something going wrong with stdlib source remapping -rm tests/ui/consts/miri_unleashed/drop.rs # same -rm tests/ui/error-emitter/multiline-removal-suggestion.rs # same -rm tests/ui/lint/lint-const-item-mutation.rs # same -rm tests/ui/lint/use-redundant/use-redundant-issue-71450.rs # same -rm tests/ui/lint/use-redundant/use-redundant-prelude-rust-2021.rs # same -rm tests/ui/specialization/const_trait_impl.rs # same -rm tests/ui/thir-print/offset_of.rs # same -rm tests/ui/traits/const-traits/const_closure-const_trait_impl-ice-113381.rs # same +rm -r tests/ui/extern/extern-types-field-offset.rs # expects /rustc/ rather than /rustc/FAKE_PREFIX +rm -r tests/ui/process/println-with-broken-pipe.rs # same +rm tests/codegen-units/item-collection/opaque-return-impls.rs # extra mono item. possibly due to other configuration # genuine bugs # ============ @@ -170,11 +176,7 @@ rm -r tests/run-make/panic-abort-eh_frame # .eh_frame emitted with panic=abort # bugs in the test suite # ====================== rm tests/ui/process/nofile-limit.rs # TODO some AArch64 linking issue -rm tests/ui/backtrace/synchronized-panic-handler.rs # missing needs-unwind annotation -rm tests/ui/lint/non-snake-case/lint-non-snake-case-crate.rs # same -rm tests/ui/async-await/async-drop/async-drop-initial.rs # same (rust-lang/rust#140493) rm -r tests/ui/codegen/equal-pointers-unequal # make incorrect assumptions about the location of stack variables -rm -r tests/run-make-cargo/rustdoc-scrape-examples-paths # FIXME(rust-lang/rust#145580) incr comp bug rm -r tests/incremental/extern_static/issue-49153.rs # assumes reference to undefined static gets optimized away rm tests/ui/intrinsics/panic-uninitialized-zeroed.rs # really slow with unoptimized libstd diff --git a/src/abi/mod.rs b/src/abi/mod.rs index 5a46f79e2ba0..133e7e26c0de 100644 --- a/src/abi/mod.rs +++ b/src/abi/mod.rs @@ -53,8 +53,10 @@ pub(crate) fn conv_to_call_conv( default_call_conv: CallConv, ) -> CallConv { match c { - CanonAbi::Rust | CanonAbi::C => default_call_conv, - CanonAbi::RustCold => CallConv::Cold, + CanonAbi::Rust | CanonAbi::RustCold | CanonAbi::C => default_call_conv, + + // Cranelift doesn't currently have anything for this. + CanonAbi::RustPreserveNone => default_call_conv, // Cranelift doesn't currently have anything for this. CanonAbi::RustPreserveNone => default_call_conv, From 6a1537830e393dfb937caf2e97d1c2f80049620f Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:10:37 +0000 Subject: [PATCH 0064/1059] Fix broken merge --- src/abi/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/abi/mod.rs b/src/abi/mod.rs index 133e7e26c0de..97a19b8976d3 100644 --- a/src/abi/mod.rs +++ b/src/abi/mod.rs @@ -58,9 +58,6 @@ pub(crate) fn conv_to_call_conv( // Cranelift doesn't currently have anything for this. CanonAbi::RustPreserveNone => default_call_conv, - // Cranelift doesn't currently have anything for this. - CanonAbi::RustPreserveNone => default_call_conv, - // Functions with this calling convention can only be called from assembly, but it is // possible to declare an `extern "custom"` block, so the backend still needs a calling // convention for declaring foreign functions. From ee75e9b59896b2cf098935b31da4fda011503af3 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:22:36 +0000 Subject: [PATCH 0065/1059] Format jit-helper.py --- scripts/jit-helpers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/jit-helpers.py b/scripts/jit-helpers.py index 1128521c0dfb..4542aef7cb52 100644 --- a/scripts/jit-helpers.py +++ b/scripts/jit-helpers.py @@ -1,5 +1,6 @@ import gdb + def jitmap_raw(): pid = gdb.selected_inferior().pid jitmap_file = open("/tmp/perf-%d.map" % (pid,), "r") @@ -7,6 +8,7 @@ def jitmap_raw(): jitmap_file.close() return jitmap + def jit_functions(): jitmap = jitmap_raw() @@ -17,6 +19,7 @@ def jit_functions(): return functions + class JitDecorator(gdb.FrameDecorator.FrameDecorator): def __init__(self, fobj, name): super(JitDecorator, self).__init__(fobj) @@ -25,13 +28,14 @@ class JitDecorator(gdb.FrameDecorator.FrameDecorator): def function(self): return self.name + class JitFilter: """ A backtrace filter which reads perf map files produced by cranelift-jit. """ def __init__(self): - self.name = 'JitFilter' + self.name = "JitFilter" self.enabled = True self.priority = 0 @@ -42,11 +46,12 @@ class JitFilter: def filter(self, frame_iter): for frame in frame_iter: frame_addr = frame.inferior_frame().pc() - for (addr, size, name) in jit_functions(): + for addr, size, name in jit_functions(): if frame_addr >= addr and frame_addr < addr + size: yield JitDecorator(frame, name) break else: yield frame + JitFilter() From 8b83d4b4a6fe563fdf4b89c7b4dfe2c1fb0048ce Mon Sep 17 00:00:00 2001 From: Ada Bohm Date: Wed, 18 Feb 2026 16:44:18 +0100 Subject: [PATCH 0066/1059] Adds recursion limit into FindParamInClause --- .../src/solve/assembly/mod.rs | 10 +++++++++- .../find-param-recursion-issue-152716.rs | 17 +++++++++++++++++ .../find-param-recursion-issue-152716.stderr | 7 +++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/ui/traits/next-solver/find-param-recursion-issue-152716.rs create mode 100644 tests/ui/traits/next-solver/find-param-recursion-issue-152716.stderr diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index 63f246db8a5f..bf59f7cab8e4 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -1233,6 +1233,7 @@ fn characterize_param_env_assumption( ecx: self, param_env, universes: vec![], + recursion_depth: 0, }) { ControlFlow::Break(Err(NoSolution)) => Err(NoSolution), ControlFlow::Break(Ok(())) => Ok(CandidateSource::ParamEnv(ParamEnvSource::NonGlobal)), @@ -1245,6 +1246,7 @@ struct FindParamInClause<'a, 'b, D: SolverDelegate, I: Interner> { ecx: &'a mut EvalCtxt<'b, D>, param_env: I::ParamEnv, universes: Vec>, + recursion_depth: usize, } impl TypeVisitor for FindParamInClause<'_, '_, D, I> @@ -1274,7 +1276,13 @@ fn visit_ty(&mut self, ty: I::Ty) -> Self::Result { ControlFlow::Continue(()) } } else if ty.has_type_flags(TypeFlags::HAS_PLACEHOLDER | TypeFlags::HAS_RE_INFER) { - ty.super_visit_with(self) + self.recursion_depth += 1; + if self.recursion_depth > self.ecx.cx().recursion_limit() { + return ControlFlow::Break(Err(NoSolution)); + } + let result = ty.super_visit_with(self); + self.recursion_depth -= 1; + result } else { ControlFlow::Continue(()) } diff --git a/tests/ui/traits/next-solver/find-param-recursion-issue-152716.rs b/tests/ui/traits/next-solver/find-param-recursion-issue-152716.rs new file mode 100644 index 000000000000..410781e955d7 --- /dev/null +++ b/tests/ui/traits/next-solver/find-param-recursion-issue-152716.rs @@ -0,0 +1,17 @@ +//@ compile-flags: -Znext-solver +//~^ ERROR overflow normalizing the associated type `>::Assoc` [E0275] + +// Regression test for . + +trait Trait {} +trait Proj<'a> { + type Assoc; +} +fn foo() +where + T: for<'a> Proj<'a, Assoc = for<'b> fn(>::Assoc)>, + (): Trait<>::Assoc> +{ +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/find-param-recursion-issue-152716.stderr b/tests/ui/traits/next-solver/find-param-recursion-issue-152716.stderr new file mode 100644 index 000000000000..9886e303eaf7 --- /dev/null +++ b/tests/ui/traits/next-solver/find-param-recursion-issue-152716.stderr @@ -0,0 +1,7 @@ +error[E0275]: overflow normalizing the associated type `>::Assoc` + | + = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`find_param_recursion_issue_152716`) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0275`. From 4da823ba4be74cdd840c6db854ee57cc0c864d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Miku=C5=82a?= Date: Thu, 19 Feb 2026 22:11:58 +0100 Subject: [PATCH 0067/1059] Do not enable split debuginfo for windows-gnu Because rustc doesn't handle split debuginfo for these targets, enabling that option causes them to be missing some of the debuginfo. --- bootstrap.example.toml | 7 ++++--- src/bootstrap/src/core/config/target_selection.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index e0cbb0c0e747..432a68140361 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -1008,9 +1008,10 @@ # its historical default, but when compiling the compiler itself, we skip it by # default since we know it's safe to do so in that case. # -# On Windows platforms, packed debuginfo is the only supported option, -# producing a `.pdb` file. -#split-debuginfo = if linux { off } else if windows { packed } else if apple { unpacked } +# On Windows MSVC platforms, packed debuginfo is the only supported option, +# producing a `.pdb` file. On Windows GNU rustc doesn't support splitting debuginfo, +# and enabling it causes issues. +#split-debuginfo = if linux || windows-gnu { off } else if windows-msvc { packed } else if apple { unpacked } # Path to the `llvm-config` binary of the installation of a custom LLVM to link # against. Note that if this is specified we don't compile LLVM at all for this diff --git a/src/bootstrap/src/core/config/target_selection.rs b/src/bootstrap/src/core/config/target_selection.rs index 47f6d6f386df..8457607b897d 100644 --- a/src/bootstrap/src/core/config/target_selection.rs +++ b/src/bootstrap/src/core/config/target_selection.rs @@ -142,7 +142,7 @@ impl SplitDebuginfo { pub fn default_for_platform(target: TargetSelection) -> Self { if target.contains("apple") { SplitDebuginfo::Unpacked - } else if target.is_windows() { + } else if target.is_msvc() { SplitDebuginfo::Packed } else { SplitDebuginfo::Off From 11953d71f5e18701e6a9f7c10f3bb612422e726b Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:48:58 +0000 Subject: [PATCH 0068/1059] Rustup to rustc 1.95.0-nightly (7f99507f5 2026-02-19) --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index fe967c84352c..62e07412a7f8 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2026-02-18" +channel = "nightly-2026-02-20" components = ["rust-src", "rustc-dev", "llvm-tools", "rustfmt"] profile = "minimal" From 96e7213d310861698667e36b5568147c9217c84e Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:01:07 +0000 Subject: [PATCH 0069/1059] Fix rustc test suite --- scripts/setup_rust_fork.sh | 18 +++++++++--------- scripts/test_rustc_tests.sh | 1 - 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/scripts/setup_rust_fork.sh b/scripts/setup_rust_fork.sh index bb9f69b5c974..2ca0c3cab910 100644 --- a/scripts/setup_rust_fork.sh +++ b/scripts/setup_rust_fork.sh @@ -63,18 +63,18 @@ index 2e16f2cf27..3ac3df99a8 100644 # Note that RUSTFLAGS_BOOTSTRAP should always be added to the end of # RUSTFLAGS, since that causes RUSTFLAGS_BOOTSTRAP to override RUSTFLAGS. diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs -index a656927b1f6..44fc5546fac 100644 +index bc68bfe396..00143ef3ed 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs -@@ -2249,7 +2249,7 @@ pub fn parse_download_ci_llvm<'a>( - } +@@ -2230,7 +2230,7 @@ pub fn download_ci_rustc_commit<'a>( + return None; + } - #[cfg(not(test))] -- if b && dwn_ctx.is_running_on_ci && CiEnv::is_rust_lang_managed_ci_job() { -+ if false && dwn_ctx.is_running_on_ci && CiEnv::is_rust_lang_managed_ci_job() { - // On rust-lang CI, we must always rebuild LLVM if there were any modifications to it - panic!( - "\`llvm.download-ci-llvm\` cannot be set to \`true\` on CI. Use \`if-unchanged\` instead." +- if dwn_ctx.is_running_on_ci() { ++ if false && dwn_ctx.is_running_on_ci() { + eprintln!("CI rustc commit matches with HEAD and we are in CI."); + eprintln!( + "\`rustc.download-ci\` functionality will be skipped as artifacts are not available." diff --git a/src/build_helper/src/git.rs b/src/build_helper/src/git.rs index 330fb465de..a4593ed96f 100644 --- a/src/build_helper/src/git.rs diff --git a/scripts/test_rustc_tests.sh b/scripts/test_rustc_tests.sh index 4cad18f2a94f..798bef7ba3e3 100755 --- a/scripts/test_rustc_tests.sh +++ b/scripts/test_rustc_tests.sh @@ -20,7 +20,6 @@ for test in $(rg -i --files-with-matches "//(\[\w+\])?~[^\|]*\s*ERR|//@ error-pa done git checkout -- tests/ui/issues/auxiliary/issue-3136-a.rs # contains //~ERROR, but shouldn't be removed -git checkout -- tests/ui/proc-macro/pretty-print-hack/ git checkout -- tests/ui/entry-point/auxiliary/bad_main_functions.rs # missing features From d31b87f20b28d3cc2e4f26bc3de465aa209de670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Freitas?= Date: Mon, 12 Jan 2026 01:30:00 -0300 Subject: [PATCH 0070/1059] Add Apple AArch64 support for outline atomics Add support for ELF and Mach-O AArch64 relocation specifiers for symbol access, enabling outline-atomics to work on Apple targets. Remove the Linux-only OS gate in tests. Co-authored-by: Trevor Gross --- .../builtins-test/tests/lse.rs | 2 +- .../src/aarch64_outline_atomics.rs | 69 +++++++++++++++++-- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/library/compiler-builtins/builtins-test/tests/lse.rs b/library/compiler-builtins/builtins-test/tests/lse.rs index 56891be8a8ac..854ea021b4b1 100644 --- a/library/compiler-builtins/builtins-test/tests/lse.rs +++ b/library/compiler-builtins/builtins-test/tests/lse.rs @@ -1,6 +1,6 @@ #![feature(decl_macro)] // so we can use pub(super) #![feature(macro_metavar_expr_concat)] -#![cfg(all(target_arch = "aarch64", target_os = "linux"))] +#![cfg(target_arch = "aarch64")] /// Translate a byte size to a Rust type. macro int_ty { diff --git a/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs b/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs index df0cf7650222..3245523280d7 100644 --- a/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs +++ b/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs @@ -135,18 +135,73 @@ macro_rules! stxp { }; } +// The AArch64 assembly syntax for relocation specifiers +// when accessing symbols changes depending on the target executable format. +// In ELF (used in Linux), we have a prefix notation surrounded by colons (:specifier:sym), +// while in Mach-O object files (used in MacOS), a postfix notation is used (sym@specifier). + +/// AArch64 ELF position-independent addressing: +/// +/// adrp xN, symbol +/// add xN, xN, :lo12:symbol +/// +/// The :lo12: modifier selects the low 12 bits of the symbol address +/// and emits an ELF relocation such as R_AARCH64_ADD_ABS_LO12_NC. +/// +/// Defined by the AArch64 ELF psABI. +/// See: . +#[cfg(not(target_vendor = "apple"))] +macro_rules! sym { + ($sym:literal) => { + $sym + }; +} + +#[cfg(not(target_vendor = "apple"))] +macro_rules! sym_off { + ($sym:literal) => { + concat!(":lo12:", $sym) + }; +} + +/// Mach-O ARM64 relocation types: +/// ARM64_RELOC_PAGE21 +/// ARM64_RELOC_PAGEOFF12 +/// +/// These relocations implement the @PAGE / @PAGEOFF split used by +/// adrp + add sequences on Apple platforms. +/// +/// adrp xN, symbol@PAGE -> ARM64_RELOC_PAGE21 +/// add xN, xN, symbol@PAGEOFF -> ARM64_RELOC_PAGEOFF12 +/// +/// Relocation types defined by Apple in XNU: . +/// See: . +#[cfg(target_vendor = "apple")] +macro_rules! sym { + ($sym:literal) => { + concat!($sym, "@PAGE") + }; +} + +#[cfg(target_vendor = "apple")] +macro_rules! sym_off { + ($sym:literal) => { + concat!($sym, "@PAGEOFF") + }; +} + // If supported, perform the requested LSE op and return, or fallthrough. macro_rules! try_lse_op { ($op: literal, $ordering:ident, $bytes:tt, $($reg:literal,)* [ $mem:ident ] ) => { concat!( - ".arch_extension lse; ", - "adrp x16, {have_lse}; ", - "ldrb w16, [x16, :lo12:{have_lse}]; ", - "cbz w16, 8f; ", + ".arch_extension lse\n", + concat!("adrp x16, ", sym!("{have_lse}"), "\n"), + concat!("ldrb w16, [x16, ", sym_off!("{have_lse}"), "]\n"), + "cbz w16, 8f\n", // LSE_OP s(reg),* [$mem] - concat!(lse!($op, $ordering, $bytes), $( " ", reg!($bytes, $reg), ", " ,)* "[", stringify!($mem), "]; ",), - "ret; ", - "8:" + concat!(lse!($op, $ordering, $bytes), $( " ", reg!($bytes, $reg), ", " ,)* "[", stringify!($mem), "]\n",), + "ret + 8:" ) }; } From 010e20923b4b660d567b6d260fcb408a337e7cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Freitas?= Date: Thu, 22 Jan 2026 00:39:03 -0300 Subject: [PATCH 0071/1059] Change atomic function signatures to use unsigned integer types Replace signed integer types (i8, i16, i32, i64, i128) with their unsigned equivalents (u8, u16, u32, u64, u128) in function declarations and the int_ty! macro, avoiding the need for sign extension in emitted assembly. --- .../builtins-test/tests/lse.rs | 10 +++++----- .../src/aarch64_outline_atomics.rs | 20 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/library/compiler-builtins/builtins-test/tests/lse.rs b/library/compiler-builtins/builtins-test/tests/lse.rs index 854ea021b4b1..b727a223e5c9 100644 --- a/library/compiler-builtins/builtins-test/tests/lse.rs +++ b/library/compiler-builtins/builtins-test/tests/lse.rs @@ -4,11 +4,11 @@ /// Translate a byte size to a Rust type. macro int_ty { - (1) => { i8 }, - (2) => { i16 }, - (4) => { i32 }, - (8) => { i64 }, - (16) => { i128 } + (1) => { u8 }, + (2) => { u16 }, + (4) => { u32 }, + (8) => { u64 }, + (16) => { u128 } } mod cas { diff --git a/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs b/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs index 3245523280d7..54232fe5c62e 100644 --- a/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs +++ b/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs @@ -37,11 +37,11 @@ pub extern "C" fn __rust_enable_lse() { /// Translate a byte size to a Rust type. #[rustfmt::skip] macro_rules! int_ty { - (1) => { i8 }; - (2) => { i16 }; - (4) => { i32 }; - (8) => { i64 }; - (16) => { i128 }; + (1) => { u8 }; + (2) => { u16 }; + (4) => { u32 }; + (8) => { u64 }; + (16) => { u128 }; } /// Given a byte size and a register number, return a register of the appropriate size. @@ -258,15 +258,15 @@ macro_rules! compare_and_swap { }; } -// i128 uses a completely different impl, so it has its own macro. -macro_rules! compare_and_swap_i128 { +// u128 uses a completely different impl, so it has its own macro. +macro_rules! compare_and_swap_u128 { ($ordering:ident, $name:ident) => { intrinsics! { #[maybe_use_optimized_c_shim] #[unsafe(naked)] pub unsafe extern "C" fn $name ( - expected: i128, desired: i128, ptr: *mut i128 - ) -> i128 { + expected: u128, desired: u128, ptr: *mut u128 + ) -> u128 { core::arch::naked_asm! { // CASP x0, x1, x2, x3, [x4]; if LSE supported. try_lse_op!("cas", $ordering, 16, 0, 1, 2, 3, [x4]), @@ -446,7 +446,7 @@ macro_rules! foreach_ldset { } foreach_cas!(compare_and_swap); -foreach_cas16!(compare_and_swap_i128); +foreach_cas16!(compare_and_swap_u128); foreach_swp!(swap); foreach_ldadd!(add); foreach_ldclr!(and); From 008f3134668f667a2430008131452d7cbacf2354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Freitas?= Date: Mon, 12 Jan 2026 02:30:22 -0300 Subject: [PATCH 0072/1059] Add comprehensive tests for AArch64 atomics with and without LSE Enable aarch64 outline-atomics in tests via the mangled-names feature gate, and add comprehensive compare-and-swap tests that run both with LSE enabled and disabled. Improve assertion messages to better describe expected behavior. Co-authored-by: Trevor Gross --- .../builtins-test/tests/lse.rs | 102 +++++++++++++----- .../src/aarch64_outline_atomics.rs | 13 +++ .../compiler-builtins/src/lib.rs | 7 +- 3 files changed, 93 insertions(+), 29 deletions(-) diff --git a/library/compiler-builtins/builtins-test/tests/lse.rs b/library/compiler-builtins/builtins-test/tests/lse.rs index b727a223e5c9..03fe9467a2fe 100644 --- a/library/compiler-builtins/builtins-test/tests/lse.rs +++ b/library/compiler-builtins/builtins-test/tests/lse.rs @@ -2,6 +2,45 @@ #![feature(macro_metavar_expr_concat)] #![cfg(target_arch = "aarch64")] +use std::sync::Mutex; + +use compiler_builtins::aarch64_outline_atomics::{get_have_lse_atomics, set_have_lse_atomics}; +use compiler_builtins::int::{Int, MinInt}; +use compiler_builtins::{foreach_bytes, foreach_ordering}; + +#[track_caller] +fn with_maybe_lse_atomics(use_lse: bool, f: impl FnOnce()) { + // Ensure tests run in parallel don't interleave global settings + static LOCK: Mutex<()> = Mutex::new(()); + let _g = LOCK.lock().unwrap(); + let old = get_have_lse_atomics(); + // safety: as the caller of the unsafe fn `set_have_lse_atomics`, we + // have to ensure the CPU supports LSE. This is why we make this assertion. + if use_lse || old { + assert!(std::arch::is_aarch64_feature_detected!("lse")); + } + unsafe { set_have_lse_atomics(use_lse) }; + f(); + unsafe { set_have_lse_atomics(old) }; +} + +pub fn run_fuzz_tests_with_lse_variants(n: u32, f: F) +where + ::Unsigned: Int, +{ + // We use `fuzz_2` because our subject function `f` requires two inputs + let test_fn = || { + builtins_test::fuzz_2(n, f); + }; + // Always run without LSE + with_maybe_lse_atomics(false, test_fn); + + // Conditionally run with LSE + if std::arch::is_aarch64_feature_detected!("lse") { + with_maybe_lse_atomics(true, test_fn); + } +} + /// Translate a byte size to a Rust type. macro int_ty { (1) => { u8 }, @@ -15,16 +54,17 @@ mod cas { pub(super) macro test($_ordering:ident, $bytes:tt, $name:ident) { #[test] fn $name() { - builtins_test::fuzz_2(10000, |expected: super::int_ty!($bytes), new| { + crate::run_fuzz_tests_with_lse_variants(10000, |expected: super::int_ty!($bytes), new| { let mut target = expected.wrapping_add(10); + let ret: super::int_ty!($bytes) = unsafe { + compiler_builtins::aarch64_outline_atomics::$name::$name( + expected, + new, + &mut target, + ) + }; assert_eq!( - unsafe { - compiler_builtins::aarch64_outline_atomics::$name::$name( - expected, - new, - &mut target, - ) - }, + ret, expected.wrapping_add(10), "return value should always be the previous value", ); @@ -35,15 +75,17 @@ fn $name() { ); target = expected; + let ret: super::int_ty!($bytes) = unsafe { + compiler_builtins::aarch64_outline_atomics::$name::$name( + expected, + new, + &mut target, + ) + }; assert_eq!( - unsafe { - compiler_builtins::aarch64_outline_atomics::$name::$name( - expected, - new, - &mut target, - ) - }, - expected + ret, + expected, + "the new return value should always be the previous value (i.e. the first parameter passed to the function)", ); assert_eq!(target, new, "should have updated target"); }); @@ -59,16 +101,21 @@ mod swap { pub(super) macro test($_ordering:ident, $bytes:tt, $name:ident) { #[test] fn $name() { - builtins_test::fuzz_2(10000, |left: super::int_ty!($bytes), mut right| { - let orig_right = right; - assert_eq!( - unsafe { - compiler_builtins::aarch64_outline_atomics::$name::$name(left, &mut right) - }, - orig_right - ); - assert_eq!(left, right); - }); + crate::run_fuzz_tests_with_lse_variants( + 10000, + |left: super::int_ty!($bytes), mut right| { + let orig_right = right; + assert_eq!( + unsafe { + compiler_builtins::aarch64_outline_atomics::$name::$name( + left, &mut right, + ) + }, + orig_right + ); + assert_eq!(left, right); + }, + ); } } } @@ -80,7 +127,7 @@ mod $mod { ($_ordering:ident, $bytes:tt, $name:ident) => { #[test] fn $name() { - builtins_test::fuzz_2(10000, |old, val| { + crate::run_fuzz_tests_with_lse_variants(10000, |old, val| { let mut target = old; let op: fn(super::int_ty!($bytes), super::int_ty!($bytes)) -> _ = $($op)*; let expected = op(old, val); @@ -98,7 +145,6 @@ fn $name() { test_op!(clr, |left, right| left & !right); test_op!(xor, std::ops::BitXor::bitxor); test_op!(or, std::ops::BitOr::bitor); -use compiler_builtins::{foreach_bytes, foreach_ordering}; compiler_builtins::foreach_cas!(cas::test); compiler_builtins::foreach_cas16!(test_cas16); compiler_builtins::foreach_swp!(swap::test); diff --git a/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs b/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs index 54232fe5c62e..100b67150772 100644 --- a/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs +++ b/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs @@ -34,6 +34,19 @@ pub extern "C" fn __rust_enable_lse() { } } +/// Function to enable/disable LSE. To be used only for testing purposes. +#[cfg(feature = "mangled-names")] +pub unsafe fn set_have_lse_atomics(has_lse: bool) { + let lse_flag = if has_lse { 1 } else { 0 }; + HAVE_LSE_ATOMICS.store(lse_flag, Ordering::Relaxed); +} + +/// Function to obtain whether LSE is enabled or not. To be used only for testing purposes. +#[cfg(feature = "mangled-names")] +pub fn get_have_lse_atomics() -> bool { + HAVE_LSE_ATOMICS.load(Ordering::Relaxed) != 0 +} + /// Translate a byte size to a Rust type. #[rustfmt::skip] macro_rules! int_ty { diff --git a/library/compiler-builtins/compiler-builtins/src/lib.rs b/library/compiler-builtins/compiler-builtins/src/lib.rs index 80395a4738eb..a027cd978af2 100644 --- a/library/compiler-builtins/compiler-builtins/src/lib.rs +++ b/library/compiler-builtins/compiler-builtins/src/lib.rs @@ -57,7 +57,12 @@ #[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))] pub mod aarch64; -#[cfg(all(target_arch = "aarch64", target_feature = "outline-atomics"))] +// Note that we enable the module on "mangled-names" because that is the default feature +// in the builtins-test tests. So this is a way of enabling the module during testing. +#[cfg(all( + target_arch = "aarch64", + any(target_feature = "outline-atomics", feature = "mangled-names") +))] pub mod aarch64_outline_atomics; #[cfg(target_arch = "avr")] From 00ea98816d1ccd5c7c5d4e6aee287e656e2d1d60 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 20 Feb 2026 16:43:15 -0500 Subject: [PATCH 0073/1059] test: Gate LSE tests by mangled-names Resolve a CI issue when running tests with `--no-default-features`. --- library/compiler-builtins/builtins-test/tests/lse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/compiler-builtins/builtins-test/tests/lse.rs b/library/compiler-builtins/builtins-test/tests/lse.rs index 03fe9467a2fe..c45fc161b6c4 100644 --- a/library/compiler-builtins/builtins-test/tests/lse.rs +++ b/library/compiler-builtins/builtins-test/tests/lse.rs @@ -1,6 +1,6 @@ #![feature(decl_macro)] // so we can use pub(super) #![feature(macro_metavar_expr_concat)] -#![cfg(target_arch = "aarch64")] +#![cfg(all(target_arch = "aarch64", feature = "mangled-names"))] use std::sync::Mutex; From 7ba44756aa8ef5bfca675e8424e6043ce6d609b9 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 05:10:16 -0600 Subject: [PATCH 0074/1059] c-b: Clean up unused imports These are remnants of historical chkstk implementations that are no longer relevant, so clean up the imports here. --- library/compiler-builtins/compiler-builtins/src/aarch64.rs | 4 ---- library/compiler-builtins/compiler-builtins/src/x86.rs | 4 ---- library/compiler-builtins/compiler-builtins/src/x86_64.rs | 4 ---- 3 files changed, 12 deletions(-) diff --git a/library/compiler-builtins/compiler-builtins/src/aarch64.rs b/library/compiler-builtins/compiler-builtins/src/aarch64.rs index 1b230a214eef..2c12cb7f3095 100644 --- a/library/compiler-builtins/compiler-builtins/src/aarch64.rs +++ b/library/compiler-builtins/compiler-builtins/src/aarch64.rs @@ -1,7 +1,3 @@ -#![allow(unused_imports)] - -use core::intrinsics; - intrinsics! { #[unsafe(naked)] #[cfg(any(all(windows, target_env = "gnu"), target_os = "uefi"))] diff --git a/library/compiler-builtins/compiler-builtins/src/x86.rs b/library/compiler-builtins/compiler-builtins/src/x86.rs index 1a3c41860945..45f4ba207e6e 100644 --- a/library/compiler-builtins/compiler-builtins/src/x86.rs +++ b/library/compiler-builtins/compiler-builtins/src/x86.rs @@ -1,7 +1,3 @@ -#![allow(unused_imports)] - -use core::intrinsics; - // NOTE These functions are implemented using assembly because they use a custom // calling convention which can't be implemented using a normal Rust function diff --git a/library/compiler-builtins/compiler-builtins/src/x86_64.rs b/library/compiler-builtins/compiler-builtins/src/x86_64.rs index 99a527ee9ac5..a3a5c8f7e10f 100644 --- a/library/compiler-builtins/compiler-builtins/src/x86_64.rs +++ b/library/compiler-builtins/compiler-builtins/src/x86_64.rs @@ -1,7 +1,3 @@ -#![allow(unused_imports)] - -use core::intrinsics; - // NOTE These functions are implemented using assembly because they use a custom // calling convention which can't be implemented using a normal Rust function From c5e7c934061869ecc494bc8a3557a0f3e34558c2 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 05:53:06 -0600 Subject: [PATCH 0075/1059] c-b: Remove `#![feature(naked_functions)]` This feature has been stable for a while. --- library/compiler-builtins/compiler-builtins/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/library/compiler-builtins/compiler-builtins/src/lib.rs b/library/compiler-builtins/compiler-builtins/src/lib.rs index a027cd978af2..07960222f20f 100644 --- a/library/compiler-builtins/compiler-builtins/src/lib.rs +++ b/library/compiler-builtins/compiler-builtins/src/lib.rs @@ -7,7 +7,6 @@ #![feature(compiler_builtins)] #![feature(core_intrinsics)] #![feature(linkage)] -#![feature(naked_functions)] #![feature(repr_simd)] #![feature(macro_metavar_expr_concat)] #![feature(rustc_attrs)] From 2b76199bfb822e356d9faff67e43f5769ef31beb Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 05:57:14 -0600 Subject: [PATCH 0076/1059] c-b: Remove unused features in tests --- library/compiler-builtins/builtins-test-intrinsics/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/compiler-builtins/builtins-test-intrinsics/src/main.rs b/library/compiler-builtins/builtins-test-intrinsics/src/main.rs index b9d19ea77256..848f00de44d1 100644 --- a/library/compiler-builtins/builtins-test-intrinsics/src/main.rs +++ b/library/compiler-builtins/builtins-test-intrinsics/src/main.rs @@ -3,10 +3,8 @@ // compiling a C implementation and forget to implement that intrinsic in Rust, this file will fail // to link due to the missing intrinsic (symbol). -#![allow(unused_features)] #![allow(internal_features)] #![deny(dead_code)] -#![feature(allocator_api)] #![feature(f128)] #![feature(f16)] #![feature(lang_items)] From 8b9bbbccef2da23cb195a3d2d783f886002611ac Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 04:37:49 -0600 Subject: [PATCH 0077/1059] c-b: Replace `core::intrinsics::{cold_path, likely}` Replace the use of intrinsics with `core::hint::cold_path` which has been unstably available for a while, and is on track to become stable in the next release. --- .../compiler-builtins/src/mem/impls.rs | 13 +++++++++---- .../compiler-builtins/libm/src/math/support/mod.rs | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/library/compiler-builtins/compiler-builtins/src/mem/impls.rs b/library/compiler-builtins/compiler-builtins/src/mem/impls.rs index da16dee25ce5..9681f5d6dac6 100644 --- a/library/compiler-builtins/compiler-builtins/src/mem/impls.rs +++ b/library/compiler-builtins/compiler-builtins/src/mem/impls.rs @@ -16,7 +16,8 @@ // crate doing wrapping pointer arithmetic with a method that must not wrap won't be the problem if // something does go wrong at runtime. use core::ffi::c_int; -use core::intrinsics::likely; + +use crate::support::cold_path; const WORD_SIZE: usize = core::mem::size_of::(); const WORD_MASK: usize = WORD_SIZE - 1; @@ -209,9 +210,10 @@ unsafe fn copy_forward_misaligned_words(dest: *mut u8, src: *const u8, n: usize) let n_words = n & !WORD_MASK; let src_misalignment = src as usize & WORD_MASK; - if likely(src_misalignment == 0) { + if src_misalignment == 0 { copy_forward_aligned_words(dest, src, n_words); } else { + cold_path(); copy_forward_misaligned_words(dest, src, n_words); } dest = dest.wrapping_add(n_words); @@ -327,9 +329,10 @@ unsafe fn copy_backward_misaligned_words(dest: *mut u8, src: *const u8, n: usize let n_words = n & !WORD_MASK; let src_misalignment = src as usize & WORD_MASK; - if likely(src_misalignment == 0) { + if src_misalignment == 0 { copy_backward_aligned_words(dest, src, n_words); } else { + cold_path(); copy_backward_misaligned_words(dest, src, n_words); } dest = dest.wrapping_sub(n_words); @@ -368,7 +371,7 @@ pub unsafe fn set_bytes_words(s: *mut u8, c: u8, n: usize) { } } - if likely(n >= WORD_COPY_THRESHOLD) { + if n >= WORD_COPY_THRESHOLD { // Align s // Because of n >= 2 * WORD_SIZE, dst_misalignment < n let misalignment = (s as usize).wrapping_neg() & WORD_MASK; @@ -380,6 +383,8 @@ pub unsafe fn set_bytes_words(s: *mut u8, c: u8, n: usize) { set_bytes_words(s, c, n_words); s = s.wrapping_add(n_words); n -= n_words; + } else { + cold_path(); } set_bytes_bytes(s, c, n); } diff --git a/library/compiler-builtins/libm/src/math/support/mod.rs b/library/compiler-builtins/libm/src/math/support/mod.rs index 15ab010dc8d5..128ba36e7f01 100644 --- a/library/compiler-builtins/libm/src/math/support/mod.rs +++ b/library/compiler-builtins/libm/src/math/support/mod.rs @@ -35,5 +35,5 @@ /// Hint to the compiler that the current path is cold. pub fn cold_path() { #[cfg(intrinsics_enabled)] - core::intrinsics::cold_path(); + core::hint::cold_path(); } From b4ec29c9939909ae4ed0157ab8bfa2eb9f099b7a Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 05:10:16 -0600 Subject: [PATCH 0078/1059] libm: Replace `div!` with `unchecked_div_*` `div!` looks innocuous but is actually unsafe. Replace it with function calls that require us to actually use `unsafe`, and add the precondition notes. These can likely be removed completely in the near future. There is one case in `rem_pio2_large` and one in `exp2` where preconditions aren't as easy to define, but fortunately it seems like we are able to just use regular division there without affecting codegen. --- .../compiler-builtins/libm/src/math/exp2.rs | 2 +- .../libm/src/math/lgamma_r.rs | 4 ++- .../libm/src/math/lgammaf_r.rs | 4 ++- .../compiler-builtins/libm/src/math/mod.rs | 18 ---------- .../libm/src/math/rem_pio2_large.rs | 2 +- .../libm/src/math/support/mod.rs | 33 +++++++++++++++++++ .../compiler-builtins/libm/src/math/tgamma.rs | 4 ++- 7 files changed, 44 insertions(+), 23 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/exp2.rs b/library/compiler-builtins/libm/src/math/exp2.rs index 08b71587f6de..d4c9e9665200 100644 --- a/library/compiler-builtins/libm/src/math/exp2.rs +++ b/library/compiler-builtins/libm/src/math/exp2.rs @@ -380,7 +380,7 @@ pub fn exp2(mut x: f64) -> f64 { let mut i0 = ui as u32; i0 = i0.wrapping_add(TBLSIZE as u32 / 2); let ku = i0 / TBLSIZE as u32 * TBLSIZE as u32; - let ki = div!(ku as i32, TBLSIZE as i32); + let ki = (ku as i32) / TBLSIZE as i32; i0 %= TBLSIZE as u32; let uf = f64::from_bits(ui) - redux; let mut z = x - uf; diff --git a/library/compiler-builtins/libm/src/math/lgamma_r.rs b/library/compiler-builtins/libm/src/math/lgamma_r.rs index 38eb270f6839..f3c6fef1464d 100644 --- a/library/compiler-builtins/libm/src/math/lgamma_r.rs +++ b/library/compiler-builtins/libm/src/math/lgamma_r.rs @@ -79,6 +79,7 @@ */ use super::{floor, k_cos, k_sin, log}; +use crate::support::unchecked_div_i32; const PI: f64 = 3.14159265358979311600e+00; /* 0x400921FB, 0x54442D18 */ const A0: f64 = 7.72156649015328655494e-02; /* 0x3FB3C467, 0xE37DB0C8 */ @@ -152,7 +153,8 @@ fn sin_pi(mut x: f64) -> f64 { x = 2.0 * (x * 0.5 - floor(x * 0.5)); /* x mod 2.0 */ n = (x * 4.0) as i32; - n = div!(n + 1, 2); + // SAFETY: nonzero divisor, nonnegative dividend (`n < 8`). + n = unsafe { unchecked_div_i32(n + 1, 2) }; x -= (n as f64) * 0.5; x *= PI; diff --git a/library/compiler-builtins/libm/src/math/lgammaf_r.rs b/library/compiler-builtins/libm/src/math/lgammaf_r.rs index a0b6a678a670..5c087f14d7df 100644 --- a/library/compiler-builtins/libm/src/math/lgammaf_r.rs +++ b/library/compiler-builtins/libm/src/math/lgammaf_r.rs @@ -14,6 +14,7 @@ */ use super::{floorf, k_cosf, k_sinf, logf}; +use crate::support::unchecked_div_isize; const PI: f32 = 3.1415927410e+00; /* 0x40490fdb */ const A0: f32 = 7.7215664089e-02; /* 0x3d9e233f */ @@ -88,7 +89,8 @@ fn sin_pi(mut x: f32) -> f32 { x = 2.0 * (x * 0.5 - floorf(x * 0.5)); /* x mod 2.0 */ n = (x * 4.0) as isize; - n = div!(n + 1, 2); + // SAFETY: nonzero divisor, nonnegative dividend (`n < 8`). + n = unsafe { unchecked_div_isize(n + 1, 2) }; y = (x as f64) - (n as f64) * 0.5; y *= 3.14159265358979323846; match n { diff --git a/library/compiler-builtins/libm/src/math/mod.rs b/library/compiler-builtins/libm/src/math/mod.rs index b34ab8465349..4bee4478164a 100644 --- a/library/compiler-builtins/libm/src/math/mod.rs +++ b/library/compiler-builtins/libm/src/math/mod.rs @@ -58,24 +58,6 @@ macro_rules! i { }; } -// Temporary macro to avoid panic codegen for division (in debug mode too). At -// the time of this writing this is only used in a few places, and once -// rust-lang/rust#72751 is fixed then this macro will no longer be necessary and -// the native `/` operator can be used and panics won't be codegen'd. -#[cfg(any(debug_assertions, not(intrinsics_enabled)))] -macro_rules! div { - ($a:expr, $b:expr) => { - $a / $b - }; -} - -#[cfg(all(not(debug_assertions), intrinsics_enabled))] -macro_rules! div { - ($a:expr, $b:expr) => { - unsafe { core::intrinsics::unchecked_div($a, $b) } - }; -} - // `support` may be public for testing #[macro_use] #[cfg(feature = "unstable-public-internals")] diff --git a/library/compiler-builtins/libm/src/math/rem_pio2_large.rs b/library/compiler-builtins/libm/src/math/rem_pio2_large.rs index bb2c532916b2..5065ca496b22 100644 --- a/library/compiler-builtins/libm/src/math/rem_pio2_large.rs +++ b/library/compiler-builtins/libm/src/math/rem_pio2_large.rs @@ -255,7 +255,7 @@ extern "C" fn floor(x: f64) -> f64 { /* determine jx,jv,q0, note that 3>q0 */ let jx = nx - 1; - let mut jv = div!(e0 - 3, 24); + let mut jv = (e0 - 3) / 24; if jv < 0 { jv = 0; } diff --git a/library/compiler-builtins/libm/src/math/support/mod.rs b/library/compiler-builtins/libm/src/math/support/mod.rs index 128ba36e7f01..9dc872cdc150 100644 --- a/library/compiler-builtins/libm/src/math/support/mod.rs +++ b/library/compiler-builtins/libm/src/math/support/mod.rs @@ -37,3 +37,36 @@ pub fn cold_path() { #[cfg(intrinsics_enabled)] core::hint::cold_path(); } + +/// # Safety +/// +/// `y` must not be zero and the result must not overflow (`x > i32::MIN`). +pub unsafe fn unchecked_div_i32(x: i32, y: i32) -> i32 { + cfg_if! { + if #[cfg(all(not(debug_assertions), intrinsics_enabled))] { + // Temporary macro to avoid panic codegen for division (in debug mode too). At + // the time of this writing this is only used in a few places, and once + // rust-lang/rust#72751 is fixed then this macro will no longer be necessary and + // the native `/` operator can be used and panics won't be codegen'd. + // + // Note: I am not sure whether the above comment is still up to date, we need + // to double check whether panics are elided where we use this. + unsafe { core::intrinsics::unchecked_div(x, y) } + } else { + x / y + } + } +} + +/// # Safety +/// +/// `y` must not be zero and the result must not overflow (`x > isize::MIN`). +pub unsafe fn unchecked_div_isize(x: isize, y: isize) -> isize { + cfg_if! { + if #[cfg(all(not(debug_assertions), intrinsics_enabled))] { + unsafe { core::intrinsics::unchecked_div(x, y) } + } else { + x / y + } + } +} diff --git a/library/compiler-builtins/libm/src/math/tgamma.rs b/library/compiler-builtins/libm/src/math/tgamma.rs index 41415d9d1258..c526842470c8 100644 --- a/library/compiler-builtins/libm/src/math/tgamma.rs +++ b/library/compiler-builtins/libm/src/math/tgamma.rs @@ -23,6 +23,7 @@ most ideas and constants are from boost and python */ use super::{exp, floor, k_cos, k_sin, pow}; +use crate::support::unchecked_div_isize; const PI: f64 = 3.141592653589793238462643383279502884; @@ -37,7 +38,8 @@ fn sinpi(mut x: f64) -> f64 { /* reduce x into [-.25,.25] */ n = (4.0 * x) as isize; - n = div!(n + 1, 2); + // SAFETY: nonzero divisor, nonnegative dividend (`n < 8`). + n = unsafe { unchecked_div_isize(n + 1, 2) }; x -= (n as f64) * 0.5; x *= PI; From 71d656edde5b2e588a129951beef1c5c7dbe35e7 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sat, 14 Feb 2026 17:41:51 +0300 Subject: [PATCH 0079/1059] LinkedGraph: Use IndexVec instead of Vec in LinkedGraph nodes --- .../src/graph/linked_graph/mod.rs | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_data_structures/src/graph/linked_graph/mod.rs b/compiler/rustc_data_structures/src/graph/linked_graph/mod.rs index ecb0095626b4..6d53b155e9a2 100644 --- a/compiler/rustc_data_structures/src/graph/linked_graph/mod.rs +++ b/compiler/rustc_data_structures/src/graph/linked_graph/mod.rs @@ -23,6 +23,7 @@ use std::fmt::Debug; use rustc_index::bit_set::DenseBitSet; +use rustc_index::{Idx, IndexSlice, IndexVec}; use tracing::debug; #[cfg(test)] @@ -45,7 +46,7 @@ /// and does not implement those traits, so it has its own implementations of a /// few basic graph algorithms. pub struct LinkedGraph { - nodes: Vec>, + nodes: IndexVec>, edges: Vec>, } @@ -62,7 +63,7 @@ pub struct Edge { pub data: E, } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct NodeIndex(pub usize); #[derive(Copy, Clone, PartialEq, Debug)] @@ -87,19 +88,29 @@ pub fn node_id(self) -> usize { } } +impl Idx for NodeIndex { + fn new(idx: usize) -> NodeIndex { + NodeIndex(idx) + } + + fn index(self) -> usize { + self.0 + } +} + impl LinkedGraph { pub fn new() -> Self { - Self { nodes: Vec::new(), edges: Vec::new() } + Self { nodes: IndexVec::new(), edges: Vec::new() } } pub fn with_capacity(nodes: usize, edges: usize) -> Self { - Self { nodes: Vec::with_capacity(nodes), edges: Vec::with_capacity(edges) } + Self { nodes: IndexVec::with_capacity(nodes), edges: Vec::with_capacity(edges) } } // # Simple accessors #[inline] - pub fn all_nodes(&self) -> &[Node] { + pub fn all_nodes(&self) -> &IndexSlice> { &self.nodes } @@ -131,15 +142,15 @@ pub fn add_node(&mut self, data: N) -> NodeIndex { } pub fn mut_node_data(&mut self, idx: NodeIndex) -> &mut N { - &mut self.nodes[idx.0].data + &mut self.nodes[idx].data } pub fn node_data(&self, idx: NodeIndex) -> &N { - &self.nodes[idx.0].data + &self.nodes[idx].data } pub fn node(&self, idx: NodeIndex) -> &Node { - &self.nodes[idx.0] + &self.nodes[idx] } // # Edge construction and queries @@ -154,16 +165,16 @@ pub fn add_edge(&mut self, source: NodeIndex, target: NodeIndex, data: E) -> Edg let idx = self.next_edge_index(); // read current first of the list of edges from each node - let source_first = self.nodes[source.0].first_edge[OUTGOING.repr]; - let target_first = self.nodes[target.0].first_edge[INCOMING.repr]; + let source_first = self.nodes[source].first_edge[OUTGOING.repr]; + let target_first = self.nodes[target].first_edge[INCOMING.repr]; // create the new edge, with the previous firsts from each node // as the next pointers self.edges.push(Edge { next_edge: [source_first, target_first], source, target, data }); // adjust the firsts for each node target be the next object. - self.nodes[source.0].first_edge[OUTGOING.repr] = idx; - self.nodes[target.0].first_edge[INCOMING.repr] = idx; + self.nodes[source].first_edge[OUTGOING.repr] = idx; + self.nodes[target].first_edge[INCOMING.repr] = idx; idx } From 96697d41ed0c2d70dc4bb64cb4e660da1b82150d Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sat, 14 Feb 2026 17:53:00 +0300 Subject: [PATCH 0080/1059] LinkedGraph: support adding nodes and edges in arbitrary order If an edge uses some not-yet-known node, we just leave the node's data empty, that data can be added later. Use this support to avoid skipping edges in DepGraphQuery --- .../src/graph/linked_graph/mod.rs | 24 ++++++++++++++----- .../src/graph/linked_graph/tests.rs | 2 +- .../rustc_middle/src/dep_graph/retained.rs | 17 ++++--------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_data_structures/src/graph/linked_graph/mod.rs b/compiler/rustc_data_structures/src/graph/linked_graph/mod.rs index 6d53b155e9a2..2223e85a2495 100644 --- a/compiler/rustc_data_structures/src/graph/linked_graph/mod.rs +++ b/compiler/rustc_data_structures/src/graph/linked_graph/mod.rs @@ -52,7 +52,7 @@ pub struct LinkedGraph { pub struct Node { first_edge: [EdgeIndex; 2], // see module comment - pub data: N, + pub data: Option, } #[derive(Debug)] @@ -135,18 +135,30 @@ pub fn next_node_index(&self) -> NodeIndex { NodeIndex(self.nodes.len()) } + fn ensure_node(&mut self, idx: NodeIndex) -> &mut Node { + self.nodes.ensure_contains_elem(idx, || Node { + first_edge: [INVALID_EDGE_INDEX, INVALID_EDGE_INDEX], + data: None, + }) + } + + pub fn add_node_with_idx(&mut self, idx: NodeIndex, data: N) { + let old_data = self.ensure_node(idx).data.replace(data); + debug_assert!(old_data.is_none()); + } + pub fn add_node(&mut self, data: N) -> NodeIndex { let idx = self.next_node_index(); - self.nodes.push(Node { first_edge: [INVALID_EDGE_INDEX, INVALID_EDGE_INDEX], data }); + self.add_node_with_idx(idx, data); idx } pub fn mut_node_data(&mut self, idx: NodeIndex) -> &mut N { - &mut self.nodes[idx].data + self.nodes[idx].data.as_mut().unwrap() } pub fn node_data(&self, idx: NodeIndex) -> &N { - &self.nodes[idx].data + self.nodes[idx].data.as_ref().unwrap() } pub fn node(&self, idx: NodeIndex) -> &Node { @@ -165,8 +177,8 @@ pub fn add_edge(&mut self, source: NodeIndex, target: NodeIndex, data: E) -> Edg let idx = self.next_edge_index(); // read current first of the list of edges from each node - let source_first = self.nodes[source].first_edge[OUTGOING.repr]; - let target_first = self.nodes[target].first_edge[INCOMING.repr]; + let source_first = self.ensure_node(source).first_edge[OUTGOING.repr]; + let target_first = self.ensure_node(target).first_edge[INCOMING.repr]; // create the new edge, with the previous firsts from each node // as the next pointers diff --git a/compiler/rustc_data_structures/src/graph/linked_graph/tests.rs b/compiler/rustc_data_structures/src/graph/linked_graph/tests.rs index 357aa81a57ca..da416cd638d4 100644 --- a/compiler/rustc_data_structures/src/graph/linked_graph/tests.rs +++ b/compiler/rustc_data_structures/src/graph/linked_graph/tests.rs @@ -40,7 +40,7 @@ fn each_node() { let expected = ["A", "B", "C", "D", "E", "F"]; graph.each_node(|idx, node| { assert_eq!(&expected[idx.0], graph.node_data(idx)); - assert_eq!(expected[idx.0], node.data); + assert_eq!(expected[idx.0], node.data.unwrap()); true }); } diff --git a/compiler/rustc_middle/src/dep_graph/retained.rs b/compiler/rustc_middle/src/dep_graph/retained.rs index 4427982e3708..626b3b782179 100644 --- a/compiler/rustc_middle/src/dep_graph/retained.rs +++ b/compiler/rustc_middle/src/dep_graph/retained.rs @@ -1,6 +1,5 @@ use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::graph::linked_graph::{Direction, INCOMING, LinkedGraph, NodeIndex}; -use rustc_index::IndexVec; use super::{DepNode, DepNodeIndex}; @@ -13,7 +12,6 @@ pub struct RetainedDepGraph { pub inner: LinkedGraph, pub indices: FxHashMap, - pub dep_index_to_index: IndexVec>, } impl RetainedDepGraph { @@ -23,27 +21,22 @@ pub fn new(prev_node_count: usize) -> Self { let inner = LinkedGraph::with_capacity(node_count, edge_count); let indices = FxHashMap::default(); - let dep_index_to_index = IndexVec::new(); - Self { inner, indices, dep_index_to_index } + Self { inner, indices } } pub fn push(&mut self, index: DepNodeIndex, node: DepNode, edges: &[DepNodeIndex]) { - let source = self.inner.add_node(node); - self.dep_index_to_index.insert(index, source); + let source = NodeIndex(index.as_usize()); + self.inner.add_node_with_idx(source, node); self.indices.insert(node, source); for &target in edges.iter() { - // We may miss the edges that are pushed while the `DepGraphQuery` is being accessed. - // Skip them to issues. - if let Some(&Some(target)) = self.dep_index_to_index.get(target) { - self.inner.add_edge(source, target, ()); - } + self.inner.add_edge(source, NodeIndex(target.as_usize()), ()); } } pub fn nodes(&self) -> Vec<&DepNode> { - self.inner.all_nodes().iter().map(|n| &n.data).collect() + self.inner.all_nodes().iter().map(|n| n.data.as_ref().unwrap()).collect() } pub fn edges(&self) -> Vec<(&DepNode, &DepNode)> { From 6b1ee2c7018e97189c06a1734937da0bf1dd32a8 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 7 Feb 2026 20:42:31 +0100 Subject: [PATCH 0081/1059] Stabilize `cfg_select` --- patches/0027-sysroot_tests-128bit-atomic-operations.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/0027-sysroot_tests-128bit-atomic-operations.patch b/patches/0027-sysroot_tests-128bit-atomic-operations.patch index 6ed0b17f679c..7ba4475e3145 100644 --- a/patches/0027-sysroot_tests-128bit-atomic-operations.patch +++ b/patches/0027-sysroot_tests-128bit-atomic-operations.patch @@ -17,8 +17,8 @@ index 1e336bf..35e6f54 100644 @@ -2,4 +2,3 @@ // tidy-alphabetical-start -#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))] - #![cfg_attr(test, feature(cfg_select))] #![feature(array_ptr_get)] + #![feature(array_try_from_fn)] diff --git a/coretests/tests/atomic.rs b/coretests/tests/atomic.rs index b735957..ea728b6 100644 --- a/coretests/tests/atomic.rs From 9b2b57085d8e2c2707028ef8a82c2c51b235d2c0 Mon Sep 17 00:00:00 2001 From: linshuy2 Date: Thu, 12 Feb 2026 23:10:44 +0000 Subject: [PATCH 0082/1059] fix: `unnecessary_safety_comment` FP on code blocks inside inner docs --- clippy_lints/src/undocumented_unsafe_blocks.rs | 4 +++- tests/ui/unnecessary_safety_comment.rs | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 1b26b1b32b80..c56a1fc8c510 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -822,7 +822,9 @@ fn text_has_safety_comment( // Don't lint if the safety comment is part of a codeblock in a doc comment. // It may or may not be required, and we can't very easily check it (and we shouldn't, since // the safety comment isn't referring to the node we're currently checking) - if line.trim_start_matches("///").trim_start().starts_with("```") { + if let Some(doc) = line.strip_prefix("///").or_else(|| line.strip_prefix("//!")) + && doc.trim_start().starts_with("```") + { in_codeblock = !in_codeblock; } diff --git a/tests/ui/unnecessary_safety_comment.rs b/tests/ui/unnecessary_safety_comment.rs index d82a7b969080..75e8315343d3 100644 --- a/tests/ui/unnecessary_safety_comment.rs +++ b/tests/ui/unnecessary_safety_comment.rs @@ -100,3 +100,13 @@ pub fn point_to_five() -> *const u8 { } fn main() {} + +mod issue16553 { + //! ``` + //! // SAFETY: All is well. + //! unsafe { + //! foo() + //! } + //! ``` + mod blah {} +} From fa99d5b72b7d45fc6875141f85d2b1db5d28423b Mon Sep 17 00:00:00 2001 From: joboet Date: Mon, 23 Feb 2026 18:49:55 +0100 Subject: [PATCH 0083/1059] core: respect precision in `ByteStr` `Display` --- library/core/src/bstr/mod.rs | 110 ++++++++++++++++++++++---------- library/coretests/tests/bstr.rs | 15 +++++ 2 files changed, 91 insertions(+), 34 deletions(-) diff --git a/library/core/src/bstr/mod.rs b/library/core/src/bstr/mod.rs index 2be7dfc9bfdd..9d623d3af8b5 100644 --- a/library/core/src/bstr/mod.rs +++ b/library/core/src/bstr/mod.rs @@ -6,7 +6,7 @@ pub use traits::{impl_partial_eq, impl_partial_eq_n, impl_partial_eq_ord}; use crate::borrow::{Borrow, BorrowMut}; -use crate::fmt; +use crate::fmt::{self, Alignment}; use crate::ops::{Deref, DerefMut, DerefPure}; /// A wrapper for `&[u8]` representing a human-readable string that's conventionally, but not @@ -174,43 +174,85 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[unstable(feature = "bstr", issue = "134915")] impl fmt::Display for ByteStr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let nchars: usize = self - .utf8_chunks() - .map(|chunk| { - chunk.valid().chars().count() + if chunk.invalid().is_empty() { 0 } else { 1 } - }) - .sum(); - - let padding = f.width().unwrap_or(0).saturating_sub(nchars); - let fill = f.fill(); - - let (lpad, rpad) = match f.align() { - Some(fmt::Alignment::Right) => (padding, 0), - Some(fmt::Alignment::Center) => { - let half = padding / 2; - (half, half + padding % 2) + fn emit(byte_str: &ByteStr, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for chunk in byte_str.utf8_chunks() { + f.write_str(chunk.valid())?; + if !chunk.invalid().is_empty() { + f.write_str("\u{FFFD}")?; + } + } + + Ok(()) + } + + let requested_width = f.width().unwrap_or(0); + if requested_width == 0 && f.precision().is_none() { + // Avoid counting the characters if no truncation or padding was + // requested. + return emit(self, f); + } + + let (truncated, actual_width) = match f.precision() { + // The entire string is truncated away. Weird, but ok. + Some(0) => (ByteStr::new(&[]), 0), + // Advance through string until we run out of space. + Some(precision) => { + let mut remaining_width = precision; + let mut chunks = self.utf8_chunks(); + let mut current_width = 0; + let mut offset = 0; + loop { + let Some(chunk) = chunks.next() else { + // We reached the end of the string without running out + // of space, so print the entire string. + break (self, current_width); + }; + + let mut chars = chunk.valid().char_indices(); + let Err(remaining) = chars.advance_by(remaining_width) else { + // We've counted off `precision` characters, so truncate + // the string at the current offset. + break (&self[..offset + chars.offset()], precision); + }; + + offset += chunk.valid().len(); + current_width += remaining_width - remaining.get(); + remaining_width = remaining.get(); + + // `remaining_width` cannot be zero, there is still space + // remaining. So next, count the � character emitted for + // the invalid chunk (if it exists). + if !chunk.invalid().is_empty() { + offset += chunk.invalid().len(); + current_width += 1; + remaining_width -= 1; + + if remaining_width == 0 { + break (&self[..offset], precision); + } + } + } + } + // The string shouldn't be truncated at all, so just count the number + // of characters to calculate the padding. + None => { + let actual_width = self + .utf8_chunks() + .map(|chunk| { + chunk.valid().chars().count() + + if chunk.invalid().is_empty() { 0 } else { 1 } + }) + .sum(); + (self, actual_width) } - // Either alignment is not specified or it's left aligned - // which behaves the same with padding - _ => (0, padding), }; - for _ in 0..lpad { - write!(f, "{fill}")?; - } + // The width is originally stored as a 16-bit number, so this cannot fail. + let padding = u16::try_from(requested_width.saturating_sub(actual_width)).unwrap(); - for chunk in self.utf8_chunks() { - f.write_str(chunk.valid())?; - if !chunk.invalid().is_empty() { - f.write_str("\u{FFFD}")?; - } - } - - for _ in 0..rpad { - write!(f, "{fill}")?; - } - - Ok(()) + let post_padding = f.padding(padding, Alignment::Left)?; + emit(truncated, f)?; + post_padding.write(f) } } diff --git a/library/coretests/tests/bstr.rs b/library/coretests/tests/bstr.rs index cd4d69d6b337..dffc7e3f0349 100644 --- a/library/coretests/tests/bstr.rs +++ b/library/coretests/tests/bstr.rs @@ -49,4 +49,19 @@ fn test_display() { assert_eq!(&format!("{b2:->3}!"), "�(��!"); assert_eq!(&format!("{b2:-^3}!"), "�(��!"); assert_eq!(&format!("{b2:-^2}!"), "�(��!"); + + assert_eq!(&format!("{b1:.1}!"), &format!("{:.1}!", "abc")); + assert_eq!(&format!("{b1:.2}!"), &format!("{:.2}!", "abc")); + assert_eq!(&format!("{b1:.3}!"), &format!("{:.3}!", "abc")); + assert_eq!(&format!("{b1:-<5.2}!"), &format!("{:-<5.2}!", "abc")); + assert_eq!(&format!("{b1:-^5.2}!"), &format!("{:-^5.2}!", "abc")); + assert_eq!(&format!("{b1:->5.2}!"), &format!("{:->5.2}!", "abc")); + + assert_eq!(&format!("{b2:.1}!"), "�!"); + assert_eq!(&format!("{b2:.2}!"), "�(!"); + assert_eq!(&format!("{b2:.3}!"), "�(�!"); + assert_eq!(&format!("{b2:.4}!"), "�(��!"); + assert_eq!(&format!("{b2:-<6.3}!"), "�(�---!"); + assert_eq!(&format!("{b2:-^6.3}!"), "-�(�--!"); + assert_eq!(&format!("{b2:->6.3}!"), "---�(�!"); } From 3282e2cb9c8c16f6d22d90a6af2515b592b2c692 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 24 Feb 2026 16:08:32 +0000 Subject: [PATCH 0084/1059] Rustup to rustc 1.95.0-nightly (b3869b94c 2026-02-23) --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 62e07412a7f8..85b1cf4d6117 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2026-02-20" +channel = "nightly-2026-02-24" components = ["rust-src", "rustc-dev", "llvm-tools", "rustfmt"] profile = "minimal" From 890434af218987d48f092524490c9a25ff910eeb Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 24 Feb 2026 16:13:56 +0000 Subject: [PATCH 0085/1059] Fix rustc test suite --- scripts/test_rustc_tests.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/test_rustc_tests.sh b/scripts/test_rustc_tests.sh index 798bef7ba3e3..6dc981531be3 100755 --- a/scripts/test_rustc_tests.sh +++ b/scripts/test_rustc_tests.sh @@ -151,7 +151,6 @@ rm -r tests/run-make/short-ice # ICE backtrace begin/end marker mismatch rm -r tests/run-make/remap-path-prefix-dwarf # requires llvm-dwarfdump rm -r tests/run-make/strip # same rm -r tests/run-make-cargo/compiler-builtins # Expects lib/rustlib/src/rust to contains the standard library source -rm -r tests/run-make/translation # same rm -r tests/run-make-cargo/panic-immediate-abort-works # same rm -r tests/run-make-cargo/panic-immediate-abort-codegen # same rm -r tests/run-make/missing-unstable-trait-bound # This disables support for unstable features, but running cg_clif needs some unstable features From 06b0bd21d74db05eaffd33aacdb7f67bedef5f45 Mon Sep 17 00:00:00 2001 From: cyrgani Date: Tue, 24 Feb 2026 17:43:06 +0000 Subject: [PATCH 0086/1059] stop marking `deref_patterns` as an incomplete feature --- compiler/rustc_feature/src/unstable.rs | 2 +- .../src/language-features/deref-patterns.md | 6 +----- .../building/match/deref-patterns/string.rs | 1 - tests/ui/pattern/deref-patterns/basic.rs | 1 - tests/ui/pattern/deref-patterns/bindings.rs | 1 - tests/ui/pattern/deref-patterns/branch.rs | 1 - .../deref-patterns/byte-string-type-errors.rs | 1 - .../byte-string-type-errors.stderr | 18 +++++++++--------- .../deref-patterns/cant_move_out_of_pattern.rs | 1 - .../cant_move_out_of_pattern.stderr | 8 ++++---- .../pattern/deref-patterns/closure_capture.rs | 1 - .../const-pats-do-not-mislead-inference.rs | 1 - ...pats-do-not-mislead-inference.stable.stderr | 8 ++++---- .../ui/pattern/deref-patterns/default-infer.rs | 1 - tests/ui/pattern/deref-patterns/deref-box.rs | 1 - ...ont-ice-on-slice-in-deref-pat-in-closure.rs | 1 - .../ui/pattern/deref-patterns/fake_borrows.rs | 1 - .../pattern/deref-patterns/fake_borrows.stderr | 8 ++++---- ...adjust-mode-unimplemented-for-constblock.rs | 1 - ...st-mode-unimplemented-for-constblock.stderr | 8 ++++---- .../deref-patterns/implicit-const-deref.rs | 1 - .../deref-patterns/implicit-const-deref.stderr | 2 +- .../deref-patterns/implicit-cow-deref.rs | 1 - .../pattern/deref-patterns/recursion-limit.rs | 1 - .../deref-patterns/recursion-limit.stderr | 6 +++--- tests/ui/pattern/deref-patterns/ref-mut.rs | 1 - tests/ui/pattern/deref-patterns/ref-mut.stderr | 15 +++------------ tests/ui/pattern/deref-patterns/refs.rs | 1 - tests/ui/pattern/deref-patterns/strings.rs | 1 - tests/ui/pattern/deref-patterns/typeck.rs | 1 - tests/ui/pattern/deref-patterns/typeck_fail.rs | 1 - .../pattern/deref-patterns/typeck_fail.stderr | 2 +- .../deref-patterns/unsatisfied-bounds.rs | 1 - .../deref-patterns/unsatisfied-bounds.stderr | 4 ++-- .../deref-patterns/usefulness/empty-types.rs | 1 - .../usefulness/empty-types.stderr | 4 ++-- .../usefulness/mixed-constructors.rs | 1 - .../usefulness/mixed-constructors.stderr | 10 +++++----- .../usefulness/non-exhaustive.rs | 1 - .../usefulness/non-exhaustive.stderr | 8 ++++---- .../usefulness/unreachable-patterns.rs | 1 - .../usefulness/unreachable-patterns.stderr | 14 +++++++------- 42 files changed, 55 insertions(+), 94 deletions(-) diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 1d123385961a..d8afc8c794b6 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -451,7 +451,7 @@ pub fn internal(&self, feature: Symbol) -> bool { /// Allows having using `suggestion` in the `#[deprecated]` attribute. (unstable, deprecated_suggestion, "1.61.0", Some(94785)), /// Allows deref patterns. - (incomplete, deref_patterns, "1.79.0", Some(87121)), + (unstable, deref_patterns, "1.79.0", Some(87121)), /// Allows deriving the From trait on single-field structs. (unstable, derive_from, "1.91.0", Some(144889)), /// Allows giving non-const impls custom diagnostic messages if attempted to be used as const diff --git a/src/doc/unstable-book/src/language-features/deref-patterns.md b/src/doc/unstable-book/src/language-features/deref-patterns.md index 23c2dc688400..a0c9a7e30277 100644 --- a/src/doc/unstable-book/src/language-features/deref-patterns.md +++ b/src/doc/unstable-book/src/language-features/deref-patterns.md @@ -6,8 +6,7 @@ The tracking issue for this feature is: [#87121] ------------------------ -> **Note**: This feature is incomplete. In the future, it is meant to supersede -> [`box_patterns`]. +> **Note**: This feature supersedes [`box_patterns`]. This feature permits pattern matching on [smart pointers in the standard library] through their `Deref` target types, either implicitly or with explicit `deref!(_)` patterns (the syntax of which @@ -15,7 +14,6 @@ is currently a placeholder). ```rust #![feature(deref_patterns)] -#![allow(incomplete_features)] let mut v = vec![Box::new(Some(0))]; @@ -58,7 +56,6 @@ Like [`box_patterns`], deref patterns may move out of boxes: ```rust # #![feature(deref_patterns)] -# #![allow(incomplete_features)] struct NoCopy; let deref!(x) = Box::new(NoCopy); drop::(x); @@ -69,7 +66,6 @@ allowing then to be used in deref patterns: ```rust # #![feature(deref_patterns)] -# #![allow(incomplete_features)] match ("test".to_string(), Box::from("test"), b"test".to_vec()) { ("test", "test", b"test") => {} _ => panic!(), diff --git a/tests/mir-opt/building/match/deref-patterns/string.rs b/tests/mir-opt/building/match/deref-patterns/string.rs index 1f8d6fbb0bd2..92232c7df79d 100644 --- a/tests/mir-opt/building/match/deref-patterns/string.rs +++ b/tests/mir-opt/building/match/deref-patterns/string.rs @@ -2,7 +2,6 @@ //@ compile-flags: -Z mir-opt-level=0 -C panic=abort #![feature(deref_patterns)] -#![expect(incomplete_features)] #![crate_type = "lib"] // EMIT_MIR string.foo.PreCodegen.after.mir diff --git a/tests/ui/pattern/deref-patterns/basic.rs b/tests/ui/pattern/deref-patterns/basic.rs index dee4521e1f95..0cb80b43944d 100644 --- a/tests/ui/pattern/deref-patterns/basic.rs +++ b/tests/ui/pattern/deref-patterns/basic.rs @@ -1,7 +1,6 @@ //@ run-pass //@ check-run-results #![feature(deref_patterns)] -#![expect(incomplete_features)] fn main() { test(Some(String::from("42"))); diff --git a/tests/ui/pattern/deref-patterns/bindings.rs b/tests/ui/pattern/deref-patterns/bindings.rs index 92c01d737bac..c3165ebf6cef 100644 --- a/tests/ui/pattern/deref-patterns/bindings.rs +++ b/tests/ui/pattern/deref-patterns/bindings.rs @@ -1,7 +1,6 @@ //@ revisions: explicit implicit //@ run-pass #![feature(deref_patterns)] -#![allow(incomplete_features)] use std::rc::Rc; diff --git a/tests/ui/pattern/deref-patterns/branch.rs b/tests/ui/pattern/deref-patterns/branch.rs index 9d72b35fd2f1..5b87742eb319 100644 --- a/tests/ui/pattern/deref-patterns/branch.rs +++ b/tests/ui/pattern/deref-patterns/branch.rs @@ -2,7 +2,6 @@ //@ run-pass // Test the execution of deref patterns. #![feature(deref_patterns)] -#![allow(incomplete_features)] #[cfg(explicit)] fn branch(vec: Vec) -> u32 { diff --git a/tests/ui/pattern/deref-patterns/byte-string-type-errors.rs b/tests/ui/pattern/deref-patterns/byte-string-type-errors.rs index fdcc6cb46111..633044a59772 100644 --- a/tests/ui/pattern/deref-patterns/byte-string-type-errors.rs +++ b/tests/ui/pattern/deref-patterns/byte-string-type-errors.rs @@ -5,7 +5,6 @@ //@ dont-require-annotations: NOTE #![feature(deref_patterns)] -#![expect(incomplete_features)] fn main() { // Baseline 1: under normal circumstances, byte string literal patterns have type `&[u8; N]`, diff --git a/tests/ui/pattern/deref-patterns/byte-string-type-errors.stderr b/tests/ui/pattern/deref-patterns/byte-string-type-errors.stderr index 046682004be7..531d5ba292c4 100644 --- a/tests/ui/pattern/deref-patterns/byte-string-type-errors.stderr +++ b/tests/ui/pattern/deref-patterns/byte-string-type-errors.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/byte-string-type-errors.rs:13:12 + --> $DIR/byte-string-type-errors.rs:12:12 | LL | if let b"test" = () {} | ^^^^^^^ -- this expression has type `()` @@ -7,7 +7,7 @@ LL | if let b"test" = () {} | expected `()`, found `&[u8; 4]` error[E0308]: mismatched types - --> $DIR/byte-string-type-errors.rs:20:12 + --> $DIR/byte-string-type-errors.rs:19:12 | LL | if let b"test" = &[] as &[i8] {} | ^^^^^^^ ------------ this expression has type `&[i8]` @@ -18,7 +18,7 @@ LL | if let b"test" = &[] as &[i8] {} found reference `&'static [u8]` error[E0308]: mismatched types - --> $DIR/byte-string-type-errors.rs:25:12 + --> $DIR/byte-string-type-errors.rs:24:12 | LL | if let b"test" = *(&[] as &[i8]) {} | ^^^^^^^ --------------- this expression has type `[i8]` @@ -29,7 +29,7 @@ LL | if let b"test" = *(&[] as &[i8]) {} found slice `[u8]` error[E0308]: mismatched types - --> $DIR/byte-string-type-errors.rs:30:12 + --> $DIR/byte-string-type-errors.rs:29:12 | LL | if let b"test" = [()] {} | ^^^^^^^ ---- this expression has type `[(); 1]` @@ -40,7 +40,7 @@ LL | if let b"test" = [()] {} found array `[u8; 4]` error[E0308]: mismatched types - --> $DIR/byte-string-type-errors.rs:33:12 + --> $DIR/byte-string-type-errors.rs:32:12 | LL | if let b"test" = *b"this array is too long" {} | ^^^^^^^ -------------------------- this expression has type `[u8; 22]` @@ -48,7 +48,7 @@ LL | if let b"test" = *b"this array is too long" {} | expected an array with a size of 22, found one with a size of 4 error[E0308]: mismatched types - --> $DIR/byte-string-type-errors.rs:39:12 + --> $DIR/byte-string-type-errors.rs:38:12 | LL | if let b"test" = &mut () {} | ^^^^^^^ ------- this expression has type `&mut ()` @@ -56,7 +56,7 @@ LL | if let b"test" = &mut () {} | expected `()`, found `&[u8; 4]` error[E0308]: mismatched types - --> $DIR/byte-string-type-errors.rs:44:12 + --> $DIR/byte-string-type-errors.rs:43:12 | LL | if let b"test" = &mut [] as &mut [i8] {} | ^^^^^^^ -------------------- this expression has type `&mut [i8]` @@ -67,7 +67,7 @@ LL | if let b"test" = &mut [] as &mut [i8] {} found slice `[u8]` error[E0308]: mismatched types - --> $DIR/byte-string-type-errors.rs:48:12 + --> $DIR/byte-string-type-errors.rs:47:12 | LL | if let b"test" = &mut [()] {} | ^^^^^^^ --------- this expression has type `&mut [(); 1]` @@ -78,7 +78,7 @@ LL | if let b"test" = &mut [()] {} found array `[u8; 4]` error[E0308]: mismatched types - --> $DIR/byte-string-type-errors.rs:52:12 + --> $DIR/byte-string-type-errors.rs:51:12 | LL | if let b"test" = &mut *b"this array is too long" {} | ^^^^^^^ ------------------------------- this expression has type `&mut [u8; 22]` diff --git a/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.rs b/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.rs index 2b4746e33e67..36d9ae70efdd 100644 --- a/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.rs +++ b/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.rs @@ -1,5 +1,4 @@ #![feature(deref_patterns)] -#![allow(incomplete_features)] use std::rc::Rc; diff --git a/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.stderr b/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.stderr index a548ac5909a8..532dd496b235 100644 --- a/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.stderr +++ b/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.stderr @@ -1,5 +1,5 @@ error[E0508]: cannot move out of type `[Struct]`, a non-copy slice - --> $DIR/cant_move_out_of_pattern.rs:9:11 + --> $DIR/cant_move_out_of_pattern.rs:8:11 | LL | match b { | ^ cannot move out of here @@ -16,7 +16,7 @@ LL | deref!([ref x]) => x, | +++ error[E0507]: cannot move out of a shared reference - --> $DIR/cant_move_out_of_pattern.rs:17:11 + --> $DIR/cant_move_out_of_pattern.rs:16:11 | LL | match rc { | ^^ @@ -33,7 +33,7 @@ LL | deref!(ref x) => x, | +++ error[E0508]: cannot move out of type `[Struct]`, a non-copy slice - --> $DIR/cant_move_out_of_pattern.rs:25:11 + --> $DIR/cant_move_out_of_pattern.rs:24:11 | LL | match b { | ^ cannot move out of here @@ -50,7 +50,7 @@ LL | [ref x] => x, | +++ error[E0507]: cannot move out of a shared reference - --> $DIR/cant_move_out_of_pattern.rs:35:11 + --> $DIR/cant_move_out_of_pattern.rs:34:11 | LL | match rc { | ^^ diff --git a/tests/ui/pattern/deref-patterns/closure_capture.rs b/tests/ui/pattern/deref-patterns/closure_capture.rs index 497ec622b0cf..c02c82e9ea0c 100644 --- a/tests/ui/pattern/deref-patterns/closure_capture.rs +++ b/tests/ui/pattern/deref-patterns/closure_capture.rs @@ -1,6 +1,5 @@ //@ run-pass #![feature(deref_patterns)] -#![allow(incomplete_features)] use std::rc::Rc; diff --git a/tests/ui/pattern/deref-patterns/const-pats-do-not-mislead-inference.rs b/tests/ui/pattern/deref-patterns/const-pats-do-not-mislead-inference.rs index 3a2531f4b95e..08dec4885846 100644 --- a/tests/ui/pattern/deref-patterns/const-pats-do-not-mislead-inference.rs +++ b/tests/ui/pattern/deref-patterns/const-pats-do-not-mislead-inference.rs @@ -8,7 +8,6 @@ //! we'd get without `deref_patterns` enabled. #![cfg_attr(deref_patterns, feature(deref_patterns))] -#![cfg_attr(deref_patterns, expect(incomplete_features))] fn uninferred() -> T { unimplemented!() } diff --git a/tests/ui/pattern/deref-patterns/const-pats-do-not-mislead-inference.stable.stderr b/tests/ui/pattern/deref-patterns/const-pats-do-not-mislead-inference.stable.stderr index 61079718c5d5..0770569e0406 100644 --- a/tests/ui/pattern/deref-patterns/const-pats-do-not-mislead-inference.stable.stderr +++ b/tests/ui/pattern/deref-patterns/const-pats-do-not-mislead-inference.stable.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/const-pats-do-not-mislead-inference.rs:33:12 + --> $DIR/const-pats-do-not-mislead-inference.rs:32:12 | LL | if let b"..." = &&x {} | ^^^^^^ --- this expression has type `&&_` @@ -10,7 +10,7 @@ LL | if let b"..." = &&x {} found reference `&'static [u8; 3]` error[E0308]: mismatched types - --> $DIR/const-pats-do-not-mislead-inference.rs:39:12 + --> $DIR/const-pats-do-not-mislead-inference.rs:38:12 | LL | if let "..." = &Box::new(x) {} | ^^^^^ ------------ this expression has type `&Box<_>` @@ -25,7 +25,7 @@ LL | if let "..." = &*Box::new(x) {} | + error[E0308]: mismatched types - --> $DIR/const-pats-do-not-mislead-inference.rs:45:12 + --> $DIR/const-pats-do-not-mislead-inference.rs:44:12 | LL | if let b"..." = Box::new(&x) {} | ^^^^^^ ------------ this expression has type `Box<&_>` @@ -40,7 +40,7 @@ LL | if let b"..." = *Box::new(&x) {} | + error[E0308]: mismatched types - --> $DIR/const-pats-do-not-mislead-inference.rs:51:12 + --> $DIR/const-pats-do-not-mislead-inference.rs:50:12 | LL | if let "..." = &mut x {} | ^^^^^ ------ this expression has type `&mut _` diff --git a/tests/ui/pattern/deref-patterns/default-infer.rs b/tests/ui/pattern/deref-patterns/default-infer.rs index fb0b2add132a..38c8793a9b44 100644 --- a/tests/ui/pattern/deref-patterns/default-infer.rs +++ b/tests/ui/pattern/deref-patterns/default-infer.rs @@ -1,6 +1,5 @@ //@ check-pass #![feature(deref_patterns)] -#![expect(incomplete_features)] fn main() { match <_ as Default>::default() { diff --git a/tests/ui/pattern/deref-patterns/deref-box.rs b/tests/ui/pattern/deref-patterns/deref-box.rs index 39b23dcab51e..1af13f82f3f9 100644 --- a/tests/ui/pattern/deref-patterns/deref-box.rs +++ b/tests/ui/pattern/deref-patterns/deref-box.rs @@ -3,7 +3,6 @@ //! and `DerefMut::deref_mut`. Test that they work as expected. #![feature(deref_patterns)] -#![expect(incomplete_features)] fn unbox_1(b: Box) -> T { let deref!(x) = b; diff --git a/tests/ui/pattern/deref-patterns/dont-ice-on-slice-in-deref-pat-in-closure.rs b/tests/ui/pattern/deref-patterns/dont-ice-on-slice-in-deref-pat-in-closure.rs index e1a37b9c65f4..e656ef9a4380 100644 --- a/tests/ui/pattern/deref-patterns/dont-ice-on-slice-in-deref-pat-in-closure.rs +++ b/tests/ui/pattern/deref-patterns/dont-ice-on-slice-in-deref-pat-in-closure.rs @@ -3,7 +3,6 @@ //! inside a deref pattern inside a closure: rust-lang/rust#125059 #![feature(deref_patterns)] -#![allow(incomplete_features, unused)] fn simple_vec(vec: Vec) -> u32 { (|| match Vec::::new() { diff --git a/tests/ui/pattern/deref-patterns/fake_borrows.rs b/tests/ui/pattern/deref-patterns/fake_borrows.rs index fba2873fd02a..74292fe25d8c 100644 --- a/tests/ui/pattern/deref-patterns/fake_borrows.rs +++ b/tests/ui/pattern/deref-patterns/fake_borrows.rs @@ -1,5 +1,4 @@ #![feature(deref_patterns)] -#![allow(incomplete_features)] #[rustfmt::skip] fn main() { diff --git a/tests/ui/pattern/deref-patterns/fake_borrows.stderr b/tests/ui/pattern/deref-patterns/fake_borrows.stderr index 7dc3001739e6..312de4760a45 100644 --- a/tests/ui/pattern/deref-patterns/fake_borrows.stderr +++ b/tests/ui/pattern/deref-patterns/fake_borrows.stderr @@ -1,5 +1,5 @@ error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable - --> $DIR/fake_borrows.rs:9:16 + --> $DIR/fake_borrows.rs:8:16 | LL | match v { | - immutable borrow occurs here @@ -10,7 +10,7 @@ LL | _ if { v[0] = true; false } => {} | mutable borrow occurs here error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable - --> $DIR/fake_borrows.rs:16:16 + --> $DIR/fake_borrows.rs:15:16 | LL | match v { | - immutable borrow occurs here @@ -21,7 +21,7 @@ LL | _ if { v[0] = true; false } => {} | mutable borrow occurs here error[E0510]: cannot assign `*b` in match guard - --> $DIR/fake_borrows.rs:26:16 + --> $DIR/fake_borrows.rs:25:16 | LL | match b { | - value is immutable in match guard @@ -30,7 +30,7 @@ LL | _ if { *b = true; false } => {} | ^^^^^^^^^ cannot assign error[E0510]: cannot assign `*b` in match guard - --> $DIR/fake_borrows.rs:33:16 + --> $DIR/fake_borrows.rs:32:16 | LL | match b { | - value is immutable in match guard diff --git a/tests/ui/pattern/deref-patterns/ice-adjust-mode-unimplemented-for-constblock.rs b/tests/ui/pattern/deref-patterns/ice-adjust-mode-unimplemented-for-constblock.rs index fbc742aa8477..b775cb3ac756 100644 --- a/tests/ui/pattern/deref-patterns/ice-adjust-mode-unimplemented-for-constblock.rs +++ b/tests/ui/pattern/deref-patterns/ice-adjust-mode-unimplemented-for-constblock.rs @@ -1,5 +1,4 @@ #![feature(deref_patterns)] -#![expect(incomplete_features)] fn main() { let vec![const { vec![] }]: Vec = vec![]; diff --git a/tests/ui/pattern/deref-patterns/ice-adjust-mode-unimplemented-for-constblock.stderr b/tests/ui/pattern/deref-patterns/ice-adjust-mode-unimplemented-for-constblock.stderr index 48728acbc291..d18ca2589bbc 100644 --- a/tests/ui/pattern/deref-patterns/ice-adjust-mode-unimplemented-for-constblock.stderr +++ b/tests/ui/pattern/deref-patterns/ice-adjust-mode-unimplemented-for-constblock.stderr @@ -1,5 +1,5 @@ error[E0532]: expected a pattern, found a function call - --> $DIR/ice-adjust-mode-unimplemented-for-constblock.rs:5:9 + --> $DIR/ice-adjust-mode-unimplemented-for-constblock.rs:4:9 | LL | let vec![const { vec![] }]: Vec = vec![]; | ^^^^^^^^^^^^^^^^^^^^^^ not a tuple struct or tuple variant @@ -7,7 +7,7 @@ LL | let vec![const { vec![] }]: Vec = vec![]; = note: function calls are not allowed in patterns: error[E0532]: expected a pattern, found a function call - --> $DIR/ice-adjust-mode-unimplemented-for-constblock.rs:5:9 + --> $DIR/ice-adjust-mode-unimplemented-for-constblock.rs:4:9 | LL | let vec![const { vec![] }]: Vec = vec![]; | ^^^^^^^^^^^^^^^^^^^^^^ not a tuple struct or tuple variant @@ -15,7 +15,7 @@ LL | let vec![const { vec![] }]: Vec = vec![]; = note: function calls are not allowed in patterns: error: arbitrary expressions aren't allowed in patterns - --> $DIR/ice-adjust-mode-unimplemented-for-constblock.rs:5:14 + --> $DIR/ice-adjust-mode-unimplemented-for-constblock.rs:4:14 | LL | let vec![const { vec![] }]: Vec = vec![]; | ^^^^^^^^^^^^^^^^ @@ -23,7 +23,7 @@ LL | let vec![const { vec![] }]: Vec = vec![]; = help: use a named `const`-item or an `if`-guard (`x if x == const { ... }`) instead error[E0164]: expected tuple struct or tuple variant, found associated function `::alloc::boxed::Box::new_uninit` - --> $DIR/ice-adjust-mode-unimplemented-for-constblock.rs:5:9 + --> $DIR/ice-adjust-mode-unimplemented-for-constblock.rs:4:9 | LL | let vec![const { vec![] }]: Vec = vec![]; | ^^^^^^^^^^^^^^^^^^^^^^ `fn` calls are not allowed in patterns diff --git a/tests/ui/pattern/deref-patterns/implicit-const-deref.rs b/tests/ui/pattern/deref-patterns/implicit-const-deref.rs index 70f89629bc22..4aa5695c7399 100644 --- a/tests/ui/pattern/deref-patterns/implicit-const-deref.rs +++ b/tests/ui/pattern/deref-patterns/implicit-const-deref.rs @@ -4,7 +4,6 @@ //! scrutinee and end up with a type error; this would prevent us from reporting that only constants //! supporting structural equality can be used as patterns. #![feature(deref_patterns)] -#![allow(incomplete_features)] const EMPTY: Vec<()> = Vec::new(); diff --git a/tests/ui/pattern/deref-patterns/implicit-const-deref.stderr b/tests/ui/pattern/deref-patterns/implicit-const-deref.stderr index 6d4301846287..a75542ffd4ad 100644 --- a/tests/ui/pattern/deref-patterns/implicit-const-deref.stderr +++ b/tests/ui/pattern/deref-patterns/implicit-const-deref.stderr @@ -1,5 +1,5 @@ error: constant of non-structural type `Vec<()>` in a pattern - --> $DIR/implicit-const-deref.rs:15:9 + --> $DIR/implicit-const-deref.rs:14:9 | LL | const EMPTY: Vec<()> = Vec::new(); | -------------------- constant defined here diff --git a/tests/ui/pattern/deref-patterns/implicit-cow-deref.rs b/tests/ui/pattern/deref-patterns/implicit-cow-deref.rs index 24770261edc5..107ae12f3b74 100644 --- a/tests/ui/pattern/deref-patterns/implicit-cow-deref.rs +++ b/tests/ui/pattern/deref-patterns/implicit-cow-deref.rs @@ -1,7 +1,6 @@ //@ run-pass //! Test that implicit deref patterns interact as expected with `Cow` constructor patterns. #![feature(deref_patterns)] -#![allow(incomplete_features)] use std::borrow::Cow; use std::rc::Rc; diff --git a/tests/ui/pattern/deref-patterns/recursion-limit.rs b/tests/ui/pattern/deref-patterns/recursion-limit.rs index c5fe520f6f1a..75c64cb1f539 100644 --- a/tests/ui/pattern/deref-patterns/recursion-limit.rs +++ b/tests/ui/pattern/deref-patterns/recursion-limit.rs @@ -1,6 +1,5 @@ //! Test that implicit deref patterns respect the recursion limit #![feature(deref_patterns)] -#![allow(incomplete_features)] #![recursion_limit = "8"] use std::ops::Deref; diff --git a/tests/ui/pattern/deref-patterns/recursion-limit.stderr b/tests/ui/pattern/deref-patterns/recursion-limit.stderr index 7c140e4493e7..e7f6a272432e 100644 --- a/tests/ui/pattern/deref-patterns/recursion-limit.stderr +++ b/tests/ui/pattern/deref-patterns/recursion-limit.stderr @@ -1,5 +1,5 @@ error[E0055]: reached the recursion limit while auto-dereferencing `Cyclic` - --> $DIR/recursion-limit.rs:18:9 + --> $DIR/recursion-limit.rs:17:9 | LL | () => {} | ^^ deref recursion limit reached @@ -7,13 +7,13 @@ LL | () => {} = help: consider increasing the recursion limit by adding a `#![recursion_limit = "16"]` attribute to your crate (`recursion_limit`) error[E0277]: the trait bound `Cyclic: DerefPure` is not satisfied - --> $DIR/recursion-limit.rs:18:9 + --> $DIR/recursion-limit.rs:17:9 | LL | () => {} | ^^ unsatisfied trait bound | help: the nightly-only, unstable trait `DerefPure` is not implemented for `Cyclic` - --> $DIR/recursion-limit.rs:8:1 + --> $DIR/recursion-limit.rs:7:1 | LL | struct Cyclic; | ^^^^^^^^^^^^^ diff --git a/tests/ui/pattern/deref-patterns/ref-mut.rs b/tests/ui/pattern/deref-patterns/ref-mut.rs index 43738671346d..7751a13565e8 100644 --- a/tests/ui/pattern/deref-patterns/ref-mut.rs +++ b/tests/ui/pattern/deref-patterns/ref-mut.rs @@ -1,5 +1,4 @@ #![feature(deref_patterns)] -//~^ WARN the feature `deref_patterns` is incomplete use std::rc::Rc; diff --git a/tests/ui/pattern/deref-patterns/ref-mut.stderr b/tests/ui/pattern/deref-patterns/ref-mut.stderr index 24a35b418e95..1359410de827 100644 --- a/tests/ui/pattern/deref-patterns/ref-mut.stderr +++ b/tests/ui/pattern/deref-patterns/ref-mut.stderr @@ -1,14 +1,5 @@ -warning: the feature `deref_patterns` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/ref-mut.rs:1:12 - | -LL | #![feature(deref_patterns)] - | ^^^^^^^^^^^^^^ - | - = note: see issue #87121 for more information - = note: `#[warn(incomplete_features)]` on by default - error[E0277]: the trait bound `Rc<{integer}>: DerefMut` is not satisfied - --> $DIR/ref-mut.rs:17:9 + --> $DIR/ref-mut.rs:16:9 | LL | deref!(x) => {} | ^^^^^^^^^ the trait `DerefMut` is not implemented for `Rc<{integer}>` @@ -16,11 +7,11 @@ LL | deref!(x) => {} = note: this error originates in the macro `deref` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Rc<({integer},)>: DerefMut` is not satisfied - --> $DIR/ref-mut.rs:22:9 + --> $DIR/ref-mut.rs:21:9 | LL | (x,) => {} | ^^^^ the trait `DerefMut` is not implemented for `Rc<({integer},)>` -error: aborting due to 2 previous errors; 1 warning emitted +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/pattern/deref-patterns/refs.rs b/tests/ui/pattern/deref-patterns/refs.rs index 51826225856b..13566510e7a5 100644 --- a/tests/ui/pattern/deref-patterns/refs.rs +++ b/tests/ui/pattern/deref-patterns/refs.rs @@ -1,6 +1,5 @@ //@ check-pass #![feature(deref_patterns)] -#![expect(incomplete_features)] fn foo(s: &String) -> i32 { match *s { diff --git a/tests/ui/pattern/deref-patterns/strings.rs b/tests/ui/pattern/deref-patterns/strings.rs index fac15a9aee3d..5b6cfb9425d6 100644 --- a/tests/ui/pattern/deref-patterns/strings.rs +++ b/tests/ui/pattern/deref-patterns/strings.rs @@ -2,7 +2,6 @@ //! Test deref patterns using string and bytestring literals. #![feature(deref_patterns)] -#![allow(incomplete_features)] fn main() { for (test_in, test_expect) in [("zero", 0), ("one", 1), ("two", 2)] { diff --git a/tests/ui/pattern/deref-patterns/typeck.rs b/tests/ui/pattern/deref-patterns/typeck.rs index 3a7ce9d1deb3..a1a055d5f0e7 100644 --- a/tests/ui/pattern/deref-patterns/typeck.rs +++ b/tests/ui/pattern/deref-patterns/typeck.rs @@ -1,6 +1,5 @@ //@ check-pass #![feature(deref_patterns)] -#![allow(incomplete_features)] use std::rc::Rc; diff --git a/tests/ui/pattern/deref-patterns/typeck_fail.rs b/tests/ui/pattern/deref-patterns/typeck_fail.rs index 6ae87bb7bc36..14ddb670e57f 100644 --- a/tests/ui/pattern/deref-patterns/typeck_fail.rs +++ b/tests/ui/pattern/deref-patterns/typeck_fail.rs @@ -1,5 +1,4 @@ #![feature(deref_patterns)] -#![allow(incomplete_features)] fn main() { // Make sure we don't try implicitly dereferncing any ADT. diff --git a/tests/ui/pattern/deref-patterns/typeck_fail.stderr b/tests/ui/pattern/deref-patterns/typeck_fail.stderr index fc29caac5630..6e81e9f6a064 100644 --- a/tests/ui/pattern/deref-patterns/typeck_fail.stderr +++ b/tests/ui/pattern/deref-patterns/typeck_fail.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/typeck_fail.rs:7:9 + --> $DIR/typeck_fail.rs:6:9 | LL | match Some(0) { | ------- this expression has type `Option<{integer}>` diff --git a/tests/ui/pattern/deref-patterns/unsatisfied-bounds.rs b/tests/ui/pattern/deref-patterns/unsatisfied-bounds.rs index 9e95f4ec4096..db76e6fb7f94 100644 --- a/tests/ui/pattern/deref-patterns/unsatisfied-bounds.rs +++ b/tests/ui/pattern/deref-patterns/unsatisfied-bounds.rs @@ -1,5 +1,4 @@ #![feature(deref_patterns)] -#![allow(incomplete_features)] struct MyPointer; diff --git a/tests/ui/pattern/deref-patterns/unsatisfied-bounds.stderr b/tests/ui/pattern/deref-patterns/unsatisfied-bounds.stderr index 3ee6efefe697..3b680b4238a0 100644 --- a/tests/ui/pattern/deref-patterns/unsatisfied-bounds.stderr +++ b/tests/ui/pattern/deref-patterns/unsatisfied-bounds.stderr @@ -1,11 +1,11 @@ error[E0277]: the trait bound `MyPointer: DerefPure` is not satisfied - --> $DIR/unsatisfied-bounds.rs:17:9 + --> $DIR/unsatisfied-bounds.rs:16:9 | LL | () => {} | ^^ unsatisfied trait bound | help: the nightly-only, unstable trait `DerefPure` is not implemented for `MyPointer` - --> $DIR/unsatisfied-bounds.rs:4:1 + --> $DIR/unsatisfied-bounds.rs:3:1 | LL | struct MyPointer; | ^^^^^^^^^^^^^^^^ diff --git a/tests/ui/pattern/deref-patterns/usefulness/empty-types.rs b/tests/ui/pattern/deref-patterns/usefulness/empty-types.rs index 03419030e72f..17de65380169 100644 --- a/tests/ui/pattern/deref-patterns/usefulness/empty-types.rs +++ b/tests/ui/pattern/deref-patterns/usefulness/empty-types.rs @@ -4,7 +4,6 @@ // FIXME(deref_patterns): On stabilization, cases for deref patterns could be worked into that file // to keep the tests for empty types in one place and test more thoroughly. #![feature(deref_patterns)] -#![expect(incomplete_features)] #![deny(unreachable_patterns)] enum Void {} diff --git a/tests/ui/pattern/deref-patterns/usefulness/empty-types.stderr b/tests/ui/pattern/deref-patterns/usefulness/empty-types.stderr index e32477085661..4eb49b54b83b 100644 --- a/tests/ui/pattern/deref-patterns/usefulness/empty-types.stderr +++ b/tests/ui/pattern/deref-patterns/usefulness/empty-types.stderr @@ -1,5 +1,5 @@ error[E0004]: non-exhaustive patterns: `deref!(Some(_))` not covered - --> $DIR/empty-types.rs:21:11 + --> $DIR/empty-types.rs:20:11 | LL | match box_opt_void { | ^^^^^^^^^^^^ pattern `deref!(Some(_))` not covered @@ -15,7 +15,7 @@ LL + deref!(Some(_)) => todo!() | error[E0004]: non-exhaustive patterns: `Some(_)` not covered - --> $DIR/empty-types.rs:35:11 + --> $DIR/empty-types.rs:34:11 | LL | match *box_opt_void { | ^^^^^^^^^^^^^ pattern `Some(_)` not covered diff --git a/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.rs b/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.rs index f567dc07bb59..8858f628217c 100644 --- a/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.rs +++ b/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.rs @@ -2,7 +2,6 @@ //! doesn't support this, so make sure we catch it beforehand. As a consequence, it takes priority //! over non-exhaustive match and unreachable pattern errors. #![feature(deref_patterns)] -#![expect(incomplete_features)] #![deny(unreachable_patterns)] use std::borrow::Cow; diff --git a/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.stderr b/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.stderr index 5ad24164b985..3d036afc9e90 100644 --- a/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.stderr +++ b/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.stderr @@ -1,5 +1,5 @@ error: mix of deref patterns and normal constructors - --> $DIR/mixed-constructors.rs:16:9 + --> $DIR/mixed-constructors.rs:15:9 | LL | false => {} | ^^^^^ matches on the result of dereferencing `Cow<'_, bool>` @@ -7,7 +7,7 @@ LL | Cow::Borrowed(_) => {} | ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>` error: mix of deref patterns and normal constructors - --> $DIR/mixed-constructors.rs:22:9 + --> $DIR/mixed-constructors.rs:21:9 | LL | Cow::Borrowed(_) => {} | ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>` @@ -15,7 +15,7 @@ LL | true => {} | ^^^^ matches on the result of dereferencing `Cow<'_, bool>` error: mix of deref patterns and normal constructors - --> $DIR/mixed-constructors.rs:29:9 + --> $DIR/mixed-constructors.rs:28:9 | LL | Cow::Owned(_) => {} | ^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>` @@ -23,7 +23,7 @@ LL | false => {} | ^^^^^ matches on the result of dereferencing `Cow<'_, bool>` error: mix of deref patterns and normal constructors - --> $DIR/mixed-constructors.rs:36:10 + --> $DIR/mixed-constructors.rs:35:10 | LL | (Cow::Borrowed(_), 0) => {} | ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>` @@ -31,7 +31,7 @@ LL | (true, 0) => {} | ^^^^ matches on the result of dereferencing `Cow<'_, bool>` error: mix of deref patterns and normal constructors - --> $DIR/mixed-constructors.rs:43:13 + --> $DIR/mixed-constructors.rs:42:13 | LL | (0, Cow::Borrowed(_)) => {} | ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>` diff --git a/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.rs b/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.rs index bab6308223e5..64f5f14f199a 100644 --- a/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.rs +++ b/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.rs @@ -1,6 +1,5 @@ //! Test non-exhaustive matches involving deref patterns. #![feature(deref_patterns)] -#![expect(incomplete_features)] #![deny(unreachable_patterns)] fn main() { diff --git a/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.stderr b/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.stderr index a1abd5f0e3f4..75c8b1864c78 100644 --- a/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.stderr +++ b/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.stderr @@ -1,5 +1,5 @@ error[E0004]: non-exhaustive patterns: `deref!(true)` not covered - --> $DIR/non-exhaustive.rs:7:11 + --> $DIR/non-exhaustive.rs:6:11 | LL | match Box::new(false) { | ^^^^^^^^^^^^^^^ pattern `deref!(true)` not covered @@ -14,7 +14,7 @@ LL + deref!(true) => todo!() | error[E0004]: non-exhaustive patterns: `deref!(deref!(false))` not covered - --> $DIR/non-exhaustive.rs:12:11 + --> $DIR/non-exhaustive.rs:11:11 | LL | match Box::new(Box::new(false)) { | ^^^^^^^^^^^^^^^^^^^^^^^^^ pattern `deref!(deref!(false))` not covered @@ -29,7 +29,7 @@ LL + deref!(deref!(false)) => todo!() | error[E0004]: non-exhaustive patterns: `deref!((true, deref!(true)))` and `deref!((false, deref!(false)))` not covered - --> $DIR/non-exhaustive.rs:17:11 + --> $DIR/non-exhaustive.rs:16:11 | LL | match Box::new((true, Box::new(false))) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ patterns `deref!((true, deref!(true)))` and `deref!((false, deref!(false)))` not covered @@ -44,7 +44,7 @@ LL + deref!((true, deref!(true))) | deref!((false, deref!(false))) => to | error[E0004]: non-exhaustive patterns: `deref!((deref!(T::C), _))` not covered - --> $DIR/non-exhaustive.rs:24:11 + --> $DIR/non-exhaustive.rs:23:11 | LL | match Box::new((Box::new(T::A), Box::new(T::A))) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pattern `deref!((deref!(T::C), _))` not covered diff --git a/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.rs b/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.rs index 2677fc54dedc..5559ee8f5566 100644 --- a/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.rs +++ b/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.rs @@ -1,6 +1,5 @@ //! Test unreachable patterns involving deref patterns. #![feature(deref_patterns)] -#![expect(incomplete_features)] #![deny(unreachable_patterns)] fn main() { diff --git a/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.stderr b/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.stderr index 045e11be3196..88ac7b2351a7 100644 --- a/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.stderr +++ b/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.stderr @@ -1,5 +1,5 @@ error: unreachable pattern - --> $DIR/unreachable-patterns.rs:10:9 + --> $DIR/unreachable-patterns.rs:9:9 | LL | false => {} | ----- matches all the relevant values @@ -7,13 +7,13 @@ LL | false => {} | ^^^^^ no value can reach this | note: the lint level is defined here - --> $DIR/unreachable-patterns.rs:4:9 + --> $DIR/unreachable-patterns.rs:3:9 | LL | #![deny(unreachable_patterns)] | ^^^^^^^^^^^^^^^^^^^^ error: unreachable pattern - --> $DIR/unreachable-patterns.rs:16:9 + --> $DIR/unreachable-patterns.rs:15:9 | LL | true => {} | ---- matches all the relevant values @@ -22,13 +22,13 @@ LL | true => {} | ^^^^ no value can reach this error: unreachable pattern - --> $DIR/unreachable-patterns.rs:23:9 + --> $DIR/unreachable-patterns.rs:22:9 | LL | _ => {} | ^ no value can reach this | note: multiple earlier patterns match some of the same values - --> $DIR/unreachable-patterns.rs:23:9 + --> $DIR/unreachable-patterns.rs:22:9 | LL | (true, _) => {} | --------- matches some of the same values @@ -40,7 +40,7 @@ LL | _ => {} | ^ collectively making this unreachable error: unreachable pattern - --> $DIR/unreachable-patterns.rs:29:9 + --> $DIR/unreachable-patterns.rs:28:9 | LL | (T::A | T::B, T::A | T::C) => {} | -------------------------- matches all the relevant values @@ -48,7 +48,7 @@ LL | (T::A, T::C) => {} | ^^^^^^^^^^^^ no value can reach this error: unreachable pattern - --> $DIR/unreachable-patterns.rs:30:9 + --> $DIR/unreachable-patterns.rs:29:9 | LL | (T::A | T::B, T::A | T::C) => {} | -------------------------- matches all the relevant values From 29df68bb1243a0d90217b4cd9cfb84673f34046a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 24 Feb 2026 20:41:24 +0100 Subject: [PATCH 0087/1059] Remove one DOM level for lint additional info --- util/gh-pages/index_template.html | 8 ++------ util/gh-pages/style.css | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 91a5c1263195..dd9e9b837640 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -203,15 +203,11 @@ Otherwise, have a great day =^.^= {{lint.version}} {# #} {# Open related issues #} -
{# #} - Related Issues {# #} -
+ Related Issues {# Jump to source #} {% if let Some(id_location) = lint.id_location %} -
{# #} - View Source {# #} -
+ View Source {% endif %} {# #} {# #} diff --git a/util/gh-pages/style.css b/util/gh-pages/style.css index ce478a3e18d0..59868ddce847 100644 --- a/util/gh-pages/style.css +++ b/util/gh-pages/style.css @@ -459,7 +459,7 @@ article:hover .panel-title-name .anchor { display: inline;} display: flex; flex-flow: column; } - .lint-additional-info > div + div { + .lint-additional-info > * + * { border-top: 1px solid var(--theme-popup-border); } } @@ -468,12 +468,12 @@ article:hover .panel-title-name .anchor { display: inline;} display: flex; flex-flow: row; } - .lint-additional-info > div + div { + .lint-additional-info > * + * { border-left: 1px solid var(--theme-popup-border); } } -.lint-additional-info > div { +.lint-additional-info > * { display: inline-flex; min-width: 200px; flex-grow: 1; From 0a22189ba132980e36dda24eee99d3bae064e919 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 24 Feb 2026 20:46:05 +0100 Subject: [PATCH 0088/1059] Remove unused `label-default` CSS class --- util/gh-pages/index_template.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index dd9e9b837640..35254fcfab79 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -194,13 +194,13 @@ Otherwise, have a great day =^.^= {# Applicability #}
{# #} Applicability: {#+ #} - {{ lint.applicability_str() }} {# #} + {{ lint.applicability_str() }} {# #} (?) {# #}
{# Clippy version #}
{# #} {% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif +%} in: {#+ #} - {{lint.version}} {# #} + {{lint.version}} {# #}
{# Open related issues #} Related Issues From db18ecf2637f9994ad2508144bcf4f018ac46315 Mon Sep 17 00:00:00 2001 From: sayantn Date: Wed, 25 Feb 2026 06:17:53 +0530 Subject: [PATCH 0089/1059] Require avxvnni for avx10.2 --- compiler/rustc_target/src/target_features.rs | 6 +- library/std_detect/src/detect/os/x86.rs | 68 +++++++++++--------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index c3de7e257662..aa272ebd4b77 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -392,7 +392,11 @@ pub fn toggle_allowed(&self) -> Result<(), &'static str> { "avx512vpopcntdq", ], ), - ("avx10.2", Unstable(sym::avx10_target_feature), &["avx10.1"]), + ( + "avx10.2", + Unstable(sym::avx10_target_feature), + &["avx10.1", "avxvnni", "avxvnniint8", "avxvnniint16"], + ), ("avx512bf16", Stable, &["avx512bw"]), ("avx512bitalg", Stable, &["avx512bw"]), ("avx512bw", Stable, &["avx512f"]), diff --git a/library/std_detect/src/detect/os/x86.rs b/library/std_detect/src/detect/os/x86.rs index f2205ba07dd4..b24ef6a37ef5 100644 --- a/library/std_detect/src/detect/os/x86.rs +++ b/library/std_detect/src/detect/os/x86.rs @@ -202,6 +202,28 @@ pub(crate) fn detect_features() -> cache::Initializer { // Test `XCR0.APX[19]` with the mask `0b1000_0000_0000_0000_0000 == 0x80000` let os_apx_support = xcr0 & 0x80000 == 0x80000; + if os_amx_support { + enable(extended_features_edx, 24, Feature::amx_tile); + enable(extended_features_edx, 25, Feature::amx_int8); + enable(extended_features_edx, 22, Feature::amx_bf16); + enable(extended_features_eax_leaf_1, 21, Feature::amx_fp16); + enable(extended_features_edx_leaf_1, 8, Feature::amx_complex); + + if max_basic_leaf >= 0x1e { + let CpuidResult { eax: amx_feature_flags_eax, .. } = + __cpuid_count(0x1e_u32, 1); + + enable(amx_feature_flags_eax, 4, Feature::amx_fp8); + enable(amx_feature_flags_eax, 6, Feature::amx_tf32); + enable(amx_feature_flags_eax, 7, Feature::amx_avx512); + enable(amx_feature_flags_eax, 8, Feature::amx_movrs); + } + } + + if os_apx_support { + enable(extended_features_edx_leaf_1, 21, Feature::apxf); + } + // Only if the OS and the CPU support saving/restoring the AVX // registers we enable `xsave` support: if os_avx_support { @@ -236,9 +258,10 @@ pub(crate) fn detect_features() -> cache::Initializer { enable(extended_features_ebx, 5, Feature::avx2); // "Short" versions of AVX512 instructions - enable(extended_features_eax_leaf_1, 4, Feature::avxvnni); - enable(extended_features_eax_leaf_1, 23, Feature::avxifma); - enable(extended_features_edx_leaf_1, 4, Feature::avxvnniint8); + let avxvnni = enable(extended_features_eax_leaf_1, 4, Feature::avxvnni); + let avxvnniint8 = enable(extended_features_eax_leaf_1, 23, Feature::avxifma); + let avxvnniint16 = + enable(extended_features_edx_leaf_1, 4, Feature::avxvnniint8); enable(extended_features_edx_leaf_1, 5, Feature::avxneconvert); enable(extended_features_edx_leaf_1, 10, Feature::avxvnniint16); @@ -269,37 +292,18 @@ pub(crate) fn detect_features() -> cache::Initializer { enable(extended_features_edx, 8, Feature::avx512vp2intersect); enable(extended_features_edx, 23, Feature::avx512fp16); enable(extended_features_eax_leaf_1, 5, Feature::avx512bf16); - } - } - if os_amx_support { - enable(extended_features_edx, 24, Feature::amx_tile); - enable(extended_features_edx, 25, Feature::amx_int8); - enable(extended_features_edx, 22, Feature::amx_bf16); - enable(extended_features_eax_leaf_1, 21, Feature::amx_fp16); - enable(extended_features_edx_leaf_1, 8, Feature::amx_complex); + let avx10_1 = enable(extended_features_edx_leaf_1, 19, Feature::avx10_1); + if avx10_1 { + let CpuidResult { ebx, .. } = __cpuid(0x24); + let avx10_version = ebx & 0xff; - if max_basic_leaf >= 0x1e { - let CpuidResult { eax: amx_feature_flags_eax, .. } = - __cpuid_count(0x1e_u32, 1); - - enable(amx_feature_flags_eax, 4, Feature::amx_fp8); - enable(amx_feature_flags_eax, 6, Feature::amx_tf32); - enable(amx_feature_flags_eax, 7, Feature::amx_avx512); - enable(amx_feature_flags_eax, 8, Feature::amx_movrs); - } - } - - if os_apx_support { - enable(extended_features_edx_leaf_1, 21, Feature::apxf); - } - - let avx10_1 = enable(extended_features_edx_leaf_1, 19, Feature::avx10_1); - if avx10_1 { - let CpuidResult { ebx, .. } = __cpuid(0x24); - let avx10_version = ebx & 0xff; - if avx10_version >= 2 { - value.set(Feature::avx10_2 as u32); + // AVX10.2 supports masked versions of dot-product instructions available in avxvnni etc, + // so it doesn't make sense to have it without the unmasked versions + if avx10_version >= 2 && avxvnni && avxvnniint8 && avxvnniint16 { + value.set(Feature::avx10_2 as u32); + } + } } } } From f62b2f3b4dc85bac50b4b6f517587f479e79f9e1 Mon Sep 17 00:00:00 2001 From: Claire Fan Date: Mon, 23 Feb 2026 18:47:11 +0800 Subject: [PATCH 0090/1059] [BPF] add target feature allows-misaligned-mem-access --- compiler/rustc_codegen_llvm/src/llvm_util.rs | 5 +++- compiler/rustc_target/src/target_features.rs | 6 +++-- .../crates/hir-ty/src/target_feature.rs | 1 + tests/assembly-llvm/bpf_unaligned.rs | 27 +++++++++++++++++++ tests/auxiliary/minicore.rs | 4 +++ tests/codegen-llvm/bpf-allows-unaligned.rs | 11 ++++++++ tests/ui/check-cfg/target_feature.stderr | 1 + 7 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 tests/assembly-llvm/bpf_unaligned.rs create mode 100644 tests/codegen-llvm/bpf-allows-unaligned.rs diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index fbb582fe8601..66bd6dfffec1 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -252,7 +252,10 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option Some(LLVMFeature::new("fullfp16")), s => Some(LLVMFeature::new(s)), }, - + Arch::Bpf => match s { + "allows-misaligned-mem-access" if major < 22 => None, + s => Some(LLVMFeature::new(s)), + }, // Filter out features that are not supported by the current LLVM version Arch::LoongArch32 | Arch::LoongArch64 => match s { "32s" if major < 21 => None, diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index c3de7e257662..8a87cee91117 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -756,8 +756,10 @@ pub fn toggle_allowed(&self) -> Result<(), &'static str> { // tidy-alphabetical-end ]; -const BPF_FEATURES: &[(&str, Stability, ImpliedFeatures)] = - &[("alu32", Unstable(sym::bpf_target_feature), &[])]; +const BPF_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ + ("alu32", Unstable(sym::bpf_target_feature), &[]), + ("allows-misaligned-mem-access", Unstable(sym::bpf_target_feature), &[]), +]; static CSKY_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ // tidy-alphabetical-start diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/target_feature.rs b/src/tools/rust-analyzer/crates/hir-ty/src/target_feature.rs index 2bd675ba124e..29a933f92263 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/target_feature.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/target_feature.rs @@ -217,6 +217,7 @@ pub(crate) fn from_fn_no_implications(db: &'db dyn HirDatabase, owner: FunctionI ("relaxed-simd", &["simd128"]), // BPF ("alu32", &[]), + ("allows-misaligned-mem-access", &[]), // CSKY ("10e60", &["7e10"]), ("2e3", &["e2"]), diff --git a/tests/assembly-llvm/bpf_unaligned.rs b/tests/assembly-llvm/bpf_unaligned.rs new file mode 100644 index 000000000000..466c3e411ec2 --- /dev/null +++ b/tests/assembly-llvm/bpf_unaligned.rs @@ -0,0 +1,27 @@ +//@ add-minicore +//@ assembly-output: emit-asm +//@ compile-flags: --target bpfel-unknown-none -C target_feature=+allows-misaligned-mem-access +//@ min-llvm-version: 22 +//@ needs-llvm-components: bpf +#![feature(no_core)] +#![crate_type = "rlib"] +#![no_core] + +extern crate minicore; +use minicore::*; + +// CHECK-LABEL: test_load_i64: +// CHECK: r0 = *(u64 *)(r1 + 0) +#[no_mangle] +pub unsafe fn test_load_i64(p: *const u64) -> u64 { + let mut tmp: u64 = 0; + copy_nonoverlapping(p as *const u8, &mut tmp as *mut u64 as *mut u8, 8); + tmp +} + +// CHECK-LABEL: test_store_i64: +// CHECK: *(u64 *)(r1 + 0) = r2 +#[no_mangle] +pub unsafe fn test_store_i64(p: *mut u64, v: u64) { + copy_nonoverlapping(&v as *const u64 as *const u8, p as *mut u8, 8); +} diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index 3b3472340e73..11f0c9927cdb 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -275,6 +275,10 @@ trait Drop { fn drop(&mut self); } +#[rustc_nounwind] +#[rustc_intrinsic] +pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize); + pub mod mem { #[rustc_nounwind] #[rustc_intrinsic] diff --git a/tests/codegen-llvm/bpf-allows-unaligned.rs b/tests/codegen-llvm/bpf-allows-unaligned.rs new file mode 100644 index 000000000000..c7a70d5b2e50 --- /dev/null +++ b/tests/codegen-llvm/bpf-allows-unaligned.rs @@ -0,0 +1,11 @@ +//@ only-bpf +#![crate_type = "lib"] +#![feature(bpf_target_feature)] +#![no_std] + +#[no_mangle] +#[target_feature(enable = "allows-misaligned-mem-access")] +// CHECK: define noundef zeroext i8 @foo(i8 noundef returned %arg) unnamed_addr #0 { +pub unsafe fn foo(arg: u8) -> u8 { + arg +} diff --git a/tests/ui/check-cfg/target_feature.stderr b/tests/ui/check-cfg/target_feature.stderr index 06a7f477a7fd..06384c2202f1 100644 --- a/tests/ui/check-cfg/target_feature.stderr +++ b/tests/ui/check-cfg/target_feature.stderr @@ -17,6 +17,7 @@ LL | cfg!(target_feature = "_UNEXPECTED_VALUE"); `addsubiw` `adx` `aes` +`allows-misaligned-mem-access` `altivec` `alu32` `amx-avx512` From f0336511fa7bc27e05fbab5c851b8cf592844132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 27 Jan 2026 16:09:52 +0100 Subject: [PATCH 0091/1059] remove debug requirement from hooks --- compiler/rustc_middle/src/hooks/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_middle/src/hooks/mod.rs b/compiler/rustc_middle/src/hooks/mod.rs index 0ddcdac817b8..1a7a8f5cae60 100644 --- a/compiler/rustc_middle/src/hooks/mod.rs +++ b/compiler/rustc_middle/src/hooks/mod.rs @@ -35,8 +35,10 @@ pub struct Providers { impl Default for Providers { fn default() -> Self { + #[allow(unused)] Providers { - $($name: |_, $($arg,)*| default_hook(stringify!($name), &($($arg,)*))),* + $($name: + |_, $($arg,)*| default_hook(stringify!($name))),* } } } @@ -118,8 +120,6 @@ fn clone(&self) -> Self { *self } } #[cold] -fn default_hook(name: &str, args: &dyn std::fmt::Debug) -> ! { - bug!( - "`tcx.{name}{args:?}` cannot be called as `{name}` was never assigned to a provider function" - ) +fn default_hook(name: &str) -> ! { + bug!("`tcx.{name}` cannot be called as `{name}` was never assigned to a provider function") } From 244b6d5ca8fd6257df414c8b8d48c9c8d26269de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 27 Jan 2026 15:00:36 +0100 Subject: [PATCH 0092/1059] Regression test for trait-system-refactor#262 --- .../generalize/eagely-normalizing-aliases.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs diff --git a/tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs b/tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs new file mode 100644 index 000000000000..463fe49e5531 --- /dev/null +++ b/tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs @@ -0,0 +1,26 @@ +//@ revisions: old next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) +//@ check-pass +// Regression test for trait-system-refactor-initiative#262 + +trait View {} +trait HasAssoc { + type Assoc; +} + +struct StableVec(T); +impl View for StableVec {} + +fn assert_view(f: F) -> F { f } + + +fn store(x: StableVec) +where + T: HasAssoc, + StableVec: View, +{ + let _: StableVec = assert_view(x); +} + +fn main() {} From 5d0863b147c0a0084dfb15e6435004909363865f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 27 Jan 2026 15:00:36 +0100 Subject: [PATCH 0093/1059] eagerly normalize during generalization --- .../src/type_check/relate_tys.rs | 5 + compiler/rustc_infer/src/infer/at.rs | 6 + .../src/infer/relate/generalize.rs | 103 ++++++++++++++---- .../rustc_infer/src/infer/relate/lattice.rs | 5 + .../src/infer/relate/type_relating.rs | 9 ++ .../src/solve/assembly/structural_traits.rs | 7 +- .../src/solve/eval_ctxt/mod.rs | 35 +++++- compiler/rustc_type_ir/src/relate/combine.rs | 2 + .../src/relate/solver_relating.rs | 49 ++++++--- 9 files changed, 179 insertions(+), 42 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/relate_tys.rs b/compiler/rustc_borrowck/src/type_check/relate_tys.rs index e2d684e12a81..152a7674490c 100644 --- a/compiler/rustc_borrowck/src/type_check/relate_tys.rs +++ b/compiler/rustc_borrowck/src/type_check/relate_tys.rs @@ -616,4 +616,9 @@ fn register_alias_relate_predicate(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) { } })]); } + + fn try_eagerly_normalize_alias(&mut self, _alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { + // Past hir typeck, so we don't have to worry about type inference anymore. + self.type_checker.infcx.next_ty_var(self.span()) + } } diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index 70e3d7dc9fef..84ad05fa8ea2 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -140,6 +140,8 @@ pub fn sup( ty::Contravariant, actual, self.cause.span, + // TODO: should normalize + &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -173,6 +175,8 @@ pub fn sub( ty::Covariant, actual, self.cause.span, + // TODO: should normalize + &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -225,6 +229,8 @@ pub fn eq_trace( ty::Invariant, actual, self.cause.span, + // TODO: should normalize + &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index 69c090b662e5..34fd32c45bd2 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -76,6 +76,7 @@ pub fn instantiate_ty_var>>( target_vid, instantiation_variance, source_ty, + &mut |alias| relation.try_eagerly_normalize_alias(alias), )?; // Constrain `b_vid` to the generalized type `generalized_ty`. @@ -210,6 +211,7 @@ pub(crate) fn instantiate_const_var target_vid, ty::Invariant, source_ct, + &mut |alias| relation.try_eagerly_normalize_alias(alias), )?; debug_assert!(!generalized_ct.is_ct_infer()); @@ -249,6 +251,7 @@ fn generalize> + Relate>>( target_vid: impl Into, ambient_variance: ty::Variance, source_term: T, + normalize: &mut dyn FnMut(ty::AliasTy<'tcx>) -> Ty<'tcx>, ) -> RelateResult<'tcx, Generalization> { assert!(!source_term.has_escaping_bound_vars()); let (for_universe, root_vid) = match target_vid.into() { @@ -269,8 +272,9 @@ fn generalize> + Relate>>( for_universe, root_term: source_term.into(), ambient_variance, - in_alias: false, + state: GeneralizationState::Default, cache: Default::default(), + normalize, }; let value_may_be_infer = generalizer.relate(source_term, source_term)?; @@ -317,6 +321,13 @@ fn visit_region(&mut self, r: ty::Region<'tcx>) { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +enum GeneralizationState { + Default, + InAlias, + InNormalizedAlias, +} + /// The "generalizer" is used when handling inference variables. /// /// The basic strategy for handling a constraint like `?A <: B` is to @@ -361,9 +372,14 @@ struct Generalizer<'me, 'tcx> { /// This is necessary to correctly handle /// `::Assoc>::Assoc == ?0`. This equality can /// hold by either normalizing the outer or the inner associated type. - in_alias: bool, + // TODO: update doc comment + state: GeneralizationState, - cache: SsoHashMap<(Ty<'tcx>, ty::Variance, bool), Ty<'tcx>>, + cache: SsoHashMap<(Ty<'tcx>, ty::Variance, GeneralizationState), Ty<'tcx>>, + + /// Normalize an alias in the trait solver. + /// If normalization fails, a fresh infer var is returned. + normalize: &'me mut dyn FnMut(ty::AliasTy<'tcx>) -> Ty<'tcx>, } impl<'tcx> Generalizer<'_, 'tcx> { @@ -409,17 +425,34 @@ fn generalize_alias_ty( // with inference variables can cause incorrect ambiguity. // // cc trait-system-refactor-initiative#110 - if self.infcx.next_trait_solver() && !alias.has_escaping_bound_vars() && !self.in_alias { - return Ok(self.next_ty_var_for_alias()); + if self.infcx.next_trait_solver() + && !alias.has_escaping_bound_vars() + && match self.state { + GeneralizationState::Default => true, + GeneralizationState::InAlias => false, + // When generalizing an alias after normalizing, + // the outer alias should be treated as rigid and we shouldn't try generalizing it again. + // If we recursively find more aliases, the state should have been set back to InAlias. + GeneralizationState::InNormalizedAlias => unreachable!(), + } + { + let normalized_alias = (self.normalize)(alias); + + self.state = GeneralizationState::InNormalizedAlias; + // recursively generalize, treat the outer alias as rigid to avoid infinite recursion + let res = self.relate(normalized_alias, normalized_alias); + + // only one way to get here + self.state = GeneralizationState::Default; + + return res; } - let is_nested_alias = mem::replace(&mut self.in_alias, true); + let previous_state = mem::replace(&mut self.state, GeneralizationState::InAlias); let result = match self.relate(alias, alias) { Ok(alias) => Ok(alias.to_ty(self.cx())), - Err(e) => { - if is_nested_alias { - return Err(e); - } else { + Err(e) => match previous_state { + GeneralizationState::Default => { let mut visitor = MaxUniverse::new(); alias.visit_with(&mut visitor); let infer_replacement_is_complete = @@ -432,9 +465,14 @@ fn generalize_alias_ty( debug!("generalization failure in alias"); Ok(self.next_ty_var_for_alias()) } - } + GeneralizationState::InAlias => return Err(e), + // When generalizing an alias after normalizing, + // the outer alias should be treated as rigid and we shouldn't try generalizing it again. + // If we recursively find more aliases, the state should have been set back to InAlias. + GeneralizationState::InNormalizedAlias => unreachable!(), + }, }; - self.in_alias = is_nested_alias; + self.state = previous_state; result } } @@ -488,7 +526,7 @@ fn relate_with_variance>>( fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { assert_eq!(t, t2); // we are misusing TypeRelation here; both LHS and RHS ought to be == - if let Some(&result) = self.cache.get(&(t, self.ambient_variance, self.in_alias)) { + if let Some(&result) = self.cache.get(&(t, self.ambient_variance, self.state)) { return Ok(result); } @@ -553,9 +591,17 @@ fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { // cc trait-system-refactor-initiative#108 if self.infcx.next_trait_solver() && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) - && self.in_alias { - inner.type_variables().equate(vid, new_var_id); + match self.state { + GeneralizationState::InAlias => { + inner.type_variables().equate(vid, new_var_id); + } + GeneralizationState::Default + | GeneralizationState::InNormalizedAlias => {} + } + GeneralizerState::Default + | GeneralizerState::ShallowStructurallyRelateAliases + | GeneralizerState::StructurallyRelateAliases => {} } debug!("replacing original vid={:?} with new={:?}", vid, new_var_id); @@ -585,14 +631,25 @@ fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { } ty::Alias(_, data) => match self.structurally_relate_aliases { - StructurallyRelateAliases::No => self.generalize_alias_ty(data), + StructurallyRelateAliases::No => match self.state { + GeneralizationState::Default | GeneralizationState::InAlias => { + self.generalize_alias_ty(data) + } + GeneralizationState::InNormalizedAlias => { + // We can switch back to default, we've treated one layer as rigid by doing this operation. + self.state = GeneralizationState::Default; + let res = relate::structurally_relate_tys(self, t, t); + self.state = GeneralizationState::InNormalizedAlias; + res + } + }, StructurallyRelateAliases::Yes => relate::structurally_relate_tys(self, t, t), }, _ => relate::structurally_relate_tys(self, t, t), }?; - self.cache.insert((t, self.ambient_variance, self.in_alias), g); + self.cache.insert((t, self.ambient_variance, self.state), g); Ok(g) } @@ -683,9 +740,17 @@ fn consts( // for more details. if self.infcx.next_trait_solver() && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) - && self.in_alias { - variable_table.union(vid, new_var_id); + match self.state { + GeneralizationState::InAlias => { + variable_table.union(vid, new_var_id); + } + GeneralizationState::Default + | GeneralizationState::InNormalizedAlias => {} + } + GeneralizerState::Default + | GeneralizerState::ShallowStructurallyRelateAliases + | GeneralizerState::StructurallyRelateAliases => {} } Ok(ty::Const::new_var(self.cx(), new_var_id)) } diff --git a/compiler/rustc_infer/src/infer/relate/lattice.rs b/compiler/rustc_infer/src/infer/relate/lattice.rs index a05e2d40e829..2cb256b1c63c 100644 --- a/compiler/rustc_infer/src/infer/relate/lattice.rs +++ b/compiler/rustc_infer/src/infer/relate/lattice.rs @@ -299,4 +299,9 @@ fn register_alias_relate_predicate(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) { ty::AliasRelationDirection::Equate, ))]); } + + fn try_eagerly_normalize_alias(&mut self, _alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { + // TODO: this should actually normalize + self.infcx.next_ty_var(self.span()) + } } diff --git a/compiler/rustc_infer/src/infer/relate/type_relating.rs b/compiler/rustc_infer/src/infer/relate/type_relating.rs index 96a0375f5fba..67f9dc69a4a6 100644 --- a/compiler/rustc_infer/src/infer/relate/type_relating.rs +++ b/compiler/rustc_infer/src/infer/relate/type_relating.rs @@ -396,4 +396,13 @@ fn register_alias_relate_predicate(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) { } })]); } + + fn try_eagerly_normalize_alias( + &mut self, + _alias: rustc_type_ir::AliasTy< as rustc_type_ir::InferCtxtLike>::Interner>, + ) -> < as rustc_type_ir::InferCtxtLike>::Interner as rustc_type_ir::Interner>::Ty + { + // We only try to eagerly normalize aliases if we're using the new solver. + unreachable!() + } } diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs index 05ea217c1de0..f94b0f0c30ed 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs @@ -976,7 +976,12 @@ fn try_eagerly_replace_alias( let replacement = self.ecx.instantiate_binder_with_infer(*replacement); self.nested.extend( self.ecx - .eq_and_get_goals(self.param_env, alias_term, replacement.projection_term) + .relate_and_get_goals( + self.param_env, + alias_term, + ty::Invariant, + replacement.projection_term, + ) .expect("expected to be able to unify goal projection with dyn's projection"), ); diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 6841fe1c5124..fedb6390d958 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -408,7 +408,7 @@ pub(super) fn ignore_candidate_head_usages(&mut self, usages: CandidateHeadUsage /// Recursively evaluates `goal`, returning whether any inference vars have /// been constrained and the certainty of the result. - fn evaluate_goal( + pub(super) fn evaluate_goal( &mut self, source: GoalSource, goal: Goal, @@ -1018,7 +1018,8 @@ pub(super) fn relate>( variance: ty::Variance, rhs: T, ) -> Result<(), NoSolution> { - let goals = self.delegate.relate(param_env, lhs, variance, rhs, self.origin_span)?; + let goals = self.relate_and_get_goals(param_env, lhs, variance, rhs)?; + for &goal in goals.iter() { let source = match goal.predicate.kind().skip_binder() { ty::PredicateKind::Subtype { .. } | ty::PredicateKind::AliasRelate(..) => { @@ -1039,13 +1040,37 @@ pub(super) fn relate>( /// If possible, try using `eq` instead which automatically handles nested /// goals correctly. #[instrument(level = "trace", skip(self, param_env), ret)] - pub(super) fn eq_and_get_goals>( - &self, + pub(super) fn relate_and_get_goals>( + &mut self, param_env: I::ParamEnv, lhs: T, + variance: ty::Variance, rhs: T, ) -> Result>, NoSolution> { - Ok(self.delegate.relate(param_env, lhs, ty::Variance::Invariant, rhs, self.origin_span)?) + let cx = self.cx(); + let delegate = self.delegate; + let origin_span = self.origin_span; + + let mut normalize = |alias: ty::AliasTy| { + let inference_var = self.next_ty_infer(); + + let goal = Goal::new( + cx, + param_env, + ty::PredicateKind::AliasRelate( + alias.to_ty(cx).into(), + inference_var.into(), + ty::AliasRelationDirection::Equate, + ), + ); + + // Ignore the result. If we can't eagerly normalize, returning the inference variable is enough. + let _ = self.evaluate_goal(GoalSource::TypeRelating, goal, None); + + self.resolve_vars_if_possible(inference_var) + }; + + Ok(delegate.relate(param_env, lhs, variance, rhs, origin_span, &mut normalize)?) } pub(super) fn instantiate_binder_with_infer + Copy>( diff --git a/compiler/rustc_type_ir/src/relate/combine.rs b/compiler/rustc_type_ir/src/relate/combine.rs index 64b87fac77f9..72d54c23733e 100644 --- a/compiler/rustc_type_ir/src/relate/combine.rs +++ b/compiler/rustc_type_ir/src/relate/combine.rs @@ -40,6 +40,8 @@ fn register_predicates( /// Register `AliasRelate` obligation(s) that both types must be related to each other. fn register_alias_relate_predicate(&mut self, a: I::Ty, b: I::Ty); + + fn try_eagerly_normalize_alias(&mut self, alias: ty::AliasTy) -> I::Ty; } pub fn super_combine_tys( diff --git a/compiler/rustc_type_ir/src/relate/solver_relating.rs b/compiler/rustc_type_ir/src/relate/solver_relating.rs index 82ee4f75fcb0..541b2531fe74 100644 --- a/compiler/rustc_type_ir/src/relate/solver_relating.rs +++ b/compiler/rustc_type_ir/src/relate/solver_relating.rs @@ -15,6 +15,7 @@ fn relate>( variance: ty::Variance, rhs: T, span: ::Span, + normalize: &mut dyn FnMut(ty::AliasTy) -> ::Ty, ) -> Result< Vec::Predicate>>, TypeError, @@ -32,40 +33,46 @@ fn eq_structurally_relating_aliases>( >; } -impl RelateExt for Infcx { - fn relate>( +impl> RelateExt for Infcx { + fn relate>( &self, - param_env: ::ParamEnv, + param_env: I::ParamEnv, lhs: T, variance: ty::Variance, rhs: T, - span: ::Span, - ) -> Result< - Vec::Predicate>>, - TypeError, - > { - let mut relate = - SolverRelating::new(self, StructurallyRelateAliases::No, variance, param_env, span); + span: I::Span, + normalize: &mut dyn FnMut(ty::AliasTy) -> I::Ty, + ) -> Result>, TypeError> { + let mut relate = SolverRelating::new( + self, + StructurallyRelateAliases::No, + variance, + param_env, + span, + normalize, + ); relate.relate(lhs, rhs)?; Ok(relate.goals) } - fn eq_structurally_relating_aliases>( + fn eq_structurally_relating_aliases>( &self, - param_env: ::ParamEnv, + param_env: I::ParamEnv, lhs: T, rhs: T, - span: ::Span, - ) -> Result< - Vec::Predicate>>, - TypeError, - > { + span: I::Span, + ) -> Result>, TypeError> { + // Structurally relating, we treat aliases as rigid, + // so we shouldn't ever try to normalize them. + let mut normalize_unreachable = |_alias| unreachable!(); + let mut relate = SolverRelating::new( self, StructurallyRelateAliases::Yes, ty::Invariant, param_env, span, + &mut normalize_unreachable, ); relate.relate(lhs, rhs)?; Ok(relate.goals) @@ -75,12 +82,14 @@ fn eq_structurally_relating_aliases>( /// Enforce that `a` is equal to or a subtype of `b`. pub struct SolverRelating<'infcx, Infcx, I: Interner> { infcx: &'infcx Infcx, + // Immutable fields. structurally_relate_aliases: StructurallyRelateAliases, param_env: I::ParamEnv, span: I::Span, // Mutable fields. ambient_variance: ty::Variance, + normalize: &'infcx mut dyn FnMut(ty::AliasTy) -> I::Ty, goals: Vec>, /// The cache only tracks the `ambient_variance` as it's the /// only field which is mutable and which meaningfully changes @@ -118,12 +127,14 @@ pub fn new( ambient_variance: ty::Variance, param_env: I::ParamEnv, span: I::Span, + normalize: &'infcx mut dyn FnMut(ty::AliasTy) -> I::Ty, ) -> Self { SolverRelating { infcx, structurally_relate_aliases, span, ambient_variance, + normalize, param_env, goals: vec![], cache: Default::default(), @@ -406,4 +417,8 @@ fn register_alias_relate_predicate(&mut self, a: I::Ty, b: I::Ty) { } })]); } + + fn try_eagerly_normalize_alias(&mut self, alias: ty::AliasTy) -> I::Ty { + (self.normalize)(alias) + } } From 04f2c0191ebe7a1018e00cf6db5f8394352c2da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 27 Jan 2026 15:46:09 +0100 Subject: [PATCH 0094/1059] implement eager normalization in a fresh context during typeck --- compiler/rustc_infer/src/infer/at.rs | 15 +++-- compiler/rustc_infer/src/infer/mod.rs | 13 ++++- .../rustc_infer/src/infer/relate/lattice.rs | 5 +- compiler/rustc_interface/src/passes.rs | 2 +- compiler/rustc_middle/src/hooks/mod.rs | 24 +++++++- compiler/rustc_trait_selection/src/solve.rs | 56 +++++++++++++++++-- 6 files changed, 98 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index 84ad05fa8ea2..3487286d5883 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -140,8 +140,9 @@ pub fn sup( ty::Contravariant, actual, self.cause.span, - // TODO: should normalize - &mut |_alias| self.infcx.next_ty_var(self.cause.span), + &mut |alias| { + self.infcx.try_eagerly_normalize_alias(self.param_env, self.cause.span, alias) + }, ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -175,8 +176,9 @@ pub fn sub( ty::Covariant, actual, self.cause.span, - // TODO: should normalize - &mut |_alias| self.infcx.next_ty_var(self.cause.span), + &mut |alias| { + self.infcx.try_eagerly_normalize_alias(self.param_env, self.cause.span, alias) + }, ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -229,8 +231,9 @@ pub fn eq_trace( ty::Invariant, actual, self.cause.span, - // TODO: should normalize - &mut |_alias| self.infcx.next_ty_var(self.cause.span), + &mut |alias| { + self.infcx.try_eagerly_normalize_alias(self.param_env, self.cause.span, alias) + }, ) .map(|goals| self.goals_to_obligations(goals)) } else { diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index b57306536260..2a2ac7c15924 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1,5 +1,5 @@ use std::cell::{Cell, RefCell}; -use std::fmt; +use std::{fmt, mem}; pub use at::DefineOpaqueTypes; use free_regions::RegionRelations; @@ -21,6 +21,7 @@ use rustc_macros::extension; pub use rustc_macros::{TypeFoldable, TypeVisitable}; use rustc_middle::bug; +use rustc_middle::hooks::TypeErasedInfcx; use rustc_middle::infer::canonical::{CanonicalQueryInput, CanonicalVarValues}; use rustc_middle::mir::ConstraintCategory; use rustc_middle::traits::select; @@ -1498,6 +1499,16 @@ pub fn ty_or_const_infer_var_changed(&self, infer_var: TyOrConstInferVar) -> boo } } + pub fn try_eagerly_normalize_alias<'a>( + &'a self, + param_env: ty::ParamEnv<'tcx>, + span: Span, + alias: ty::AliasTy<'tcx>, + ) -> Ty<'tcx> { + let erased = unsafe { mem::transmute::<_, TypeErasedInfcx<'a, 'tcx>>(self) }; + self.tcx.try_eagerly_normalize_alias(erased, param_env, span, alias) + } + /// Attach a callback to be invoked on each root obligation evaluated in the new trait solver. pub fn attach_obligation_inspector(&self, inspector: ObligationInspector<'tcx>) { debug_assert!( diff --git a/compiler/rustc_infer/src/infer/relate/lattice.rs b/compiler/rustc_infer/src/infer/relate/lattice.rs index 2cb256b1c63c..7e480df7dda6 100644 --- a/compiler/rustc_infer/src/infer/relate/lattice.rs +++ b/compiler/rustc_infer/src/infer/relate/lattice.rs @@ -300,8 +300,7 @@ fn register_alias_relate_predicate(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) { ))]); } - fn try_eagerly_normalize_alias(&mut self, _alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { - // TODO: this should actually normalize - self.infcx.next_ty_var(self.span()) + fn try_eagerly_normalize_alias(&mut self, alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { + self.infcx.try_eagerly_normalize_alias(self.param_env(), self.span(), alias) } } diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 15addd240785..225fff380ef5 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -900,7 +900,7 @@ pub fn write_interface<'tcx>(tcx: TyCtxt<'tcx>) { rustc_hir_typeck::provide(&mut providers.queries); ty::provide(&mut providers.queries); traits::provide(&mut providers.queries); - solve::provide(&mut providers.queries); + solve::provide(providers); rustc_passes::provide(&mut providers.queries); rustc_traits::provide(&mut providers.queries); rustc_ty_utils::provide(&mut providers.queries); diff --git a/compiler/rustc_middle/src/hooks/mod.rs b/compiler/rustc_middle/src/hooks/mod.rs index 1a7a8f5cae60..c09d9fc2d468 100644 --- a/compiler/rustc_middle/src/hooks/mod.rs +++ b/compiler/rustc_middle/src/hooks/mod.rs @@ -3,14 +3,16 @@ //! similar to queries, but queries come with a lot of machinery for caching and incremental //! compilation, whereas hooks are just plain function pointers without any of the query magic. +use std::marker::PhantomData; + use rustc_hir::def_id::{DefId, DefPathHash}; use rustc_session::StableCrateId; use rustc_span::def_id::{CrateNum, LocalDefId}; -use rustc_span::{ExpnHash, ExpnId}; +use rustc_span::{ExpnHash, ExpnId, Span}; -use crate::mir; use crate::query::on_disk_cache::{CacheEncoder, EncodedDepNodeIndex}; use crate::ty::{Ty, TyCtxt}; +use crate::{mir, ty}; macro_rules! declare_hooks { ($($(#[$attr:meta])*hook $name:ident($($arg:ident: $K:ty),*) -> $V:ty;)*) => { @@ -117,6 +119,24 @@ fn clone(&self) -> Self { *self } encoder: &mut CacheEncoder<'_, 'tcx>, query_result_index: &mut EncodedDepNodeIndex ) -> (); + + /// Tries to normalize an alias, ignoring any errors. + /// + /// Generalization with the new trait solver calls into this, + /// when generalizing outside of the trait solver in `hir_typeck`. + hook try_eagerly_normalize_alias( + type_erased_infcx: TypeErasedInfcx<'_, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + span: Span, + alias: ty::AliasTy<'tcx> + ) -> Ty<'tcx>; +} + +// `repr(transparent)` so we can transmute a `&'a Infcx<'tcx>` to this struct. +#[repr(transparent)] +pub struct TypeErasedInfcx<'a, 'tcx> { + _infcx: *const (), + phantom: PhantomData<&'a mut &'tcx ()>, } #[cold] diff --git a/compiler/rustc_trait_selection/src/solve.rs b/compiler/rustc_trait_selection/src/solve.rs index 5d200c4d340b..cb0288513903 100644 --- a/compiler/rustc_trait_selection/src/solve.rs +++ b/compiler/rustc_trait_selection/src/solve.rs @@ -1,3 +1,8 @@ +use std::mem; + +use rustc_infer::infer::InferCtxt; +use rustc_infer::traits::{Obligation, ObligationCause}; +use rustc_middle::hooks::TypeErasedInfcx; pub use rustc_next_trait_solver::solve::*; mod delegate; @@ -13,10 +18,13 @@ deeply_normalize, deeply_normalize_with_skipped_universes, deeply_normalize_with_skipped_universes_and_ambiguous_coroutine_goals, }; -use rustc_middle::query::Providers; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::util::Providers; +use rustc_span::Span; pub use select::InferCtxtSelectExt; +use crate::traits::ObligationCtxt; + fn evaluate_root_goal_for_proof_tree_raw<'tcx>( tcx: TyCtxt<'tcx>, canonical_input: CanonicalInput>, @@ -27,6 +35,46 @@ fn evaluate_root_goal_for_proof_tree_raw<'tcx>( ) } -pub fn provide(providers: &mut Providers) { - *providers = Providers { evaluate_root_goal_for_proof_tree_raw, ..*providers }; +fn try_eagerly_normalize_alias<'a, 'tcx>( + tcx: TyCtxt<'tcx>, + type_erased_infcx: TypeErasedInfcx<'a, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + span: Span, + alias: ty::AliasTy<'tcx>, +) -> Ty<'tcx> { + let infcx = unsafe { + mem::transmute::, &'a InferCtxt<'tcx>>(type_erased_infcx) + }; + + let ocx = ObligationCtxt::new(infcx); + + let infer_term = infcx.next_ty_var(span); + + // Dummy because we ignore the error anyway. + // We do provide a span, because this span is used when registering opaque types. + // For example, if we don't provide a span here, some diagnostics talking about TAIT will refer to a dummy span. + let cause = ObligationCause::dummy_with_span(span); + let obligation = Obligation::new( + tcx, + // we ignore the error anyway + ObligationCause::dummy(), + param_env, + ty::PredicateKind::AliasRelate( + alias.to_ty(tcx).into(), + infer_term.into(), + ty::AliasRelationDirection::Equate, + ), + ); + + ocx.register_obligation(obligation); + + // This only tries to eagerly resolve, if it errors we don't care. + let _ = ocx.try_evaluate_obligations(); + + infcx.resolve_vars_if_possible(infer_term) +} + +pub fn provide(providers: &mut Providers) { + providers.hooks.try_eagerly_normalize_alias = try_eagerly_normalize_alias; + providers.queries.evaluate_root_goal_for_proof_tree_raw = evaluate_root_goal_for_proof_tree_raw; } From 917713b4db3c627f2a35174c09d3ca715dd814b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 27 Jan 2026 16:03:10 +0100 Subject: [PATCH 0095/1059] merge generalizer state and structurally relate aliases --- .../src/infer/relate/generalize.rs | 117 ++++++++---------- 1 file changed, 53 insertions(+), 64 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index 34fd32c45bd2..d5c0726ed9a8 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -267,12 +267,14 @@ fn generalize> + Relate>>( let mut generalizer = Generalizer { infcx: self, span, - structurally_relate_aliases, root_vid, for_universe, root_term: source_term.into(), ambient_variance, - state: GeneralizationState::Default, + state: match structurally_relate_aliases { + StructurallyRelateAliases::No => GeneralizerState::Default, + StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, + }, cache: Default::default(), normalize, }; @@ -322,10 +324,13 @@ fn visit_region(&mut self, r: ty::Region<'tcx>) { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -enum GeneralizationState { +enum GeneralizerState { + /// Treat aliases as potentially normalizable. Default, - InAlias, - InNormalizedAlias, + IncompletelyRelateHigherRankedAlias, + /// Only one layer + ShallowStructurallyRelateAliases, + StructurallyRelateAliases, } /// The "generalizer" is used when handling inference variables. @@ -346,10 +351,6 @@ struct Generalizer<'me, 'tcx> { span: Span, - /// Whether aliases should be related structurally. If not, we have to - /// be careful when generalizing aliases. - structurally_relate_aliases: StructurallyRelateAliases, - /// The vid of the type variable that is in the process of being /// instantiated. If we find this within the value we are folding, /// that means we would have created a cyclic value. @@ -372,10 +373,9 @@ struct Generalizer<'me, 'tcx> { /// This is necessary to correctly handle /// `::Assoc>::Assoc == ?0`. This equality can /// hold by either normalizing the outer or the inner associated type. - // TODO: update doc comment - state: GeneralizationState, + state: GeneralizerState, - cache: SsoHashMap<(Ty<'tcx>, ty::Variance, GeneralizationState), Ty<'tcx>>, + cache: SsoHashMap<(Ty<'tcx>, ty::Variance, GeneralizerState), Ty<'tcx>>, /// Normalize an alias in the trait solver. /// If normalization fails, a fresh infer var is returned. @@ -415,44 +415,51 @@ fn next_ty_var_for_alias(&self) -> Ty<'tcx> { /// continue generalizing the alias. This ends up pulling down the universe of the /// inference variable and is incomplete in case the alias would normalize to a type /// which does not mention that inference variable. - fn generalize_alias_ty( + fn handle_alias_ty( &mut self, + alias_ty: Ty<'tcx>, alias: ty::AliasTy<'tcx>, ) -> Result, TypeError<'tcx>> { - // We do not eagerly replace aliases with inference variables if they have - // escaping bound vars, see the method comment for details. However, when we - // are inside of an alias with escaping bound vars replacing nested aliases - // with inference variables can cause incorrect ambiguity. - // - // cc trait-system-refactor-initiative#110 - if self.infcx.next_trait_solver() - && !alias.has_escaping_bound_vars() - && match self.state { - GeneralizationState::Default => true, - GeneralizationState::InAlias => false, - // When generalizing an alias after normalizing, - // the outer alias should be treated as rigid and we shouldn't try generalizing it again. - // If we recursively find more aliases, the state should have been set back to InAlias. - GeneralizationState::InNormalizedAlias => unreachable!(), + match self.state { + GeneralizerState::ShallowStructurallyRelateAliases => { + // We can switch back to default, we've treated one layer as rigid by doing this operation. + self.state = GeneralizerState::Default; + let res = relate::structurally_relate_tys(self, alias_ty, alias_ty); + self.state = GeneralizerState::ShallowStructurallyRelateAliases; + return res; } - { - let normalized_alias = (self.normalize)(alias); + GeneralizerState::StructurallyRelateAliases => { + return relate::structurally_relate_tys(self, alias_ty, alias_ty); + } + GeneralizerState::Default + if self.infcx.next_trait_solver() && !alias.has_escaping_bound_vars() => + { + // We do not eagerly replace aliases with inference variables if they have + // escaping bound vars, see the method comment for details. However, when we + // are inside of an alias with escaping bound vars replacing nested aliases + // with inference variables can cause incorrect ambiguity. + // + // cc trait-system-refactor-initiative#110 + let normalized_alias = (self.normalize)(alias); - self.state = GeneralizationState::InNormalizedAlias; - // recursively generalize, treat the outer alias as rigid to avoid infinite recursion - let res = self.relate(normalized_alias, normalized_alias); + self.state = GeneralizerState::ShallowStructurallyRelateAliases; + // recursively generalize, treat the outer alias as rigid to avoid infinite recursion + let res = self.relate(normalized_alias, normalized_alias); - // only one way to get here - self.state = GeneralizationState::Default; + // only one way to get here + self.state = GeneralizerState::Default; - return res; + return res; + } + GeneralizerState::Default | GeneralizerState::IncompletelyRelateHigherRankedAlias => {} } - let previous_state = mem::replace(&mut self.state, GeneralizationState::InAlias); + let previous_state = + mem::replace(&mut self.state, GeneralizerState::IncompletelyRelateHigherRankedAlias); let result = match self.relate(alias, alias) { Ok(alias) => Ok(alias.to_ty(self.cx())), Err(e) => match previous_state { - GeneralizationState::Default => { + GeneralizerState::Default => { let mut visitor = MaxUniverse::new(); alias.visit_with(&mut visitor); let infer_replacement_is_complete = @@ -465,11 +472,11 @@ fn generalize_alias_ty( debug!("generalization failure in alias"); Ok(self.next_ty_var_for_alias()) } - GeneralizationState::InAlias => return Err(e), - // When generalizing an alias after normalizing, - // the outer alias should be treated as rigid and we shouldn't try generalizing it again. - // If we recursively find more aliases, the state should have been set back to InAlias. - GeneralizationState::InNormalizedAlias => unreachable!(), + GeneralizerState::IncompletelyRelateHigherRankedAlias => return Err(e), + + // Early return. + GeneralizerState::ShallowStructurallyRelateAliases + | GeneralizerState::StructurallyRelateAliases => unreachable!(), }, }; self.state = previous_state; @@ -593,11 +600,9 @@ fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) { match self.state { - GeneralizationState::InAlias => { + GeneralizerState::IncompletelyRelateHigherRankedAlias => { inner.type_variables().equate(vid, new_var_id); } - GeneralizationState::Default - | GeneralizationState::InNormalizedAlias => {} } GeneralizerState::Default | GeneralizerState::ShallowStructurallyRelateAliases @@ -630,21 +635,7 @@ fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { } } - ty::Alias(_, data) => match self.structurally_relate_aliases { - StructurallyRelateAliases::No => match self.state { - GeneralizationState::Default | GeneralizationState::InAlias => { - self.generalize_alias_ty(data) - } - GeneralizationState::InNormalizedAlias => { - // We can switch back to default, we've treated one layer as rigid by doing this operation. - self.state = GeneralizationState::Default; - let res = relate::structurally_relate_tys(self, t, t); - self.state = GeneralizationState::InNormalizedAlias; - res - } - }, - StructurallyRelateAliases::Yes => relate::structurally_relate_tys(self, t, t), - }, + ty::Alias(_, data) => self.handle_alias_ty(t, data), _ => relate::structurally_relate_tys(self, t, t), }?; @@ -742,11 +733,9 @@ fn consts( && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) { match self.state { - GeneralizationState::InAlias => { + GeneralizerState::IncompletelyRelateHigherRankedAlias => { variable_table.union(vid, new_var_id); } - GeneralizationState::Default - | GeneralizationState::InNormalizedAlias => {} } GeneralizerState::Default | GeneralizerState::ShallowStructurallyRelateAliases From 7a123e87b21cd95fef62b80571ee6c644ac3a1c3 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 29 Jan 2026 18:49:58 +0100 Subject: [PATCH 0096/1059] explicitly provide type in transmute --- compiler/rustc_infer/src/infer/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 2a2ac7c15924..351d0001c677 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1505,7 +1505,8 @@ pub fn try_eagerly_normalize_alias<'a>( span: Span, alias: ty::AliasTy<'tcx>, ) -> Ty<'tcx> { - let erased = unsafe { mem::transmute::<_, TypeErasedInfcx<'a, 'tcx>>(self) }; + let erased = + unsafe { mem::transmute::<&'a InferCtxt<'tcx>, TypeErasedInfcx<'a, 'tcx>>(self) }; self.tcx.try_eagerly_normalize_alias(erased, param_env, span, alias) } From f50591fc1059d98ba57c79094f6e2d46db4b25b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Fri, 30 Jan 2026 19:00:20 +0100 Subject: [PATCH 0097/1059] normalize at the start of generalize if we can --- .../src/infer/relate/generalize.rs | 82 +++++++++++++------ .../src/canonical/canonicalizer.rs | 6 +- 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index d5c0726ed9a8..1d4dab001616 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -38,6 +38,31 @@ fn from(value: ty::ConstVid) -> Self { } impl<'tcx> InferCtxt<'tcx> { + fn check_generalized_alias_normalizes_to_tyvar>( + &self, + relation: &mut R, + source_ty: Ty<'tcx>, + ) -> Option> { + if !self.next_trait_solver() + || matches!(relation.structurally_relate_aliases(), StructurallyRelateAliases::Yes) + { + return None; + } + + // If we get an alias + let ty::Alias(_, alias) = source_ty.kind() else { + return None; + }; + + if alias.has_escaping_bound_vars() { + return None; + } + + let normalized_alias = relation.try_eagerly_normalize_alias(*alias); + + normalized_alias.is_ty_var().then_some(normalized_alias) + } + /// The idea is that we should ensure that the type variable `target_vid` /// is equal to, a subtype of, or a supertype of `source_ty`. /// @@ -51,7 +76,7 @@ impl<'tcx> InferCtxt<'tcx> { /// `TypeRelation`. Do not use this, and instead please use `At::eq`, for all /// other usecases (i.e. setting the value of a type var). #[instrument(level = "debug", skip(self, relation))] - pub fn instantiate_ty_var>>( + pub fn instantiate_ty_var>( &self, relation: &mut R, target_is_expected: bool, @@ -61,30 +86,31 @@ pub fn instantiate_ty_var>>( ) -> RelateResult<'tcx, ()> { debug_assert!(self.inner.borrow_mut().type_variables().probe(target_vid).is_unknown()); - // Generalize `source_ty` depending on the current variance. As an example, assume - // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference - // variable. - // - // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh - // region/type inference variables. - // - // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and - // `?1 <: ?3`. - let Generalization { value_may_be_infer: generalized_ty } = self.generalize( - relation.span(), - relation.structurally_relate_aliases(), - target_vid, - instantiation_variance, - source_ty, - &mut |alias| relation.try_eagerly_normalize_alias(alias), - )?; + let generalized_ty = + match self.check_generalized_alias_normalizes_to_tyvar(relation, source_ty) { + Some(tyvar) => tyvar, + None => { + // Generalize `source_ty` depending on the current variance. As an example, assume + // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference + // variable. + // + // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh + // region/type inference variables. + // + // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and + // `?1 <: ?3`. + let generalizer = self.generalize( + relation.span(), + relation.structurally_relate_aliases(), + target_vid, + instantiation_variance, + source_ty, + &mut |alias| relation.try_eagerly_normalize_alias(alias), + )?; - // Constrain `b_vid` to the generalized type `generalized_ty`. - if let &ty::Infer(ty::TyVar(generalized_vid)) = generalized_ty.kind() { - self.inner.borrow_mut().type_variables().equate(target_vid, generalized_vid); - } else { - self.inner.borrow_mut().type_variables().instantiate(target_vid, generalized_ty); - } + generalizer.value_may_be_infer + } + }; // Finally, relate `generalized_ty` to `source_ty`, as described in previous comment. // @@ -92,7 +118,10 @@ pub fn instantiate_ty_var>>( // relations wind up attributed to the same spans. We need // to associate causes/spans with each of the relations in // the stack to get this right. - if generalized_ty.is_ty_var() { + if let &ty::Infer(ty::TyVar(generalized_vid)) = generalized_ty.kind() { + // Constrain `b_vid` to the generalized type variable. + self.inner.borrow_mut().type_variables().equate(target_vid, generalized_vid); + // This happens for cases like `::Assoc == ?0`. // We can't instantiate `?0` here as that would result in a // cyclic type. We instead delay the unification in case @@ -133,6 +162,9 @@ pub fn instantiate_ty_var>>( } } } else { + // Constrain `b_vid` to the generalized type `generalized_ty`. + self.inner.borrow_mut().type_variables().instantiate(target_vid, generalized_ty); + // NOTE: The `instantiation_variance` is not the same variance as // used by the relation. When instantiating `b`, `target_is_expected` // is flipped and the `instantiation_variance` is also flipped. To diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index ce2be24adc58..e469451da993 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -177,7 +177,11 @@ fn canonicalize_param_env( cache: Default::default(), }; let param_env = param_env.fold_with(&mut env_canonicalizer); - debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); + debug_assert!( + env_canonicalizer.sub_root_lookup_table.is_empty(), + "{:?}", + env_canonicalizer.sub_root_lookup_table + ); ( param_env, env_canonicalizer.variables, From e8e3544a71f23606c85e8586e37bd98389bc1ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 3 Feb 2026 15:14:44 +0100 Subject: [PATCH 0098/1059] try generalizing if normalization isn't a tyvar --- .../src/infer/relate/generalize.rs | 113 +++++++++--------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index 1d4dab001616..e1d3f4e6bd48 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -38,31 +38,6 @@ fn from(value: ty::ConstVid) -> Self { } impl<'tcx> InferCtxt<'tcx> { - fn check_generalized_alias_normalizes_to_tyvar>( - &self, - relation: &mut R, - source_ty: Ty<'tcx>, - ) -> Option> { - if !self.next_trait_solver() - || matches!(relation.structurally_relate_aliases(), StructurallyRelateAliases::Yes) - { - return None; - } - - // If we get an alias - let ty::Alias(_, alias) = source_ty.kind() else { - return None; - }; - - if alias.has_escaping_bound_vars() { - return None; - } - - let normalized_alias = relation.try_eagerly_normalize_alias(*alias); - - normalized_alias.is_ty_var().then_some(normalized_alias) - } - /// The idea is that we should ensure that the type variable `target_vid` /// is equal to, a subtype of, or a supertype of `source_ty`. /// @@ -86,31 +61,53 @@ pub fn instantiate_ty_var>( ) -> RelateResult<'tcx, ()> { debug_assert!(self.inner.borrow_mut().type_variables().probe(target_vid).is_unknown()); - let generalized_ty = - match self.check_generalized_alias_normalizes_to_tyvar(relation, source_ty) { - Some(tyvar) => tyvar, - None => { - // Generalize `source_ty` depending on the current variance. As an example, assume - // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference - // variable. - // - // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh - // region/type inference variables. - // - // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and - // `?1 <: ?3`. - let generalizer = self.generalize( - relation.span(), - relation.structurally_relate_aliases(), - target_vid, - instantiation_variance, - source_ty, - &mut |alias| relation.try_eagerly_normalize_alias(alias), - )?; + let generalized_ty = if self.next_trait_solver() + && matches!(relation.structurally_relate_aliases(), StructurallyRelateAliases::No) + && let ty::Alias(_, alias) = source_ty.kind() + { + let normalized_alias = relation.try_eagerly_normalize_alias(*alias); - generalizer.value_may_be_infer - } - }; + if normalized_alias.is_ty_var() { + normalized_alias + } else { + let Generalization { value_may_be_infer: generalized_ty } = self.generalize( + relation.span(), + GeneralizerState::ShallowStructurallyRelateAliases, + target_vid, + instantiation_variance, + normalized_alias, + &mut |alias| relation.try_eagerly_normalize_alias(alias), + )?; + + // The only way to get a tyvar back is if the outermost type is an alias. + // However, here, though we know it *is* an alias, we initialize the generalizer + // with `ShallowStructurallyRelateAliases` so we treat the outermost alias as rigid, + // ensuring this is never a tyvar. + assert!(!generalized_ty.is_ty_var()); + + generalized_ty + } + } else { + // Generalize `source_ty` depending on the current variance. As an example, assume + // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference + // variable. + // + // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh + // region/type inference variables. + // + // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and + // `?1 <: ?3`. + let Generalization { value_may_be_infer: generalized_ty } = self.generalize( + relation.span(), + relation.structurally_relate_aliases().into(), + target_vid, + instantiation_variance, + source_ty, + &mut |alias| relation.try_eagerly_normalize_alias(alias), + )?; + + generalized_ty + }; // Finally, relate `generalized_ty` to `source_ty`, as described in previous comment. // @@ -239,7 +236,7 @@ pub(crate) fn instantiate_const_var // constants and generic expressions are not yet handled correctly. let Generalization { value_may_be_infer: generalized_ct } = self.generalize( relation.span(), - relation.structurally_relate_aliases(), + relation.structurally_relate_aliases().into(), target_vid, ty::Invariant, source_ct, @@ -279,7 +276,7 @@ pub(crate) fn instantiate_const_var fn generalize> + Relate>>( &self, span: Span, - structurally_relate_aliases: StructurallyRelateAliases, + initial_state: GeneralizerState, target_vid: impl Into, ambient_variance: ty::Variance, source_term: T, @@ -303,10 +300,7 @@ fn generalize> + Relate>>( for_universe, root_term: source_term.into(), ambient_variance, - state: match structurally_relate_aliases { - StructurallyRelateAliases::No => GeneralizerState::Default, - StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, - }, + state: initial_state, cache: Default::default(), normalize, }; @@ -365,6 +359,15 @@ enum GeneralizerState { StructurallyRelateAliases, } +impl From for GeneralizerState { + fn from(structurally_relate_aliases: StructurallyRelateAliases) -> Self { + match structurally_relate_aliases { + StructurallyRelateAliases::No => GeneralizerState::Default, + StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, + } + } +} + /// The "generalizer" is used when handling inference variables. /// /// The basic strategy for handling a constraint like `?A <: B` is to From 2d411a0faad447b5bfc968b954fd3e9c10596325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Thu, 5 Feb 2026 11:35:47 +0100 Subject: [PATCH 0099/1059] fixup span in obligation cause --- compiler/rustc_trait_selection/src/solve.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_trait_selection/src/solve.rs b/compiler/rustc_trait_selection/src/solve.rs index cb0288513903..118bd8c81b1e 100644 --- a/compiler/rustc_trait_selection/src/solve.rs +++ b/compiler/rustc_trait_selection/src/solve.rs @@ -57,7 +57,7 @@ fn try_eagerly_normalize_alias<'a, 'tcx>( let obligation = Obligation::new( tcx, // we ignore the error anyway - ObligationCause::dummy(), + ObligationCause::dummy_with_span(span), param_env, ty::PredicateKind::AliasRelate( alias.to_ty(tcx).into(), From 6edf58fab80d713b5e017d98a57c4075c12b833e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 2 Feb 2026 19:31:10 +0100 Subject: [PATCH 0100/1059] bless some tests --- .../impl-trait/unsized_coercion.next.stderr | 11 ------ tests/ui/impl-trait/unsized_coercion.rs | 2 +- .../impl-trait/unsized_coercion3.next.stderr | 36 +++++++++++++++++-- .../impl-trait/unsized_coercion3.old.stderr | 2 +- tests/ui/impl-trait/unsized_coercion3.rs | 2 ++ ...id-alias-bound-is-not-inherent.next.stderr | 2 +- ...rg-type-mismatch-issue-45727.current.fixed | 3 +- ...-arg-type-mismatch-issue-45727.next.stderr | 11 ++---- .../closure-arg-type-mismatch-issue-45727.rs | 3 +- 9 files changed, 42 insertions(+), 30 deletions(-) delete mode 100644 tests/ui/impl-trait/unsized_coercion.next.stderr diff --git a/tests/ui/impl-trait/unsized_coercion.next.stderr b/tests/ui/impl-trait/unsized_coercion.next.stderr deleted file mode 100644 index bea5ddb0aefc..000000000000 --- a/tests/ui/impl-trait/unsized_coercion.next.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error[E0277]: the size for values of type `dyn Trait` cannot be known at compilation time - --> $DIR/unsized_coercion.rs:14:17 - | -LL | let x = hello(); - | ^^^^^^^ doesn't have a size known at compile-time - | - = help: the trait `Sized` is not implemented for `dyn Trait` - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/impl-trait/unsized_coercion.rs b/tests/ui/impl-trait/unsized_coercion.rs index 2cbf0d25d7ec..6a9a53903fed 100644 --- a/tests/ui/impl-trait/unsized_coercion.rs +++ b/tests/ui/impl-trait/unsized_coercion.rs @@ -4,6 +4,7 @@ //@ revisions: next old //@[next] compile-flags: -Znext-solver //@[old] check-pass +//@[next] check-pass trait Trait {} @@ -12,7 +13,6 @@ impl Trait for u32 {} fn hello() -> Box { if true { let x = hello(); - //[next]~^ ERROR: the size for values of type `dyn Trait` cannot be known at compilation time let y: Box = x; } Box::new(1u32) diff --git a/tests/ui/impl-trait/unsized_coercion3.next.stderr b/tests/ui/impl-trait/unsized_coercion3.next.stderr index a480a69a3864..db758761d795 100644 --- a/tests/ui/impl-trait/unsized_coercion3.next.stderr +++ b/tests/ui/impl-trait/unsized_coercion3.next.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `dyn Send: Trait` is not satisfied - --> $DIR/unsized_coercion3.rs:13:17 + --> $DIR/unsized_coercion3.rs:14:17 | LL | let x = hello(); | ^^^^^^^ the trait `Trait` is not implemented for `dyn Send` @@ -9,7 +9,37 @@ help: the trait `Trait` is implemented for `u32` | LL | impl Trait for u32 {} | ^^^^^^^^^^^^^^^^^^ +note: required by a bound in `Box` + --> $SRC_DIR/alloc/src/boxed.rs:LL:COL -error: aborting due to 1 previous error +error[E0308]: mismatched types + --> $DIR/unsized_coercion3.rs:19:5 + | +LL | fn hello() -> Box { + | ------------------------ + | | | + | | the expected opaque type + | expected `Box` because of return type +... +LL | Box::new(1u32) + | ^^^^^^^^^^^^^^ types differ + | + = note: expected struct `Box` + found struct `Box` -For more information about this error, try `rustc --explain E0277`. +error[E0277]: the trait bound `dyn Send: Trait` is not satisfied + --> $DIR/unsized_coercion3.rs:11:1 + | +LL | fn hello() -> Box { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Trait` is not implemented for `dyn Send` + | +help: the trait `Trait` is implemented for `u32` + --> $DIR/unsized_coercion3.rs:9:1 + | +LL | impl Trait for u32 {} + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0277, E0308. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui/impl-trait/unsized_coercion3.old.stderr b/tests/ui/impl-trait/unsized_coercion3.old.stderr index 52a72b84a8dd..3bb9f9c20951 100644 --- a/tests/ui/impl-trait/unsized_coercion3.old.stderr +++ b/tests/ui/impl-trait/unsized_coercion3.old.stderr @@ -1,5 +1,5 @@ error[E0277]: the size for values of type `impl Trait + ?Sized` cannot be known at compilation time - --> $DIR/unsized_coercion3.rs:15:32 + --> $DIR/unsized_coercion3.rs:16:32 | LL | let y: Box = x; | ^ doesn't have a size known at compile-time diff --git a/tests/ui/impl-trait/unsized_coercion3.rs b/tests/ui/impl-trait/unsized_coercion3.rs index ebfbb2955de5..c1dd5350e229 100644 --- a/tests/ui/impl-trait/unsized_coercion3.rs +++ b/tests/ui/impl-trait/unsized_coercion3.rs @@ -9,6 +9,7 @@ trait Trait {} impl Trait for u32 {} fn hello() -> Box { + //[next]~^ ERROR: the trait bound `dyn Send: Trait` is not satisfied if true { let x = hello(); //[next]~^ ERROR: the trait bound `dyn Send: Trait` is not satisfied @@ -16,6 +17,7 @@ fn hello() -> Box { //[old]~^ ERROR: the size for values of type `impl Trait + ?Sized` cannot be know } Box::new(1u32) + //[next]~^ ERROR: mismatched types } fn main() {} diff --git a/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr b/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr index afacb3a7d521..4652bf5e3c58 100644 --- a/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr +++ b/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr @@ -9,7 +9,7 @@ note: candidate #1 is defined in the trait `Trait1` | LL | fn method(&self) { | ^^^^^^^^^^^^^^^^ -note: candidate #2 is defined in the trait `Trait2` +note: candidate #2 is defined in an impl of the trait `Trait2` for the type `T` --> $DIR/rigid-alias-bound-is-not-inherent.rs:27:5 | LL | fn method(&self) { diff --git a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed index ba46a447802c..1c45a2c0adb3 100644 --- a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed +++ b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed @@ -8,6 +8,5 @@ fn main() { //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found let _ = (-10..=10).find(|x: &i32| x.signum() == 0); //[current]~^ ERROR type mismatch in closure arguments - //[next]~^^ ERROR expected `RangeInclusive<{integer}>` to be an iterator that yields `&&i32`, but it yields `{integer}` - //[next]~| ERROR expected a `FnMut(& as Iterator>::Item)` closure, found + //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found } diff --git a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr index 7912ed4d7071..36e49c20c433 100644 --- a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr +++ b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr @@ -12,12 +12,6 @@ LL | let _ = (-10..=10).find(|x: i32| x.signum() == 0); note: required by a bound in `find` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL -error[E0271]: expected `RangeInclusive<{integer}>` to be an iterator that yields `&&i32`, but it yields `{integer}` - --> $DIR/closure-arg-type-mismatch-issue-45727.rs:9:24 - | -LL | let _ = (-10..=10).find(|x: &&&i32| x.signum() == 0); - | ^^^^ expected `&&i32`, found integer - error[E0277]: expected a `FnMut(& as Iterator>::Item)` closure, found `{closure@$DIR/closure-arg-type-mismatch-issue-45727.rs:9:29: 9:40}` --> $DIR/closure-arg-type-mismatch-issue-45727.rs:9:29 | @@ -32,7 +26,6 @@ LL | let _ = (-10..=10).find(|x: &&&i32| x.signum() == 0); note: required by a bound in `find` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors -Some errors have detailed explanations: E0271, E0277. -For more information about an error, try `rustc --explain E0271`. +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs index 0fd56707763e..20d6fed3b35b 100644 --- a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs +++ b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs @@ -8,6 +8,5 @@ fn main() { //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found let _ = (-10..=10).find(|x: &&&i32| x.signum() == 0); //[current]~^ ERROR type mismatch in closure arguments - //[next]~^^ ERROR expected `RangeInclusive<{integer}>` to be an iterator that yields `&&i32`, but it yields `{integer}` - //[next]~| ERROR expected a `FnMut(& as Iterator>::Item)` closure, found + //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found } From 9aa065bc49e5c9a0e07dbfc4bb0823821e5f130d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 9 Feb 2026 12:31:37 +0100 Subject: [PATCH 0101/1059] inline into --- .../src/infer/relate/generalize.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index e1d3f4e6bd48..f3843c371e2c 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -99,7 +99,10 @@ pub fn instantiate_ty_var>( // `?1 <: ?3`. let Generalization { value_may_be_infer: generalized_ty } = self.generalize( relation.span(), - relation.structurally_relate_aliases().into(), + match relation.structurally_relate_aliases() { + StructurallyRelateAliases::No => GeneralizerState::Default, + StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, + }, target_vid, instantiation_variance, source_ty, @@ -236,7 +239,10 @@ pub(crate) fn instantiate_const_var // constants and generic expressions are not yet handled correctly. let Generalization { value_may_be_infer: generalized_ct } = self.generalize( relation.span(), - relation.structurally_relate_aliases().into(), + match relation.structurally_relate_aliases() { + StructurallyRelateAliases::No => GeneralizerState::Default, + StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, + }, target_vid, ty::Invariant, source_ct, @@ -359,15 +365,6 @@ enum GeneralizerState { StructurallyRelateAliases, } -impl From for GeneralizerState { - fn from(structurally_relate_aliases: StructurallyRelateAliases) -> Self { - match structurally_relate_aliases { - StructurallyRelateAliases::No => GeneralizerState::Default, - StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, - } - } -} - /// The "generalizer" is used when handling inference variables. /// /// The basic strategy for handling a constraint like `?A <: B` is to From 7adb9bac0c7d2bf469e6b76c6cc47099afb02123 Mon Sep 17 00:00:00 2001 From: "LevitatingBusinessMan (Rein Fernhout)" Date: Fri, 27 Feb 2026 09:30:27 +0100 Subject: [PATCH 0102/1059] Add is_disconnected functions to mpsc and mpmc channels --- library/std/src/sync/mpmc/mod.rs | 46 +++++++++++++++++++++++++++++++ library/std/src/sync/mpmc/zero.rs | 6 ++++ library/std/src/sync/mpsc.rs | 38 +++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/library/std/src/sync/mpmc/mod.rs b/library/std/src/sync/mpmc/mod.rs index 8df81a580f7b..6249e8a033e8 100644 --- a/library/std/src/sync/mpmc/mod.rs +++ b/library/std/src/sync/mpmc/mod.rs @@ -623,6 +623,29 @@ pub fn same_channel(&self, other: &Sender) -> bool { _ => false, } } + + /// Returns `true` if the channel is disconnected. + /// + /// # Examples + /// + /// ``` + /// #![feature(mpmc_channel)] + /// + /// use std::sync::mpmc::channel; + /// + /// let (tx, rx) = channel::(); + /// assert!(!tx.is_disconnected()); + /// drop(rx); + /// assert!(tx.is_disconnected()); + /// ``` + #[unstable(feature = "mpmc_channel", issue = "126840")] + pub fn is_disconnected(&self) -> bool { + match &self.flavor { + SenderFlavor::Array(chan) => chan.is_disconnected(), + SenderFlavor::List(chan) => chan.is_disconnected(), + SenderFlavor::Zero(chan) => chan.is_disconnected(), + } + } } #[unstable(feature = "mpmc_channel", issue = "126840")] @@ -1349,6 +1372,29 @@ pub fn same_channel(&self, other: &Receiver) -> bool { pub fn iter(&self) -> Iter<'_, T> { Iter { rx: self } } + + /// Returns `true` if the channel is disconnected. + /// + /// # Examples + /// + /// ``` + /// #![feature(mpmc_channel)] + /// + /// use std::sync::mpmc::channel; + /// + /// let (tx, rx) = channel::(); + /// assert!(!rx.is_disconnected()); + /// drop(tx); + /// assert!(rx.is_disconnected()); + /// ``` + #[unstable(feature = "mpmc_channel", issue = "126840")] + pub fn is_disconnected(&self) -> bool { + match &self.flavor { + ReceiverFlavor::Array(chan) => chan.is_disconnected(), + ReceiverFlavor::List(chan) => chan.is_disconnected(), + ReceiverFlavor::Zero(chan) => chan.is_disconnected(), + } + } } #[unstable(feature = "mpmc_channel", issue = "126840")] diff --git a/library/std/src/sync/mpmc/zero.rs b/library/std/src/sync/mpmc/zero.rs index f1ecf80fcb9f..c74346250192 100644 --- a/library/std/src/sync/mpmc/zero.rs +++ b/library/std/src/sync/mpmc/zero.rs @@ -316,4 +316,10 @@ pub(crate) fn is_empty(&self) -> bool { pub(crate) fn is_full(&self) -> bool { true } + + /// Returns `true` if the channel is disconnected. + pub(crate) fn is_disconnected(&self) -> bool { + let inner = self.inner.lock().unwrap(); + inner.is_disconnected + } } diff --git a/library/std/src/sync/mpsc.rs b/library/std/src/sync/mpsc.rs index 0ae23f6e13bf..81bb5849fb77 100644 --- a/library/std/src/sync/mpsc.rs +++ b/library/std/src/sync/mpsc.rs @@ -607,6 +607,25 @@ impl Sender { pub fn send(&self, t: T) -> Result<(), SendError> { self.inner.send(t) } + + /// Returns `true` if the channel is disconnected. + /// + /// # Examples + /// + /// ``` + /// #![feature(mpsc_is_disconnected)] + /// + /// use std::sync::mpsc::channel; + /// + /// let (tx, rx) = channel::(); + /// assert!(!tx.is_disconnected()); + /// drop(rx); + /// assert!(tx.is_disconnected()); + /// ``` + #[unstable(feature = "mpsc_is_disconnected", issue = "none")] + pub fn is_disconnected(&self) -> bool { + self.inner.is_disconnected() + } } #[stable(feature = "rust1", since = "1.0.0")] @@ -1038,6 +1057,25 @@ pub fn iter(&self) -> Iter<'_, T> { pub fn try_iter(&self) -> TryIter<'_, T> { TryIter { rx: self } } + + /// Returns `true` if the channel is disconnected. + /// + /// # Examples + /// + /// ``` + /// #![feature(mpsc_is_disconnected)] + /// + /// use std::sync::mpsc::channel; + /// + /// let (tx, rx) = channel::(); + /// assert!(!rx.is_disconnected()); + /// drop(tx); + /// assert!(rx.is_disconnected()); + /// ``` + #[unstable(feature = "mpsc_is_disconnected", issue = "none")] + pub fn is_disconnected(&self) -> bool { + self.inner.is_disconnected() + } } #[stable(feature = "rust1", since = "1.0.0")] From 3ecd18ca376b59e96db9b1daa8d14a4fe6856adc Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Mon, 23 Feb 2026 01:45:28 +0000 Subject: [PATCH 0103/1059] std: make `OsString::truncate` a no-op when `len > current_len` Align `OsString::truncate` (and the underlying WTF-8 implementation) with `String::truncate` by making it a no-op when `len > self.len()`. Previously, `OsString::truncate` would panic if `len > self.len()`, while `String::truncate` treats such cases as a no-op. --- library/alloc/src/wtf8/mod.rs | 12 ++++++++---- library/alloc/src/wtf8/tests.rs | 4 ++-- library/std/src/ffi/os_str.rs | 12 +++++++++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/library/alloc/src/wtf8/mod.rs b/library/alloc/src/wtf8/mod.rs index e4834a24bf43..394c41bf3672 100644 --- a/library/alloc/src/wtf8/mod.rs +++ b/library/alloc/src/wtf8/mod.rs @@ -342,14 +342,18 @@ pub fn push(&mut self, code_point: CodePoint) { /// Shortens a string to the specified length. /// + /// If `new_len` is greater than the string's current length, this has no + /// effect. + /// /// # Panics /// - /// Panics if `new_len` > current length, - /// or if `new_len` is not a code point boundary. + /// Panics if `new_len` does not lie on a code point boundary. #[inline] pub fn truncate(&mut self, new_len: usize) { - assert!(self.is_code_point_boundary(new_len)); - self.bytes.truncate(new_len) + if new_len <= self.len() { + assert!(self.is_code_point_boundary(new_len)); + self.bytes.truncate(new_len) + } } /// Consumes the WTF-8 string and tries to convert it to a vec of bytes. diff --git a/library/alloc/src/wtf8/tests.rs b/library/alloc/src/wtf8/tests.rs index a72ad0837d11..79e0bbdef50f 100644 --- a/library/alloc/src/wtf8/tests.rs +++ b/library/alloc/src/wtf8/tests.rs @@ -308,10 +308,10 @@ fn wtf8buf_truncate_fail_code_point_boundary() { } #[test] -#[should_panic] -fn wtf8buf_truncate_fail_longer() { +fn wtf8buf_truncate_invalid_len() { let mut string = Wtf8Buf::from_str("aé"); string.truncate(4); + assert_eq!(string, Wtf8Buf::from_str("aé")); } #[test] diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs index 6b4cf3cea831..3d1acc24918c 100644 --- a/library/std/src/ffi/os_str.rs +++ b/library/std/src/ffi/os_str.rs @@ -576,15 +576,21 @@ pub fn leak<'a>(self) -> &'a mut OsStr { /// Truncate the `OsString` to the specified length. /// + /// If `new_len` is greater than the string's current length, this has no + /// effect. + /// /// # Panics + /// /// Panics if `len` does not lie on a valid `OsStr` boundary /// (as described in [`OsStr::slice_encoded_bytes`]). #[inline] #[unstable(feature = "os_string_truncate", issue = "133262")] pub fn truncate(&mut self, len: usize) { - self.as_os_str().inner.check_public_boundary(len); - // SAFETY: The length was just checked to be at a valid boundary. - unsafe { self.inner.truncate_unchecked(len) }; + if len <= self.len() { + self.as_os_str().inner.check_public_boundary(len); + // SAFETY: The length was just checked to be at a valid boundary. + unsafe { self.inner.truncate_unchecked(len) }; + } } /// Provides plumbing to `Vec::extend_from_slice` without giving full From feef7b4eafedfae6c8fd40caf711343b6cc3fe43 Mon Sep 17 00:00:00 2001 From: "LevitatingBusinessMan (Rein Fernhout)" Date: Sat, 28 Feb 2026 19:52:50 +0100 Subject: [PATCH 0104/1059] warn of possible race condition in channel is_disconnected doc --- library/std/src/sync/mpmc/mod.rs | 8 ++++++++ library/std/src/sync/mpsc.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/library/std/src/sync/mpmc/mod.rs b/library/std/src/sync/mpmc/mod.rs index 6249e8a033e8..16ae8a88370b 100644 --- a/library/std/src/sync/mpmc/mod.rs +++ b/library/std/src/sync/mpmc/mod.rs @@ -626,6 +626,10 @@ pub fn same_channel(&self, other: &Sender) -> bool { /// Returns `true` if the channel is disconnected. /// + /// Note that a return value of `false` does not guarantee the channel will + /// remain connected. The channel may be disconnected immediately after this method + /// returns, so a subsequent [`Sender::send`] may still fail with [`SendError`]. + /// /// # Examples /// /// ``` @@ -1375,6 +1379,10 @@ pub fn iter(&self) -> Iter<'_, T> { /// Returns `true` if the channel is disconnected. /// + /// Note that a return value of `false` does not guarantee the channel will + /// remain connected. The channel may be disconnected immediately after this method + /// returns, so a subsequent [`Receiver::recv`] may still fail with [`RecvError`]. + /// /// # Examples /// /// ``` diff --git a/library/std/src/sync/mpsc.rs b/library/std/src/sync/mpsc.rs index 81bb5849fb77..2abf7bfe341d 100644 --- a/library/std/src/sync/mpsc.rs +++ b/library/std/src/sync/mpsc.rs @@ -610,6 +610,10 @@ pub fn send(&self, t: T) -> Result<(), SendError> { /// Returns `true` if the channel is disconnected. /// + /// Note that a return value of `false` does not guarantee the channel will + /// remain connected. The channel may be disconnected immediately after this method + /// returns, so a subsequent [`Sender::send`] may still fail with [`SendError`]. + /// /// # Examples /// /// ``` @@ -1060,6 +1064,10 @@ pub fn try_iter(&self) -> TryIter<'_, T> { /// Returns `true` if the channel is disconnected. /// + /// Note that a return value of `false` does not guarantee the channel will + /// remain connected. The channel may be disconnected immediately after this method + /// returns, so a subsequent [`Receiver::recv`] may still fail with [`RecvError`]. + /// /// # Examples /// /// ``` From 8c93b88076d7f7c1957c75ba5916da98dce98078 Mon Sep 17 00:00:00 2001 From: Cathal Mullan Date: Sat, 28 Feb 2026 00:52:20 +0000 Subject: [PATCH 0105/1059] Extend x86 inline asm to support `ymm` and `zmm` vector registers --- example/std_example.rs | 72 ++++++++++++++++++++++++++++++++++++++++++ src/inline_asm.rs | 63 +++++++++++++++++++----------------- 2 files changed, 106 insertions(+), 29 deletions(-) diff --git a/example/std_example.rs b/example/std_example.rs index 33db75f0943a..408c9b3dad0f 100644 --- a/example/std_example.rs +++ b/example/std_example.rs @@ -9,6 +9,8 @@ )] #![allow(internal_features)] +#[cfg(target_arch = "x86_64")] +use std::arch::asm; #[cfg(target_arch = "x86_64")] use std::arch::x86_64::*; use std::hint::black_box; @@ -279,6 +281,17 @@ unsafe fn test_simd() { #[cfg(not(jit))] test_crc32(); + + #[cfg(not(jit))] + test_xmm_roundtrip(); + #[cfg(not(jit))] + if is_x86_feature_detected!("avx") { + test_ymm_roundtrip(); + } + #[cfg(not(jit))] + if is_x86_feature_detected!("avx512f") { + test_zmm_roundtrip(); + } } } @@ -576,6 +589,65 @@ unsafe fn test_mm_cvtps_ph() { assert_eq_m128i(r, e); } +#[cfg(target_arch = "x86_64")] +#[cfg(not(jit))] +unsafe fn test_xmm_roundtrip() { + unsafe { + let input = [1u8; 16]; + let mut output = [0u8; 16]; + + asm!( + "movups {xmm}, [{input}]", + "movups [{output}], {xmm}", + input = in(reg) input.as_ptr(), + output = in(reg) output.as_mut_ptr(), + xmm = out(xmm_reg) _, + ); + + assert_eq!(input, output); + } +} + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx")] +#[cfg(not(jit))] +unsafe fn test_ymm_roundtrip() { + unsafe { + let input = [1u8; 32]; + let mut output = [0u8; 32]; + + asm!( + "vmovups {ymm}, [{input}]", + "vmovups [{output}], {ymm}", + input = in(reg) input.as_ptr(), + output = in(reg) output.as_mut_ptr(), + ymm = out(ymm_reg) _, + ); + + assert_eq!(input, output); + } +} + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx512f")] +#[cfg(not(jit))] +unsafe fn test_zmm_roundtrip() { + unsafe { + let input = [1u8; 64]; + let mut output = [0u8; 64]; + + asm!( + "vmovups {zmm}, [{input}]", + "vmovups [{output}], {zmm}", + input = in(reg) input.as_ptr(), + output = in(reg) output.as_mut_ptr(), + zmm = out(zmm_reg) _, + ); + + assert_eq!(input, output); + } +} + fn test_checked_mul() { let u: Option = u8::from_str_radix("1000", 10).ok(); assert_eq!(u, None); diff --git a/src/inline_asm.rs b/src/inline_asm.rs index ac0da06cbb8e..8f1bb0b471e6 100644 --- a/src/inline_asm.rs +++ b/src/inline_asm.rs @@ -548,22 +548,21 @@ fn generate_asm_wrapper(&self, asm_name: &str) -> String { match self.arch { InlineAsmArch::X86_64 => match reg { InlineAsmReg::X86(reg) - if reg as u32 >= X86InlineAsmReg::xmm0 as u32 - && reg as u32 <= X86InlineAsmReg::xmm15 as u32 => + if matches!( + reg.reg_class(), + X86InlineAsmRegClass::xmm_reg + | X86InlineAsmRegClass::ymm_reg + | X86InlineAsmRegClass::zmm_reg + ) => { - // rustc emits x0 rather than xmm0 - let class = match *modifier { - None | Some('x') => "xmm", - Some('y') => "ymm", - Some('z') => "zmm", - _ => unreachable!(), - }; - write!( - generated_asm, - "{class}{}", - reg as u32 - X86InlineAsmReg::xmm0 as u32 - ) - .unwrap(); + // rustc emits x0/y0/z0 rather than xmm0/ymm0/zmm0 + let name = reg.name(); + if let Some(prefix) = modifier { + let index = &name[3..]; + write!(generated_asm, "{prefix}mm{index}").unwrap(); + } else { + write!(generated_asm, "{name}").unwrap(); + } } _ => reg .emit(&mut generated_asm, InlineAsmArch::X86_64, *modifier) @@ -716,12 +715,17 @@ fn save_register( InlineAsmArch::X86_64 => { match reg { InlineAsmReg::X86(reg) - if reg as u32 >= X86InlineAsmReg::xmm0 as u32 - && reg as u32 <= X86InlineAsmReg::xmm15 as u32 => + if matches!( + reg.reg_class(), + X86InlineAsmRegClass::xmm_reg + | X86InlineAsmRegClass::ymm_reg + | X86InlineAsmRegClass::zmm_reg + ) => { - // rustc emits x0 rather than xmm0 - write!(generated_asm, " movups [rbx+0x{:x}], ", offset.bytes()).unwrap(); - write!(generated_asm, "xmm{}", reg as u32 - X86InlineAsmReg::xmm0 as u32) + // rustc emits x0/y0/z0 rather than xmm0/ymm0/zmm0 + let name = reg.name(); + let mov = if name.starts_with("xmm") { "movups" } else { "vmovups" }; + write!(generated_asm, " {mov} [rbx+0x{:x}], {name}", offset.bytes()) .unwrap(); } _ => { @@ -761,16 +765,17 @@ fn restore_register( InlineAsmArch::X86_64 => { match reg { InlineAsmReg::X86(reg) - if reg as u32 >= X86InlineAsmReg::xmm0 as u32 - && reg as u32 <= X86InlineAsmReg::xmm15 as u32 => + if matches!( + reg.reg_class(), + X86InlineAsmRegClass::xmm_reg + | X86InlineAsmRegClass::ymm_reg + | X86InlineAsmRegClass::zmm_reg + ) => { - // rustc emits x0 rather than xmm0 - write!( - generated_asm, - " movups xmm{}", - reg as u32 - X86InlineAsmReg::xmm0 as u32 - ) - .unwrap(); + // rustc emits x0/y0/z0 rather than xmm0/ymm0/zmm0 + let name = reg.name(); + let mov = if name.starts_with("xmm") { "movups" } else { "vmovups" }; + write!(generated_asm, " {mov} {name}").unwrap(); } _ => { generated_asm.push_str(" mov "); From d0a33abd4d65f59e7affdc85ef19f33a05e08304 Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Fri, 6 Feb 2026 16:55:17 -0500 Subject: [PATCH 0106/1059] fixed VecDeque::splice() not filling the buffer correctly when resizing the buffer on start = end range --- .../alloc/src/collections/vec_deque/splice.rs | 47 ++++++++++++++++++- library/alloctests/tests/vec_deque.rs | 13 +++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/library/alloc/src/collections/vec_deque/splice.rs b/library/alloc/src/collections/vec_deque/splice.rs index d7b9a96291c3..b82f9fba7ceb 100644 --- a/library/alloc/src/collections/vec_deque/splice.rs +++ b/library/alloc/src/collections/vec_deque/splice.rs @@ -138,8 +138,48 @@ unsafe fn fill>(&mut self, replace_with: &mut I) -> bool { /// self.deque must be valid. unsafe fn move_tail(&mut self, additional: usize) { let deque = unsafe { self.deque.as_mut() }; - let tail_start = deque.len + self.drain_len; - deque.buf.reserve(tail_start + self.tail_len, additional); + + // `Drain::new` modifies the deque's len (so does `Drain::fill` here) + // directly with the start bound of the range passed into + // `VecDeque::splice`. This causes a few different issue: + // - Most notably, there will be a hole at the end of the + // buffer when our buffer resizes in the case that our + // data wraps around. + // - We cannot use `VecDeque::reserve` directly because + // how it reserves more space and updates the `VecDeque`'s + // `head` field accordingly depends on the `VecDeque`'s + // actual `len`. + // - We cannot just directly modify `VecDeque`'s `len` and + // and call `VecDeque::reserve` afterward because if + // `VecDeque::reserve` panics on capacity overflow, + // well now our `VecDeque`'s head does not get updated + // and we still have a potential hole at the end of the + // buffer. + // Therefore, we manually reserve additional space (if necessary) + // based on calculating the actual `len` of the `VecDeque` and adjust + // `VecDeque`'s len right *after* the panicking region of `VecDeque::reserve` + // (that is `RawVec` `reserve()` call) + + let drain_start = deque.len; + let tail_start = drain_start + self.drain_len; + + // Actual VecDeque's len = drain_start + tail_len + drain_len + let actual_len = drain_start + self.tail_len + self.drain_len; + let new_cap = actual_len.checked_add(additional).expect("capacity overflow"); + let old_cap = deque.capacity(); + + if new_cap > old_cap { + deque.buf.reserve(actual_len, additional); + // If new_cap doesn't panic, we can safely set the `VecDeque` len to its + // actual len; this needs to be done in order to set deque.head correctly + // on `VecDeque::handle_capacity_increase` + deque.len = actual_len; + // SAFETY: this cannot panic since our internal buffer's new_cap should + // be bigger than the passed in old_cap + unsafe { + deque.handle_capacity_increase(old_cap); + } + } let new_tail_start = tail_start + additional; unsafe { @@ -149,6 +189,9 @@ unsafe fn move_tail(&mut self, additional: usize) { self.tail_len, ); } + + // revert the `VecDeque` len to what it was before + deque.len = drain_start; self.drain_len += additional; } } diff --git a/library/alloctests/tests/vec_deque.rs b/library/alloctests/tests/vec_deque.rs index 92853fe00fd6..91843dfd0058 100644 --- a/library/alloctests/tests/vec_deque.rs +++ b/library/alloctests/tests/vec_deque.rs @@ -2347,3 +2347,16 @@ fn test_splice_wrapping() { assert_eq!(Vec::from(vec), [7, 8, 9]); } + +#[test] +fn test_splice_wrapping_and_resize() { + let mut vec = VecDeque::new(); + + for i in [1; 6] { + vec.push_front(i); + } + + vec.splice(1..1, [2, 3, 4]); + + assert_eq!(Vec::from(vec), [1, 2, 3, 4, 1, 1, 1, 1, 1]) +} From e5250ec5eef76d00f3e708c108da24593fcc6a52 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Sun, 1 Mar 2026 10:42:29 +0530 Subject: [PATCH 0107/1059] tools: remote-test-server: Add UEFI run support Tested with OVMF on QEMU with Linux as the host running remote-test-client. The instructions for running tests are provided below: 1. Use rust-lld linker: `bootstrap.toml` ```toml [rust] lld = true [target.x86_64-unknown-uefi] linker = "rust-lld" ``` 2. Use a custom startup.nsh script to auto-launch remote-test-server. This is optional. ```shell fs1: run.efi --sequential --bind "10.0.2.15:12345" --batch ``` 3. Launch remote-test-server in QEMU. I am using uefi-run script [0]. ```shell uefi-run build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-uefi/release/remote-test-server.efi -n --startup startup.nsh --tcp-port 12345 ``` 4. Run tests: ```shell RUST_TEST_THREADS=1 TEST_DEVICE_ADDR="localhost:12345" ./x.py test tests/ui/abi --target x86_64-unknown-uefi --stage 1 ``` [0]: https://github.com/Ayush1325/dotfiles/blob/2d13056bf8ca1931a234b72967d9e857c4afbf1a/uefi/.local/bin/uefi-run Signed-off-by: Ayush Singh --- src/tools/remote-test-server/src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/remote-test-server/src/main.rs b/src/tools/remote-test-server/src/main.rs index c0820e4a6854..b4d30289cabb 100644 --- a/src/tools/remote-test-server/src/main.rs +++ b/src/tools/remote-test-server/src/main.rs @@ -123,6 +123,8 @@ fn main() { let listener = bind_socket(config.bind); let (work, tmp): (PathBuf, PathBuf) = if cfg!(target_os = "android") { ("/data/local/tmp/work".into(), "/data/local/tmp/work/tmp".into()) + } else if cfg!(target_os = "uefi") { + ("tmp\\work".into(), "tmp\\work\\tmp".into()) } else { let mut work_dir = env::temp_dir(); work_dir.push("work"); @@ -274,6 +276,8 @@ fn handle_run(socket: TcpStream, work: &Path, tmp: &Path, lock: &Mutex<()>, conf } else if cfg!(target_vendor = "apple") { // On Apple platforms, the environment variable is named differently. "DYLD_LIBRARY_PATH" + } else if cfg!(target_os = "uefi") { + "path" } else { "LD_LIBRARY_PATH" }; From 5a9807742b1cf626ef730df1f9ca53097aa14b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 28 Feb 2026 20:50:05 +0100 Subject: [PATCH 0108/1059] Mention `@bors squash` in squashing documentation --- src/doc/rustc-dev-guide/src/git.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/git.md b/src/doc/rustc-dev-guide/src/git.md index bf31e79a9a15..34e419fa5225 100644 --- a/src/doc/rustc-dev-guide/src/git.md +++ b/src/doc/rustc-dev-guide/src/git.md @@ -383,6 +383,11 @@ Both the upside and downside of this is that it simplifies the history. On the one hand, you lose track of the steps in which changes were made, but the history becomes easier to work with. +The easiest way to squash your commits in a PR on the `rust-lang/rust` repository is to use the `@bors squash` command in a comment on the PR. By default, [bors] combines all commit messages of the PR into the squashed commit message. To customize the commit message, use `@bors squash msg=`. + + +If you want to squash commits using local git operations, read on below. + If there are no conflicts and you are just squashing to clean up the history, use `git rebase --interactive --keep-base main`. This keeps the fork point of your PR the same, making it easier to review the diff of what happened @@ -410,11 +415,6 @@ because they only represent "fixups" and not real changes. For example, `git rebase --interactive HEAD~2` will allow you to edit the two commits only. -For pull requests in `rust-lang/rust`, you can ask [bors] to squash by commenting -`@bors squash` on the PR. -By default, [bors] combines all commit messages in the PR. -To customize the commit message, use `@bors squash [msg|message=]`. - [bors]: https://github.com/rust-lang/bors ### `git range-diff` From fd8bb2167640600d59ac9199a4ddeb1a2edf7f4f Mon Sep 17 00:00:00 2001 From: irelaxcn Date: Mon, 2 Mar 2026 00:34:09 +0800 Subject: [PATCH 0109/1059] fix: `question_mark` suggestion caused error --- clippy_lints/src/question_mark.rs | 5 +++-- tests/ui/question_mark.fixed | 13 +++++++++++++ tests/ui/question_mark.rs | 19 +++++++++++++++++++ tests/ui/question_mark.stderr | 21 ++++++++++++++++++++- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 719f364357d3..e4bb3f13aea0 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -501,7 +501,8 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: let mut applicability = Applicability::MachineApplicable; let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); - let requires_semi = matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(_)); + let parent = cx.tcx.parent_hir_node(expr.hir_id); + let requires_semi = matches!(parent, Node::Stmt(_)) || cx.typeck_results().expr_ty(expr).is_unit(); let method_call_str = match by_ref { ByRef::Yes(_, Mutability::Mut) => ".as_mut()", ByRef::Yes(_, Mutability::Not) => ".as_ref()", @@ -512,7 +513,7 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: "{receiver_str}{method_call_str}?{}", if requires_semi { ";" } else { "" } ); - if is_else_clause(cx.tcx, expr) { + if is_else_clause(cx.tcx, expr) || (requires_semi && !matches!(parent, Node::Stmt(_) | Node::Block(_))) { sugg = format!("{{ {sugg} }}"); } diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 102517d34c61..e209da5c8258 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -524,3 +524,16 @@ fn issue16429(b: i32) -> Option { Some(0) } + +fn issue16654() -> Result<(), i32> { + let result = func_returning_result(); + + #[allow(clippy::collapsible_if)] + if true { + result?; + } + + _ = [{ result?; }]; + + Ok(()) +} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index cfea1277fe76..579b51461d13 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -649,3 +649,22 @@ fn issue16429(b: i32) -> Option { Some(0) } + +fn issue16654() -> Result<(), i32> { + let result = func_returning_result(); + + #[allow(clippy::collapsible_if)] + if true { + if let Err(err) = result { + //~^ question_mark + return Err(err); + } + } + + _ = [if let Err(err) = result { + //~^ question_mark + return Err(err); + }]; + + Ok(()) +} diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index c243f12de040..1d7f665a2662 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -362,5 +362,24 @@ LL | | return None; LL | | }; | |_____^ help: replace it with: `{ a? }` -error: aborting due to 38 previous errors +error: this block may be rewritten with the `?` operator + --> tests/ui/question_mark.rs:658:9 + | +LL | / if let Err(err) = result { +LL | | +LL | | return Err(err); +LL | | } + | |_________^ help: replace it with: `result?;` + +error: this block may be rewritten with the `?` operator + --> tests/ui/question_mark.rs:664:10 + | +LL | _ = [if let Err(err) = result { + | __________^ +LL | | +LL | | return Err(err); +LL | | }]; + | |_____^ help: replace it with: `{ result?; }` + +error: aborting due to 40 previous errors From 0b9fb671d8cef497beff74766ce1d14acedd6968 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 2 Mar 2026 04:40:37 +0000 Subject: [PATCH 0110/1059] Prepare for merging from rust-lang/rust This updates the rust-version file to e7d90c695a39426baf5ae705de2f9570a72229de. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index b6e1b2bc55df..4dedd81b0066 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -c78a29473a68f07012904af11c92ecffa68fcc75 +e7d90c695a39426baf5ae705de2f9570a72229de From affe60917ce26bb2dfc0d100bdbe29d5804fe17f Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Mon, 2 Mar 2026 15:18:53 +0100 Subject: [PATCH 0111/1059] tests: codegen-llvm: iter-repeat-n-trivial-drop: Allow non-zero lower bound to __rust_alloc size LLVM emits a lower bound of 8 for the size parameter to __rust_alloc when targeting x86_64-unknown-hermit. Since that is also completely valid, relax the lower bound check. --- tests/codegen-llvm/iter-repeat-n-trivial-drop.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/codegen-llvm/iter-repeat-n-trivial-drop.rs b/tests/codegen-llvm/iter-repeat-n-trivial-drop.rs index a4e5c885a139..98e906d852f7 100644 --- a/tests/codegen-llvm/iter-repeat-n-trivial-drop.rs +++ b/tests/codegen-llvm/iter-repeat-n-trivial-drop.rs @@ -47,7 +47,7 @@ pub fn iter_repeat_n_next(it: &mut std::iter::RepeatN) -> Option Vec { - // CHECK: %[[ADDR:.+]] = tail call {{(noalias )?}}noundef dereferenceable_or_null(1234) ptr @{{.*}}__rust_alloc(i64 noundef {{(range\(i64 0, -9223372036854775808\) )?}}1234, i64 noundef {{(range\(i64 1, -9223372036854775807\) )?}}1) + // CHECK: %[[ADDR:.+]] = tail call {{(noalias )?}}noundef dereferenceable_or_null(1234) ptr @{{.*}}__rust_alloc(i64 noundef {{(range\(i64 [0-9]+, -9223372036854775808\) )?}}1234, i64 noundef {{(range\(i64 1, -9223372036854775807\) )?}}1) // CHECK: tail call void @llvm.memset.p0.i64(ptr noundef nonnull align 1 dereferenceable(1234) %[[ADDR]], i8 42, i64 1234, let n = 1234_usize; From 022d6959d361a2cc6d5243307802d4f98ebf5780 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:33:23 +0000 Subject: [PATCH 0112/1059] Use CompiledModules inside CodegenResults In preparation for fully replacing CodegenResults with CompiledModules. --- src/driver/aot.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/driver/aot.rs b/src/driver/aot.rs index fc5c634d9570..7a736de967d2 100644 --- a/src/driver/aot.rs +++ b/src/driver/aot.rs @@ -10,9 +10,9 @@ use cranelift_object::{ObjectBuilder, ObjectModule}; use rustc_codegen_ssa::assert_module_sources::CguReuse; -use rustc_codegen_ssa::back::write::{CompiledModules, produce_final_output_artifacts}; +use rustc_codegen_ssa::back::write::produce_final_output_artifacts; use rustc_codegen_ssa::base::determine_cgu_reuse; -use rustc_codegen_ssa::{CodegenResults, CompiledModule, CrateInfo, ModuleKind}; +use rustc_codegen_ssa::{CodegenResults, CompiledModule, CompiledModules, CrateInfo, ModuleKind}; use rustc_data_structures::profiling::SelfProfilerRef; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::sync::{IntoDynSyncSend, par_map}; @@ -126,15 +126,7 @@ pub(crate) fn join( produce_final_output_artifacts(sess, &compiled_modules, outputs); - ( - CodegenResults { - crate_info: self.crate_info, - - modules: compiled_modules.modules, - allocator_module: compiled_modules.allocator_module, - }, - work_products, - ) + (CodegenResults { compiled_modules, crate_info: self.crate_info }, work_products) } } From 3047cffe9228d880b3720dd692d14730ff839d72 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:33:06 +0000 Subject: [PATCH 0113/1059] Replace CodegenResults with CompiledModules This is already CodegenResults without CrateInfo. The driver can calculate the CrateInfo and pass it by-ref to the backend. Using CompiledModules makes it a bit easier to move some other things out of the backend as will be necessary for moving LTO to the link phase. --- src/driver/aot.rs | 15 +++------------ src/driver/jit.rs | 13 +++++++------ src/lib.rs | 17 +++++++++++++---- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/driver/aot.rs b/src/driver/aot.rs index 7a736de967d2..79a321456808 100644 --- a/src/driver/aot.rs +++ b/src/driver/aot.rs @@ -12,7 +12,7 @@ use rustc_codegen_ssa::assert_module_sources::CguReuse; use rustc_codegen_ssa::back::write::produce_final_output_artifacts; use rustc_codegen_ssa::base::determine_cgu_reuse; -use rustc_codegen_ssa::{CodegenResults, CompiledModule, CompiledModules, CrateInfo, ModuleKind}; +use rustc_codegen_ssa::{CompiledModule, CompiledModules, ModuleKind}; use rustc_data_structures::profiling::SelfProfilerRef; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::sync::{IntoDynSyncSend, par_map}; @@ -54,7 +54,6 @@ fn hash_stable(&self, _: &mut HCX, _: &mut StableHasher) { pub(crate) struct OngoingCodegen { modules: Vec, allocator_module: Option, - crate_info: CrateInfo, concurrency_limiter: ConcurrencyLimiter, } @@ -63,7 +62,7 @@ pub(crate) fn join( self, sess: &Session, outputs: &OutputFilenames, - ) -> (CodegenResults, FxIndexMap) { + ) -> (CompiledModules, FxIndexMap) { let mut work_products = FxIndexMap::default(); let mut modules = vec![]; let disable_incr_cache = disable_incr_cache(); @@ -126,7 +125,7 @@ pub(crate) fn join( produce_final_output_artifacts(sess, &compiled_modules, outputs); - (CodegenResults { compiled_modules, crate_info: self.crate_info }, work_products) + (compiled_modules, work_products) } } @@ -475,13 +474,6 @@ fn emit_allocator_module(tcx: TyCtxt<'_>) -> Option { } pub(crate) fn run_aot(tcx: TyCtxt<'_>) -> Box { - // FIXME handle `-Ctarget-cpu=native` - let target_cpu = match tcx.sess.opts.cg.target_cpu { - Some(ref name) => name, - None => tcx.sess.target.cpu.as_ref(), - } - .to_owned(); - let cgus = tcx.collect_and_partition_mono_items(()).codegen_units; if tcx.dep_graph.is_fully_enabled() { @@ -541,7 +533,6 @@ pub(crate) fn run_aot(tcx: TyCtxt<'_>) -> Box { Box::new(OngoingCodegen { modules, allocator_module, - crate_info: CrateInfo::new(tcx, target_cpu), concurrency_limiter: concurrency_limiter.0, }) } diff --git a/src/driver/jit.rs b/src/driver/jit.rs index 3a8ca25a5fc0..9bbc338a8e07 100644 --- a/src/driver/jit.rs +++ b/src/driver/jit.rs @@ -16,13 +16,14 @@ use crate::prelude::*; use crate::unwind_module::UnwindModule; -fn create_jit_module(tcx: TyCtxt<'_>) -> (UnwindModule, Option) { - let crate_info = CrateInfo::new(tcx, "dummy_target_cpu".to_string()); - +fn create_jit_module( + tcx: TyCtxt<'_>, + crate_info: &CrateInfo, +) -> (UnwindModule, Option) { let isa = crate::build_isa(tcx.sess, true); let mut jit_builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names()); crate::compiler_builtins::register_functions_for_jit(&mut jit_builder); - jit_builder.symbol_lookup_fn(dep_symbol_lookup_fn(tcx.sess, crate_info)); + jit_builder.symbol_lookup_fn(dep_symbol_lookup_fn(tcx.sess, crate_info.clone())); let mut jit_module = UnwindModule::new(JITModule::new(jit_builder), false); let cx = DebugContext::new(tcx, jit_module.isa(), false, "dummy_cgu_name"); @@ -32,14 +33,14 @@ fn create_jit_module(tcx: TyCtxt<'_>) -> (UnwindModule, Option, jit_args: Vec) -> ! { +pub(crate) fn run_jit(tcx: TyCtxt<'_>, crate_info: &CrateInfo, jit_args: Vec) -> ! { if !tcx.crate_types().contains(&rustc_session::config::CrateType::Executable) { tcx.dcx().fatal("can't jit non-executable crate"); } let output_filenames = tcx.output_filenames(()); let should_write_ir = crate::pretty_clif::should_write_ir(tcx.sess); - let (mut jit_module, mut debug_context) = create_jit_module(tcx); + let (mut jit_module, mut debug_context) = create_jit_module(tcx, crate_info); let mut cached_context = Context::new(); let cgus = tcx.collect_and_partition_mono_items(()).codegen_units; diff --git a/src/lib.rs b/src/lib.rs index 7bab07def63d..82c2e91b4b0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::settings::{self, Configurable}; use rustc_codegen_ssa::traits::CodegenBackend; -use rustc_codegen_ssa::{CodegenResults, TargetConfig}; +use rustc_codegen_ssa::{CompiledModules, CrateInfo, TargetConfig}; use rustc_log::tracing::info; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_session::Session; @@ -200,12 +200,21 @@ fn print_version(&self) { println!("Cranelift version: {}", cranelift_codegen::VERSION); } - fn codegen_crate(&self, tcx: TyCtxt<'_>) -> Box { + fn target_cpu(&self, sess: &Session) -> String { + // FIXME handle `-Ctarget-cpu=native` + match sess.opts.cg.target_cpu { + Some(ref name) => name, + None => sess.target.cpu.as_ref(), + } + .to_owned() + } + + fn codegen_crate(&self, tcx: TyCtxt<'_>, _crate_info: &CrateInfo) -> Box { info!("codegen crate {}", tcx.crate_name(LOCAL_CRATE)); let config = self.config.get().unwrap(); if config.jit_mode { #[cfg(feature = "jit")] - driver::jit::run_jit(tcx, config.jit_args.clone()); + driver::jit::run_jit(tcx, _crate_info, config.jit_args.clone()); #[cfg(not(feature = "jit"))] tcx.dcx().fatal("jit support was disabled when compiling rustc_codegen_cranelift"); @@ -219,7 +228,7 @@ fn join_codegen( ongoing_codegen: Box, sess: &Session, outputs: &OutputFilenames, - ) -> (CodegenResults, FxIndexMap) { + ) -> (CompiledModules, FxIndexMap) { ongoing_codegen.downcast::().unwrap().join(sess, outputs) } } From 13ddff53be423b17615837846dc959592c32d93d Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Mon, 2 Mar 2026 21:06:11 +0100 Subject: [PATCH 0114/1059] Adapt codegen test to accept operand bundles --- tests/codegen-llvm/issues/issue-37945.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/codegen-llvm/issues/issue-37945.rs b/tests/codegen-llvm/issues/issue-37945.rs index 23d0eab8ae46..3167a045575a 100644 --- a/tests/codegen-llvm/issues/issue-37945.rs +++ b/tests/codegen-llvm/issues/issue-37945.rs @@ -1,5 +1,8 @@ //@ compile-flags: -Copt-level=3 -Zmerge-functions=disabled //@ ignore-32bit LLVM has a bug with them +//@ revisions: new old +//@ [old] max-llvm-major-version: 22 +//@ [new] min-llvm-version: 23 // Check that LLVM understands that `Iter` pointer is not null. Issue #37945. @@ -11,8 +14,9 @@ pub fn is_empty_1(xs: Iter) -> bool { // CHECK-LABEL: @is_empty_1( // CHECK-NEXT: start: - // CHECK-NEXT: [[A:%.*]] = icmp ne ptr {{%xs.0|%xs.1}}, null - // CHECK-NEXT: tail call void @llvm.assume(i1 [[A]]) + // old-NEXT: [[A:%.*]] = icmp ne ptr {{%xs.0|%xs.1}}, null + // old-NEXT: tail call void @llvm.assume(i1 [[A]]) + // new-NEXT: call void @llvm.assume(i1 true) [ "nonnull"(ptr {{%xs.0|%xs.1}}) ] // The order between %xs.0 and %xs.1 on the next line doesn't matter // and different LLVM versions produce different order. // CHECK-NEXT: [[B:%.*]] = icmp eq ptr {{%xs.0, %xs.1|%xs.1, %xs.0}} @@ -24,8 +28,9 @@ pub fn is_empty_1(xs: Iter) -> bool { pub fn is_empty_2(xs: Iter) -> bool { // CHECK-LABEL: @is_empty_2 // CHECK-NEXT: start: - // CHECK-NEXT: [[C:%.*]] = icmp ne ptr {{%xs.0|%xs.1}}, null - // CHECK-NEXT: tail call void @llvm.assume(i1 [[C]]) + // old-NEXT: [[C:%.*]] = icmp ne ptr {{%xs.0|%xs.1}}, null + // old-NEXT: tail call void @llvm.assume(i1 [[C]]) + // new-NEXT: call void @llvm.assume(i1 true) [ "nonnull"(ptr {{%xs.0|%xs.1}}) ] // The order between %xs.0 and %xs.1 on the next line doesn't matter // and different LLVM versions produce different order. // CHECK-NEXT: [[D:%.*]] = icmp eq ptr {{%xs.0, %xs.1|%xs.1, %xs.0}} From f5a2bb6263ad332ceb2d9b3b82afe935259a7723 Mon Sep 17 00:00:00 2001 From: "Andrew V. Teylu" Date: Mon, 2 Mar 2026 20:28:16 +0000 Subject: [PATCH 0115/1059] Add hygiene annotations for tokens in macro_rules! bodies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `-Zunpretty=expanded,hygiene` was not printing syntax context annotations for identifiers and lifetimes inside `macro_rules!` bodies. These tokens are printed via `print_tt()` → `token_to_string_ext()`, which converts tokens to strings without calling `ann_post()`. This meant that macro-generated `macro_rules!` definitions with hygienic metavar parameters (e.g. multiple `$marg` distinguished only by hygiene) were printed with no way to tell them apart. This was fixed by adding a match on `token.kind` in `print_tt()` to call `ann_post()` for `Ident`, `NtIdent`, `Lifetime`, and `NtLifetime` tokens, matching how `print_ident()` and `print_lifetime()` already handle AST-level identifiers and lifetimes. Signed-off-by: Andrew V. Teylu --- compiler/rustc_ast_pretty/src/pprust/state.rs | 17 ++++++ .../hygiene/unpretty-debug-lifetimes.stdout | 8 +-- tests/ui/hygiene/unpretty-debug-metavars.rs | 25 +++++++++ .../ui/hygiene/unpretty-debug-metavars.stdout | 52 +++++++++++++++++++ tests/ui/hygiene/unpretty-debug.stdout | 10 +++- tests/ui/proc-macro/meta-macro-hygiene.stdout | 5 +- .../nonterminal-token-hygiene.stdout | 17 ++++-- 7 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 tests/ui/hygiene/unpretty-debug-metavars.rs create mode 100644 tests/ui/hygiene/unpretty-debug-metavars.stdout diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index f4168301bc5d..7d963dd2037c 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -735,6 +735,23 @@ fn print_tt(&mut self, tt: &TokenTree, convert_dollar_crate: bool) -> Spacing { TokenTree::Token(token, spacing) => { let token_str = self.token_to_string_ext(token, convert_dollar_crate); self.word(token_str); + // Emit hygiene annotations for identity-bearing tokens, + // matching how print_ident() and print_lifetime() call ann_post(). + match token.kind { + token::Ident(name, _) => { + self.ann_post(Ident::new(name, token.span)); + } + token::NtIdent(ident, _) => { + self.ann_post(ident); + } + token::Lifetime(name, _) => { + self.ann_post(Ident::new(name, token.span)); + } + token::NtLifetime(ident, _) => { + self.ann_post(ident); + } + _ => {} + } if let token::DocComment(..) = token.kind { self.hardbreak() } diff --git a/tests/ui/hygiene/unpretty-debug-lifetimes.stdout b/tests/ui/hygiene/unpretty-debug-lifetimes.stdout index 28a5c70a02d7..689453326c0b 100644 --- a/tests/ui/hygiene/unpretty-debug-lifetimes.stdout +++ b/tests/ui/hygiene/unpretty-debug-lifetimes.stdout @@ -7,15 +7,17 @@ // Don't break whenever Symbol numbering changes //@ normalize-stdout: "\d+#" -> "0#" -#![feature /* 0#0 */(decl_macro)] -#![feature /* 0#0 */(no_core)] +#![feature /* 0#0 */(decl_macro /* 0#0 */)] +#![feature /* 0#0 */(no_core /* 0#0 */)] #![no_core /* 0#0 */] macro lifetime_hygiene /* 0#0 */ { - ($f:ident<$a:lifetime>) => { fn $f<$a, 'a>() {} } + ($f /* 0#0 */:ident /* 0#0 */<$a /* 0#0 */:lifetime /* 0#0 */>) + => + { fn /* 0#0 */ $f /* 0#0 */<$a /* 0#0 */, 'a /* 0#0 */>() {} } } fn f /* 0#0 */<'a /* 0#0 */, 'a /* 0#1 */>() {} diff --git a/tests/ui/hygiene/unpretty-debug-metavars.rs b/tests/ui/hygiene/unpretty-debug-metavars.rs new file mode 100644 index 000000000000..a0c47bec3ccf --- /dev/null +++ b/tests/ui/hygiene/unpretty-debug-metavars.rs @@ -0,0 +1,25 @@ +//@ check-pass +//@ compile-flags: -Zunpretty=expanded,hygiene + +// Regression test: metavar parameters in macro-generated macro_rules! +// definitions should have hygiene annotations so that textually identical +// `$marg` bindings are distinguishable by their syntax contexts. + +// Don't break whenever Symbol numbering changes +//@ normalize-stdout: "\d+#" -> "0#" + +#![feature(no_core)] +#![no_core] + +macro_rules! make_macro { + (@inner $name:ident ($dol:tt) $a:ident) => { + macro_rules! $name { + ($dol $a : expr, $dol marg : expr) => {} + } + }; + ($name:ident) => { + make_macro!{@inner $name ($) marg} + }; +} + +make_macro!(add2); diff --git a/tests/ui/hygiene/unpretty-debug-metavars.stdout b/tests/ui/hygiene/unpretty-debug-metavars.stdout new file mode 100644 index 000000000000..307c5e1b8de9 --- /dev/null +++ b/tests/ui/hygiene/unpretty-debug-metavars.stdout @@ -0,0 +1,52 @@ +//@ check-pass +//@ compile-flags: -Zunpretty=expanded,hygiene + +// Regression test: metavar parameters in macro-generated macro_rules! +// definitions should have hygiene annotations so that textually identical +// `$marg` bindings are distinguishable by their syntax contexts. + +// Don't break whenever Symbol numbering changes +//@ normalize-stdout: "\d+#" -> "0#" + +#![feature /* 0#0 */(no_core /* 0#0 */)] +#![no_core /* 0#0 */] + +macro_rules! make_macro + /* + 0#0 + */ { + (@inner /* 0#0 */ $name /* 0#0 */:ident /* 0#0 + */($dol /* 0#0 */:tt /* 0#0 */) $a /* 0#0 */:ident /* 0#0 */) + => + { + macro_rules /* 0#0 */! $name /* 0#0 */ + { + ($dol /* 0#0 */ $a /* 0#0 */ : expr /* 0#0 */, $dol /* + 0#0 */ marg /* 0#0 */ : expr /* 0#0 */) => {} + } + }; ($name /* 0#0 */:ident /* 0#0 */) => + { + make_macro /* 0#0 + */!{@inner /* 0#0 */ $name /* 0#0 */($) marg /* 0#0 */} + }; +} +macro_rules! add2 + /* + 0#0 + */ { + ($ marg /* 0#1 */ : expr /* 0#2 */, $marg /* 0#2 */ : expr /* + 0#2 */) => {} +} + + +/* +Expansions: +crate0::{{expn0}}: parent: crate0::{{expn0}}, call_site_ctxt: #0, def_site_ctxt: #0, kind: Root +crate0::{{expn1}}: parent: crate0::{{expn0}}, call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro(Bang, "make_macro") +crate0::{{expn2}}: parent: crate0::{{expn1}}, call_site_ctxt: #1, def_site_ctxt: #0, kind: Macro(Bang, "make_macro") + +SyntaxContexts: +#0: parent: #0, outer_mark: (crate0::{{expn0}}, Opaque) +#1: parent: #0, outer_mark: (crate0::{{expn1}}, SemiOpaque) +#2: parent: #0, outer_mark: (crate0::{{expn2}}, SemiOpaque) +*/ diff --git a/tests/ui/hygiene/unpretty-debug.stdout b/tests/ui/hygiene/unpretty-debug.stdout index f35bd7a7cb2c..ac6051e2d542 100644 --- a/tests/ui/hygiene/unpretty-debug.stdout +++ b/tests/ui/hygiene/unpretty-debug.stdout @@ -5,10 +5,16 @@ //@ normalize-stdout: "\d+#" -> "0#" // minimal junk -#![feature /* 0#0 */(no_core)] +#![feature /* 0#0 */(no_core /* 0#0 */)] #![no_core /* 0#0 */] -macro_rules! foo /* 0#0 */ { ($x: ident) => { y + $x } } +macro_rules! foo + /* + 0#0 + */ { + ($x /* 0#0 */: ident /* 0#0 */) => + { y /* 0#0 */ + $x /* 0#0 */ } +} fn bar /* 0#0 */() { let x /* 0#0 */ = 1; diff --git a/tests/ui/proc-macro/meta-macro-hygiene.stdout b/tests/ui/proc-macro/meta-macro-hygiene.stdout index b5db9922b31a..a03e567bd48a 100644 --- a/tests/ui/proc-macro/meta-macro-hygiene.stdout +++ b/tests/ui/proc-macro/meta-macro-hygiene.stdout @@ -1,7 +1,7 @@ Def site: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) Input: TokenStream [Ident { ident: "$crate", span: $DIR/meta-macro-hygiene.rs:26:37: 26:43 (#3) }, Punct { ch: ':', spacing: Joint, span: $DIR/meta-macro-hygiene.rs:26:43: 26:44 (#3) }, Punct { ch: ':', spacing: Alone, span: $DIR/meta-macro-hygiene.rs:26:44: 26:45 (#3) }, Ident { ident: "dummy", span: $DIR/meta-macro-hygiene.rs:26:45: 26:50 (#3) }, Punct { ch: '!', spacing: Alone, span: $DIR/meta-macro-hygiene.rs:26:50: 26:51 (#3) }, Group { delimiter: Parenthesis, stream: TokenStream [], span: $DIR/meta-macro-hygiene.rs:26:51: 26:53 (#3) }] Respanned: TokenStream [Ident { ident: "$crate", span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }, Punct { ch: ':', spacing: Joint, span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }, Punct { ch: ':', spacing: Alone, span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }, Ident { ident: "dummy", span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }, Punct { ch: '!', spacing: Alone, span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }, Group { delimiter: Parenthesis, stream: TokenStream [], span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }] -#![feature /* 0#0 */(prelude_import)] +#![feature /* 0#0 */(prelude_import /* 0#0 */)] //@ aux-build:make-macro.rs //@ proc-macro: meta-macro.rs //@ edition:2018 @@ -30,7 +30,8 @@ macro_rules! produce_it */ { () => { - meta_macro::print_def_site!($crate::dummy!()); + meta_macro /* 0#0 */::print_def_site /* 0#0 + */!($crate /* 0#0 */::dummy /* 0#0 */!()); // `print_def_site!` will respan the `$crate` identifier // with `Span::def_site()`. This should cause it to resolve // relative to `meta_macro`, *not* `make_macro` (despite diff --git a/tests/ui/proc-macro/nonterminal-token-hygiene.stdout b/tests/ui/proc-macro/nonterminal-token-hygiene.stdout index e45abab03b4c..61b55782e6e9 100644 --- a/tests/ui/proc-macro/nonterminal-token-hygiene.stdout +++ b/tests/ui/proc-macro/nonterminal-token-hygiene.stdout @@ -20,7 +20,7 @@ PRINT-BANG INPUT (DEBUG): TokenStream [ span: $DIR/nonterminal-token-hygiene.rs:23:27: 23:32 (#4), }, ] -#![feature /* 0#0 */(prelude_import)] +#![feature /* 0#0 */(prelude_import /* 0#0 */)] #![no_std /* 0#0 */] // Make sure that marks from declarative macros are applied to tokens in nonterminal. @@ -34,7 +34,7 @@ PRINT-BANG INPUT (DEBUG): TokenStream [ //@ proc-macro: test-macros.rs //@ edition: 2015 -#![feature /* 0#0 */(decl_macro)] +#![feature /* 0#0 */(decl_macro /* 0#0 */)] #![no_std /* 0#0 */] extern crate core /* 0#2 */; #[prelude_import /* 0#1 */] @@ -49,15 +49,22 @@ macro_rules! outer /* 0#0 */ { - ($item:item) => + ($item /* 0#0 */:item /* 0#0 */) => { - macro inner() { print_bang! { $item } } inner!(); + macro /* 0#0 */ inner /* 0#0 */() + { print_bang /* 0#0 */! { $item /* 0#0 */ } } inner /* 0#0 + */!(); }; } struct S /* 0#0 */; -macro inner /* 0#3 */ { () => { print_bang! { struct S; } } } +macro inner + /* + 0#3 + */ { + () => { print_bang /* 0#3 */! { struct /* 0#0 */ S /* 0#0 */; } } +} struct S /* 0#5 */; // OK, not a duplicate definition of `S` From cf6e7bcf5946bdae784a47aebed56083c97caa6a Mon Sep 17 00:00:00 2001 From: Cathal Mullan Date: Mon, 2 Mar 2026 20:32:29 +0000 Subject: [PATCH 0116/1059] Add leading underscore to asm symbols on Mach-O --- src/global_asm.rs | 16 ++++++++++++++-- src/inline_asm.rs | 8 +++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/global_asm.rs b/src/global_asm.rs index 1daf428acf76..b14988577845 100644 --- a/src/global_asm.rs +++ b/src/global_asm.rs @@ -120,9 +120,15 @@ fn codegen_global_asm_inner<'tcx>( } let symbol = tcx.symbol_name(instance); + let symbol_name = if tcx.sess.target.is_like_darwin { + format!("_{}", symbol.name) + } else { + symbol.name.to_owned() + }; + // FIXME handle the case where the function was made private to the // current codegen unit - global_asm.push_str(&escape_symbol_name(tcx, symbol.name, span)); + global_asm.push_str(&escape_symbol_name(tcx, &symbol_name, span)); } GlobalAsmOperandRef::SymStatic { def_id } => { if cfg!(not(feature = "inline_asm_sym")) { @@ -134,7 +140,13 @@ fn codegen_global_asm_inner<'tcx>( let instance = Instance::mono(tcx, def_id); let symbol = tcx.symbol_name(instance); - global_asm.push_str(&escape_symbol_name(tcx, symbol.name, span)); + let symbol_name = if tcx.sess.target.is_like_darwin { + format!("_{}", symbol.name) + } else { + symbol.name.to_owned() + }; + + global_asm.push_str(&escape_symbol_name(tcx, &symbol_name, span)); } } } diff --git a/src/inline_asm.rs b/src/inline_asm.rs index 8f1bb0b471e6..044243f2c716 100644 --- a/src/inline_asm.rs +++ b/src/inline_asm.rs @@ -574,7 +574,13 @@ fn generate_asm_wrapper(&self, asm_name: &str) -> String { CInlineAsmOperand::Const { ref value } => { generated_asm.push_str(value); } - CInlineAsmOperand::Symbol { ref symbol } => generated_asm.push_str(symbol), + CInlineAsmOperand::Symbol { ref symbol } => { + if binary_format == BinaryFormat::Macho { + generated_asm.push('_'); + } + + generated_asm.push_str(symbol); + } } } } From 6d6493a7986e66e3a99b41b3f563f83faa4fd2b0 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Mon, 3 Nov 2025 17:24:53 +0100 Subject: [PATCH 0117/1059] tweak r-a default settings 1. Explain that you need to make paths absolute in helix settings 2. Use the correct compiler (set `RUSTC` and `CARGO` to stage0, instead of `RUSTUP_TOOLCHAIN=nightly`) 3. Add comments to vscode and zed settings --- src/bootstrap/src/core/build_steps/setup.rs | 3 +++ src/etc/rust_analyzer_helix.toml | 8 ++++++-- src/etc/rust_analyzer_settings.json | 14 +++++++++++++- src/etc/rust_analyzer_zed.json | 14 +++++++++++++- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/setup.rs b/src/bootstrap/src/core/build_steps/setup.rs index 7dfd566a58cc..f4e6a20ec849 100644 --- a/src/bootstrap/src/core/build_steps/setup.rs +++ b/src/bootstrap/src/core/build_steps/setup.rs @@ -607,6 +607,7 @@ fn hashes(&self) -> &'static [&'static str] { "1c43ead340b20792b91d02b08494ee68708e7e09f56b6766629b4b72079208f1", "eec09a09452682060afd23dd5d3536ccac5615b3cdbf427366446901215fb9f6", "cb653043852d9d5ff4a5be56407b859ff9928be055ad3f307eb309aad04765e6", + "e28b1930d16d3d8bbdeed7bd4a995613e648b49e08c9b6f5271880f520637fed", ], EditorKind::Vim | EditorKind::VsCode => &[ "ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8", @@ -626,6 +627,7 @@ fn hashes(&self) -> &'static [&'static str] { "02a49ac2d31f00ef6e4531c44e00dac51cea895112e480553f1ba060b3942a47", "0aa4748848de0d1cb7ece92a0123c8897fef6de2f58aff8fda1426f098b7a798", "e5e357862e5d6d0d9da335e9823c07b8a7dc42bbf18d72cc5206ad1049cd8fcc", + "a68fd5828e75f3e921f265e29ce1e9efa554083c3773fdb4b8e1ab3b2d9dc6cd", ], EditorKind::Zed => &[ "bbce727c269d1bd0c98afef4d612eb4ce27aea3c3a8968c5f10b31affbc40b6c", @@ -636,6 +638,7 @@ fn hashes(&self) -> &'static [&'static str] { "5ef83292111d9a8bb63b6afc3abf42d0bc78fe24985f0d2e039e73258b5dab8f", "74420c13094b530a986b37c4f1d23cb58c0e8e2295f5858ded129fb1574e66f9", "2d3b592c089b2ad2c528686a1e371af49922edad1c59accd5d5f31612a441568", + "0767a2398ccc253274b184adbb9e018ce931bd0ef45baad06dad19b652c52951", ], } } diff --git a/src/etc/rust_analyzer_helix.toml b/src/etc/rust_analyzer_helix.toml index c9ee1ca181d5..ae2c2d906488 100644 --- a/src/etc/rust_analyzer_helix.toml +++ b/src/etc/rust_analyzer_helix.toml @@ -9,6 +9,9 @@ # x fmt --check # ``` # (if that doesn't work -- do `x clean` first) +# +# NOTE: helix doesn't support getting workspace root in configs, for this config +# to work replace WORKSPACE_ROOT with the path to your rustc checkout [language-server.rust-analyzer.config] linkedProjects = [ @@ -33,7 +36,7 @@ overrideCommand = [ [language-server.rust-analyzer.config.rustfmt] overrideCommand = [ - "build/host/rustfmt/bin/rustfmt", + "WORKSPACE_ROOT/build/host/rustfmt/bin/rustfmt", "--edition=2024" ] @@ -64,7 +67,8 @@ overrideCommand = [ ] [language-server.rust-analyzer.environment] -RUSTUP_TOOLCHAIN = "nightly" +RUSTC = "WORKSPACE_ROOT/build/host/stage0/bin/rustc" +CARGO = "WORKSPACE_ROOT/build/host/stage0/bin/cargo" [[language]] name = "rust" diff --git a/src/etc/rust_analyzer_settings.json b/src/etc/rust_analyzer_settings.json index 8e94fbaa163e..ea5bb544c700 100644 --- a/src/etc/rust_analyzer_settings.json +++ b/src/etc/rust_analyzer_settings.json @@ -1,3 +1,14 @@ +// This config uses a separate build directory for rust-analyzer, +// so that r-a's checks don't block user `x` commands and vice-verse. +// R-a's build directory is located in `build-rust-analyzer`. +// +// To download rustfmt and proc macro server for r-a run the following command +// (proc macro server is downloaded automatically with pretty much any command, +// this specific one also downloads rustfmt): +// ``` +// x fmt --check +// ``` +// (if that doesn't work -- do `x clean` first) { "git.detectSubmodulesLimit": 20, "rust-analyzer.linkedProjects": [ @@ -40,7 +51,8 @@ "build-rust-analyzer" ], "rust-analyzer.server.extraEnv": { - "RUSTUP_TOOLCHAIN": "nightly" + "RUSTC": "${workspaceFolder}/build/host/stage0/bin/rustc", + "CARGO": "${workspaceFolder}/build/host/stage0/bin/cargo" }, "files.associations": { "*.fixed": "rust", diff --git a/src/etc/rust_analyzer_zed.json b/src/etc/rust_analyzer_zed.json index df1f7334556c..e69800c0624d 100644 --- a/src/etc/rust_analyzer_zed.json +++ b/src/etc/rust_analyzer_zed.json @@ -1,3 +1,14 @@ +// This config uses a separate build directory for rust-analyzer, +// so that r-a's checks don't block user `x` commands and vice-verse. +// R-a's build directory is located in `build-rust-analyzer`. +// +// To download rustfmt and proc macro server for r-a run the following command +// (proc macro server is downloaded automatically with pretty much any command, +// this specific one also downloads rustfmt): +// ``` +// x fmt --check +// ``` +// (if that doesn't work -- do `x clean` first) { "lsp": { "rust-analyzer": { @@ -55,7 +66,8 @@ }, "server": { "extraEnv": { - "RUSTUP_TOOLCHAIN": "nightly" + "RUSTC": "build/host/stage0/bin/rustc", + "CARGO": "build/host/stage0/bin/cargo" } } } From bf6db4f345799e1f922b3109a7278d8be11f3058 Mon Sep 17 00:00:00 2001 From: "Andrew V. Teylu" Date: Tue, 3 Mar 2026 13:06:55 +0000 Subject: [PATCH 0118/1059] Add regression tests for token hygiene annotations in macro bodies Add `unpretty-debug-shadow` test covering macro body tokens that reference a shadowed variable, and simplify the `unpretty-debug-metavars` test macro. Signed-off-by: Andrew V. Teylu --- tests/ui/hygiene/unpretty-debug-metavars.rs | 7 +++-- .../ui/hygiene/unpretty-debug-metavars.stdout | 7 +++-- tests/ui/hygiene/unpretty-debug-shadow.rs | 20 +++++++++++++ tests/ui/hygiene/unpretty-debug-shadow.stdout | 30 +++++++++++++++++++ 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 tests/ui/hygiene/unpretty-debug-shadow.rs create mode 100644 tests/ui/hygiene/unpretty-debug-shadow.stdout diff --git a/tests/ui/hygiene/unpretty-debug-metavars.rs b/tests/ui/hygiene/unpretty-debug-metavars.rs index a0c47bec3ccf..41bf75cd0d98 100644 --- a/tests/ui/hygiene/unpretty-debug-metavars.rs +++ b/tests/ui/hygiene/unpretty-debug-metavars.rs @@ -1,9 +1,10 @@ //@ check-pass //@ compile-flags: -Zunpretty=expanded,hygiene -// Regression test: metavar parameters in macro-generated macro_rules! -// definitions should have hygiene annotations so that textually identical -// `$marg` bindings are distinguishable by their syntax contexts. +// Regression test for token hygiene annotations in -Zunpretty=expanded,hygiene +// Previously, metavar parameters in macro-generated macro_rules! definitions +// were missing hygiene annotations, making identical `$marg` bindings +// indistinguishable. // Don't break whenever Symbol numbering changes //@ normalize-stdout: "\d+#" -> "0#" diff --git a/tests/ui/hygiene/unpretty-debug-metavars.stdout b/tests/ui/hygiene/unpretty-debug-metavars.stdout index 307c5e1b8de9..89658bc909a1 100644 --- a/tests/ui/hygiene/unpretty-debug-metavars.stdout +++ b/tests/ui/hygiene/unpretty-debug-metavars.stdout @@ -1,9 +1,10 @@ //@ check-pass //@ compile-flags: -Zunpretty=expanded,hygiene -// Regression test: metavar parameters in macro-generated macro_rules! -// definitions should have hygiene annotations so that textually identical -// `$marg` bindings are distinguishable by their syntax contexts. +// Regression test for token hygiene annotations in -Zunpretty=expanded,hygiene +// Previously, metavar parameters in macro-generated macro_rules! definitions +// were missing hygiene annotations, making identical `$marg` bindings +// indistinguishable. // Don't break whenever Symbol numbering changes //@ normalize-stdout: "\d+#" -> "0#" diff --git a/tests/ui/hygiene/unpretty-debug-shadow.rs b/tests/ui/hygiene/unpretty-debug-shadow.rs new file mode 100644 index 000000000000..2aa68c8aec11 --- /dev/null +++ b/tests/ui/hygiene/unpretty-debug-shadow.rs @@ -0,0 +1,20 @@ +//@ check-pass +//@ compile-flags: -Zunpretty=expanded,hygiene + +// Regression test for token hygiene annotations in -Zunpretty=expanded,hygiene +// Previously, tokens in macro_rules! bodies were missing hygiene annotations, +// making it impossible to see how a macro's reference to a shadowed variable +// is distinguished from the shadowing binding. + +// Don't break whenever Symbol numbering changes +//@ normalize-stdout: "\d+#" -> "0#" + +#![feature(no_core)] +#![no_core] + +fn f() { + let x = 0; + macro_rules! use_x { () => { x }; } + let x = 1; + use_x!(); +} diff --git a/tests/ui/hygiene/unpretty-debug-shadow.stdout b/tests/ui/hygiene/unpretty-debug-shadow.stdout new file mode 100644 index 000000000000..36076b6a968f --- /dev/null +++ b/tests/ui/hygiene/unpretty-debug-shadow.stdout @@ -0,0 +1,30 @@ +//@ check-pass +//@ compile-flags: -Zunpretty=expanded,hygiene + +// Regression test for token hygiene annotations in -Zunpretty=expanded,hygiene +// Previously, tokens in macro_rules! bodies were missing hygiene annotations, +// making it impossible to see how a macro's reference to a shadowed variable +// is distinguished from the shadowing binding. + +// Don't break whenever Symbol numbering changes +//@ normalize-stdout: "\d+#" -> "0#" + +#![feature /* 0#0 */(no_core /* 0#0 */)] +#![no_core /* 0#0 */] + +fn f /* 0#0 */() { + let x /* 0#0 */ = 0; + macro_rules! use_x /* 0#0 */ { () => { x /* 0#0 */ }; } + let x /* 0#0 */ = 1; + x /* 0#1 */; +} + +/* +Expansions: +crate0::{{expn0}}: parent: crate0::{{expn0}}, call_site_ctxt: #0, def_site_ctxt: #0, kind: Root +crate0::{{expn1}}: parent: crate0::{{expn0}}, call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro(Bang, "use_x") + +SyntaxContexts: +#0: parent: #0, outer_mark: (crate0::{{expn0}}, Opaque) +#1: parent: #0, outer_mark: (crate0::{{expn1}}, SemiOpaque) +*/ From e0637766029752d6d9a2aaf088edbc469b4f66a0 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 4 Mar 2026 01:00:11 +0100 Subject: [PATCH 0119/1059] Fix LegacyKeyValueFormat report from docker build: powerpc --- src/ci/docker/host-x86_64/dist-powerpc-linux/Dockerfile | 4 ++-- .../docker/host-x86_64/dist-powerpc64-linux-gnu/Dockerfile | 4 ++-- .../host-x86_64/dist-powerpc64-linux-musl/Dockerfile | 7 +++---- .../host-x86_64/dist-powerpc64le-linux-gnu/Dockerfile | 7 +++---- .../host-x86_64/dist-powerpc64le-linux-musl/Dockerfile | 7 +++---- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/ci/docker/host-x86_64/dist-powerpc-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-powerpc-linux/Dockerfile index 7081d9527f06..825392414671 100644 --- a/src/ci/docker/host-x86_64/dist-powerpc-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-powerpc-linux/Dockerfile @@ -26,5 +26,5 @@ ENV \ ENV HOSTS=powerpc-unknown-linux-gnu -ENV RUST_CONFIGURE_ARGS --enable-extended --enable-profiler --disable-docs -ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS +ENV RUST_CONFIGURE_ARGS="--enable-extended --enable-profiler --disable-docs" +ENV SCRIPT="python3 ../x.py dist --host $HOSTS --target $HOSTS" diff --git a/src/ci/docker/host-x86_64/dist-powerpc64-linux-gnu/Dockerfile b/src/ci/docker/host-x86_64/dist-powerpc64-linux-gnu/Dockerfile index 046406224c34..ad0e21019017 100644 --- a/src/ci/docker/host-x86_64/dist-powerpc64-linux-gnu/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-powerpc64-linux-gnu/Dockerfile @@ -26,5 +26,5 @@ ENV \ ENV HOSTS=powerpc64-unknown-linux-gnu -ENV RUST_CONFIGURE_ARGS --enable-extended --enable-profiler --disable-docs -ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS +ENV RUST_CONFIGURE_ARGS="--enable-extended --enable-profiler --disable-docs" +ENV SCRIPT="python3 ../x.py dist --host $HOSTS --target $HOSTS" diff --git a/src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/Dockerfile b/src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/Dockerfile index 7c8a1e657ac2..17783b9c7358 100644 --- a/src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/Dockerfile @@ -27,13 +27,12 @@ ENV \ ENV HOSTS=powerpc64-unknown-linux-musl -ENV RUST_CONFIGURE_ARGS \ - --enable-extended \ +ENV RUST_CONFIGURE_ARGS="--enable-extended \ --enable-full-tools \ --enable-profiler \ --enable-sanitizers \ --disable-docs \ --set target.powerpc64-unknown-linux-musl.crt-static=false \ - --musl-root-powerpc64=/x-tools/powerpc64-unknown-linux-musl/powerpc64-unknown-linux-musl/sysroot/usr + --musl-root-powerpc64=/x-tools/powerpc64-unknown-linux-musl/powerpc64-unknown-linux-musl/sysroot/usr" -ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS +ENV SCRIPT="python3 ../x.py dist --host $HOSTS --target $HOSTS" diff --git a/src/ci/docker/host-x86_64/dist-powerpc64le-linux-gnu/Dockerfile b/src/ci/docker/host-x86_64/dist-powerpc64le-linux-gnu/Dockerfile index e3ba51e8ffce..6a9573cb4231 100644 --- a/src/ci/docker/host-x86_64/dist-powerpc64le-linux-gnu/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-powerpc64le-linux-gnu/Dockerfile @@ -27,11 +27,10 @@ ENV \ ENV HOSTS=powerpc64le-unknown-linux-gnu -ENV RUST_CONFIGURE_ARGS \ - --enable-extended \ +ENV RUST_CONFIGURE_ARGS="--enable-extended \ --enable-full-tools \ --enable-profiler \ --enable-sanitizers \ - --disable-docs + --disable-docs" -ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS +ENV SCRIPT="python3 ../x.py dist --host $HOSTS --target $HOSTS" diff --git a/src/ci/docker/host-x86_64/dist-powerpc64le-linux-musl/Dockerfile b/src/ci/docker/host-x86_64/dist-powerpc64le-linux-musl/Dockerfile index 601c8e905858..be16966fbe1b 100644 --- a/src/ci/docker/host-x86_64/dist-powerpc64le-linux-musl/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-powerpc64le-linux-musl/Dockerfile @@ -27,13 +27,12 @@ ENV \ ENV HOSTS=powerpc64le-unknown-linux-musl -ENV RUST_CONFIGURE_ARGS \ - --enable-extended \ +ENV RUST_CONFIGURE_ARGS="--enable-extended \ --enable-full-tools \ --enable-profiler \ --enable-sanitizers \ --disable-docs \ --set target.powerpc64le-unknown-linux-musl.crt-static=false \ - --musl-root-powerpc64le=/x-tools/powerpc64le-unknown-linux-musl/powerpc64le-unknown-linux-musl/sysroot/usr + --musl-root-powerpc64le=/x-tools/powerpc64le-unknown-linux-musl/powerpc64le-unknown-linux-musl/sysroot/usr" -ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS +ENV SCRIPT="python3 ../x.py dist --host $HOSTS --target $HOSTS" From 1b86d31ae236d7e7e1fd2ed46be491fd38af2b17 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 4 Mar 2026 01:25:25 +0100 Subject: [PATCH 0120/1059] Fix LegacyKeyValueFormat report from docker build: dist-x86_64 --- .../host-x86_64/dist-x86_64-freebsd/Dockerfile | 7 +++---- .../host-x86_64/dist-x86_64-illumos/Dockerfile | 4 ++-- .../docker/host-x86_64/dist-x86_64-linux/Dockerfile | 13 ++++++------- .../docker/host-x86_64/dist-x86_64-musl/Dockerfile | 7 +++---- .../host-x86_64/dist-x86_64-netbsd/Dockerfile | 4 ++-- .../host-x86_64/dist-x86_64-solaris/Dockerfile | 4 ++-- 6 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/ci/docker/host-x86_64/dist-x86_64-freebsd/Dockerfile b/src/ci/docker/host-x86_64/dist-x86_64-freebsd/Dockerfile index f42e6f770ebe..e62aa74662be 100644 --- a/src/ci/docker/host-x86_64/dist-x86_64-freebsd/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-x86_64-freebsd/Dockerfile @@ -35,10 +35,9 @@ ENV \ ENV HOSTS=x86_64-unknown-freebsd -ENV RUST_CONFIGURE_ARGS \ - --enable-full-tools \ +ENV RUST_CONFIGURE_ARGS="--enable-full-tools \ --enable-extended \ --enable-profiler \ --enable-sanitizers \ - --disable-docs -ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS + --disable-docs" +ENV SCRIPT="python3 ../x.py dist --host $HOSTS --target $HOSTS" diff --git a/src/ci/docker/host-x86_64/dist-x86_64-illumos/Dockerfile b/src/ci/docker/host-x86_64/dist-x86_64-illumos/Dockerfile index 37a8dc56a5fa..0675c3cafd9e 100644 --- a/src/ci/docker/host-x86_64/dist-x86_64-illumos/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-x86_64-illumos/Dockerfile @@ -36,5 +36,5 @@ ENV \ ENV HOSTS=x86_64-unknown-illumos -ENV RUST_CONFIGURE_ARGS --enable-extended --disable-docs -ENV SCRIPT python2.7 ../x.py dist --host $HOSTS --target $HOSTS +ENV RUST_CONFIGURE_ARGS="--enable-extended --disable-docs" +ENV SCRIPT="python2.7 ../x.py dist --host $HOSTS --target $HOSTS" diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile index cb5747876199..b168c739c979 100644 --- a/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile @@ -80,8 +80,7 @@ ENV PGO_HOST=x86_64-unknown-linux-gnu ENV HOSTS=x86_64-unknown-linux-gnu -ENV RUST_CONFIGURE_ARGS \ - --enable-full-tools \ +ENV RUST_CONFIGURE_ARGS="--enable-full-tools \ --enable-sanitizers \ --enable-profiler \ --enable-compiler-docs \ @@ -94,23 +93,23 @@ ENV RUST_CONFIGURE_ARGS \ --set rust.jemalloc \ --set rust.bootstrap-override-lld=true \ --set rust.lto=thin \ - --set rust.codegen-units=1 + --set rust.codegen-units=1" COPY host-x86_64/dist-x86_64-linux/dist.sh /scripts/ COPY host-x86_64/dist-x86_64-linux/dist-alt.sh /scripts/ -ENV SCRIPT "Must specify DOCKER_SCRIPT for this image" +ENV SCRIPT="Must specify DOCKER_SCRIPT for this image" ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=clang # This is the only builder which will create source tarballs -ENV DIST_SRC 1 +ENV DIST_SRC="1" # When we build cargo in this container, we don't want it to use the system # libcurl, instead it should compile its own. -ENV LIBCURL_NO_PKG_CONFIG 1 +ENV LIBCURL_NO_PKG_CONFIG="1" -ENV DIST_REQUIRE_ALL_TOOLS 1 +ENV DIST_REQUIRE_ALL_TOOLS="1" # FIXME: Without this, LLVMgold.so incorrectly resolves to the system # libstdc++, instead of the one we build. diff --git a/src/ci/docker/host-x86_64/dist-x86_64-musl/Dockerfile b/src/ci/docker/host-x86_64/dist-x86_64-musl/Dockerfile index 32b5058bce70..68f914a81552 100644 --- a/src/ci/docker/host-x86_64/dist-x86_64-musl/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-x86_64-musl/Dockerfile @@ -35,14 +35,13 @@ RUN sh /scripts/sccache.sh ENV HOSTS=x86_64-unknown-linux-musl -ENV RUST_CONFIGURE_ARGS \ - --musl-root-x86_64=/usr/local/x86_64-linux-musl \ +ENV RUST_CONFIGURE_ARGS="--musl-root-x86_64=/usr/local/x86_64-linux-musl \ --enable-extended \ --enable-sanitizers \ --enable-profiler \ --enable-lld \ --set target.x86_64-unknown-linux-musl.crt-static=false \ - --build $HOSTS + --build $HOSTS" # Newer binutils broke things on some vms/distros (i.e., linking against # unknown relocs disabled by the following flag), so we need to go out of our @@ -54,4 +53,4 @@ ENV CFLAGS_x86_64_unknown_linux_musl="-Wa,-mrelax-relocations=no -Wa,--compress- -Wl,--compress-debug-sections=none" # To run native tests replace `dist` below with `test` -ENV SCRIPT python3 ../x.py dist --build $HOSTS +ENV SCRIPT="python3 ../x.py dist --build $HOSTS" diff --git a/src/ci/docker/host-x86_64/dist-x86_64-netbsd/Dockerfile b/src/ci/docker/host-x86_64/dist-x86_64-netbsd/Dockerfile index effdc99d9a65..3bb2c7433e03 100644 --- a/src/ci/docker/host-x86_64/dist-x86_64-netbsd/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-x86_64-netbsd/Dockerfile @@ -22,5 +22,5 @@ ENV \ ENV HOSTS=x86_64-unknown-netbsd -ENV RUST_CONFIGURE_ARGS --enable-extended --disable-docs -ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS +ENV RUST_CONFIGURE_ARGS="--enable-extended --disable-docs" +ENV SCRIPT="python3 ../x.py dist --host $HOSTS --target $HOSTS" diff --git a/src/ci/docker/host-x86_64/dist-x86_64-solaris/Dockerfile b/src/ci/docker/host-x86_64/dist-x86_64-solaris/Dockerfile index 4d77f0aad26b..b11e8cdeaa73 100644 --- a/src/ci/docker/host-x86_64/dist-x86_64-solaris/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-x86_64-solaris/Dockerfile @@ -32,5 +32,5 @@ ENV \ ENV HOSTS=x86_64-pc-solaris -ENV RUST_CONFIGURE_ARGS --enable-extended --disable-docs -ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS +ENV RUST_CONFIGURE_ARGS="--enable-extended --disable-docs" +ENV SCRIPT="python3 ../x.py dist --host $HOSTS --target $HOSTS" From bf69dfa024c670e0f8f6d38dd1cde385959117c9 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 4 Mar 2026 10:51:22 +0000 Subject: [PATCH 0121/1059] Rustup to rustc 1.96.0-nightly (d9563937f 2026-03-03) --- patches/0027-stdlib-128bit-atomic-operations.patch | 13 +------------ rust-toolchain.toml | 2 +- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/patches/0027-stdlib-128bit-atomic-operations.patch b/patches/0027-stdlib-128bit-atomic-operations.patch index 38bb43f8204b..b7276e43153b 100644 --- a/patches/0027-stdlib-128bit-atomic-operations.patch +++ b/patches/0027-stdlib-128bit-atomic-operations.patch @@ -37,16 +37,7 @@ diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs index bf2b6d59f88..d5ccce03bbf 100644 --- a/library/core/src/sync/atomic.rs +++ b/library/core/src/sync/atomic.rs -@@ -300,8 +300,6 @@ impl_atomic_primitive!(AtomicI32(i32), size("32"), align(4)); - impl_atomic_primitive!(AtomicU32(u32), size("32"), align(4)); - impl_atomic_primitive!(AtomicI64(i64), size("64"), align(8)); - impl_atomic_primitive!(AtomicU64(u64), size("64"), align(8)); --impl_atomic_primitive!(AtomicI128(i128), size("128"), align(16)); --impl_atomic_primitive!(AtomicU128(u128), size("128"), align(16)); - - #[cfg(target_pointer_width = "16")] - impl_atomic_primitive!(AtomicIsize(isize), size("ptr"), align(2)); -@@ -3585,44 +3585,6 @@ pub const fn as_ptr(&self) -> *mut $int_type { +@@ -3585,42 +3585,6 @@ pub const fn as_ptr(&self) -> *mut $int_type { 8, u64 AtomicU64 } @@ -62,7 +53,6 @@ index bf2b6d59f88..d5ccce03bbf 100644 - unstable(feature = "integer_atomics", issue = "99069"), - rustc_const_unstable(feature = "integer_atomics", issue = "99069"), - rustc_const_unstable(feature = "integer_atomics", issue = "99069"), -- rustc_diagnostic_item = "AtomicI128", - "i128", - "#![feature(integer_atomics)]\n\n", - atomic_min, atomic_max, @@ -81,7 +71,6 @@ index bf2b6d59f88..d5ccce03bbf 100644 - unstable(feature = "integer_atomics", issue = "99069"), - rustc_const_unstable(feature = "integer_atomics", issue = "99069"), - rustc_const_unstable(feature = "integer_atomics", issue = "99069"), -- rustc_diagnostic_item = "AtomicU128", - "u128", - "#![feature(integer_atomics)]\n\n", - atomic_umin, atomic_umax, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 85b1cf4d6117..44ae84d9ac69 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2026-02-24" +channel = "nightly-2026-03-04" components = ["rust-src", "rustc-dev", "llvm-tools", "rustfmt"] profile = "minimal" From dd9922151fc6546580c0b5d7316d6b097d87c63a Mon Sep 17 00:00:00 2001 From: Tony Kan Date: Tue, 3 Mar 2026 18:12:21 -0800 Subject: [PATCH 0122/1059] refactor(autodiff): Simplify rlib dep handling; use fn_ptr_ty in `adjust_activity_to_abi`, drop mono-collection & cross-crate-inline workarounds --- .../src/builder/autodiff.rs | 18 +++---- compiler/rustc_codegen_llvm/src/intrinsic.rs | 4 +- .../src/cross_crate_inline.rs | 5 -- compiler/rustc_monomorphize/src/collector.rs | 5 -- .../src/collector/autodiff.rs | 50 ------------------- 5 files changed, 11 insertions(+), 71 deletions(-) delete mode 100644 compiler/rustc_monomorphize/src/collector/autodiff.rs diff --git a/compiler/rustc_codegen_llvm/src/builder/autodiff.rs b/compiler/rustc_codegen_llvm/src/builder/autodiff.rs index f8f6439a7b0e..1cefdaae5ebd 100644 --- a/compiler/rustc_codegen_llvm/src/builder/autodiff.rs +++ b/compiler/rustc_codegen_llvm/src/builder/autodiff.rs @@ -6,7 +6,7 @@ use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods}; use rustc_data_structures::thin_vec::ThinVec; use rustc_hir::attrs::RustcAutodiff; -use rustc_middle::ty::{Instance, PseudoCanonicalInput, TyCtxt, TypingEnv}; +use rustc_middle::ty::{PseudoCanonicalInput, Ty, TyCtxt, TypingEnv}; use rustc_middle::{bug, ty}; use rustc_target::callconv::PassMode; use tracing::debug; @@ -18,25 +18,23 @@ pub(crate) fn adjust_activity_to_abi<'tcx>( tcx: TyCtxt<'tcx>, - instance: Instance<'tcx>, + fn_ptr_ty: Ty<'tcx>, typing_env: TypingEnv<'tcx>, da: &mut ThinVec, ) { - let fn_ty = instance.ty(tcx, typing_env); - - if !matches!(fn_ty.kind(), ty::FnDef(..)) { - bug!("expected fn def for autodiff, got {:?}", fn_ty); + if !matches!(fn_ptr_ty.kind(), ty::FnPtr(..)) { + bug!("expected fn ptr for autodiff, got {:?}", fn_ptr_ty); } // We don't actually pass the types back into the type system. // All we do is decide how to handle the arguments. - let sig = fn_ty.fn_sig(tcx).skip_binder(); + let fn_sig = fn_ptr_ty.fn_sig(tcx); + let sig = fn_sig.skip_binder(); // FIXME(Sa4dUs): pass proper varargs once we have support for differentiating variadic functions - let Ok(fn_abi) = - tcx.fn_abi_of_instance(typing_env.as_query_input((instance, ty::List::empty()))) + let Ok(fn_abi) = tcx.fn_abi_of_fn_ptr(typing_env.as_query_input((fn_sig, ty::List::empty()))) else { - bug!("failed to get fn_abi of instance with empty varargs"); + bug!("failed to get fn_abi of fn_ptr with empty varargs"); }; let mut new_activities = vec![]; diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index cf088ed50909..47ba227735a4 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -1374,9 +1374,11 @@ fn codegen_autodiff<'ll, 'tcx>( bug!("could not find autodiff attrs") }; + let fn_ptr_ty = + Ty::new_fn_ptr(tcx, fn_source.ty(tcx, TypingEnv::fully_monomorphized()).fn_sig(tcx)); adjust_activity_to_abi( tcx, - fn_source, + fn_ptr_ty, TypingEnv::fully_monomorphized(), &mut diff_attrs.input_activity, ); diff --git a/compiler/rustc_mir_transform/src/cross_crate_inline.rs b/compiler/rustc_mir_transform/src/cross_crate_inline.rs index 19ffffdd1eca..53aa5f450dbb 100644 --- a/compiler/rustc_mir_transform/src/cross_crate_inline.rs +++ b/compiler/rustc_mir_transform/src/cross_crate_inline.rs @@ -35,11 +35,6 @@ fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { return true; } - // FIXME(autodiff): replace this as per discussion in https://github.com/rust-lang/rust/pull/149033#discussion_r2535465880 - if find_attr!(tcx, def_id, RustcAutodiff(..)) { - return true; - } - if find_attr!(tcx, def_id, RustcIntrinsic) { // Intrinsic fallback bodies are always cross-crate inlineable. // To ensure that the MIR inliner doesn't cluelessly try to inline fallback diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 3aa55cc8eb9f..3b1b5ba9673a 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -205,8 +205,6 @@ //! this is not implemented however: a mono item will be produced //! regardless of whether it is actually needed or not. -mod autodiff; - use std::cell::OnceCell; use std::ops::ControlFlow; @@ -240,7 +238,6 @@ use rustc_span::{DUMMY_SP, Span}; use tracing::{debug, instrument, trace}; -use crate::collector::autodiff::collect_autodiff_fn; use crate::errors::{ self, EncounteredErrorWhileInstantiating, EncounteredErrorWhileInstantiatingGlobalAsm, NoOptimizedMir, RecursionLimit, @@ -990,8 +987,6 @@ fn visit_instance_use<'tcx>( return; } if let Some(intrinsic) = tcx.intrinsic(instance.def_id()) { - collect_autodiff_fn(tcx, instance, intrinsic, output); - if let Some(_requirement) = ValidityRequirement::from_intrinsic(intrinsic.name) { // The intrinsics assert_inhabited, assert_zero_valid, and assert_mem_uninitialized_valid will // be lowered in codegen to nothing or a call to panic_nounwind. So if we encounter any diff --git a/compiler/rustc_monomorphize/src/collector/autodiff.rs b/compiler/rustc_monomorphize/src/collector/autodiff.rs deleted file mode 100644 index 67d4b8c8afff..000000000000 --- a/compiler/rustc_monomorphize/src/collector/autodiff.rs +++ /dev/null @@ -1,50 +0,0 @@ -use rustc_middle::bug; -use rustc_middle::ty::{self, GenericArg, IntrinsicDef, TyCtxt}; - -use crate::collector::{MonoItems, create_fn_mono_item}; - -// Here, we force both primal and diff function to be collected in -// mono so this does not interfere in `autodiff` intrinsics -// codegen process. If they are unused, LLVM will remove them when -// compiling with O3. -// FIXME(autodiff): Remove this whole file, as per discussion in -// https://github.com/rust-lang/rust/pull/149033#discussion_r2535465880 -pub(crate) fn collect_autodiff_fn<'tcx>( - tcx: TyCtxt<'tcx>, - instance: ty::Instance<'tcx>, - intrinsic: IntrinsicDef, - output: &mut MonoItems<'tcx>, -) { - if intrinsic.name != rustc_span::sym::autodiff { - return; - }; - - collect_autodiff_fn_from_arg(instance.args[0], tcx, output); -} - -fn collect_autodiff_fn_from_arg<'tcx>( - arg: GenericArg<'tcx>, - tcx: TyCtxt<'tcx>, - output: &mut MonoItems<'tcx>, -) { - let (instance, span) = match arg.kind() { - ty::GenericArgKind::Type(ty) => match *ty.kind() { - ty::FnDef(def_id, substs) => { - let span = tcx.def_span(def_id); - let instance = ty::Instance::expect_resolve( - tcx, - ty::TypingEnv::non_body_analysis(tcx, def_id), - def_id, - substs, - span, - ); - - (instance, span) - } - _ => bug!("expected autodiff function"), - }, - _ => bug!("expected type when matching autodiff arg"), - }; - - output.push(create_fn_mono_item(tcx, instance, span)); -} From 8a3d0f48677f111d85450e0634d8ecaacc16269b Mon Sep 17 00:00:00 2001 From: Tony Kan Date: Wed, 4 Mar 2026 03:20:03 -0800 Subject: [PATCH 0123/1059] refactor(autodiff): Cast primal to fn ptr, drop Instance::try_resolve for source --- compiler/rustc_builtin_macros/src/autodiff.rs | 53 +++++++++++++++++-- compiler/rustc_codegen_llvm/src/intrinsic.rs | 32 ++--------- tests/pretty/autodiff/autodiff_forward.pp | 43 +++++++++------ tests/pretty/autodiff/autodiff_reverse.pp | 15 ++++-- tests/pretty/autodiff/inherent_impl.pp | 4 +- 5 files changed, 93 insertions(+), 54 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs index 2a0175043b6f..f18326328f41 100644 --- a/compiler/rustc_builtin_macros/src/autodiff.rs +++ b/compiler/rustc_builtin_macros/src/autodiff.rs @@ -20,7 +20,7 @@ mod llvm_enzyme { }; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_hir::attrs::RustcAutodiff; - use rustc_span::{Ident, Span, Symbol, sym}; + use rustc_span::{Ident, Span, Symbol, kw, sym}; use thin_vec::{ThinVec, thin_vec}; use tracing::{debug, trace}; @@ -197,7 +197,7 @@ pub(crate) fn expand_reverse( /// } /// #[rustc_autodiff(Reverse, Duplicated, Active)] /// fn cos_box(x: &Box, dx: &mut Box, dret: f32) -> f32 { - /// std::intrinsics::autodiff(sin::<>, cos_box::<>, (x, dx, dret)) + /// std::intrinsics::autodiff(sin::<> as fn(..) -> .., cos_box::<>, (x, dx, dret)) /// } /// ``` /// FIXME(ZuseZ4): Once autodiff is enabled by default, make this a doc comment which is checked @@ -326,6 +326,7 @@ pub(crate) fn expand_with_mode( primal, first_ident(&meta_item_vec[0]), span, + &sig, &d_sig, &generics, impl_of_trait, @@ -496,18 +497,62 @@ fn assure_mut_ref(ty: &ast::Ty) -> ast::Ty { // Generate `autodiff` intrinsic call // ``` - // std::intrinsics::autodiff(source, diff, (args)) + // std::intrinsics::autodiff(source as fn(..) -> .., diff, (args)) // ``` fn call_autodiff( ecx: &ExtCtxt<'_>, primal: Ident, diff: Ident, span: Span, + p_sig: &FnSig, d_sig: &FnSig, generics: &Generics, is_impl: bool, ) -> rustc_ast::Stmt { let primal_path_expr = gen_turbofish_expr(ecx, primal, generics, span, is_impl); + + let self_ty = || ecx.ty_path(ast::Path::from_ident(Ident::with_dummy_span(kw::SelfUpper))); + let fn_ptr_params: ThinVec = p_sig + .decl + .inputs + .iter() + .map(|param| { + let ty = match ¶m.ty.kind { + TyKind::ImplicitSelf => self_ty(), + TyKind::Ref(lt, mt) if matches!(mt.ty.kind, TyKind::ImplicitSelf) => ecx.ty( + span, + TyKind::Ref(lt.clone(), ast::MutTy { ty: self_ty(), mutbl: mt.mutbl }), + ), + TyKind::Ptr(mt) if matches!(mt.ty.kind, TyKind::ImplicitSelf) => { + ecx.ty(span, TyKind::Ptr(ast::MutTy { ty: self_ty(), mutbl: mt.mutbl })) + } + _ => param.ty.clone(), + }; + ast::Param { + attrs: ast::AttrVec::new(), + ty, + pat: Box::new(ecx.pat_wild(span)), + id: ast::DUMMY_NODE_ID, + span, + is_placeholder: false, + } + }) + .collect(); + let fn_ptr_ty = ecx.ty( + span, + TyKind::FnPtr(Box::new(ast::FnPtrTy { + safety: p_sig.header.safety, + ext: p_sig.header.ext, + generic_params: ThinVec::new(), + decl: Box::new(ast::FnDecl { + inputs: fn_ptr_params, + output: p_sig.decl.output.clone(), + }), + decl_span: span, + })), + ); + let primal_fn_ptr = ecx.expr(span, ast::ExprKind::Cast(primal_path_expr, fn_ptr_ty)); + let diff_path_expr = gen_turbofish_expr(ecx, diff, generics, span, is_impl); let tuple_expr = ecx.expr_tuple( @@ -529,7 +574,7 @@ fn call_autodiff( let call_expr = ecx.expr_call( span, ecx.expr_path(enzyme_path), - vec![primal_path_expr, diff_path_expr, tuple_expr].into(), + vec![primal_fn_ptr, diff_path_expr, tuple_expr].into(), ); ecx.stmt_expr(call_expr) diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 47ba227735a4..8c7d6997bf83 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -1323,29 +1323,8 @@ fn codegen_autodiff<'ll, 'tcx>( let ret_ty = sig.output(); let llret_ty = bx.layout_of(ret_ty).llvm_type(bx); - // Get source, diff, and attrs - let (source_id, source_args) = match fn_args.into_type_list(tcx)[0].kind() { - ty::FnDef(def_id, source_params) => (def_id, source_params), - _ => bug!("invalid autodiff intrinsic args"), - }; - - let fn_source = match Instance::try_resolve(tcx, bx.cx.typing_env(), *source_id, source_args) { - Ok(Some(instance)) => instance, - Ok(None) => bug!( - "could not resolve ({:?}, {:?}) to a specific autodiff instance", - source_id, - source_args - ), - Err(_) => { - // An error has already been emitted - return; - } - }; - - let source_symbol = symbol_name_for_instance_in_crate(tcx, fn_source.clone(), LOCAL_CRATE); - let Some(fn_to_diff) = bx.cx.get_function(&source_symbol) else { - bug!("could not find source function") - }; + let source_fn_ptr_ty = fn_args.into_type_list(tcx)[0]; + let fn_to_diff = args[0].immediate(); let (diff_id, diff_args) = match fn_args.into_type_list(tcx)[1].kind() { ty::FnDef(def_id, diff_args) => (def_id, diff_args), @@ -1374,17 +1353,14 @@ fn codegen_autodiff<'ll, 'tcx>( bug!("could not find autodiff attrs") }; - let fn_ptr_ty = - Ty::new_fn_ptr(tcx, fn_source.ty(tcx, TypingEnv::fully_monomorphized()).fn_sig(tcx)); adjust_activity_to_abi( tcx, - fn_ptr_ty, + source_fn_ptr_ty, TypingEnv::fully_monomorphized(), &mut diff_attrs.input_activity, ); - let fnc_tree = - rustc_middle::ty::fnc_typetrees(tcx, fn_source.ty(tcx, TypingEnv::fully_monomorphized())); + let fnc_tree = rustc_middle::ty::fnc_typetrees(tcx, source_fn_ptr_ty); // Build body generate_enzyme_call( diff --git a/tests/pretty/autodiff/autodiff_forward.pp b/tests/pretty/autodiff/autodiff_forward.pp index c64da3f60884..746754637f5c 100644 --- a/tests/pretty/autodiff/autodiff_forward.pp +++ b/tests/pretty/autodiff/autodiff_forward.pp @@ -35,7 +35,8 @@ pub fn f1(x: &[f64], y: f64) -> f64 { } #[rustc_autodiff(Forward, 1, Dual, Const, Dual)] pub fn df1(x: &[f64], bx_0: &[f64], y: f64) -> (f64, f64) { - ::core::intrinsics::autodiff(f1::<>, df1::<>, (x, bx_0, y)) + ::core::intrinsics::autodiff(f1::<> as fn(_: &[f64], _: f64) -> f64, + df1::<>, (x, bx_0, y)) } #[rustc_autodiff] pub fn f2(x: &[f64], y: f64) -> f64 { @@ -43,7 +44,8 @@ pub fn f2(x: &[f64], y: f64) -> f64 { } #[rustc_autodiff(Forward, 1, Dual, Const, Const)] pub fn df2(x: &[f64], bx_0: &[f64], y: f64) -> f64 { - ::core::intrinsics::autodiff(f2::<>, df2::<>, (x, bx_0, y)) + ::core::intrinsics::autodiff(f2::<> as fn(_: &[f64], _: f64) -> f64, + df2::<>, (x, bx_0, y)) } #[rustc_autodiff] pub fn f3(x: &[f64], y: f64) -> f64 { @@ -51,27 +53,33 @@ pub fn f3(x: &[f64], y: f64) -> f64 { } #[rustc_autodiff(Forward, 1, Dual, Const, Const)] pub fn df3(x: &[f64], bx_0: &[f64], y: f64) -> f64 { - ::core::intrinsics::autodiff(f3::<>, df3::<>, (x, bx_0, y)) + ::core::intrinsics::autodiff(f3::<> as fn(_: &[f64], _: f64) -> f64, + df3::<>, (x, bx_0, y)) } #[rustc_autodiff] pub fn f4() {} #[rustc_autodiff(Forward, 1, None)] -pub fn df4() -> () { ::core::intrinsics::autodiff(f4::<>, df4::<>, ()) } +pub fn df4() -> () { + ::core::intrinsics::autodiff(f4::<> as fn(), df4::<>, ()) +} #[rustc_autodiff] pub fn f5(x: &[f64], y: f64) -> f64 { ::core::panicking::panic("not implemented") } #[rustc_autodiff(Forward, 1, Const, Dual, Const)] pub fn df5_y(x: &[f64], y: f64, by_0: f64) -> f64 { - ::core::intrinsics::autodiff(f5::<>, df5_y::<>, (x, y, by_0)) + ::core::intrinsics::autodiff(f5::<> as fn(_: &[f64], _: f64) -> f64, + df5_y::<>, (x, y, by_0)) } #[rustc_autodiff(Forward, 1, Dual, Const, Const)] pub fn df5_x(x: &[f64], bx_0: &[f64], y: f64) -> f64 { - ::core::intrinsics::autodiff(f5::<>, df5_x::<>, (x, bx_0, y)) + ::core::intrinsics::autodiff(f5::<> as fn(_: &[f64], _: f64) -> f64, + df5_x::<>, (x, bx_0, y)) } #[rustc_autodiff(Reverse, 1, Duplicated, Const, Active)] pub fn df5_rev(x: &[f64], dx_0: &mut [f64], y: f64, dret: f64) -> f64 { - ::core::intrinsics::autodiff(f5::<>, df5_rev::<>, (x, dx_0, y, dret)) + ::core::intrinsics::autodiff(f5::<> as fn(_: &[f64], _: f64) -> f64, + df5_rev::<>, (x, dx_0, y, dret)) } struct DoesNotImplDefault; #[rustc_autodiff] @@ -80,13 +88,14 @@ pub fn f6() -> DoesNotImplDefault { } #[rustc_autodiff(Forward, 1, Const)] pub fn df6() -> DoesNotImplDefault { - ::core::intrinsics::autodiff(f6::<>, df6::<>, ()) + ::core::intrinsics::autodiff(f6::<> as fn() -> DoesNotImplDefault, + df6::<>, ()) } #[rustc_autodiff] pub fn f7(x: f32) -> () {} #[rustc_autodiff(Forward, 1, Const, None)] pub fn df7(x: f32) -> () { - ::core::intrinsics::autodiff(f7::<>, df7::<>, (x,)) + ::core::intrinsics::autodiff(f7::<> as fn(_: f32) -> (), df7::<>, (x,)) } #[no_mangle] #[rustc_autodiff] @@ -94,29 +103,32 @@ fn f8(x: &f32) -> f32 { ::core::panicking::panic("not implemented") } #[rustc_autodiff(Forward, 4, Dual, Dual)] fn f8_3(x: &f32, bx_0: &f32, bx_1: &f32, bx_2: &f32, bx_3: &f32) -> [f32; 5usize] { - ::core::intrinsics::autodiff(f8::<>, f8_3::<>, + ::core::intrinsics::autodiff(f8::<> as fn(_: &f32) -> f32, f8_3::<>, (x, bx_0, bx_1, bx_2, bx_3)) } #[rustc_autodiff(Forward, 4, Dual, DualOnly)] fn f8_2(x: &f32, bx_0: &f32, bx_1: &f32, bx_2: &f32, bx_3: &f32) -> [f32; 4usize] { - ::core::intrinsics::autodiff(f8::<>, f8_2::<>, + ::core::intrinsics::autodiff(f8::<> as fn(_: &f32) -> f32, f8_2::<>, (x, bx_0, bx_1, bx_2, bx_3)) } #[rustc_autodiff(Forward, 1, Dual, DualOnly)] fn f8_1(x: &f32, bx_0: &f32) -> f32 { - ::core::intrinsics::autodiff(f8::<>, f8_1::<>, (x, bx_0)) + ::core::intrinsics::autodiff(f8::<> as fn(_: &f32) -> f32, f8_1::<>, + (x, bx_0)) } pub fn f9() { #[rustc_autodiff] fn inner(x: f32) -> f32 { x * x } #[rustc_autodiff(Forward, 1, Dual, Dual)] fn d_inner_2(x: f32, bx_0: f32) -> (f32, f32) { - ::core::intrinsics::autodiff(inner::<>, d_inner_2::<>, (x, bx_0)) + ::core::intrinsics::autodiff(inner::<> as fn(_: f32) -> f32, + d_inner_2::<>, (x, bx_0)) } #[rustc_autodiff(Forward, 1, Dual, DualOnly)] fn d_inner_1(x: f32, bx_0: f32) -> f32 { - ::core::intrinsics::autodiff(inner::<>, d_inner_1::<>, (x, bx_0)) + ::core::intrinsics::autodiff(inner::<> as fn(_: f32) -> f32, + d_inner_1::<>, (x, bx_0)) } } #[rustc_autodiff] @@ -124,6 +136,7 @@ pub fn f10 + Copy>(x: &T) -> T { *x * *x } #[rustc_autodiff(Reverse, 1, Duplicated, Active)] pub fn d_square + Copy>(x: &T, dx_0: &mut T, dret: T) -> T { - ::core::intrinsics::autodiff(f10::, d_square::, (x, dx_0, dret)) + ::core::intrinsics::autodiff(f10:: as fn(_: &T) -> T, d_square::, + (x, dx_0, dret)) } fn main() {} diff --git a/tests/pretty/autodiff/autodiff_reverse.pp b/tests/pretty/autodiff/autodiff_reverse.pp index 61ab121b31bc..e2088e0ac13d 100644 --- a/tests/pretty/autodiff/autodiff_reverse.pp +++ b/tests/pretty/autodiff/autodiff_reverse.pp @@ -28,32 +28,37 @@ pub fn f1(x: &[f64], y: f64) -> f64 { } #[rustc_autodiff(Reverse, 1, Duplicated, Const, Active)] pub fn df1(x: &[f64], dx_0: &mut [f64], y: f64, dret: f64) -> f64 { - ::core::intrinsics::autodiff(f1::<>, df1::<>, (x, dx_0, y, dret)) + ::core::intrinsics::autodiff(f1::<> as fn(_: &[f64], _: f64) -> f64, + df1::<>, (x, dx_0, y, dret)) } #[rustc_autodiff] pub fn f2() {} #[rustc_autodiff(Reverse, 1, None)] -pub fn df2() { ::core::intrinsics::autodiff(f2::<>, df2::<>, ()) } +pub fn df2() { ::core::intrinsics::autodiff(f2::<> as fn(), df2::<>, ()) } #[rustc_autodiff] pub fn f3(x: &[f64], y: f64) -> f64 { ::core::panicking::panic("not implemented") } #[rustc_autodiff(Reverse, 1, Duplicated, Const, Active)] pub fn df3(x: &[f64], dx_0: &mut [f64], y: f64, dret: f64) -> f64 { - ::core::intrinsics::autodiff(f3::<>, df3::<>, (x, dx_0, y, dret)) + ::core::intrinsics::autodiff(f3::<> as fn(_: &[f64], _: f64) -> f64, + df3::<>, (x, dx_0, y, dret)) } enum Foo { Reverse, } use Foo::Reverse; #[rustc_autodiff] pub fn f4(x: f32) { ::core::panicking::panic("not implemented") } #[rustc_autodiff(Reverse, 1, Const, None)] -pub fn df4(x: f32) { ::core::intrinsics::autodiff(f4::<>, df4::<>, (x,)) } +pub fn df4(x: f32) { + ::core::intrinsics::autodiff(f4::<> as fn(_: f32), df4::<>, (x,)) +} #[rustc_autodiff] pub fn f5(x: *const f32, y: &f32) { ::core::panicking::panic("not implemented") } #[rustc_autodiff(Reverse, 1, DuplicatedOnly, Duplicated, None)] pub unsafe fn df5(x: *const f32, dx_0: *mut f32, y: &f32, dy_0: &mut f32) { - ::core::intrinsics::autodiff(f5::<>, df5::<>, (x, dx_0, y, dy_0)) + ::core::intrinsics::autodiff(f5::<> as fn(_: *const f32, _: &f32), + df5::<>, (x, dx_0, y, dy_0)) } fn main() {} diff --git a/tests/pretty/autodiff/inherent_impl.pp b/tests/pretty/autodiff/inherent_impl.pp index 1c83c66c8edf..d3a5a71b8bcb 100644 --- a/tests/pretty/autodiff/inherent_impl.pp +++ b/tests/pretty/autodiff/inherent_impl.pp @@ -30,7 +30,7 @@ impl MyTrait for Foo { } #[rustc_autodiff(Reverse, 1, Const, Active, Active)] fn df(&self, x: f64, dret: f64) -> (f64, f64) { - ::core::intrinsics::autodiff(Self::f::<>, Self::df::<>, - (self, x, dret)) + ::core::intrinsics::autodiff(Self::f::<> as + fn(_: &Self, _: f64) -> f64, Self::df::<>, (self, x, dret)) } } From aaa4523d4bd6786a04b3291073321798a95fe38a Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 4 Mar 2026 15:21:10 +0100 Subject: [PATCH 0124/1059] explain how to control target features with Miri --- src/tools/miri/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index f254eb357a45..d9a2ee10ef6f 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -154,6 +154,16 @@ platform. For example `cargo miri test --target s390x-unknown-linux-gnu` will run your test suite on a big-endian target, which is useful for testing endian-sensitive code. +### Controlling target features + +Controlling target features works similar to regular rustc invocations: +`RUSTFLAGS="-Ctarget-features=+avx512f" cargo miri test` runs the tests with AVX512 enabled. (Miri +only supports very few AVX512 intrinsics at the moment.) `-Ctarget-cpu` also works. Unlike regular +rustc, this command has *two* effects: it builds the code with that target feature available (which +affects `cfg(target_feature)`), and it tells Miri to consider the "virtual CPU" that the interpreted +program runs on as having the feature available (meaning the code is allowed to invoke the +corresponding intrinsics). + ### Testing multiple different executions Certain parts of the execution are picked randomly by Miri, such as the exact base address From a2fbeffe2ef625bea85efb970ba2975f22e1feca Mon Sep 17 00:00:00 2001 From: N1ark Date: Tue, 3 Mar 2026 16:22:07 +0000 Subject: [PATCH 0125/1059] Clarify `-Zmiri-disable-validation` in the README Co-Authored-By: Ralf Jung --- src/tools/miri/README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index f254eb357a45..2473832e3cad 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -421,10 +421,11 @@ to Miri failing to detect cases of undefined behavior in a program. are experimental). Later flags take precedence: borrow tracking can be reactivated by `-Zmiri-tree-borrows`. * `-Zmiri-disable-validation` disables enforcing validity invariants, which are - enforced by default. This is mostly useful to focus on other failures (such - as out-of-bounds accesses) first. Setting this flag means Miri can miss bugs - in your program. However, this can also help to make Miri run faster. Using - this flag is **unsound**. + enforced by default. This only disables these checks for typed copies; using + invalid values in any other operation will still cause an error. This is mostly useful + to focus on other failures (such as out-of-bounds accesses) first. Setting this + flag means Miri can miss bugs in your program. However, this can also help to + make Miri run faster. Using this flag is **unsound**. * `-Zmiri-disable-weak-memory-emulation` disables the emulation of some C++11 weak memory effects. * `-Zmiri-fixed-schedule` disables preemption (like `-Zmiri-preemption-rate=0.0`) and furthermore From 390f683cfca9e3c39d73e9df776a65ec0f141290 Mon Sep 17 00:00:00 2001 From: joboet Date: Mon, 2 Mar 2026 12:52:30 +0100 Subject: [PATCH 0126/1059] std: move non-path functions into dedicated module in PAL --- .../std/src/sys/net/connection/socket/unix.rs | 2 +- library/std/src/sys/pal/teeos/conf.rs | 5 + library/std/src/sys/pal/teeos/mod.rs | 1 + library/std/src/sys/pal/teeos/os.rs | 6 -- library/std/src/sys/pal/unix/conf.rs | 97 +++++++++++++++++ .../src/sys/pal/unix/{os => conf}/tests.rs | 0 library/std/src/sys/pal/unix/mod.rs | 1 + library/std/src/sys/pal/unix/os.rs | 101 ++---------------- .../std/src/sys/pal/unix/stack_overflow.rs | 4 +- library/std/src/sys/pal/wasi/conf.rs | 4 + library/std/src/sys/pal/wasi/mod.rs | 2 + library/std/src/sys/pal/wasi/os.rs | 5 - library/std/src/sys/process/unix/unix.rs | 2 +- library/std/src/sys/thread/teeos.rs | 4 +- library/std/src/sys/thread/unix.rs | 2 +- 15 files changed, 124 insertions(+), 112 deletions(-) create mode 100644 library/std/src/sys/pal/teeos/conf.rs create mode 100644 library/std/src/sys/pal/unix/conf.rs rename library/std/src/sys/pal/unix/{os => conf}/tests.rs (100%) create mode 100644 library/std/src/sys/pal/wasi/conf.rs diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs index 5e20c0ffdfa6..f3c860804de0 100644 --- a/library/std/src/sys/net/connection/socket/unix.rs +++ b/library/std/src/sys/net/connection/socket/unix.rs @@ -684,7 +684,7 @@ fn on_resolver_failure() { use crate::sys; // If the version fails to parse, we treat it the same as "not glibc". - if let Some(version) = sys::os::glibc_version() { + if let Some(version) = sys::pal::conf::glibc_version() { if version < (2, 26) { unsafe { libc::res_init() }; } diff --git a/library/std/src/sys/pal/teeos/conf.rs b/library/std/src/sys/pal/teeos/conf.rs new file mode 100644 index 000000000000..1d13a283c458 --- /dev/null +++ b/library/std/src/sys/pal/teeos/conf.rs @@ -0,0 +1,5 @@ +// Hardcoded to return 4096, since `sysconf` is only implemented as a stub. +pub fn page_size() -> usize { + // unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }; + 4096 +} diff --git a/library/std/src/sys/pal/teeos/mod.rs b/library/std/src/sys/pal/teeos/mod.rs index f76c26d3c966..7d2ecdbf7ec4 100644 --- a/library/std/src/sys/pal/teeos/mod.rs +++ b/library/std/src/sys/pal/teeos/mod.rs @@ -6,6 +6,7 @@ #![allow(unused_variables)] #![allow(dead_code)] +pub mod conf; pub mod os; #[path = "../unix/time.rs"] pub mod time; diff --git a/library/std/src/sys/pal/teeos/os.rs b/library/std/src/sys/pal/teeos/os.rs index c09b84f42bab..c86fa555e411 100644 --- a/library/std/src/sys/pal/teeos/os.rs +++ b/library/std/src/sys/pal/teeos/os.rs @@ -7,12 +7,6 @@ use crate::path::PathBuf; use crate::{fmt, io, path}; -// Hardcoded to return 4096, since `sysconf` is only implemented as a stub. -pub fn page_size() -> usize { - // unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }; - 4096 -} - // Everything below are stubs and copied from unsupported.rs pub fn getcwd() -> io::Result { diff --git a/library/std/src/sys/pal/unix/conf.rs b/library/std/src/sys/pal/unix/conf.rs new file mode 100644 index 000000000000..a97173a1a35a --- /dev/null +++ b/library/std/src/sys/pal/unix/conf.rs @@ -0,0 +1,97 @@ +#[cfg(test)] +mod tests; + +#[cfg(not(target_os = "espidf"))] +pub fn page_size() -> usize { + unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } +} + +/// Returns the value for [`confstr(key, ...)`][posix_confstr]. Currently only +/// used on Darwin, but should work on any unix (in case we need to get +/// `_CS_PATH` or `_CS_V[67]_ENV` in the future). +/// +/// [posix_confstr]: +/// https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html +// +// FIXME: Support `confstr` in Miri. +#[cfg(all(target_vendor = "apple", not(miri)))] +pub fn confstr( + key: crate::ffi::c_int, + size_hint: Option, +) -> crate::io::Result { + use crate::ffi::OsString; + use crate::io; + use crate::os::unix::ffi::OsStringExt; + + let mut buf: Vec = Vec::with_capacity(0); + let mut bytes_needed_including_nul = size_hint + .unwrap_or_else(|| { + // Treat "None" as "do an extra call to get the length". In theory + // we could move this into the loop below, but it's hard to do given + // that it isn't 100% clear if it's legal to pass 0 for `len` when + // the buffer isn't null. + unsafe { libc::confstr(key, core::ptr::null_mut(), 0) } + }) + .max(1); + // If the value returned by `confstr` is greater than the len passed into + // it, then the value was truncated, meaning we need to retry. Note that + // while `confstr` results don't seem to change for a process, it's unclear + // if this is guaranteed anywhere, so looping does seem required. + while bytes_needed_including_nul > buf.capacity() { + // We write into the spare capacity of `buf`. This lets us avoid + // changing buf's `len`, which both simplifies `reserve` computation, + // allows working with `Vec` instead of `Vec>`, and + // may avoid a copy, since the Vec knows that none of the bytes are needed + // when reallocating (well, in theory anyway). + buf.reserve(bytes_needed_including_nul); + // `confstr` returns + // - 0 in the case of errors: we break and return an error. + // - The number of bytes written, iff the provided buffer is enough to + // hold the entire value: we break and return the data in `buf`. + // - Otherwise, the number of bytes needed (including nul): we go + // through the loop again. + bytes_needed_including_nul = + unsafe { libc::confstr(key, buf.as_mut_ptr().cast(), buf.capacity()) }; + } + // `confstr` returns 0 in the case of an error. + if bytes_needed_including_nul == 0 { + return Err(io::Error::last_os_error()); + } + // Safety: `confstr(..., buf.as_mut_ptr(), buf.capacity())` returned a + // non-zero value, meaning `bytes_needed_including_nul` bytes were + // initialized. + unsafe { + buf.set_len(bytes_needed_including_nul); + // Remove the NUL-terminator. + let last_byte = buf.pop(); + // ... and smoke-check that it *was* a NUL-terminator. + assert_eq!(last_byte, Some(0), "`confstr` provided a string which wasn't nul-terminated"); + }; + Ok(OsString::from_vec(buf)) +} + +#[cfg(all(target_os = "linux", target_env = "gnu"))] +pub fn glibc_version() -> Option<(usize, usize)> { + use crate::ffi::CStr; + + unsafe extern "C" { + fn gnu_get_libc_version() -> *const libc::c_char; + } + let version_cstr = unsafe { CStr::from_ptr(gnu_get_libc_version()) }; + if let Ok(version_str) = version_cstr.to_str() { + parse_glibc_version(version_str) + } else { + None + } +} + +/// Returns Some((major, minor)) if the string is a valid "x.y" version, +/// ignoring any extra dot-separated parts. Otherwise return None. +#[cfg(all(target_os = "linux", target_env = "gnu"))] +fn parse_glibc_version(version: &str) -> Option<(usize, usize)> { + let mut parsed_ints = version.split('.').map(str::parse::).fuse(); + match (parsed_ints.next(), parsed_ints.next()) { + (Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)), + _ => None, + } +} diff --git a/library/std/src/sys/pal/unix/os/tests.rs b/library/std/src/sys/pal/unix/conf/tests.rs similarity index 100% rename from library/std/src/sys/pal/unix/os/tests.rs rename to library/std/src/sys/pal/unix/conf/tests.rs diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 0fbf37fda7fb..9931b4de0b9b 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -2,6 +2,7 @@ use crate::io; +pub mod conf; #[cfg(target_os = "fuchsia")] pub mod fuchsia; pub mod futex; diff --git a/library/std/src/sys/pal/unix/os.rs b/library/std/src/sys/pal/unix/os.rs index d11282682d08..f69614c2077c 100644 --- a/library/std/src/sys/pal/unix/os.rs +++ b/library/std/src/sys/pal/unix/os.rs @@ -2,9 +2,6 @@ #![allow(unused_imports)] // lots of cfg code here -#[cfg(test)] -mod tests; - use libc::{c_char, c_int, c_void}; use crate::ffi::{CStr, OsStr, OsString}; @@ -396,75 +393,15 @@ pub fn current_exe() -> io::Result { if !path.is_absolute() { getcwd().map(|cwd| cwd.join(path)) } else { Ok(path) } } -#[cfg(not(target_os = "espidf"))] -pub fn page_size() -> usize { - unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } -} - -// Returns the value for [`confstr(key, ...)`][posix_confstr]. Currently only -// used on Darwin, but should work on any unix (in case we need to get -// `_CS_PATH` or `_CS_V[67]_ENV` in the future). -// -// [posix_confstr]: -// https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html -// -// FIXME: Support `confstr` in Miri. -#[cfg(all(target_vendor = "apple", not(miri)))] -fn confstr(key: c_int, size_hint: Option) -> io::Result { - let mut buf: Vec = Vec::with_capacity(0); - let mut bytes_needed_including_nul = size_hint - .unwrap_or_else(|| { - // Treat "None" as "do an extra call to get the length". In theory - // we could move this into the loop below, but it's hard to do given - // that it isn't 100% clear if it's legal to pass 0 for `len` when - // the buffer isn't null. - unsafe { libc::confstr(key, core::ptr::null_mut(), 0) } - }) - .max(1); - // If the value returned by `confstr` is greater than the len passed into - // it, then the value was truncated, meaning we need to retry. Note that - // while `confstr` results don't seem to change for a process, it's unclear - // if this is guaranteed anywhere, so looping does seem required. - while bytes_needed_including_nul > buf.capacity() { - // We write into the spare capacity of `buf`. This lets us avoid - // changing buf's `len`, which both simplifies `reserve` computation, - // allows working with `Vec` instead of `Vec>`, and - // may avoid a copy, since the Vec knows that none of the bytes are needed - // when reallocating (well, in theory anyway). - buf.reserve(bytes_needed_including_nul); - // `confstr` returns - // - 0 in the case of errors: we break and return an error. - // - The number of bytes written, iff the provided buffer is enough to - // hold the entire value: we break and return the data in `buf`. - // - Otherwise, the number of bytes needed (including nul): we go - // through the loop again. - bytes_needed_including_nul = - unsafe { libc::confstr(key, buf.as_mut_ptr().cast::(), buf.capacity()) }; - } - // `confstr` returns 0 in the case of an error. - if bytes_needed_including_nul == 0 { - return Err(io::Error::last_os_error()); - } - // Safety: `confstr(..., buf.as_mut_ptr(), buf.capacity())` returned a - // non-zero value, meaning `bytes_needed_including_nul` bytes were - // initialized. - unsafe { - buf.set_len(bytes_needed_including_nul); - // Remove the NUL-terminator. - let last_byte = buf.pop(); - // ... and smoke-check that it *was* a NUL-terminator. - assert_eq!(last_byte, Some(0), "`confstr` provided a string which wasn't nul-terminated"); - }; - Ok(OsString::from_vec(buf)) -} - #[cfg(all(target_vendor = "apple", not(miri)))] fn darwin_temp_dir() -> PathBuf { - confstr(libc::_CS_DARWIN_USER_TEMP_DIR, Some(64)).map(PathBuf::from).unwrap_or_else(|_| { - // It failed for whatever reason (there are several possible reasons), - // so return the global one. - PathBuf::from("/tmp") - }) + crate::sys::pal::conf::confstr(libc::_CS_DARWIN_USER_TEMP_DIR, Some(64)) + .map(PathBuf::from) + .unwrap_or_else(|_| { + // It failed for whatever reason (there are several possible reasons), + // so return the global one. + PathBuf::from("/tmp") + }) } pub fn temp_dir() -> PathBuf { @@ -532,27 +469,3 @@ unsafe fn fallback() -> Option { } } } - -#[cfg(all(target_os = "linux", target_env = "gnu"))] -pub fn glibc_version() -> Option<(usize, usize)> { - unsafe extern "C" { - fn gnu_get_libc_version() -> *const libc::c_char; - } - let version_cstr = unsafe { CStr::from_ptr(gnu_get_libc_version()) }; - if let Ok(version_str) = version_cstr.to_str() { - parse_glibc_version(version_str) - } else { - None - } -} - -// Returns Some((major, minor)) if the string is a valid "x.y" version, -// ignoring any extra dot-separated parts. Otherwise return None. -#[cfg(all(target_os = "linux", target_env = "gnu"))] -fn parse_glibc_version(version: &str) -> Option<(usize, usize)> { - let mut parsed_ints = version.split('.').map(str::parse::).fuse(); - match (parsed_ints.next(), parsed_ints.next()) { - (Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)), - _ => None, - } -} diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow.rs index 040fda75a508..632f619655b3 100644 --- a/library/std/src/sys/pal/unix/stack_overflow.rs +++ b/library/std/src/sys/pal/unix/stack_overflow.rs @@ -70,7 +70,7 @@ mod imp { use super::thread_info::{delete_current_info, set_current_info, with_current_info}; use crate::ops::Range; use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; - use crate::sys::pal::unix::os; + use crate::sys::pal::unix::conf; use crate::{io, mem, ptr}; // Signal handler for the SIGSEGV and SIGBUS handlers. We've got guard pages @@ -150,7 +150,7 @@ mod imp { /// Must be called only once #[forbid(unsafe_op_in_unsafe_fn)] pub unsafe fn init() { - PAGE_SIZE.store(os::page_size(), Ordering::Relaxed); + PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); let mut guard_page_range = unsafe { install_main_guard() }; diff --git a/library/std/src/sys/pal/wasi/conf.rs b/library/std/src/sys/pal/wasi/conf.rs new file mode 100644 index 000000000000..cf76ae733358 --- /dev/null +++ b/library/std/src/sys/pal/wasi/conf.rs @@ -0,0 +1,4 @@ +#[allow(dead_code)] +pub fn page_size() -> usize { + unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } +} diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasi/mod.rs index 9b49db9af6b0..efe4af6a0c62 100644 --- a/library/std/src/sys/pal/wasi/mod.rs +++ b/library/std/src/sys/pal/wasi/mod.rs @@ -4,6 +4,8 @@ //! OS level functionality for WASI. Currently this includes both WASIp1 and //! WASIp2. +pub mod conf; + #[allow(unused)] #[path = "../wasm/atomics/futex.rs"] pub mod futex; diff --git a/library/std/src/sys/pal/wasi/os.rs b/library/std/src/sys/pal/wasi/os.rs index c8f3ddf692bc..b0148b265e32 100644 --- a/library/std/src/sys/pal/wasi/os.rs +++ b/library/std/src/sys/pal/wasi/os.rs @@ -89,11 +89,6 @@ pub fn current_exe() -> io::Result { unsupported() } -#[allow(dead_code)] -pub fn page_size() -> usize { - unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } -} - pub fn temp_dir() -> PathBuf { panic!("no filesystem on wasm") } diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index 82ff94fb1e03..a68be2543bc2 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -537,7 +537,7 @@ fn pidfd_spawnp( // Only glibc 2.24+ posix_spawn() supports returning ENOENT directly. #[cfg(all(target_os = "linux", target_env = "gnu"))] { - if let Some(version) = sys::os::glibc_version() { + if let Some(version) = sys::pal::conf::glibc_version() { if version < (2, 24) { return Ok(None); } diff --git a/library/std/src/sys/thread/teeos.rs b/library/std/src/sys/thread/teeos.rs index 5e71f757eaa4..aa679e780c18 100644 --- a/library/std/src/sys/thread/teeos.rs +++ b/library/std/src/sys/thread/teeos.rs @@ -1,5 +1,5 @@ use crate::mem::{self, ManuallyDrop}; -use crate::sys::os; +use crate::sys::pal::conf; use crate::thread::ThreadInit; use crate::time::Duration; use crate::{cmp, io, ptr}; @@ -52,7 +52,7 @@ pub unsafe fn new(stack: usize, init: Box) -> io::Result { // multiple of the system page size. Because it's definitely // >= PTHREAD_STACK_MIN, it must be an alignment issue. // Round up to the nearest page and try again. - let page_size = os::page_size(); + let page_size = conf::page_size(); let stack_size = (stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1); assert_eq!(unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) }, 0); diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index 22f9bfef5a38..5d4eabc226ed 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -76,7 +76,7 @@ pub unsafe fn new(stack: usize, init: Box) -> io::Result { // multiple of the system page size. Because it's definitely // >= PTHREAD_STACK_MIN, it must be an alignment issue. // Round up to the nearest page and try again. - let page_size = sys::os::page_size(); + let page_size = sys::pal::conf::page_size(); let stack_size = (stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1); From 866975f8a88eea5e8d6d92541e65dcd33dd9c684 Mon Sep 17 00:00:00 2001 From: joboet Date: Mon, 2 Mar 2026 12:58:59 +0100 Subject: [PATCH 0127/1059] std: move SOLID error converting out of `pal::os` --- library/std/src/sys/pal/solid/error.rs | 8 ++++++++ library/std/src/sys/pal/solid/os.rs | 10 +--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/library/std/src/sys/pal/solid/error.rs b/library/std/src/sys/pal/solid/error.rs index 3e85cdb3c1d9..a737abcc04ed 100644 --- a/library/std/src/sys/pal/solid/error.rs +++ b/library/std/src/sys/pal/solid/error.rs @@ -3,6 +3,14 @@ use crate::io; use crate::sys::net; +// SOLID directly maps `errno`s to μITRON error codes. +impl SolidError { + #[inline] + pub(crate) fn as_io_error(self) -> crate::io::Error { + crate::io::Error::from_raw_os_error(self.as_raw()) + } +} + /// Describe the specified SOLID error code. Returns `None` if it's an /// undefined error code. /// diff --git a/library/std/src/sys/pal/solid/os.rs b/library/std/src/sys/pal/solid/os.rs index 4a07d240d2e6..79bb91b17996 100644 --- a/library/std/src/sys/pal/solid/os.rs +++ b/library/std/src/sys/pal/solid/os.rs @@ -1,16 +1,8 @@ -use super::{itron, unsupported}; +use super::unsupported; use crate::ffi::{OsStr, OsString}; use crate::path::{self, PathBuf}; use crate::{fmt, io}; -// `solid` directly maps `errno`s to μITRON error codes. -impl itron::error::ItronError { - #[inline] - pub(crate) fn as_io_error(self) -> crate::io::Error { - crate::io::Error::from_raw_os_error(self.as_raw()) - } -} - pub fn getcwd() -> io::Result { unsupported() } From d31aecff6a3a7a5272e8b07a72f8cebc8ec1f517 Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 4 Mar 2026 16:56:29 +0100 Subject: [PATCH 0128/1059] std: reorganize some WASI helpers --- library/std/src/sys/env/wasi.rs | 11 ++-- library/std/src/sys/pal/wasi/helpers.rs | 11 ---- library/std/src/sys/pal/wasi/mod.rs | 72 +++++++++++++++++-------- library/std/src/sys/pal/wasi/os.rs | 43 --------------- 4 files changed, 58 insertions(+), 79 deletions(-) delete mode 100644 library/std/src/sys/pal/wasi/helpers.rs diff --git a/library/std/src/sys/env/wasi.rs b/library/std/src/sys/env/wasi.rs index c970aac18260..6b892376fd0c 100644 --- a/library/std/src/sys/env/wasi.rs +++ b/library/std/src/sys/env/wasi.rs @@ -1,11 +1,16 @@ use core::slice::memchr; pub use super::common::Env; -use crate::ffi::{CStr, OsStr, OsString}; +use crate::ffi::{CStr, OsStr, OsString, c_char}; use crate::io; use crate::os::wasi::prelude::*; use crate::sys::helpers::run_with_cstr; -use crate::sys::pal::os::{cvt, libc}; +use crate::sys::pal::cvt; + +// This is not available yet in libc. +unsafe extern "C" { + fn __wasilibc_get_environ() -> *mut *mut c_char; +} cfg_select! { target_feature = "atomics" => { @@ -37,7 +42,7 @@ pub fn env() -> Env { // Use `__wasilibc_get_environ` instead of `environ` here so that we // don't require wasi-libc to eagerly initialize the environment // variables. - let mut environ = libc::__wasilibc_get_environ(); + let mut environ = __wasilibc_get_environ(); let mut result = Vec::new(); if !environ.is_null() { diff --git a/library/std/src/sys/pal/wasi/helpers.rs b/library/std/src/sys/pal/wasi/helpers.rs deleted file mode 100644 index 4f2eb1148f0b..000000000000 --- a/library/std/src/sys/pal/wasi/helpers.rs +++ /dev/null @@ -1,11 +0,0 @@ -#![forbid(unsafe_op_in_unsafe_fn)] - -pub fn abort_internal() -> ! { - unsafe { libc::abort() } -} - -#[inline] -#[cfg(target_env = "p1")] -pub(crate) fn err2io(err: wasi::Errno) -> crate::io::Error { - crate::io::Error::from_raw_os_error(err.raw().into()) -} diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasi/mod.rs index efe4af6a0c62..66d91078a5d5 100644 --- a/library/std/src/sys/pal/wasi/mod.rs +++ b/library/std/src/sys/pal/wasi/mod.rs @@ -4,36 +4,64 @@ //! OS level functionality for WASI. Currently this includes both WASIp1 and //! WASIp2. -pub mod conf; +use crate::io; +pub mod conf; #[allow(unused)] #[path = "../wasm/atomics/futex.rs"] pub mod futex; - pub mod os; pub mod stack_overflow; #[path = "../unix/time.rs"] pub mod time; -#[path = "../unsupported/common.rs"] -#[deny(unsafe_op_in_unsafe_fn)] -#[allow(unused)] -mod common; - -pub use common::*; - -mod helpers; - -// The following exports are listed individually to work around Rust's glob -// import conflict rules. If we glob export `helpers` and `common` together, -// then the compiler complains about conflicts. - -pub(crate) use helpers::abort_internal; -#[cfg(target_env = "p1")] -pub(crate) use helpers::err2io; -#[cfg(not(target_env = "p1"))] -pub use os::IsMinusOne; -pub use os::{cvt, cvt_r}; - #[cfg(not(target_env = "p1"))] mod cabi_realloc; + +#[path = "../unsupported/common.rs"] +#[deny(unsafe_op_in_unsafe_fn)] +#[expect(dead_code)] +mod common; +pub use common::{cleanup, init, unsupported}; + +pub fn abort_internal() -> ! { + unsafe { libc::abort() } +} + +#[inline] +#[cfg(target_env = "p1")] +pub(crate) fn err2io(err: wasi::Errno) -> crate::io::Error { + crate::io::Error::from_raw_os_error(err.raw().into()) +} + +#[doc(hidden)] +pub trait IsMinusOne { + fn is_minus_one(&self) -> bool; +} + +macro_rules! impl_is_minus_one { + ($($t:ident)*) => ($(impl IsMinusOne for $t { + fn is_minus_one(&self) -> bool { + *self == -1 + } + })*) +} + +impl_is_minus_one! { i8 i16 i32 i64 isize } + +pub fn cvt(t: T) -> io::Result { + if t.is_minus_one() { Err(io::Error::last_os_error()) } else { Ok(t) } +} + +pub fn cvt_r(mut f: F) -> io::Result +where + T: IsMinusOne, + F: FnMut() -> T, +{ + loop { + match cvt(f()) { + Err(ref e) if e.is_interrupted() => {} + other => return other, + } + } +} diff --git a/library/std/src/sys/pal/wasi/os.rs b/library/std/src/sys/pal/wasi/os.rs index b0148b265e32..b1c26acc68cb 100644 --- a/library/std/src/sys/pal/wasi/os.rs +++ b/library/std/src/sys/pal/wasi/os.rs @@ -8,17 +8,6 @@ use crate::sys::unsupported; use crate::{fmt, io}; -// Add a few symbols not in upstream `libc` just yet. -pub mod libc { - pub use libc::*; - - unsafe extern "C" { - pub fn getcwd(buf: *mut c_char, size: size_t) -> *mut c_char; - pub fn chdir(dir: *const c_char) -> c_int; - pub fn __wasilibc_get_environ() -> *mut *mut c_char; - } -} - pub fn getcwd() -> io::Result { let mut buf = Vec::with_capacity(512); loop { @@ -96,35 +85,3 @@ pub fn temp_dir() -> PathBuf { pub fn home_dir() -> Option { None } - -#[doc(hidden)] -pub trait IsMinusOne { - fn is_minus_one(&self) -> bool; -} - -macro_rules! impl_is_minus_one { - ($($t:ident)*) => ($(impl IsMinusOne for $t { - fn is_minus_one(&self) -> bool { - *self == -1 - } - })*) -} - -impl_is_minus_one! { i8 i16 i32 i64 isize } - -pub fn cvt(t: T) -> io::Result { - if t.is_minus_one() { Err(io::Error::last_os_error()) } else { Ok(t) } -} - -pub fn cvt_r(mut f: F) -> io::Result -where - T: IsMinusOne, - F: FnMut() -> T, -{ - loop { - match cvt(f()) { - Err(ref e) if e.is_interrupted() => {} - other => return other, - } - } -} From 1f94e979fb9b09f4df5483ce549fb96574be1e6e Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 22 Oct 2025 14:28:05 +1100 Subject: [PATCH 0129/1059] Fix some comments about dataflow analysis. Mostly in the examples in `initialized.rs`. In particular, the `EverInitializedPlaces` example currently doesn't cover how it's initialization sites that are tracked, rather than local variables (that's the `b_0`/`b_1` distinction in the example.) --- compiler/rustc_middle/src/mir/basic_blocks.rs | 2 +- .../src/impls/initialized.rs | 54 ++++++++++--------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_middle/src/mir/basic_blocks.rs b/compiler/rustc_middle/src/mir/basic_blocks.rs index aae815c0be04..bd8c022ef42a 100644 --- a/compiler/rustc_middle/src/mir/basic_blocks.rs +++ b/compiler/rustc_middle/src/mir/basic_blocks.rs @@ -65,7 +65,7 @@ pub fn predecessors(&self) -> &Predecessors { /// Returns basic blocks in a reverse postorder. /// - /// See [`traversal::reverse_postorder`]'s docs to learn what is preorder traversal. + /// See [`traversal::reverse_postorder`]'s docs to learn what is postorder traversal. /// /// [`traversal::reverse_postorder`]: crate::mir::traversal::reverse_postorder #[inline] diff --git a/compiler/rustc_mir_dataflow/src/impls/initialized.rs b/compiler/rustc_mir_dataflow/src/impls/initialized.rs index 6a0881ec2bcb..f83ea0c8be61 100644 --- a/compiler/rustc_mir_dataflow/src/impls/initialized.rs +++ b/compiler/rustc_mir_dataflow/src/impls/initialized.rs @@ -108,21 +108,21 @@ fn new( /// ```rust /// struct S; /// #[rustfmt::skip] -/// fn foo(pred: bool) { // maybe-init: -/// // {} -/// let a = S; let mut b = S; let c; let d; // {a, b} +/// fn foo(p: bool) { // maybe-init: +/// // {p} +/// let a = S; let mut b = S; let c; let d; // {p, a, b} /// -/// if pred { -/// drop(a); // { b} -/// b = S; // { b} +/// if p { +/// drop(a); // {p, b} +/// b = S; // {p, b} /// /// } else { -/// drop(b); // {a} -/// d = S; // {a, d} +/// drop(b); // {p, a} +/// d = S; // {p, a, d} /// -/// } // {a, b, d} +/// } // {p, a, b, d} /// -/// c = S; // {a, b, c, d} +/// c = S; // {p, a, b, c, d} /// } /// ``` /// @@ -198,11 +198,11 @@ fn move_data(&self) -> &MoveData<'tcx> { /// ```rust /// struct S; /// #[rustfmt::skip] -/// fn foo(pred: bool) { // maybe-uninit: +/// fn foo(p: bool) { // maybe-uninit: /// // {a, b, c, d} /// let a = S; let mut b = S; let c; let d; // { c, d} /// -/// if pred { +/// if p { /// drop(a); // {a, c, d} /// b = S; // {a, c, d} /// @@ -278,34 +278,36 @@ fn move_data(&self) -> &MoveData<'tcx> { } } -/// `EverInitializedPlaces` tracks all places that might have ever been -/// initialized upon reaching a particular point in the control flow -/// for a function, without an intervening `StorageDead`. +/// `EverInitializedPlaces` tracks all initializations that may have occurred +/// upon reaching a particular point in the control flow for a function, +/// without an intervening `StorageDead`. /// /// This dataflow is used to determine if an immutable local variable may /// be assigned to. /// /// For example, in code like the following, we have corresponding -/// dataflow information shown in the right-hand comments. +/// dataflow information shown in the right-hand comments. Underscored indices +/// are used to distinguish between multiple initializations of the same local +/// variable, e.g. `b_0` and `b_1`. /// /// ```rust /// struct S; /// #[rustfmt::skip] -/// fn foo(pred: bool) { // ever-init: -/// // { } -/// let a = S; let mut b = S; let c; let d; // {a, b } +/// fn foo(p: bool) { // ever-init: +/// // {p, } +/// let a = S; let mut b = S; let c; let d; // {p, a, b_0, } /// -/// if pred { -/// drop(a); // {a, b, } -/// b = S; // {a, b, } +/// if p { +/// drop(a); // {p, a, b_0, } +/// b = S; // {p, a, b_0, b_1, } /// /// } else { -/// drop(b); // {a, b, } -/// d = S; // {a, b, d } +/// drop(b); // {p, a, b_0, b_1, } +/// d = S; // {p, a, b_0, b_1, d} /// -/// } // {a, b, d } +/// } // {p, a, b_0, b_1, d} /// -/// c = S; // {a, b, c, d } +/// c = S; // {p, a, b_0, b_1, c, d} /// } /// ``` pub struct EverInitializedPlaces<'a, 'tcx> { From c9bfe10af5e0eed3d10be76782ed94dd6c35b6f7 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Thu, 5 Mar 2026 05:15:12 +0000 Subject: [PATCH 0130/1059] Prepare for merging from rust-lang/rust This updates the rust-version file to f8704be04fe1150527fc2cf21dd44327f0fe87fb. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 90a48de0fcb0..7c89bcb9ab04 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -d933cf483edf1605142ac6899ff32536c0ad8b22 +f8704be04fe1150527fc2cf21dd44327f0fe87fb From fcddf7fa3cd4c9cfa8afa9f1aba830cea87971b0 Mon Sep 17 00:00:00 2001 From: Ada Bohm Date: Thu, 5 Mar 2026 10:08:57 +0100 Subject: [PATCH 0131/1059] Propagate certainty --- .../src/solve/assembly/mod.rs | 37 +++++++++++++------ .../find-param-recursion-issue-152716.rs | 2 +- .../find-param-recursion-issue-152716.stderr | 6 ++- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index bf59f7cab8e4..bb0f16e153da 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -9,6 +9,7 @@ use rustc_type_ir::inherent::*; use rustc_type_ir::lang_items::SolverTraitLangItem; use rustc_type_ir::search_graph::CandidateHeadUsages; +use rustc_type_ir::solve::Certainty::Maybe; use rustc_type_ir::solve::{AliasBoundKind, SizedTraitKind}; use rustc_type_ir::{ self as ty, Interner, TypeFlags, TypeFoldable, TypeFolder, TypeSuperFoldable, @@ -138,8 +139,10 @@ fn probe_and_consider_param_env_candidate( .enter_single_candidate(|ecx| { Self::match_assumption(ecx, goal, assumption, |ecx| { ecx.try_evaluate_added_goals()?; - source.set(ecx.characterize_param_env_assumption(goal.param_env, assumption)?); - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + let (src, certainty) = + ecx.characterize_param_env_assumption(goal.param_env, assumption)?; + source.set(src); + ecx.evaluate_added_goals_and_make_canonical_response(certainty) }) }); @@ -1222,11 +1225,11 @@ fn characterize_param_env_assumption( &mut self, param_env: I::ParamEnv, assumption: I::Clause, - ) -> Result, NoSolution> { + ) -> Result<(CandidateSource, Certainty), NoSolution> { // FIXME: This should be fixed, but it also requires changing the behavior // in the old solver which is currently relied on. if assumption.has_bound_vars() { - return Ok(CandidateSource::ParamEnv(ParamEnvSource::NonGlobal)); + return Ok((CandidateSource::ParamEnv(ParamEnvSource::NonGlobal), Certainty::Yes)); } match assumption.visit_with(&mut FindParamInClause { @@ -1236,8 +1239,12 @@ fn characterize_param_env_assumption( recursion_depth: 0, }) { ControlFlow::Break(Err(NoSolution)) => Err(NoSolution), - ControlFlow::Break(Ok(())) => Ok(CandidateSource::ParamEnv(ParamEnvSource::NonGlobal)), - ControlFlow::Continue(()) => Ok(CandidateSource::ParamEnv(ParamEnvSource::Global)), + ControlFlow::Break(Ok(certainty)) => { + Ok((CandidateSource::ParamEnv(ParamEnvSource::NonGlobal), certainty)) + } + ControlFlow::Continue(()) => { + Ok((CandidateSource::ParamEnv(ParamEnvSource::Global), Certainty::Yes)) + } } } } @@ -1254,7 +1261,7 @@ impl TypeVisitor for FindParamInClause<'_, '_, D, I> D: SolverDelegate, I: Interner, { - type Result = ControlFlow>; + type Result = ControlFlow>; fn visit_binder>(&mut self, t: &ty::Binder) -> Self::Result { self.universes.push(None); @@ -1271,14 +1278,20 @@ fn visit_ty(&mut self, ty: I::Ty) -> Self::Result { if let ty::Placeholder(p) = ty.kind() { if p.universe() == ty::UniverseIndex::ROOT { - ControlFlow::Break(Ok(())) + ControlFlow::Break(Ok(Certainty::Yes)) } else { ControlFlow::Continue(()) } } else if ty.has_type_flags(TypeFlags::HAS_PLACEHOLDER | TypeFlags::HAS_RE_INFER) { self.recursion_depth += 1; if self.recursion_depth > self.ecx.cx().recursion_limit() { - return ControlFlow::Break(Err(NoSolution)); + return ControlFlow::Break(Ok(Maybe { + cause: MaybeCause::Overflow { + suggest_increasing_limit: true, + keep_constraints: false, + }, + opaque_types_jank: OpaqueTypesJank::AllGood, + })); } let result = ty.super_visit_with(self); self.recursion_depth -= 1; @@ -1296,7 +1309,7 @@ fn visit_const(&mut self, ct: I::Const) -> Self::Result { if let ty::ConstKind::Placeholder(p) = ct.kind() { if p.universe() == ty::UniverseIndex::ROOT { - ControlFlow::Break(Ok(())) + ControlFlow::Break(Ok(Certainty::Yes)) } else { ControlFlow::Continue(()) } @@ -1312,12 +1325,12 @@ fn visit_region(&mut self, r: I::Region) -> Self::Result { ty::ReStatic | ty::ReError(_) | ty::ReBound(..) => ControlFlow::Continue(()), ty::RePlaceholder(p) => { if p.universe() == ty::UniverseIndex::ROOT { - ControlFlow::Break(Ok(())) + ControlFlow::Break(Ok(Certainty::Yes)) } else { ControlFlow::Continue(()) } } - ty::ReVar(_) => ControlFlow::Break(Ok(())), + ty::ReVar(_) => ControlFlow::Break(Ok(Certainty::Yes)), ty::ReErased | ty::ReEarlyParam(_) | ty::ReLateParam(_) => { unreachable!("unexpected region in param-env clause") } diff --git a/tests/ui/traits/next-solver/find-param-recursion-issue-152716.rs b/tests/ui/traits/next-solver/find-param-recursion-issue-152716.rs index 410781e955d7..e9635f709a99 100644 --- a/tests/ui/traits/next-solver/find-param-recursion-issue-152716.rs +++ b/tests/ui/traits/next-solver/find-param-recursion-issue-152716.rs @@ -1,5 +1,4 @@ //@ compile-flags: -Znext-solver -//~^ ERROR overflow normalizing the associated type `>::Assoc` [E0275] // Regression test for . @@ -11,6 +10,7 @@ fn foo() where T: for<'a> Proj<'a, Assoc = for<'b> fn(>::Assoc)>, (): Trait<>::Assoc> + //~^ ERROR overflow evaluating the requirement `(): Trait<>::Assoc>` [E0275] { } diff --git a/tests/ui/traits/next-solver/find-param-recursion-issue-152716.stderr b/tests/ui/traits/next-solver/find-param-recursion-issue-152716.stderr index 9886e303eaf7..3aca6cfc9c5a 100644 --- a/tests/ui/traits/next-solver/find-param-recursion-issue-152716.stderr +++ b/tests/ui/traits/next-solver/find-param-recursion-issue-152716.stderr @@ -1,4 +1,8 @@ -error[E0275]: overflow normalizing the associated type `>::Assoc` +error[E0275]: overflow evaluating the requirement `(): Trait<>::Assoc>` + --> $DIR/find-param-recursion-issue-152716.rs:12:9 + | +LL | (): Trait<>::Assoc> + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`find_param_recursion_issue_152716`) From 61304807aaa73ea9de1e920e7c3571f5ea58cb2c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 5 Mar 2026 08:33:24 +0100 Subject: [PATCH 0132/1059] remove unused features --- src/tools/miri/src/alloc/mod.rs | 4 ++-- src/tools/miri/src/alloc_addresses/mod.rs | 4 ++-- src/tools/miri/src/bin/miri.rs | 4 ++-- src/tools/miri/src/lib.rs | 18 +++++++++++------- src/tools/miri/src/machine.rs | 14 +++++++------- src/tools/miri/src/shims/foreign_items.rs | 2 +- src/tools/miri/src/shims/mod.rs | 4 ++-- 7 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/tools/miri/src/alloc/mod.rs b/src/tools/miri/src/alloc/mod.rs index 35158f50a8ff..bc306b5e8738 100644 --- a/src/tools/miri/src/alloc/mod.rs +++ b/src/tools/miri/src/alloc/mod.rs @@ -1,7 +1,7 @@ mod alloc_bytes; -#[cfg(all(unix, feature = "native-lib"))] +#[cfg(all(feature = "native-lib", unix))] pub mod isolated_alloc; -#[cfg(not(all(unix, feature = "native-lib")))] +#[cfg(not(all(feature = "native-lib", unix)))] pub mod isolated_alloc { use std::alloc::Layout; diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index 91559e76e68c..15ad967657de 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -163,7 +163,7 @@ fn addr_from_alloc_id_uncached( this.get_alloc_bytes_unchecked_raw(alloc_id)? } } - #[cfg(all(unix, feature = "native-lib"))] + #[cfg(all(feature = "native-lib", unix))] AllocKind::Function => { if let Some(GlobalAlloc::Function { instance, .. }) = this.tcx.try_get_global_alloc(alloc_id) @@ -186,7 +186,7 @@ fn addr_from_alloc_id_uncached( dummy_alloc(params) } } - #[cfg(not(all(unix, feature = "native-lib")))] + #[cfg(not(all(feature = "native-lib", unix)))] AllocKind::Function => dummy_alloc(params), AllocKind::VTable | AllocKind::VaList => dummy_alloc(params), AllocKind::TypeId | AllocKind::Dead => unreachable!(), diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 3766debb159d..0ec1464f9a43 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -382,7 +382,7 @@ fn exit(exit_code: i32) -> ! { // Drop the tracing guard before exiting, so tracing calls are flushed correctly. deinit_loggers(); // Make sure the supervisor knows about the exit code. - #[cfg(all(unix, feature = "native-lib"))] + #[cfg(all(feature = "native-lib", unix))] miri::native_lib::register_retcode_sv(exit_code); // Actually exit. std::process::exit(exit_code); @@ -756,7 +756,7 @@ fn main() -> ExitCode { debug!("crate arguments: {:?}", miri_config.args); if !miri_config.native_lib.is_empty() && miri_config.native_lib_enable_tracing { // SAFETY: No other threads are running - #[cfg(all(unix, feature = "native-lib"))] + #[cfg(all(feature = "native-lib", unix))] if unsafe { miri::native_lib::init_sv() }.is_err() { eprintln!( "warning: The native-lib tracer could not be started. Is this an x86 Linux system, and does Miri have permissions to ptrace?\n\ diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 155140165458..497c937e0b18 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -1,6 +1,15 @@ #![cfg_attr(bootstrap, feature(if_let_guard))] #![cfg_attr(bootstrap, feature(cfg_select))] -#![feature(abort_unwind)] +#![cfg_attr( + all( + feature = "native-lib", + target_os = "linux", + target_env = "gnu", + any(target_arch = "x86", target_arch = "x86_64") + ), + feature(abort_unwind) +)] +#![cfg_attr(all(feature = "native-lib", unix), feature(iter_advance_by))] #![feature(rustc_private)] #![feature(float_gamma)] #![feature(float_erf)] @@ -10,13 +19,10 @@ #![feature(io_error_more)] #![feature(variant_count)] #![feature(yeet_expr)] -#![feature(nonzero_ops)] #![feature(pointer_is_aligned_to)] -#![feature(ptr_metadata)] #![feature(unqualified_local_imports)] #![feature(derive_coerce_pointee)] #![feature(arbitrary_self_types)] -#![feature(iter_advance_by)] #![feature(macro_metavar_expr)] // Configure clippy and other lints #![allow( @@ -43,8 +49,6 @@ clippy::collapsible_match, // We are not implementing queries here so it's fine rustc::potential_query_instability, - // FIXME: Unused features should be removed in the future - unused_features, )] #![warn( rust_2018_idioms, @@ -103,7 +107,7 @@ use rustc_log::tracing::{self, info, trace}; use rustc_middle::{bug, span_bug}; -#[cfg(all(unix, feature = "native-lib"))] +#[cfg(all(feature = "native-lib", unix))] pub mod native_lib { pub use crate::shims::{init_sv, register_retcode_sv}; } diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 000f4ac4ceca..ab38828c2108 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -596,12 +596,12 @@ pub struct MiriMachine<'tcx> { pub(crate) basic_block_count: u64, /// Handle of the optional shared object file for native functions. - #[cfg(all(unix, feature = "native-lib"))] + #[cfg(all(feature = "native-lib", unix))] pub native_lib: Vec<(libloading::Library, std::path::PathBuf)>, - #[cfg(not(all(unix, feature = "native-lib")))] + #[cfg(not(all(feature = "native-lib", unix)))] pub native_lib: Vec, /// A memory location for exchanging the current `ecx` pointer with native code. - #[cfg(all(unix, feature = "native-lib"))] + #[cfg(all(feature = "native-lib", unix))] pub native_lib_ecx_interchange: &'static Cell, /// Run a garbage collector for BorTags every N basic blocks. @@ -772,7 +772,7 @@ pub(crate) fn new( report_progress: config.report_progress, basic_block_count: 0, monotonic_clock: MonotonicClock::new(config.isolated_op == IsolatedOp::Allow), - #[cfg(all(unix, feature = "native-lib"))] + #[cfg(all(feature = "native-lib", unix))] native_lib: config.native_lib.iter().map(|lib_file_path| { let host_triple = rustc_session::config::host_tuple(); let target_triple = tcx.sess.opts.target_triple.tuple(); @@ -794,9 +794,9 @@ pub(crate) fn new( lib_file_path.clone(), ) }).collect(), - #[cfg(all(unix, feature = "native-lib"))] + #[cfg(all(feature = "native-lib", unix))] native_lib_ecx_interchange: Box::leak(Box::new(Cell::new(0))), - #[cfg(not(all(unix, feature = "native-lib")))] + #[cfg(not(all(feature = "native-lib", unix)))] native_lib: config.native_lib.iter().map(|_| { panic!("calling functions from native libraries via FFI is not supported in this build of Miri") }).collect(), @@ -1032,7 +1032,7 @@ fn visit_provenance(&self, visit: &mut VisitWith<'_>) { report_progress: _, basic_block_count: _, native_lib: _, - #[cfg(all(unix, feature = "native-lib"))] + #[cfg(all(feature = "native-lib", unix))] native_lib_ecx_interchange: _, gc_interval: _, since_gc: _, diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 12a170e6a849..9669fe0e44e3 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -255,7 +255,7 @@ fn emulate_foreign_item_inner( let this = self.eval_context_mut(); // First deal with any external C functions in linked .so file. - #[cfg(all(unix, feature = "native-lib"))] + #[cfg(all(feature = "native-lib", unix))] if !this.machine.native_lib.is_empty() { use crate::shims::native_lib::EvalContextExt as _; // An Ok(false) here means that the function being called was not exported diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index 345e16b8da71..281dfc758f4e 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -5,7 +5,7 @@ mod backtrace; mod files; mod math; -#[cfg(all(unix, feature = "native-lib"))] +#[cfg(all(feature = "native-lib", unix))] pub mod native_lib; mod unix; mod windows; @@ -24,7 +24,7 @@ pub mod unwind; pub use self::files::FdTable; -#[cfg(all(unix, feature = "native-lib"))] +#[cfg(all(feature = "native-lib", unix))] pub use self::native_lib::trace::{init_sv, register_retcode_sv}; pub use self::unix::{DirTable, EpollInterestTable}; From 87880340985aac06989f3acc94365ddd83127f5c Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 5 Mar 2026 18:16:07 +0800 Subject: [PATCH 0133/1059] feat: complete block .let in closure expression Example --- ```rust fn main() { let bar = 2; let f = || bar.$0; } ``` **Before this PR** Cannot complete `.let` **After this PR** ```rust fn main() { let bar = 2; let f = || { let $1 = bar; $0 }; } ``` --- .../ide-completion/src/completions/postfix.rs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index ea53aef40c2e..4cf5c85d6fa3 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -16,7 +16,7 @@ use stdx::never; use syntax::{ SmolStr, - SyntaxKind::{EXPR_STMT, STMT_LIST}, + SyntaxKind::{CLOSURE_EXPR, EXPR_STMT, MATCH_ARM, STMT_LIST}, T, TextRange, TextSize, ToSmolStr, ast::{self, AstNode, AstToken}, format_smolstr, match_ast, @@ -156,7 +156,7 @@ pub(crate) fn complete_postfix( postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};")) .add_to(acc, ctx.db); } - _ if ast::MatchArm::can_cast(parent.kind()) => { + _ if matches!(parent.kind(), MATCH_ARM | CLOSURE_EXPR) => { postfix_snippet( "let", "let", @@ -965,6 +965,28 @@ fn main() { ); } + #[test] + fn closure_let_block() { + check_edit( + "let", + r#" +fn main() { + let bar = 2; + let f = || bar.$0; +} +"#, + r#" +fn main() { + let bar = 2; + let f = || { + let $1 = bar; + $0 +}; +} +"#, + ); + } + #[test] fn option_letelse() { check_edit( From 5701d13840031ed3061281e43794f631f41a44a6 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 19 Feb 2026 18:42:24 +0100 Subject: [PATCH 0134/1059] refactor `PointeeInfo` Make `size`/`align` always correct rather than conditionally on the `safe` field. This makes it less error prone and easier to work with for `MaybeDangling` / potential future pointer kinds like `Aligned<_>`. --- src/base.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/base.rs b/src/base.rs index e4865ece63b6..4f483cdc5d6c 100644 --- a/src/base.rs +++ b/src/base.rs @@ -10,7 +10,7 @@ use rustc_index::IndexVec; use rustc_middle::ty::TypeVisitableExt; use rustc_middle::ty::adjustment::PointerCoercion; -use rustc_middle::ty::layout::FnAbiOf; +use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv as _}; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_session::config::OutputFilenames; use rustc_span::Symbol; @@ -924,19 +924,26 @@ fn is_wide_ptr<'tcx>(fx: &FunctionCx<'_, '_, 'tcx>, ty: Ty<'tcx>) -> bool { count, }) => { let dst = codegen_operand(fx, dst); - let pointee = dst - .layout() - .pointee_info_at(fx, rustc_abi::Size::ZERO) - .expect("Expected pointer"); + + let &ty::RawPtr(pointee, _) = dst.layout().ty.kind() else { + bug!("expected pointer") + }; + let pointee_layout = fx + .tcx + .layout_of(fx.typing_env().as_query_input(pointee)) + .expect("expected pointee to have a layout"); + let elem_size: u64 = pointee_layout.layout.size().bytes(); + let dst = dst.load_scalar(fx); let src = codegen_operand(fx, src).load_scalar(fx); let count = codegen_operand(fx, count).load_scalar(fx); - let elem_size: u64 = pointee.size.bytes(); + let bytes = if elem_size != 1 { fx.bcx.ins().imul_imm(count, elem_size as i64) } else { count }; + fx.bcx.call_memcpy(fx.target_config, dst, src, bytes); } }, From b8403383ff2d9a544a04d981482cb89c138db0b7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 4 Mar 2026 16:14:57 +0100 Subject: [PATCH 0135/1059] miri: make read_discriminant UB when the tag is not in the validity range of the tag field --- .../src/interpret/discriminant.rs | 28 +++++++++++++++---- .../enum-untagged-variant-invalid-encoding.rs | 4 +-- ...m-untagged-variant-invalid-encoding.stderr | 4 +-- .../invalid_enum_op_discr_uninhabited.rs | 27 ++++++++++++++++++ .../invalid_enum_op_discr_uninhabited.stderr | 13 +++++++++ .../invalid_enum_op_niche_out_of_range.rs | 14 ++++++++++ .../invalid_enum_op_niche_out_of_range.stderr | 13 +++++++++ .../consts/const-eval/raw-bytes.32bit.stderr | 2 +- .../consts/const-eval/raw-bytes.64bit.stderr | 2 +- tests/ui/consts/const-eval/ub-enum.rs | 2 +- tests/ui/consts/const-eval/ub-enum.stderr | 2 +- 11 files changed, 97 insertions(+), 14 deletions(-) create mode 100644 src/tools/miri/tests/fail/validity/invalid_enum_op_discr_uninhabited.rs create mode 100644 src/tools/miri/tests/fail/validity/invalid_enum_op_discr_uninhabited.stderr create mode 100644 src/tools/miri/tests/fail/validity/invalid_enum_op_niche_out_of_range.rs create mode 100644 src/tools/miri/tests/fail/validity/invalid_enum_op_niche_out_of_range.stderr diff --git a/compiler/rustc_const_eval/src/interpret/discriminant.rs b/compiler/rustc_const_eval/src/interpret/discriminant.rs index b7e7f65c95c7..3a7c1ea65f3d 100644 --- a/compiler/rustc_const_eval/src/interpret/discriminant.rs +++ b/compiler/rustc_const_eval/src/interpret/discriminant.rs @@ -111,23 +111,30 @@ pub fn read_discriminant( .try_to_scalar_int() .map_err(|dbg_val| err_ub!(InvalidTag(dbg_val)))? .to_bits(tag_layout.size); + // Ensure the tag is in its layout range. Codegen adds range metadata on the + // discriminant load so we really have to make this UB. + if !tag_scalar_layout.valid_range(self).contains(tag_bits) { + throw_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size))) + } // Cast bits from tag layout to discriminant layout. // After the checks we did above, this cannot fail, as // discriminants are int-like. let discr_val = self.int_to_int_or_float(&tag_val, discr_layout).unwrap(); let discr_bits = discr_val.to_scalar().to_bits(discr_layout.size)?; - // Convert discriminant to variant index, and catch invalid discriminants. + // Convert discriminant to variant index. Since we validated the tag against the + // layout range above, this cannot fail. let index = match *ty.kind() { ty::Adt(adt, _) => { - adt.discriminants(*self.tcx).find(|(_, var)| var.val == discr_bits) + adt.discriminants(*self.tcx).find(|(_, var)| var.val == discr_bits).unwrap() } ty::Coroutine(def_id, args) => { let args = args.as_coroutine(); - args.discriminants(def_id, *self.tcx).find(|(_, var)| var.val == discr_bits) + args.discriminants(def_id, *self.tcx) + .find(|(_, var)| var.val == discr_bits) + .unwrap() } _ => span_bug!(self.cur_span(), "tagged layout for non-adt non-coroutine"), - } - .ok_or_else(|| err_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size))))?; + }; // Return the cast value, and the index. index.0 } @@ -174,13 +181,22 @@ pub fn read_discriminant( let variants = ty.ty_adt_def().expect("tagged layout for non adt").variants(); assert!(variant_index < variants.next_index()); + // This should imply that the tag is in its layout range. + assert!(tag_scalar_layout.valid_range(self).contains(tag_bits)); + if variant_index == untagged_variant { // The untagged variant can be in the niche range, but even then it - // is not a valid encoding. + // is not a valid encoding. Codegen inserts an `assume` here + // so we really have to make this UB. throw_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size))) } variant_index } else { + // Ensure the tag is in its layout range. Codegen adds range metadata on + // the discriminant load so we really have to make this UB. + if !tag_scalar_layout.valid_range(self).contains(tag_bits) { + throw_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size))) + } untagged_variant } } diff --git a/src/tools/miri/tests/fail/enum-untagged-variant-invalid-encoding.rs b/src/tools/miri/tests/fail/enum-untagged-variant-invalid-encoding.rs index bd02e7f5fb44..2bea47e32dd9 100644 --- a/src/tools/miri/tests/fail/enum-untagged-variant-invalid-encoding.rs +++ b/src/tools/miri/tests/fail/enum-untagged-variant-invalid-encoding.rs @@ -20,8 +20,8 @@ fn main() { assert!(Foo::Var1 == mem::transmute(2u8)); assert!(Foo::Var3 == mem::transmute(4u8)); - let invalid: Foo = mem::transmute(3u8); - assert!(matches!(invalid, Foo::Var2(_))); + let invalid: *const Foo = mem::transmute(&3u8); + assert!(matches!(*invalid, Foo::Var2(_))); //~^ ERROR: invalid tag } } diff --git a/src/tools/miri/tests/fail/enum-untagged-variant-invalid-encoding.stderr b/src/tools/miri/tests/fail/enum-untagged-variant-invalid-encoding.stderr index e019a350ba17..b0862cf94b9a 100644 --- a/src/tools/miri/tests/fail/enum-untagged-variant-invalid-encoding.stderr +++ b/src/tools/miri/tests/fail/enum-untagged-variant-invalid-encoding.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: enum value has invalid tag: 0x03 --> tests/fail/enum-untagged-variant-invalid-encoding.rs:LL:CC | -LL | assert!(matches!(invalid, Foo::Var2(_))); - | ^^^^^^^ Undefined Behavior occurred here +LL | assert!(matches!(*invalid, Foo::Var2(_))); + | ^^^^^^^^ Undefined Behavior occurred here | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/validity/invalid_enum_op_discr_uninhabited.rs b/src/tools/miri/tests/fail/validity/invalid_enum_op_discr_uninhabited.rs new file mode 100644 index 000000000000..3d1ae64b5782 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_enum_op_discr_uninhabited.rs @@ -0,0 +1,27 @@ +// Make sure we find these even with many checks disabled. +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation +#![feature(never_type)] + +enum Never {} + +// An enum with 4 variants of which only some are uninhabited -- so the uninhabited variants *do* +// have a discriminant. +#[allow(unused)] +enum UninhDiscriminant { + A, + B(!), + C, + D(Never), +} + +fn main() { + unsafe { + let x = 3u8; + let x_ptr: *const u8 = &x; + let cast_ptr = x_ptr as *const UninhDiscriminant; + // Reading the discriminant should fail since the tag value is not in the valid + // range for the tag field. + let _val = matches!(*cast_ptr, UninhDiscriminant::A); + //~^ ERROR: invalid tag + } +} diff --git a/src/tools/miri/tests/fail/validity/invalid_enum_op_discr_uninhabited.stderr b/src/tools/miri/tests/fail/validity/invalid_enum_op_discr_uninhabited.stderr new file mode 100644 index 000000000000..2e2ad81a0451 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_enum_op_discr_uninhabited.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: enum value has invalid tag: 0x03 + --> tests/fail/validity/invalid_enum_op_discr_uninhabited.rs:LL:CC + | +LL | let _val = matches!(*cast_ptr, UninhDiscriminant::A); + | ^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/validity/invalid_enum_op_niche_out_of_range.rs b/src/tools/miri/tests/fail/validity/invalid_enum_op_niche_out_of_range.rs new file mode 100644 index 000000000000..51237c360f1c --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_enum_op_niche_out_of_range.rs @@ -0,0 +1,14 @@ +// Make sure we find these even with many checks disabled. +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation + +fn main() { + unsafe { + let x = 12u8; + let x_ptr: *const u8 = &x; + let cast_ptr = x_ptr as *const Option; + // Reading the discriminant should fail since the tag value is not in the valid + // range for the tag field. + let _val = matches!(*cast_ptr, None); + //~^ ERROR: invalid tag + } +} diff --git a/src/tools/miri/tests/fail/validity/invalid_enum_op_niche_out_of_range.stderr b/src/tools/miri/tests/fail/validity/invalid_enum_op_niche_out_of_range.stderr new file mode 100644 index 000000000000..65fcddbb43da --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_enum_op_niche_out_of_range.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: enum value has invalid tag: 0x0c + --> tests/fail/validity/invalid_enum_op_niche_out_of_range.rs:LL:CC + | +LL | let _val = matches!(*cast_ptr, None); + | ^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/ui/consts/const-eval/raw-bytes.32bit.stderr b/tests/ui/consts/const-eval/raw-bytes.32bit.stderr index be3b539b269a..acbf19d6ad77 100644 --- a/tests/ui/consts/const-eval/raw-bytes.32bit.stderr +++ b/tests/ui/consts/const-eval/raw-bytes.32bit.stderr @@ -31,7 +31,7 @@ LL | const BAD_UNINHABITED_VARIANT1: UninhDiscriminant = unsafe { mem::transmute 01 │ . } -error[E0080]: constructing invalid value at .: encountered an uninhabited enum variant +error[E0080]: constructing invalid value at .: encountered 0x03, but expected a valid enum tag --> $DIR/raw-bytes.rs:47:1 | LL | const BAD_UNINHABITED_VARIANT2: UninhDiscriminant = unsafe { mem::transmute(3u8) }; diff --git a/tests/ui/consts/const-eval/raw-bytes.64bit.stderr b/tests/ui/consts/const-eval/raw-bytes.64bit.stderr index 9950ac726ca7..52d32fd3f875 100644 --- a/tests/ui/consts/const-eval/raw-bytes.64bit.stderr +++ b/tests/ui/consts/const-eval/raw-bytes.64bit.stderr @@ -31,7 +31,7 @@ LL | const BAD_UNINHABITED_VARIANT1: UninhDiscriminant = unsafe { mem::transmute 01 │ . } -error[E0080]: constructing invalid value at .: encountered an uninhabited enum variant +error[E0080]: constructing invalid value at .: encountered 0x03, but expected a valid enum tag --> $DIR/raw-bytes.rs:47:1 | LL | const BAD_UNINHABITED_VARIANT2: UninhDiscriminant = unsafe { mem::transmute(3u8) }; diff --git a/tests/ui/consts/const-eval/ub-enum.rs b/tests/ui/consts/const-eval/ub-enum.rs index 9c78bb6efed7..63029e17da22 100644 --- a/tests/ui/consts/const-eval/ub-enum.rs +++ b/tests/ui/consts/const-eval/ub-enum.rs @@ -83,7 +83,7 @@ enum UninhDiscriminant { const BAD_UNINHABITED_VARIANT1: UninhDiscriminant = unsafe { mem::transmute(1u8) }; //~^ ERROR uninhabited enum variant const BAD_UNINHABITED_VARIANT2: UninhDiscriminant = unsafe { mem::transmute(3u8) }; -//~^ ERROR uninhabited enum variant +//~^ ERROR expected a valid enum tag // # other diff --git a/tests/ui/consts/const-eval/ub-enum.stderr b/tests/ui/consts/const-eval/ub-enum.stderr index 1efd93832291..da63af30480e 100644 --- a/tests/ui/consts/const-eval/ub-enum.stderr +++ b/tests/ui/consts/const-eval/ub-enum.stderr @@ -86,7 +86,7 @@ LL | const BAD_UNINHABITED_VARIANT1: UninhDiscriminant = unsafe { mem::transmute HEX_DUMP } -error[E0080]: constructing invalid value at .: encountered an uninhabited enum variant +error[E0080]: constructing invalid value at .: encountered 0x03, but expected a valid enum tag --> $DIR/ub-enum.rs:85:1 | LL | const BAD_UNINHABITED_VARIANT2: UninhDiscriminant = unsafe { mem::transmute(3u8) }; From 9a224202e434c2e16f92f9803c65e07dc8e06875 Mon Sep 17 00:00:00 2001 From: Aliaksei Semianiuk Date: Sat, 21 Feb 2026 15:40:37 +0500 Subject: [PATCH 0136/1059] Changelog for Clippy 1.94 --- CHANGELOG.md | 98 +++++++++++++++++++- clippy_lints/src/casts/mod.rs | 2 +- clippy_lints/src/manual_ilog2.rs | 2 +- clippy_lints/src/methods/mod.rs | 2 +- clippy_lints/src/operators/mod.rs | 2 +- clippy_lints/src/same_length_and_capacity.rs | 2 +- 6 files changed, 102 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c798a3c2e5a..531c8794a572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,103 @@ document. ## Unreleased / Beta / In Rust Nightly -[92b4b68...master](https://github.com/rust-lang/rust-clippy/compare/92b4b68...master) +[500e0ff...master](https://github.com/rust-lang/rust-clippy/compare/500e0ff...master) + +## Rust 1.94 + +Current stable, released 2026-03-05 + +[View all 94 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-11-29T21%3A01%3A29Z..2026-01-08T20%3A33%3A22Z+base%3Amaster) + +### New Lints + +* Added [`same_length_and_capacity`] to `pedantic` + [#15656](https://github.com/rust-lang/rust-clippy/pull/15656) +* Added [`manual_ilog2`] to `pedantic` + [#15865](https://github.com/rust-lang/rust-clippy/pull/15865) +* Added [`needless_type_cast`] to `nursery` + [#16139](https://github.com/rust-lang/rust-clippy/pull/16139) +* Added [`ptr_offset_by_literal`] to `pedantic` + [#15606](https://github.com/rust-lang/rust-clippy/pull/15606) +* Added [`decimal_bitwise_operands`] to `pedantic` + [#15215](https://github.com/rust-lang/rust-clippy/pull/15215) + +### Moves and Deprecations + +* Moved [`multiple_bound_locations`] from `suspicious` to `style` + [#16302](https://github.com/rust-lang/rust-clippy/pull/16302) +* Moved [`collapsible_else_if`] from `style` to `pedantic` + [#16211](https://github.com/rust-lang/rust-clippy/pull/16211) +* Moved [`needless_type_cast`] from `pedantic` to `nursery` + [#16246](https://github.com/rust-lang/rust-clippy/pull/16246) + +### Enhancements + +* [`never_loop`] do not consider `return` as preventing the iterator from looping; lint diverging + iterator reduction closures like `for_each` and `fold` + [#16364](https://github.com/rust-lang/rust-clippy/pull/16364) +* [`single_range_in_vec_init`] don't apply the suggestion automatically + [#16365](https://github.com/rust-lang/rust-clippy/pull/16365) +* [`useless_conversion`] refine `.into_iter()` suggestions to stop at final target type + [#16238](https://github.com/rust-lang/rust-clippy/pull/16238) +* Multiple lints fix wrongly unmangled macros + [#16337](https://github.com/rust-lang/rust-clippy/pull/16337) +* [`large_stack_arrays`] do not warn for libtest harness + [#16347](https://github.com/rust-lang/rust-clippy/pull/16347) +* [`derive_ord_xor_partial_ord`] allow `expect` on `impl` block + [#16303](https://github.com/rust-lang/rust-clippy/pull/16303) +* [`match_bool`] restrict to 2 arms + [#16333](https://github.com/rust-lang/rust-clippy/pull/16333) +* [`multiple_inherent_impl`] fix false negatives for generic impl blocks + [#16284](https://github.com/rust-lang/rust-clippy/pull/16284) +* [`unnecessary_fold`] warn about semantics change and lint `Add::add`/`Mul::mul` folds + [#16324](https://github.com/rust-lang/rust-clippy/pull/16324) +* [`transmuting_null`] check const blocks and const integer casts + [#16260](https://github.com/rust-lang/rust-clippy/pull/16260) +* [`needless_pass_by_ref_mut`] preserve user-provided lifetime information + [#16273](https://github.com/rust-lang/rust-clippy/pull/16273) +* [`while_let_on_iterator`] use reborrow for non-`Sized` trait references + [#16100](https://github.com/rust-lang/rust-clippy/pull/16100) +* [`collapsible_else_if`] prevent emitting when arms only `if {..} else {..}` + [#16286](https://github.com/rust-lang/rust-clippy/pull/16286) +* [`multiple_unsafe_ops_per_block`] count only towards innermost unsafe block + [#16117](https://github.com/rust-lang/rust-clippy/pull/16117) +* [`manual_saturating_arithmetic`] lint `x.checked_sub(y).unwrap_or_default()` + [#15845](https://github.com/rust-lang/rust-clippy/pull/15845) +* [`transmute_ptr_to_ref`] handle pointer in struct + [#15948](https://github.com/rust-lang/rust-clippy/pull/15948) +* [`disallowed_methods`] skip compiler-generated code + [#16186](https://github.com/rust-lang/rust-clippy/pull/16186) +* [`missing_enforced_import_renames`] do not enforce for "as _" + [#16352](https://github.com/rust-lang/rust-clippy/pull/16352) + +### False Positive Fixes + +* [`double_parens`] fix FP on macro repetition patterns + [#16301](https://github.com/rust-lang/rust-clippy/pull/16301) +* [`assertions_on_constants`] fix false positive when there is non-constant value in condition expr + [#16297](https://github.com/rust-lang/rust-clippy/pull/16297) +* [`use_self`] fix FP on type in const generics + [#16172](https://github.com/rust-lang/rust-clippy/pull/16172) +* [`set_contains_or_insert`] fix FP when set is mutated before `insert` + [#16009](https://github.com/rust-lang/rust-clippy/pull/16009) +* [`if_then_some_else_none`] fix FP when then block contains `await` + [#16178](https://github.com/rust-lang/rust-clippy/pull/16178) +* [`match_like_matches_macro`] fix FP with guards containing `if let` + [#15876](https://github.com/rust-lang/rust-clippy/pull/15876) +* [`tuple_array_conversions`] fix FP when binded vars are used before conversion + [#16197](https://github.com/rust-lang/rust-clippy/pull/16197) +* [`map_entry`] fix FP when it would cause `MutexGuard` to be held across await + [#16199](https://github.com/rust-lang/rust-clippy/pull/16199) +* [`panicking_unwrap`] fix FP on field access with implicit deref + [#16196](https://github.com/rust-lang/rust-clippy/pull/16196) +* [`large_stack_frames`] fix FP on compiler generated targets + [#15101](https://github.com/rust-lang/rust-clippy/pull/15101) + +### ICE Fixes + +* [`needless_type_cast`] do not ICE on struct constructor + [#16245](https://github.com/rust-lang/rust-clippy/pull/16245) ### New Lints diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 75761de4ae73..151f9c956c6d 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -691,7 +691,7 @@ /// const SIZE: usize = 15; /// let arr: [u8; SIZE] = [0; SIZE]; /// ``` - #[clippy::version = "1.93.0"] + #[clippy::version = "1.94.0"] pub NEEDLESS_TYPE_CAST, nursery, "binding defined with one type but always cast to another" diff --git a/clippy_lints/src/manual_ilog2.rs b/clippy_lints/src/manual_ilog2.rs index 7c397bd3f5e8..2c368f15d670 100644 --- a/clippy_lints/src/manual_ilog2.rs +++ b/clippy_lints/src/manual_ilog2.rs @@ -31,7 +31,7 @@ /// let log = x.ilog2(); /// let log = x.ilog2(); /// ``` - #[clippy::version = "1.93.0"] + #[clippy::version = "1.94.0"] pub MANUAL_ILOG2, pedantic, "manually reimplementing `ilog2`" diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 0ed166f8dd5b..48ab42d587c8 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -3030,7 +3030,7 @@ /// ptr.sub(8); /// } /// ``` - #[clippy::version = "1.92.0"] + #[clippy::version = "1.94.0"] pub PTR_OFFSET_BY_LITERAL, pedantic, "unneeded pointer offset" diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 567443b8e86d..b4519d654722 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -221,7 +221,7 @@ /// ```rust,no_run /// let a = 0b1110 & 0b0110; /// ``` - #[clippy::version = "1.93.0"] + #[clippy::version = "1.94.0"] pub DECIMAL_BITWISE_OPERANDS, pedantic, "use binary, hex, or octal literals for bitwise operations" diff --git a/clippy_lints/src/same_length_and_capacity.rs b/clippy_lints/src/same_length_and_capacity.rs index b200fd1fe25f..ebf649c24307 100644 --- a/clippy_lints/src/same_length_and_capacity.rs +++ b/clippy_lints/src/same_length_and_capacity.rs @@ -65,7 +65,7 @@ /// // This time, leverage the previously saved capacity: /// let reconstructed = unsafe { Vec::from_raw_parts(ptr, len, cap) }; /// ``` - #[clippy::version = "1.93.0"] + #[clippy::version = "1.94.0"] pub SAME_LENGTH_AND_CAPACITY, pedantic, "`from_raw_parts` with same length and capacity" From b4d244543239d6be118a3640f1691ab9fbc19cb7 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 5 Mar 2026 17:18:20 +0100 Subject: [PATCH 0137/1059] Merge commit 'e645f93552c3926a0bb481a777df120b7bce986f' --- .github/workflows/clippy_changelog.yml | 3 +- Cargo.toml | 2 +- book/src/development/the_team.md | 2 +- clippy_config/Cargo.toml | 2 +- clippy_dev/src/edit_lints.rs | 11 +- clippy_dev/src/fmt.rs | 30 +- clippy_dev/src/generate.rs | 316 + clippy_dev/src/lib.rs | 3 +- clippy_dev/src/main.rs | 7 +- clippy_dev/src/parse.rs | 187 +- clippy_dev/src/parse/cursor.rs | 86 +- clippy_dev/src/update_lints.rs | 204 - clippy_dev/src/utils.rs | 27 + clippy_lints/Cargo.toml | 2 +- clippy_lints/src/absolute_paths.rs | 1 + clippy_lints/src/almost_complete_range.rs | 1 + clippy_lints/src/approx_const.rs | 4 +- clippy_lints/src/arc_with_non_send_sync.rs | 1 + clippy_lints/src/asm_syntax.rs | 96 +- clippy_lints/src/assertions_on_constants.rs | 1 + clippy_lints/src/assigning_clones.rs | 4 +- clippy_lints/src/attrs/mod.rs | 623 +- clippy_lints/src/await_holding_invalid.rs | 80 +- clippy_lints/src/bool_to_int_with_if.rs | 1 + clippy_lints/src/booleans.rs | 4 +- clippy_lints/src/box_default.rs | 2 +- clippy_lints/src/byte_char_slices.rs | 1 + clippy_lints/src/cargo/mod.rs | 254 +- clippy_lints/src/casts/mod.rs | 1010 +-- clippy_lints/src/checked_conversions.rs | 4 +- clippy_lints/src/cloned_ref_to_slice_refs.rs | 4 +- clippy_lints/src/coerce_container_to_any.rs | 1 + clippy_lints/src/cognitive_complexity.rs | 4 +- clippy_lints/src/collapsible_if.rs | 68 +- clippy_lints/src/collection_is_never_read.rs | 3 +- clippy_lints/src/crate_in_macro_def.rs | 1 + clippy_lints/src/dbg_macro.rs | 6 +- clippy_lints/src/default.rs | 4 +- .../src/default_constructed_unit_structs.rs | 5 +- .../src/default_instead_of_iter_empty.rs | 1 + .../src/default_union_representation.rs | 1 + clippy_lints/src/dereference.rs | 52 +- clippy_lints/src/derivable_impls.rs | 4 +- clippy_lints/src/derive/mod.rs | 128 +- clippy_lints/src/disallowed_fields.rs | 4 +- clippy_lints/src/disallowed_macros.rs | 4 +- clippy_lints/src/disallowed_methods.rs | 4 +- clippy_lints/src/disallowed_names.rs | 4 +- clippy_lints/src/disallowed_script_idents.rs | 4 +- clippy_lints/src/disallowed_types.rs | 4 +- .../doc/doc_paragraphs_missing_punctuation.rs | 22 +- clippy_lints/src/doc/mod.rs | 832 +- clippy_lints/src/drop_forget_ref.rs | 8 +- clippy_lints/src/duplicate_mod.rs | 4 +- clippy_lints/src/empty_drop.rs | 1 + clippy_lints/src/empty_line_after.rs | 80 +- clippy_lints/src/empty_with_brackets.rs | 63 +- clippy_lints/src/endian_bytes.rs | 42 +- clippy_lints/src/entry.rs | 5 +- clippy_lints/src/error_impl_error.rs | 1 + clippy_lints/src/escape.rs | 4 +- clippy_lints/src/eta_reduction.rs | 29 +- clippy_lints/src/excessive_bools.rs | 81 +- clippy_lints/src/excessive_nesting.rs | 1 + clippy_lints/src/explicit_write.rs | 4 +- .../src/extra_unused_type_parameters.rs | 4 +- .../src/field_scoped_visibility_modifiers.rs | 4 +- clippy_lints/src/float_literal.rs | 6 +- .../src/floating_point_arithmetic/mod.rs | 5 +- .../src/floating_point_arithmetic/mul_add.rs | 159 +- clippy_lints/src/format.rs | 4 +- clippy_lints/src/format_args.rs | 136 +- clippy_lints/src/format_impl.rs | 82 +- clippy_lints/src/format_push_string.rs | 1 + clippy_lints/src/formatting.rs | 138 +- clippy_lints/src/four_forward_slashes.rs | 1 + clippy_lints/src/from_over_into.rs | 4 +- clippy_lints/src/from_raw_with_void_ptr.rs | 1 + clippy_lints/src/functions/mod.rs | 552 +- clippy_lints/src/functions/must_use.rs | 2 +- clippy_lints/src/if_then_some_else_none.rs | 4 +- clippy_lints/src/ifs/mod.rs | 124 +- clippy_lints/src/ignored_unit_patterns.rs | 1 + .../impl_hash_with_borrow_str_and_bytes.rs | 4 +- clippy_lints/src/implicit_saturating_add.rs | 1 + clippy_lints/src/implicit_saturating_sub.rs | 7 +- clippy_lints/src/implied_bounds_in_impls.rs | 1 + clippy_lints/src/incompatible_msrv.rs | 4 +- .../src/inconsistent_struct_constructor.rs | 6 +- clippy_lints/src/index_refutable_slice.rs | 4 +- clippy_lints/src/indexing_slicing.rs | 60 +- clippy_lints/src/infallible_try_from.rs | 1 + clippy_lints/src/inherent_to_string.rs | 5 +- clippy_lints/src/item_name_repetitions.rs | 75 +- clippy_lints/src/iter_without_into_iter.rs | 93 +- clippy_lints/src/large_const_arrays.rs | 4 +- clippy_lints/src/large_enum_variant.rs | 4 +- clippy_lints/src/large_futures.rs | 4 +- clippy_lints/src/large_include_file.rs | 4 +- clippy_lints/src/large_stack_arrays.rs | 6 +- clippy_lints/src/large_stack_frames.rs | 4 +- clippy_lints/src/legacy_numeric_constants.rs | 9 +- clippy_lints/src/len_zero.rs | 74 +- clippy_lints/src/let_underscore.rs | 73 +- clippy_lints/src/let_with_type_underscore.rs | 1 + clippy_lints/src/lifetimes.rs | 82 +- clippy_lints/src/literal_representation.rs | 160 +- .../literal_string_with_formatting_args.rs | 4 +- .../src/loops/explicit_counter_loop.rs | 133 +- clippy_lints/src/loops/infinite_loop.rs | 12 +- .../src/loops/manual_while_let_some.rs | 2 +- clippy_lints/src/loops/mod.rs | 1004 +-- clippy_lints/src/macro_metavars_in_unsafe.rs | 1 + clippy_lints/src/macro_use.rs | 4 +- clippy_lints/src/main_recursion.rs | 4 +- clippy_lints/src/manual_bits.rs | 6 +- clippy_lints/src/manual_checked_ops.rs | 1 + clippy_lints/src/manual_clamp.rs | 1 + clippy_lints/src/manual_float_methods.rs | 50 +- clippy_lints/src/manual_hash_one.rs | 4 +- clippy_lints/src/manual_ilog2.rs | 4 +- clippy_lints/src/manual_is_ascii_check.rs | 1 + clippy_lints/src/manual_is_power_of_two.rs | 4 +- clippy_lints/src/manual_main_separator_str.rs | 4 +- clippy_lints/src/manual_non_exhaustive.rs | 4 +- clippy_lints/src/manual_option_as_slice.rs | 4 +- clippy_lints/src/manual_range_patterns.rs | 1 + clippy_lints/src/manual_rem_euclid.rs | 4 +- clippy_lints/src/manual_retain.rs | 6 +- .../src/manual_slice_size_calculation.rs | 1 + clippy_lints/src/manual_string_new.rs | 1 + clippy_lints/src/manual_strip.rs | 4 +- clippy_lints/src/manual_take.rs | 5 +- clippy_lints/src/matches/mod.rs | 1682 ++-- clippy_lints/src/mem_replace.rs | 62 +- clippy_lints/src/methods/clear_with_drain.rs | 2 +- clippy_lints/src/methods/filetype_is_file.rs | 2 +- clippy_lints/src/methods/filter_next.rs | 2 +- .../methods/from_iter_instead_of_collect.rs | 2 +- clippy_lints/src/methods/get_unwrap.rs | 2 +- .../src/methods/iter_out_of_bounds.rs | 2 +- .../src/methods/join_absolute_paths.rs | 2 +- clippy_lints/src/methods/mod.rs | 7192 +++++++++-------- .../src/methods/option_as_ref_deref.rs | 2 +- clippy_lints/src/methods/or_fun_call.rs | 3 +- .../src/methods/readonly_write_lock.rs | 5 +- .../methods/suspicious_command_arg_space.rs | 3 +- .../src/methods/unwrap_expect_used.rs | 80 +- clippy_lints/src/min_ident_chars.rs | 1 + clippy_lints/src/misc.rs | 44 +- clippy_lints/src/misc_early/mod.rs | 340 +- .../src/mismatching_type_param_order.rs | 1 + .../src/missing_asserts_for_indexing.rs | 1 + .../src/missing_const_for_thread_local.rs | 4 +- clippy_lints/src/missing_doc.rs | 4 +- .../src/missing_enforced_import_rename.rs | 4 +- clippy_lints/src/missing_fields_in_debug.rs | 1 + clippy_lints/src/missing_inline.rs | 4 +- clippy_lints/src/missing_trait_methods.rs | 1 + .../src/mixed_read_write_in_expression.rs | 61 +- .../src/multiple_unsafe_ops_per_block.rs | 1 + clippy_lints/src/mut_key.rs | 6 +- clippy_lints/src/needless_bool.rs | 1 + .../src/needless_borrows_for_generic_args.rs | 6 +- clippy_lints/src/needless_else.rs | 1 + clippy_lints/src/needless_ifs.rs | 1 + clippy_lints/src/needless_late_init.rs | 1 + clippy_lints/src/needless_maybe_sized.rs | 1 + .../src/needless_parens_on_range_literals.rs | 4 +- clippy_lints/src/needless_pass_by_ref_mut.rs | 4 +- clippy_lints/src/new_without_default.rs | 4 +- clippy_lints/src/no_effect.rs | 8 +- clippy_lints/src/no_mangle_with_rust_abi.rs | 1 + clippy_lints/src/non_canonical_impls.rs | 7 +- clippy_lints/src/non_copy_const.rs | 117 +- clippy_lints/src/non_expressive_names.rs | 86 +- .../src/non_send_fields_in_send_ty.rs | 4 +- clippy_lints/src/non_std_lazy_statics.rs | 4 +- clippy_lints/src/nonstandard_macro_braces.rs | 4 +- clippy_lints/src/only_used_in_recursion.rs | 6 +- .../src/operators/arithmetic_side_effects.rs | 3 +- clippy_lints/src/operators/cmp_owned.rs | 6 +- clippy_lints/src/operators/mod.rs | 840 +- clippy_lints/src/panic_in_result_fn.rs | 2 +- clippy_lints/src/panic_unimplemented.rs | 36 +- clippy_lints/src/partial_pub_fields.rs | 1 + clippy_lints/src/partialeq_to_none.rs | 1 + clippy_lints/src/pass_by_ref_or_value.rs | 71 +- .../src/permissions_set_readonly_false.rs | 5 +- clippy_lints/src/ptr/mod.rs | 74 +- clippy_lints/src/pub_underscore_fields.rs | 4 +- clippy_lints/src/pub_use.rs | 1 + clippy_lints/src/question_mark.rs | 4 +- clippy_lints/src/ranges.rs | 142 +- clippy_lints/src/raw_strings.rs | 44 +- clippy_lints/src/rc_clone_in_vec_init.rs | 1 + clippy_lints/src/read_zero_byte_vec.rs | 1 + clippy_lints/src/redundant_async_block.rs | 1 + clippy_lints/src/redundant_clone.rs | 30 +- clippy_lints/src/redundant_field_names.rs | 4 +- clippy_lints/src/redundant_locals.rs | 1 + clippy_lints/src/redundant_pub_crate.rs | 4 +- clippy_lints/src/redundant_slicing.rs | 50 +- .../src/redundant_static_lifetimes.rs | 4 +- .../src/redundant_type_annotations.rs | 1 + clippy_lints/src/ref_patterns.rs | 1 + clippy_lints/src/regex.rs | 64 +- clippy_lints/src/replace_box.rs | 4 +- .../src/reserve_after_initialization.rs | 1 + clippy_lints/src/returns/mod.rs | 6 +- clippy_lints/src/same_length_and_capacity.rs | 1 + clippy_lints/src/semicolon_block.rs | 2 + clippy_lints/src/shadow.rs | 58 +- clippy_lints/src/single_call_fn.rs | 1 + .../src/single_component_path_imports.rs | 4 +- clippy_lints/src/single_range_in_vec_init.rs | 1 + clippy_lints/src/size_of_ref.rs | 1 + clippy_lints/src/std_instead_of_core.rs | 102 +- clippy_lints/src/string_patterns.rs | 7 +- clippy_lints/src/strings.rs | 174 +- clippy_lints/src/strlen_on_c_strings.rs | 4 +- .../src/suspicious_operation_groupings.rs | 4 +- clippy_lints/src/suspicious_trait_impl.rs | 5 +- .../src/suspicious_xor_used_as_pow.rs | 1 + clippy_lints/src/swap.rs | 54 +- clippy_lints/src/swap_ptr_to_ref.rs | 1 + clippy_lints/src/temporary_assignment.rs | 4 +- clippy_lints/src/time_subtraction.rs | 7 +- clippy_lints/src/trailing_empty_array.rs | 1 + clippy_lints/src/trait_bounds.rs | 53 +- clippy_lints/src/transmute/mod.rs | 656 +- .../src/transmute/transmuting_null.rs | 2 +- clippy_lints/src/tuple_array_conversions.rs | 1 + clippy_lints/src/types/box_collection.rs | 2 +- clippy_lints/src/types/linked_list.rs | 2 +- clippy_lints/src/types/mod.rs | 330 +- clippy_lints/src/types/owned_cow.rs | 2 +- clippy_lints/src/types/rc_buffer.rs | 2 +- clippy_lints/src/unconditional_recursion.rs | 6 +- .../src/undocumented_unsafe_blocks.rs | 8 +- clippy_lints/src/unicode.rs | 6 +- clippy_lints/src/unit_types/mod.rs | 44 +- clippy_lints/src/unnecessary_box_returns.rs | 4 +- .../src/unnecessary_map_on_constructor.rs | 5 +- .../src/unnecessary_owned_empty_strings.rs | 5 +- clippy_lints/src/unnecessary_semicolon.rs | 4 +- .../src/unnecessary_struct_initialization.rs | 1 + clippy_lints/src/unnecessary_wraps.rs | 4 +- clippy_lints/src/unnested_or_patterns.rs | 4 +- clippy_lints/src/unused_async.rs | 4 +- clippy_lints/src/unused_result_ok.rs | 1 + clippy_lints/src/unused_rounding.rs | 1 + clippy_lints/src/unused_self.rs | 4 +- clippy_lints/src/unused_trait_names.rs | 4 +- clippy_lints/src/unwrap.rs | 56 +- clippy_lints/src/unwrap_in_result.rs | 2 +- clippy_lints/src/upper_case_acronyms.rs | 4 +- clippy_lints/src/use_self.rs | 4 +- clippy_lints/src/useless_conversion.rs | 4 +- clippy_lints/src/visibility.rs | 9 +- clippy_lints/src/volatile_composites.rs | 1 + clippy_lints/src/wildcard_imports.rs | 4 +- clippy_lints/src/write/mod.rs | 182 +- clippy_lints/src/zombie_processes.rs | 1 + .../derive_deserialize_allowing_unknown.rs | 6 +- .../src/repeated_is_diagnostic_item.rs | 6 +- clippy_lints_internal/src/symbols.rs | 2 +- clippy_utils/Cargo.toml | 2 +- clippy_utils/README.md | 2 +- clippy_utils/src/hir_utils.rs | 69 +- clippy_utils/src/macros.rs | 1 + clippy_utils/src/mir/mod.rs | 63 +- clippy_utils/src/sym.rs | 2 - declare_clippy_lint/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- tests/compile-test.rs | 9 +- tests/test_utils/mod.rs | 7 +- tests/ui/cmp_owned/with_suggestion.fixed | 8 + tests/ui/cmp_owned/with_suggestion.rs | 8 + tests/ui/cmp_owned/with_suggestion.stderr | 8 +- ...oc_paragraphs_missing_punctuation_emoji.rs | 12 + ...aragraphs_missing_punctuation_emoji.stderr | 11 + tests/ui/eta.fixed | 9 + tests/ui/eta.rs | 9 + tests/ui/eta.stderr | 8 +- tests/ui/explicit_counter_loop.rs | 15 + tests/ui/floating_point_mul_add.fixed | 12 + tests/ui/floating_point_mul_add.rs | 12 + tests/ui/floating_point_mul_add.stderr | 14 +- tests/ui/if_same_then_else.rs | 17 + tests/ui/infinite_loops.rs | 53 + tests/ui/infinite_loops.stderr | 131 +- tests/ui/must_use_candidates.fixed | 14 +- tests/ui/must_use_candidates.stderr | 14 +- ...gle_component_path_imports_nested_first.rs | 1 + ...component_path_imports_nested_first.stderr | 4 +- tests/ui/uninlined_format_args.fixed | 21 + tests/ui/uninlined_format_args.rs | 21 + ...nlined_format_args_panic.edition2018.fixed | 34 +- ...lined_format_args_panic.edition2018.stderr | 2 +- ...nlined_format_args_panic.edition2021.fixed | 34 +- ...lined_format_args_panic.edition2021.stderr | 50 +- ...nlined_format_args_panic.edition2024.fixed | 70 + ...lined_format_args_panic.edition2024.stderr | 112 + tests/ui/uninlined_format_args_panic.rs | 34 +- triagebot.toml | 25 + 306 files changed, 11449 insertions(+), 10159 deletions(-) create mode 100644 clippy_dev/src/generate.rs delete mode 100644 clippy_dev/src/update_lints.rs create mode 100644 tests/ui/doc/doc_paragraphs_missing_punctuation_emoji.rs create mode 100644 tests/ui/doc/doc_paragraphs_missing_punctuation_emoji.stderr create mode 100644 tests/ui/uninlined_format_args_panic.edition2024.fixed create mode 100644 tests/ui/uninlined_format_args_panic.edition2024.stderr diff --git a/.github/workflows/clippy_changelog.yml b/.github/workflows/clippy_changelog.yml index 4d84d6b6dae4..121e1b84543f 100644 --- a/.github/workflows/clippy_changelog.yml +++ b/.github/workflows/clippy_changelog.yml @@ -20,7 +20,8 @@ jobs: - name: Check Changelog if: ${{ github.event_name == 'pull_request' }} run: | - if [[ -z $(grep -oP 'changelog: *\K\S+' <<< "$PR_BODY") ]]; then + # Checks that the PR body contains a changelog entry, ignoring the placeholder from the PR template. + if [[ -z $(grep -oP 'changelog: *\K(?!\[`lint_name`\])\S+' <<< "$PR_BODY") ]]; then echo "::error::Pull request message must contain 'changelog: ...' with your changelog. Please add it." exit 1 fi diff --git a/Cargo.toml b/Cargo.toml index 8f5ef9ca2f8f..bcd800930c51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.95" +version = "0.1.96" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/book/src/development/the_team.md b/book/src/development/the_team.md index d22123231868..a663158c949b 100644 --- a/book/src/development/the_team.md +++ b/book/src/development/the_team.md @@ -51,7 +51,7 @@ this group to help with triaging, which can include: busy or wants to abandon it. If the reviewer is busy, the PR can be reassigned to someone else. - Checkout: https://triage.rust-lang.org/triage/rust-lang/rust-clippy to + Checkout: to monitor PRs. While not part of their duties, contributors are encouraged to review PRs diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index da5166392b4e..366c776b8f1a 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_config" -version = "0.1.95" +version = "0.1.96" edition = "2024" publish = false diff --git a/clippy_dev/src/edit_lints.rs b/clippy_dev/src/edit_lints.rs index 411bf530b22b..70c096783af8 100644 --- a/clippy_dev/src/edit_lints.rs +++ b/clippy_dev/src/edit_lints.rs @@ -1,6 +1,5 @@ use crate::parse::cursor::{self, Capture, Cursor}; use crate::parse::{ActiveLint, DeprecatedLint, Lint, LintData, LintName, ParseCx, RenamedLint}; -use crate::update_lints::generate_lint_files; use crate::utils::{ ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists, expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, @@ -40,7 +39,7 @@ pub fn deprecate<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, name }; remove_lint_declaration(name, &prev_lint, &data, &mut FileUpdater::default()); - generate_lint_files(UpdateMode::Change, &data); + data.gen_decls(UpdateMode::Change); println!("info: `{name}` has successfully been deprecated"); println!("note: you must run `cargo uitest` to update the test results"); } @@ -74,7 +73,7 @@ pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam updater.update_file(e.path(), &mut update_fn); } } - generate_lint_files(UpdateMode::Change, &data); + data.gen_decls(UpdateMode::Change); println!("info: `{old_name}` has successfully been uplifted as `{new_name}`"); println!("note: you must run `cargo uitest` to update the test results"); } @@ -151,7 +150,7 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam updater.update_file(e.path(), &mut update_fn); } } - generate_lint_files(UpdateMode::Change, &data); + data.gen_decls(UpdateMode::Change); println!("Renamed `{old_name}` to `{new_name}`"); println!("All code referencing the old name has been updated"); @@ -172,11 +171,11 @@ fn remove_lint_declaration(name: &str, lint: &ActiveLint<'_>, data: &LintData<'_ delete_file_if_exists(lint.path.as_ref()) } else { updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus { - let mut start = &src[..lint.declaration_range.start]; + let mut start = &src[..lint.declaration_range.start as usize]; if start.ends_with("\n\n") { start = &start[..start.len() - 1]; } - let mut end = &src[lint.declaration_range.end..]; + let mut end = &src[lint.declaration_range.end as usize..]; if end.starts_with("\n\n") { end = &end[1..]; } diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index 781e37e6144e..d13f966b19c3 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -1,3 +1,6 @@ +use crate::generate::gen_sorted_lints_file; +use crate::new_parse_cx; +use crate::parse::VecBuf; use crate::utils::{ ErrAction, FileUpdater, UpdateMode, UpdateStatus, expect_action, run_with_output, split_args_for_threads, walk_dir_no_dot_or_target, @@ -326,10 +329,35 @@ fn run_rustfmt(update_mode: UpdateMode) { // the "main" function of cargo dev fmt pub fn run(update_mode: UpdateMode) { - run_rustfmt(update_mode); fmt_syms(update_mode); if let Err(e) = fmt_conf(update_mode.is_check()) { e.display(); process::exit(1); } + + new_parse_cx(|cx| { + let mut data = cx.parse_lint_decls(); + let (mut lints, passes) = data.split_by_lint_file(); + let mut updater = FileUpdater::default(); + let mut ranges = VecBuf::with_capacity(256); + + for passes in passes { + let path = passes[0].path.clone(); + let mut lints = lints.remove(&*path); + let lints = lints.as_deref_mut().unwrap_or_default(); + updater.update_file_checked("cargo dev fmt", update_mode, &path, &mut |_, src, dst| { + gen_sorted_lints_file(src, dst, lints, passes, &mut ranges); + UpdateStatus::from_changed(src != dst) + }); + } + + for (&path, lints) in &mut lints { + updater.update_file_checked("cargo dev fmt", update_mode, path, &mut |_, src, dst| { + gen_sorted_lints_file(src, dst, lints, &mut [], &mut ranges); + UpdateStatus::from_changed(src != dst) + }); + } + }); + + run_rustfmt(update_mode); } diff --git a/clippy_dev/src/generate.rs b/clippy_dev/src/generate.rs new file mode 100644 index 000000000000..24e4218716ab --- /dev/null +++ b/clippy_dev/src/generate.rs @@ -0,0 +1,316 @@ +use crate::parse::cursor::Cursor; +use crate::parse::{Lint, LintData, LintPass, VecBuf}; +use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; +use core::range::Range; +use itertools::Itertools; +use std::collections::HashSet; +use std::fmt::Write; +use std::path::{self, Path}; + +const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ + // Use that command to update this file and do not edit by hand.\n\ + // Manual edits will be overwritten.\n\n"; + +const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; + +impl LintData<'_> { + #[expect(clippy::too_many_lines)] + pub fn gen_decls(&self, update_mode: UpdateMode) { + let mut updater = FileUpdater::default(); + + let mut lints: Vec<_> = self.lints.iter().map(|(&x, y)| (x, y)).collect(); + lints.sort_by_key(|&(x, _)| x); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "CHANGELOG.md", + &mut update_text_region_fn( + "\n", + "", + |dst| { + for &(lint, _) in &lints { + writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); + } + }, + ), + ); + + let mut active = Vec::with_capacity(lints.len()); + let mut deprecated = Vec::with_capacity(lints.len() / 8); + let mut renamed = Vec::with_capacity(lints.len() / 8); + for &(name, lint) in &lints { + match lint { + Lint::Active(lint) => active.push((name, lint)), + Lint::Deprecated(lint) => deprecated.push((name, lint)), + Lint::Renamed(lint) => renamed.push((name, lint)), + } + } + active.sort_by_key(|&(_, lint)| lint.module); + + // Round to avoid updating the readme every time a lint is added/deprecated. + let lint_count = active.len() / 50 * 50; + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "README.md", + &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { + write!(dst, "{lint_count}").unwrap(); + }), + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "book/src/README.md", + &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { + write!(dst, "{lint_count}").unwrap(); + }), + ); + + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "clippy_lints/src/deprecated_lints.rs", + &mut |_, src, dst| { + let mut cursor = Cursor::new(src); + assert!( + cursor.find_ident("declare_with_version").is_some() + && cursor.find_ident("declare_with_version").is_some(), + "error reading deprecated lints" + ); + dst.push_str(&src[..cursor.pos() as usize]); + dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); + for &(name, data) in &deprecated { + write!( + dst, + " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", + data.version, data.reason, + ) + .unwrap(); + } + dst.push_str( + "]}\n\n\ + #[rustfmt::skip]\n\ + declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\ + ", + ); + for &(name, data) in &renamed { + write!( + dst, + " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", + data.version, data.new_name, + ) + .unwrap(); + } + dst.push_str("]}\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "tests/ui/deprecated.rs", + &mut |_, src, dst| { + dst.push_str(GENERATED_FILE_COMMENT); + for &(lint, _) in &deprecated { + writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); + } + dst.push_str("\nfn main() {}\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "tests/ui/rename.rs", + &mut move |_, src, dst| { + let mut seen_lints = HashSet::new(); + dst.push_str(GENERATED_FILE_COMMENT); + dst.push_str("#![allow(clippy::duplicated_attributes)]\n"); + for &(_, lint) in &renamed { + if seen_lints.insert(lint.new_name) { + writeln!(dst, "#![allow({})]", lint.new_name).unwrap(); + } + } + for &(lint, _) in &renamed { + writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); + } + dst.push_str("\nfn main() {}\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + for (crate_name, lints) in active.iter().copied().into_group_map_by(|&(_, lint)| { + let Some(path::Component::Normal(name)) = lint.path.components().next() else { + // All paths should start with `{crate_name}/src` when parsed from `find_lint_decls` + panic!( + "internal error: can't read crate name from path `{}`", + lint.path.display() + ); + }; + name + }) { + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + Path::new(crate_name).join("src/lib.rs"), + &mut update_text_region_fn( + "// begin lints modules, do not remove this comment, it's used in `update_lints`\n", + "// end lints modules, do not remove this comment, it's used in `update_lints`", + |dst| { + let mut prev = ""; + for &(_, lint) in &lints { + if lint.module != prev { + writeln!(dst, "mod {};", lint.module).unwrap(); + prev = lint.module; + } + } + }, + ), + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + Path::new(crate_name).join("src/declared_lints.rs"), + &mut |_, src, dst| { + dst.push_str(GENERATED_FILE_COMMENT); + dst.push_str("pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[\n"); + let mut buf = String::new(); + for &(name, lint) in &lints { + buf.clear(); + buf.push_str(name); + buf.make_ascii_uppercase(); + if lint.module.is_empty() { + writeln!(dst, " crate::{buf}_INFO,").unwrap(); + } else { + writeln!(dst, " crate::{}::{buf}_INFO,", lint.module).unwrap(); + } + } + dst.push_str("];\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + } + } +} + +impl LintPass<'_> { + pub fn gen_mac(&self, dst: &mut String) { + let mut line_start = dst.len(); + dst.extend([self.mac.name(), "!("]); + let has_docs = write_comment_lines(self.docs, "\n ", dst); + let (list_indent, list_multi_end, end) = if has_docs { + dst.push_str("\n "); + line_start = dst.len() - 4; + (" ", "\n ", "]\n);") + } else { + (" ", "\n", "]);") + }; + dst.push_str(self.name); + if let Some(lt) = self.lt { + dst.extend(["<", lt, ">"]); + } + dst.push_str(" => ["); + let fmt = write_list( + self.lints.iter().copied(), + 80usize.saturating_sub(dst.len() - line_start), + list_indent, + dst, + ); + if matches!(fmt, ListFmt::MultiLine) { + dst.push_str(list_multi_end); + } + dst.push_str(end); + } +} + +fn write_comment_lines(s: &str, prefix: &str, dst: &mut String) -> bool { + let mut has_doc = false; + for line in s.split('\n') { + let line = line.trim_start(); + if !line.is_empty() { + has_doc = true; + dst.extend([prefix, line]); + } + } + has_doc +} + +#[derive(Clone, Copy)] +enum ListFmt { + SingleLine, + MultiLine, +} + +fn write_list<'a>( + items: impl Iterator + Clone, + single_line_limit: usize, + indent: &str, + dst: &mut String, +) -> ListFmt { + let len = items.clone().map(str::len).sum::(); + if len > single_line_limit { + for item in items { + dst.extend(["\n", indent, item, ","]); + } + ListFmt::MultiLine + } else { + let _ = write!(dst, "{}", items.format(", ")); + ListFmt::SingleLine + } +} + +/// Generates the contents of a lint's source file with all the lint and lint pass +/// declarations sorted. +pub fn gen_sorted_lints_file( + src: &str, + dst: &mut String, + lints: &mut [(&str, Range)], + passes: &mut [LintPass<'_>], + ranges: &mut VecBuf>, +) { + ranges.with(|ranges| { + ranges.extend(lints.iter().map(|&(_, x)| x)); + ranges.extend(passes.iter().map(|x| x.decl_range)); + ranges.sort_unstable_by_key(|x| x.start); + + lints.sort_unstable_by_key(|&(x, _)| x); + passes.sort_by_key(|x| x.name); + + let mut ranges = ranges.iter(); + let pos = if let Some(range) = ranges.next() { + dst.push_str(&src[..range.start as usize]); + for &(_, range) in &*lints { + dst.push_str(&src[range.start as usize..range.end as usize]); + dst.push_str("\n\n"); + } + for pass in passes { + pass.gen_mac(dst); + dst.push_str("\n\n"); + } + range.end + } else { + dst.push_str(src); + return; + }; + + let pos = ranges.fold(pos, |start, range| { + let s = &src[start as usize..range.start as usize]; + dst.push_str(if s.trim_start().is_empty() { + // Only whitespace between this and the previous item. No need to keep that. + "" + } else if src[..pos as usize].ends_with("\n\n") + && let Some(s) = s.strip_prefix("\n\n") + { + // Empty line before and after. Remove one of them. + s + } else { + // Remove only full lines unless something is in the way. + s.strip_prefix('\n').unwrap_or(s) + }); + range.end + }); + + // Since we always generate an empty line at the end, make sure to always skip it. + let s = &src[pos as usize..]; + dst.push_str(s.strip_prefix('\n').map_or(s, |s| s.strip_prefix('\n').unwrap_or(s))); + }); +} diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index cff51d34c30e..359bf17c68c5 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,6 +1,5 @@ #![feature( exit_status_error, - if_let_guard, new_range, new_range_api, os_str_slice, @@ -33,8 +32,8 @@ pub mod serve; pub mod setup; pub mod sync; -pub mod update_lints; +mod generate; mod parse; mod utils; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 8dc2290df8e4..5fe2295a1e21 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -5,7 +5,6 @@ use clap::{Args, Parser, Subcommand}; use clippy_dev::{ ClippyInfo, UpdateMode, dogfood, edit_lints, fmt, lint, new_lint, new_parse_cx, release, serve, setup, sync, - update_lints, }; use std::env; @@ -27,7 +26,9 @@ fn main() { allow_no_vcs, } => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs), DevCommand::Fmt { check } => fmt::run(UpdateMode::from_check(check)), - DevCommand::UpdateLints { check } => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::from_check(check))), + DevCommand::UpdateLints { check } => { + new_parse_cx(|cx| cx.parse_lint_decls().gen_decls(UpdateMode::from_check(check))); + }, DevCommand::NewLint { pass, name, @@ -35,7 +36,7 @@ fn main() { r#type, msrv, } => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) { - Ok(()) => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::Change)), + Ok(()) => new_parse_cx(|cx| cx.parse_lint_decls().gen_decls(UpdateMode::Change)), Err(e) => eprintln!("Unable to create lint: {e}"), }, DevCommand::Setup(SetupCommand { subcommand }) => match subcommand { diff --git a/clippy_dev/src/parse.rs b/clippy_dev/src/parse.rs index ffb50784a9a7..d51fb25552f9 100644 --- a/clippy_dev/src/parse.rs +++ b/clippy_dev/src/parse.rs @@ -1,7 +1,7 @@ pub mod cursor; use self::cursor::{Capture, Cursor}; -use crate::utils::{ErrAction, File, Scoped, expect_action, walk_dir_no_dot_or_target}; +use crate::utils::{ErrAction, File, Scoped, expect_action, slice_groups_mut, walk_dir_no_dot_or_target}; use core::fmt::{self, Display, Write as _}; use core::range::Range; use rustc_arena::DroplessArena; @@ -13,6 +13,7 @@ pub struct ParseCxImpl<'cx> { pub arena: &'cx DroplessArena, pub str_buf: StrBuf, + pub str_list_buf: VecBuf<&'cx str>, } pub type ParseCx<'cx> = &'cx mut ParseCxImpl<'cx>; @@ -22,6 +23,7 @@ pub fn new_parse_cx<'env, T>(f: impl for<'cx> FnOnce(&'cx mut Scoped<'cx, 'env, f(&mut Scoped::new(ParseCxImpl { arena: &arena, str_buf: StrBuf::with_capacity(128), + str_list_buf: VecBuf::with_capacity(128), })) } @@ -82,6 +84,20 @@ pub fn with(&mut self, f: impl FnOnce(&mut String) -> T) -> T { } } +pub struct VecBuf(Vec); +impl VecBuf { + /// Creates a new buffer with the specified initial capacity. + pub fn with_capacity(cap: usize) -> Self { + Self(Vec::with_capacity(cap)) + } + + /// Performs an operation with the freshly cleared buffer. + pub fn with(&mut self, f: impl FnOnce(&mut Vec) -> R) -> R { + self.0.clear(); + f(&mut self.0) + } +} + #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum LintTool { Rustc, @@ -128,7 +144,7 @@ pub struct ActiveLint<'cx> { pub group: &'cx str, pub module: &'cx str, pub path: PathBuf, - pub declaration_range: Range, + pub declaration_range: Range, } pub struct DeprecatedLint<'cx> { @@ -147,8 +163,59 @@ pub enum Lint<'cx> { Renamed(RenamedLint<'cx>), } +#[derive(Clone, Copy)] +pub enum LintPassMac { + Declare, + Impl, +} +impl LintPassMac { + pub fn name(self) -> &'static str { + match self { + Self::Declare => "declare_lint_pass", + Self::Impl => "impl_lint_pass", + } + } +} + +pub struct LintPass<'cx> { + /// The raw text of the documentation comments. May include leading/trailing + /// whitespace and empty lines. + pub docs: &'cx str, + pub name: &'cx str, + pub lt: Option<&'cx str>, + pub mac: LintPassMac, + pub decl_range: Range, + pub lints: &'cx [&'cx str], + pub path: PathBuf, +} + pub struct LintData<'cx> { pub lints: FxHashMap<&'cx str, Lint<'cx>>, + pub lint_passes: Vec>, +} +impl<'cx> LintData<'cx> { + #[expect(clippy::type_complexity)] + pub fn split_by_lint_file<'s>( + &'s mut self, + ) -> ( + FxHashMap<&'s Path, Vec<(&'s str, Range)>>, + impl Iterator]>, + ) { + #[expect(clippy::default_trait_access)] + let mut lints = FxHashMap::with_capacity_and_hasher(500, Default::default()); + for (&name, lint) in &self.lints { + if let Lint::Active(lint) = lint { + lints + .entry(&*lint.path) + .or_insert_with(|| Vec::with_capacity(8)) + .push((name, lint.declaration_range)); + } + } + let passes = slice_groups_mut(&mut self.lint_passes, |head, tail| { + tail.iter().take_while(|&x| x.path == head.path).count() + }); + (lints, passes) + } } impl<'cx> ParseCxImpl<'cx> { @@ -158,6 +225,7 @@ pub fn parse_lint_decls(&mut self) -> LintData<'cx> { let mut data = LintData { #[expect(clippy::default_trait_access)] lints: FxHashMap::with_capacity_and_hasher(1000, Default::default()), + lint_passes: Vec::with_capacity(400), }; let mut contents = String::new(); @@ -193,7 +261,7 @@ pub fn parse_lint_decls(&mut self) -> LintData<'cx> { self.str_buf .alloc_replaced(self.arena, path, path::MAIN_SEPARATOR, "::") }; - self.parse_clippy_lint_decls( + self.parse_lint_src_file( e.path(), File::open_read_to_cleared_string(e.path(), &mut contents), module, @@ -208,11 +276,11 @@ pub fn parse_lint_decls(&mut self) -> LintData<'cx> { } /// Parse a source file looking for `declare_clippy_lint` macro invocations. - fn parse_clippy_lint_decls(&mut self, path: &Path, contents: &str, module: &'cx str, data: &mut LintData<'cx>) { + fn parse_lint_src_file(&mut self, path: &Path, contents: &str, module: &'cx str, data: &mut LintData<'cx>) { #[allow(clippy::enum_glob_use)] use cursor::Pat::*; #[rustfmt::skip] - static DECL_TOKENS: &[cursor::Pat<'_>] = &[ + static LINT_DECL_TOKENS: &[cursor::Pat<'_>] = &[ // !{ /// docs Bang, OpenBrace, AnyComment, // #[clippy::version = "version"] @@ -220,24 +288,101 @@ fn parse_clippy_lint_decls(&mut self, path: &Path, contents: &str, module: &'cx // pub NAME, GROUP, Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, ]; + #[rustfmt::skip] + static PASS_DECL_TOKENS: &[cursor::Pat<'_>] = &[ + // !( NAME <'lt> => [ + Bang, OpenParen, CaptureDocLines, CaptureIdent, CaptureOptLifetimeArg, FatArrow, OpenBracket, + ]; let mut cursor = Cursor::new(contents); - let mut captures = [Capture::EMPTY; 2]; - while let Some(start) = cursor.find_ident("declare_clippy_lint") { - if cursor.match_all(DECL_TOKENS, &mut captures) && cursor.find_pat(CloseBrace) { - assert!( - data.lints - .insert( - self.str_buf.alloc_ascii_lower(self.arena, cursor.get_text(captures[0])), - Lint::Active(ActiveLint { - group: self.arena.alloc_str(cursor.get_text(captures[1])), - module, - path: path.into(), - declaration_range: start as usize..cursor.pos() as usize, - }), - ) - .is_none() - ); + let mut captures = [Capture::EMPTY; 3]; + while let Some(mac_name) = cursor.find_any_ident() { + match cursor.get_text(mac_name) { + "declare_clippy_lint" + if cursor.match_all(LINT_DECL_TOKENS, &mut captures) && cursor.find_pat(CloseBrace) => + { + assert!( + data.lints + .insert( + self.str_buf.alloc_ascii_lower(self.arena, cursor.get_text(captures[0])), + Lint::Active(ActiveLint { + group: self.arena.alloc_str(cursor.get_text(captures[1])), + module, + path: path.into(), + declaration_range: mac_name.pos..cursor.pos(), + }), + ) + .is_none() + ); + }, + mac @ ("declare_lint_pass" | "impl_lint_pass") if cursor.match_all(PASS_DECL_TOKENS, &mut captures) => { + let mac = if matches!(mac, "declare_lint_pass") { + LintPassMac::Declare + } else { + LintPassMac::Impl + }; + let docs = match cursor.get_text(captures[0]) { + "" => "", + x => self.arena.alloc_str(x), + }; + let name = self.arena.alloc_str(cursor.get_text(captures[1])); + let lt = cursor.get_text(captures[2]); + let lt = if lt.is_empty() { + None + } else { + Some(self.arena.alloc_str(lt)) + }; + + let lints = self.str_list_buf.with(|buf| { + // Parses a comma separated list of paths and converts each path + // to a string with whitespace removed. + while !cursor.match_pat(CloseBracket) { + buf.push(self.str_buf.with(|buf| { + if cursor.match_pat(DoubleColon) { + buf.push_str("::"); + } + let capture = cursor.capture_ident()?; + buf.push_str(cursor.get_text(capture)); + while cursor.match_pat(DoubleColon) { + buf.push_str("::"); + let capture = cursor.capture_ident()?; + buf.push_str(cursor.get_text(capture)); + } + Some(self.arena.alloc_str(buf)) + })?); + + if !cursor.match_pat(Comma) { + if !cursor.match_pat(CloseBracket) { + return None; + } + break; + } + } + + // The arena panics when allocating a size of zero. + Some(if buf.is_empty() { + &[] + } else { + buf.sort_unstable(); + &*self.arena.alloc_slice(buf) + }) + }); + + if let Some(lints) = lints + && cursor.match_all(&[CloseParen, Semi], &mut []) + { + data.lint_passes.push(LintPass { + docs, + name, + lt, + mac, + decl_range: mac_name.pos..cursor.pos(), + lints, + path: path.into(), + }); + } + }, + _ => {}, } } } diff --git a/clippy_dev/src/parse/cursor.rs b/clippy_dev/src/parse/cursor.rs index 2c142af4883a..af840532c27b 100644 --- a/clippy_dev/src/parse/cursor.rs +++ b/clippy_dev/src/parse/cursor.rs @@ -12,6 +12,7 @@ pub enum Pat<'a> { /// Matches any number of comments and doc comments. AnyComment, Ident(&'a str), + CaptureDocLines, CaptureIdent, LitStr, CaptureLitStr, @@ -22,12 +23,14 @@ pub enum Pat<'a> { Comma, DoubleColon, Eq, + FatArrow, Lifetime, Lt, Gt, OpenBrace, OpenBracket, OpenParen, + CaptureOptLifetimeArg, Pound, Semi, } @@ -112,6 +115,7 @@ pub fn step(&mut self) { /// /// For each capture made by the pattern one item will be taken from the capture /// sequence with the result placed inside. + #[expect(clippy::too_many_lines)] fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture>) -> bool { loop { match (pat, self.next_token.kind) { @@ -121,7 +125,6 @@ fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture Pat::AnyComment, TokenKind::BlockComment { terminated: true, .. } | TokenKind::LineComment { .. }, ) => self.step(), - (Pat::AnyComment, _) => return true, (Pat::Bang, TokenKind::Bang) | (Pat::CloseBrace, TokenKind::CloseBrace) | (Pat::CloseBracket, TokenKind::CloseBracket) @@ -152,12 +155,48 @@ fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture }, (Pat::DoubleColon, TokenKind::Colon) => { self.step(); - if !self.at_end() && matches!(self.next_token.kind, TokenKind::Colon) { + if matches!(self.next_token.kind, TokenKind::Colon) { self.step(); return true; } return false; }, + (Pat::FatArrow, TokenKind::Eq) => { + self.step(); + if matches!(self.next_token.kind, TokenKind::Gt) { + self.step(); + return true; + } + return false; + }, + (Pat::CaptureOptLifetimeArg, TokenKind::Lt) => { + self.step(); + loop { + match self.next_token.kind { + TokenKind::Lifetime { .. } => break, + TokenKind::Whitespace => self.step(), + _ => return false, + } + } + *captures.next().unwrap() = Capture { + pos: self.pos, + len: self.next_token.len, + }; + self.step(); + loop { + match self.next_token.kind { + TokenKind::Gt => break, + TokenKind::Whitespace => self.step(), + _ => return false, + } + } + self.step(); + return true; + }, + (Pat::CaptureOptLifetimeArg, _) => { + *captures.next().unwrap() = Capture { pos: 0, len: 0 }; + return true; + }, #[rustfmt::skip] ( Pat::CaptureLitStr, @@ -173,6 +212,28 @@ fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture self.step(); return true; }, + (Pat::CaptureDocLines, TokenKind::LineComment { doc_style: Some(_) }) => { + let pos = self.pos; + loop { + self.step(); + if !matches!( + self.next_token.kind, + TokenKind::Whitespace | TokenKind::LineComment { doc_style: Some(_) } + ) { + break; + } + } + *captures.next().unwrap() = Capture { + pos, + len: self.pos - pos, + }; + return true; + }, + (Pat::CaptureDocLines, _) => { + *captures.next().unwrap() = Capture::EMPTY; + return true; + }, + (Pat::AnyComment, _) => return true, _ => return false, } } @@ -219,8 +280,8 @@ pub fn find_any_ident(&mut self) -> Option { } } - /// Consume the returns the position of the next non-whitespace token if it's an - /// identifier. Returns `None` otherwise. + /// Consume the returns the position of the next non-whitespace token if it's the + /// specified identifier. Returns `None` otherwise. pub fn match_ident(&mut self, s: &str) -> Option { loop { match self.next_token.kind { @@ -235,6 +296,23 @@ pub fn match_ident(&mut self, s: &str) -> Option { } } + /// Consumes and captures the next non-whitespace token if it's an identifier. Returns + /// `None` otherwise. + pub fn capture_ident(&mut self) -> Option { + loop { + match self.next_token.kind { + TokenKind::Ident => { + let pos = self.pos; + let len = self.next_token.len; + self.step(); + return Some(Capture { pos, len }); + }, + TokenKind::Whitespace => self.step(), + _ => return None, + } + } + } + /// Continually attempt to match the pattern on subsequent tokens until a match is /// found. Returns whether the pattern was successfully matched. /// diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs deleted file mode 100644 index a4cf15058986..000000000000 --- a/clippy_dev/src/update_lints.rs +++ /dev/null @@ -1,204 +0,0 @@ -use crate::parse::cursor::Cursor; -use crate::parse::{Lint, LintData, ParseCx}; -use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; -use itertools::Itertools; -use std::collections::HashSet; -use std::fmt::Write; -use std::path::{self, Path}; - -const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ - // Use that command to update this file and do not edit by hand.\n\ - // Manual edits will be overwritten.\n\n"; - -const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; - -/// Runs the `update_lints` command. -/// -/// This updates various generated values from the lint source code. -/// -/// `update_mode` indicates if the files should be updated or if updates should be checked for. -/// -/// # Panics -/// -/// Panics if a file path could not read from or then written to -pub fn update(cx: ParseCx<'_>, update_mode: UpdateMode) { - let data = cx.parse_lint_decls(); - generate_lint_files(update_mode, &data); -} - -#[expect(clippy::too_many_lines)] -pub fn generate_lint_files(update_mode: UpdateMode, data: &LintData<'_>) { - let mut updater = FileUpdater::default(); - - let mut lints: Vec<_> = data.lints.iter().map(|(&x, y)| (x, y)).collect(); - lints.sort_by_key(|&(x, _)| x); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "CHANGELOG.md", - &mut update_text_region_fn( - "\n", - "", - |dst| { - for &(lint, _) in &lints { - writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); - } - }, - ), - ); - - let mut active = Vec::with_capacity(lints.len()); - let mut deprecated = Vec::with_capacity(lints.len() / 8); - let mut renamed = Vec::with_capacity(lints.len() / 8); - for &(name, lint) in &lints { - match lint { - Lint::Active(lint) => active.push((name, lint)), - Lint::Deprecated(lint) => deprecated.push((name, lint)), - Lint::Renamed(lint) => renamed.push((name, lint)), - } - } - active.sort_by_key(|&(_, lint)| lint.module); - - // Round to avoid updating the readme every time a lint is added/deprecated. - let lint_count = active.len() / 50 * 50; - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "README.md", - &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { - write!(dst, "{lint_count}").unwrap(); - }), - ); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "book/src/README.md", - &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { - write!(dst, "{lint_count}").unwrap(); - }), - ); - - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "clippy_lints/src/deprecated_lints.rs", - &mut |_, src, dst| { - let mut cursor = Cursor::new(src); - assert!( - cursor.find_ident("declare_with_version").is_some() - && cursor.find_ident("declare_with_version").is_some(), - "error reading deprecated lints" - ); - dst.push_str(&src[..cursor.pos() as usize]); - dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); - for &(name, data) in &deprecated { - write!( - dst, - " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", - data.version, data.reason, - ) - .unwrap(); - } - dst.push_str( - "]}\n\n\ - #[rustfmt::skip]\n\ - declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\ - ", - ); - for &(name, data) in &renamed { - write!( - dst, - " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", - data.version, data.new_name, - ) - .unwrap(); - } - dst.push_str("]}\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "tests/ui/deprecated.rs", - &mut |_, src, dst| { - dst.push_str(GENERATED_FILE_COMMENT); - for &(lint, _) in &deprecated { - writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); - } - dst.push_str("\nfn main() {}\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "tests/ui/rename.rs", - &mut move |_, src, dst| { - let mut seen_lints = HashSet::new(); - dst.push_str(GENERATED_FILE_COMMENT); - dst.push_str("#![allow(clippy::duplicated_attributes)]\n"); - for &(_, lint) in &renamed { - if seen_lints.insert(lint.new_name) { - writeln!(dst, "#![allow({})]", lint.new_name).unwrap(); - } - } - for &(lint, _) in &renamed { - writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); - } - dst.push_str("\nfn main() {}\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - for (crate_name, lints) in active.iter().copied().into_group_map_by(|&(_, lint)| { - let Some(path::Component::Normal(name)) = lint.path.components().next() else { - // All paths should start with `{crate_name}/src` when parsed from `find_lint_decls` - panic!( - "internal error: can't read crate name from path `{}`", - lint.path.display() - ); - }; - name - }) { - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - Path::new(crate_name).join("src/lib.rs"), - &mut update_text_region_fn( - "// begin lints modules, do not remove this comment, it's used in `update_lints`\n", - "// end lints modules, do not remove this comment, it's used in `update_lints`", - |dst| { - let mut prev = ""; - for &(_, lint) in &lints { - if lint.module != prev { - writeln!(dst, "mod {};", lint.module).unwrap(); - prev = lint.module; - } - } - }, - ), - ); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - Path::new(crate_name).join("src/declared_lints.rs"), - &mut |_, src, dst| { - dst.push_str(GENERATED_FILE_COMMENT); - dst.push_str("pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[\n"); - let mut buf = String::new(); - for &(name, lint) in &lints { - buf.clear(); - buf.push_str(name); - buf.make_ascii_uppercase(); - if lint.module.is_empty() { - writeln!(dst, " crate::{buf}_INFO,").unwrap(); - } else { - writeln!(dst, " crate::{}::{buf}_INFO,", lint.module).unwrap(); - } - } - dst.push_str("];\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - } -} diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs index 52452dd86b49..1f931140467e 100644 --- a/clippy_dev/src/utils.rs +++ b/clippy_dev/src/utils.rs @@ -1,5 +1,6 @@ use core::fmt::{self, Display}; use core::marker::PhantomData; +use core::mem; use core::num::NonZero; use core::ops::{Deref, DerefMut}; use core::range::Range; @@ -600,3 +601,29 @@ pub fn walk_dir_no_dot_or_target(p: impl AsRef) -> impl Iterator( + slice: &mut [T], + split_idx: impl FnMut(&T, &[T]) -> usize, +) -> impl Iterator { + struct I<'a, T, F> { + slice: &'a mut [T], + split_idx: F, + } + impl<'a, T, F: FnMut(&T, &[T]) -> usize> Iterator for I<'a, T, F> { + type Item = &'a mut [T]; + fn next(&mut self) -> Option { + let (head, tail) = self.slice.split_first()?; + let idx = (self.split_idx)(head, tail) + 1; + // `mem::take` makes it so `self.slice` isn't reborrowed. + if let Some((head, tail)) = mem::take(&mut self.slice).split_at_mut_checked(idx) { + self.slice = tail; + Some(head) + } else { + self.slice = &mut []; + None + } + } + } + I { slice, split_idx } +} diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index c0804dbb0492..51e753efb52e 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.95" +version = "0.1.96" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_lints/src/absolute_paths.rs b/clippy_lints/src/absolute_paths.rs index 1af6d448a93c..fd515939dfb8 100644 --- a/clippy_lints/src/absolute_paths.rs +++ b/clippy_lints/src/absolute_paths.rs @@ -52,6 +52,7 @@ restriction, "checks for usage of an item without a `use` statement" } + impl_lint_pass!(AbsolutePaths => [ABSOLUTE_PATHS]); pub struct AbsolutePaths { diff --git a/clippy_lints/src/almost_complete_range.rs b/clippy_lints/src/almost_complete_range.rs index 4f55968d5625..258970393023 100644 --- a/clippy_lints/src/almost_complete_range.rs +++ b/clippy_lints/src/almost_complete_range.rs @@ -28,6 +28,7 @@ suspicious, "almost complete range" } + impl_lint_pass!(AlmostCompleteRange => [ALMOST_COMPLETE_RANGE]); pub struct AlmostCompleteRange { diff --git a/clippy_lints/src/approx_const.rs b/clippy_lints/src/approx_const.rs index a3710ca51655..2ea921e5d461 100644 --- a/clippy_lints/src/approx_const.rs +++ b/clippy_lints/src/approx_const.rs @@ -39,6 +39,8 @@ "the approximate of a known float constant (in `std::fXX::consts`)" } +impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); + // Tuples are of the form (constant, name, min_digits, msrv) const KNOWN_CONSTS: [(f64, &str, usize, Option); 19] = [ (f64::E, "E", 4, None), @@ -111,8 +113,6 @@ fn check_known_consts(&self, cx: &LateContext<'_>, span: Span, s: symbol::Symbol } } -impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); - fn count_digits_after_dot(input: &str) -> usize { input .char_indices() diff --git a/clippy_lints/src/arc_with_non_send_sync.rs b/clippy_lints/src/arc_with_non_send_sync.rs index acfdfa65baed..e449b06199d3 100644 --- a/clippy_lints/src/arc_with_non_send_sync.rs +++ b/clippy_lints/src/arc_with_non_send_sync.rs @@ -39,6 +39,7 @@ suspicious, "using `Arc` with a type that does not implement `Send` and `Sync`" } + declare_lint_pass!(ArcWithNonSendSync => [ARC_WITH_NON_SEND_SYNC]); impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync { diff --git a/clippy_lints/src/asm_syntax.rs b/clippy_lints/src/asm_syntax.rs index 69a8eb7d94e7..3c5cf74d5a17 100644 --- a/clippy_lints/src/asm_syntax.rs +++ b/clippy_lints/src/asm_syntax.rs @@ -57,54 +57,6 @@ fn check_asm_syntax( } } -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of Intel x86 assembly syntax. - /// - /// ### Why restrict this? - /// To enforce consistent use of AT&T x86 assembly syntax. - /// - /// ### Example - /// - /// ```rust,no_run - /// # #![feature(asm)] - /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - /// # unsafe { let ptr = "".as_ptr(); - /// # use std::arch::asm; - /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); - /// # } - /// ``` - /// Use instead: - /// ```rust,no_run - /// # #![feature(asm)] - /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - /// # unsafe { let ptr = "".as_ptr(); - /// # use std::arch::asm; - /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); - /// # } - /// ``` - #[clippy::version = "1.49.0"] - pub INLINE_ASM_X86_INTEL_SYNTAX, - restriction, - "prefer AT&T x86 assembly syntax" -} - -declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]); - -impl EarlyLintPass for InlineAsmX86IntelSyntax { - fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if let ExprKind::InlineAsm(inline_asm) = &expr.kind { - check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Intel); - } - } - - fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { - if let ItemKind::GlobalAsm(inline_asm) = &item.kind { - check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, item.span, AsmStyle::Intel); - } - } -} - declare_clippy_lint! { /// ### What it does /// Checks for usage of AT&T x86 assembly syntax. @@ -137,8 +89,56 @@ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { "prefer Intel x86 assembly syntax" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of Intel x86 assembly syntax. + /// + /// ### Why restrict this? + /// To enforce consistent use of AT&T x86 assembly syntax. + /// + /// ### Example + /// + /// ```rust,no_run + /// # #![feature(asm)] + /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + /// # unsafe { let ptr = "".as_ptr(); + /// # use std::arch::asm; + /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); + /// # } + /// ``` + /// Use instead: + /// ```rust,no_run + /// # #![feature(asm)] + /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + /// # unsafe { let ptr = "".as_ptr(); + /// # use std::arch::asm; + /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); + /// # } + /// ``` + #[clippy::version = "1.49.0"] + pub INLINE_ASM_X86_INTEL_SYNTAX, + restriction, + "prefer AT&T x86 assembly syntax" +} + declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]); +declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]); + +impl EarlyLintPass for InlineAsmX86IntelSyntax { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::InlineAsm(inline_asm) = &expr.kind { + check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Intel); + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if let ItemKind::GlobalAsm(inline_asm) = &item.kind { + check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, item.span, AsmStyle::Intel); + } + } +} + impl EarlyLintPass for InlineAsmX86AttSyntax { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { if let ExprKind::InlineAsm(inline_asm) = &expr.kind { diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index 4aa55e53445c..6e57d0608bed 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -33,6 +33,7 @@ } impl_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]); + pub struct AssertionsOnConstants { msrv: Msrv, } diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index efce23d13a38..60bc9b2b5b85 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -53,6 +53,8 @@ "assigning the result of cloning may be inefficient" } +impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]); + pub struct AssigningClones { msrv: Msrv, } @@ -63,8 +65,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]); - impl<'tcx> LateLintPass<'tcx> for AssigningClones { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { if let ExprKind::Assign(lhs, rhs, _) = e.kind diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index fa2951d91934..c15a378053e3 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -25,106 +25,60 @@ declare_clippy_lint! { /// ### What it does - /// Checks for items annotated with `#[inline(always)]`, - /// unless the annotated function is empty or simply panics. + /// Checks for usage of the `#[allow]` attribute and suggests replacing it with + /// the `#[expect]` attribute (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html)) + /// + /// This lint only warns outer attributes (`#[allow]`), as inner attributes + /// (`#![allow]`) are usually used to enable or disable lints on a global scale. /// /// ### Why is this bad? - /// While there are valid uses of this annotation (and once - /// you know when to use it, by all means `allow` this lint), it's a common - /// newbie-mistake to pepper one's code with it. - /// - /// As a rule of thumb, before slapping `#[inline(always)]` on a function, - /// measure if that additional function call really affects your runtime profile - /// sufficiently to make up for the increase in compile time. - /// - /// ### Known problems - /// False positives, big time. This lint is meant to be - /// deactivated by everyone doing serious performance work. This means having - /// done the measurement. + /// `#[expect]` attributes suppress the lint emission, but emit a warning, if + /// the expectation is unfulfilled. This can be useful to be notified when the + /// lint is no longer triggered. /// /// ### Example - /// ```ignore - /// #[inline(always)] - /// fn not_quite_hot_code(..) { ... } + /// ```rust,ignore + /// #[allow(unused_mut)] + /// fn foo() -> usize { + /// let mut a = Vec::new(); + /// a.len() + /// } /// ``` - #[clippy::version = "pre 1.29.0"] - pub INLINE_ALWAYS, - pedantic, - "use of `#[inline(always)]`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `extern crate` and `use` items annotated with - /// lint attributes. - /// - /// This lint permits lint attributes for lints emitted on the items themself. - /// For `use` items these lints are: - /// * ambiguous_glob_reexports - /// * dead_code - /// * deprecated - /// * hidden_glob_reexports - /// * unreachable_pub - /// * unused - /// * unused_braces - /// * unused_import_braces - /// * clippy::disallowed_types - /// * clippy::enum_glob_use - /// * clippy::macro_use_imports - /// * clippy::module_name_repetitions - /// * clippy::redundant_pub_crate - /// * clippy::single_component_path_imports - /// * clippy::unsafe_removed_from_name - /// * clippy::wildcard_imports - /// - /// For `extern crate` items these lints are: - /// * `unused_imports` on items with `#[macro_use]` - /// - /// ### Why is this bad? - /// Lint attributes have no effect on crate imports. Most - /// likely a `!` was forgotten. - /// - /// ### Example - /// ```ignore - /// #[deny(dead_code)] - /// extern crate foo; - /// #[forbid(dead_code)] - /// use foo::bar; - /// ``` - /// /// Use instead: /// ```rust,ignore - /// #[allow(unused_imports)] - /// use foo::baz; - /// #[allow(unused_imports)] - /// #[macro_use] - /// extern crate baz; + /// #[expect(unused_mut)] + /// fn foo() -> usize { + /// let mut a = Vec::new(); + /// a.len() + /// } /// ``` - #[clippy::version = "pre 1.29.0"] - pub USELESS_ATTRIBUTE, - correctness, - "use of lint attributes on `extern crate` items" + #[clippy::version = "1.70.0"] + pub ALLOW_ATTRIBUTES, + restriction, + "`#[allow]` will not trigger if a warning isn't found. `#[expect]` triggers if there are no warnings." } declare_clippy_lint! { /// ### What it does - /// Checks for `#[deprecated]` annotations with a `since` - /// field that is not a valid semantic version. Also allows "TBD" to signal - /// future deprecation. + /// Checks for attributes that allow lints without a reason. /// - /// ### Why is this bad? - /// For checking the version of the deprecation, it must be - /// a valid semver. Failing that, the contained information is useless. + /// ### Why restrict this? + /// Justifying each `allow` helps readers understand the reasoning, + /// and may allow removing `allow` attributes if their purpose is obsolete. /// /// ### Example /// ```no_run - /// #[deprecated(since = "forever")] - /// fn something_else() { /* ... */ } + /// #![allow(clippy::some_lint)] /// ``` - #[clippy::version = "pre 1.29.0"] - pub DEPRECATED_SEMVER, - correctness, - "use of `#[deprecated(since = \"x\")]` where x is not semver" + /// + /// Use instead: + /// ```no_run + /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")] + /// ``` + #[clippy::version = "1.61.0"] + pub ALLOW_ATTRIBUTES_WITHOUT_REASON, + restriction, + "ensures that all `allow` and `expect` attributes have a reason" } declare_clippy_lint! { @@ -181,161 +135,6 @@ "usage of `cfg_attr(rustfmt)` instead of tool attributes" } -declare_clippy_lint! { - /// ### What it does - /// Checks for attributes that allow lints without a reason. - /// - /// ### Why restrict this? - /// Justifying each `allow` helps readers understand the reasoning, - /// and may allow removing `allow` attributes if their purpose is obsolete. - /// - /// ### Example - /// ```no_run - /// #![allow(clippy::some_lint)] - /// ``` - /// - /// Use instead: - /// ```no_run - /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")] - /// ``` - #[clippy::version = "1.61.0"] - pub ALLOW_ATTRIBUTES_WITHOUT_REASON, - restriction, - "ensures that all `allow` and `expect` attributes have a reason" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of the `#[allow]` attribute and suggests replacing it with - /// the `#[expect]` attribute (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html)) - /// - /// This lint only warns outer attributes (`#[allow]`), as inner attributes - /// (`#![allow]`) are usually used to enable or disable lints on a global scale. - /// - /// ### Why is this bad? - /// `#[expect]` attributes suppress the lint emission, but emit a warning, if - /// the expectation is unfulfilled. This can be useful to be notified when the - /// lint is no longer triggered. - /// - /// ### Example - /// ```rust,ignore - /// #[allow(unused_mut)] - /// fn foo() -> usize { - /// let mut a = Vec::new(); - /// a.len() - /// } - /// ``` - /// Use instead: - /// ```rust,ignore - /// #[expect(unused_mut)] - /// fn foo() -> usize { - /// let mut a = Vec::new(); - /// a.len() - /// } - /// ``` - #[clippy::version = "1.70.0"] - pub ALLOW_ATTRIBUTES, - restriction, - "`#[allow]` will not trigger if a warning isn't found. `#[expect]` triggers if there are no warnings." -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `#[should_panic]` attributes without specifying the expected panic message. - /// - /// ### Why is this bad? - /// The expected panic message should be specified to ensure that the test is actually - /// panicking with the expected message, and not another unrelated panic. - /// - /// ### Example - /// ```no_run - /// fn random() -> i32 { 0 } - /// - /// #[should_panic] - /// #[test] - /// fn my_test() { - /// let _ = 1 / random(); - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// fn random() -> i32 { 0 } - /// - /// #[should_panic = "attempt to divide by zero"] - /// #[test] - /// fn my_test() { - /// let _ = 1 / random(); - /// } - /// ``` - #[clippy::version = "1.74.0"] - pub SHOULD_PANIC_WITHOUT_EXPECT, - pedantic, - "ensures that all `should_panic` attributes specify its expected panic message" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for items with `#[repr(packed)]`-attribute without ABI qualification - /// - /// ### Why is this bad? - /// Without qualification, `repr(packed)` implies `repr(Rust)`. The Rust-ABI is inherently unstable. - /// While this is fine as long as the type is accessed correctly within Rust-code, most uses - /// of `#[repr(packed)]` involve FFI and/or data structures specified by network-protocols or - /// other external specifications. In such situations, the unstable Rust-ABI implied in - /// `#[repr(packed)]` may lead to future bugs should the Rust-ABI change. - /// - /// In case you are relying on a well defined and stable memory layout, qualify the type's - /// representation using the `C`-ABI. Otherwise, if the type in question is only ever - /// accessed from Rust-code according to Rust's rules, use the `Rust`-ABI explicitly. - /// - /// ### Example - /// ```no_run - /// #[repr(packed)] - /// struct NetworkPacketHeader { - /// header_length: u8, - /// header_version: u16 - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// #[repr(C, packed)] - /// struct NetworkPacketHeader { - /// header_length: u8, - /// header_version: u16 - /// } - /// ``` - #[clippy::version = "1.85.0"] - pub REPR_PACKED_WITHOUT_ABI, - suspicious, - "ensures that `repr(packed)` always comes with a qualified ABI" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `any` and `all` combinators in `cfg` with only one condition. - /// - /// ### Why is this bad? - /// If there is only one condition, no need to wrap it into `any` or `all` combinators. - /// - /// ### Example - /// ```no_run - /// #[cfg(any(unix))] - /// pub struct Bar; - /// ``` - /// - /// Use instead: - /// ```no_run - /// #[cfg(unix)] - /// pub struct Bar; - /// ``` - #[clippy::version = "1.71.0"] - pub NON_MINIMAL_CFG, - style, - "ensure that all `cfg(any())` and `cfg(all())` have more than one condition" -} - declare_clippy_lint! { /// ### What it does /// Checks for `#[cfg_attr(feature = "cargo-clippy", ...)]` and for @@ -364,63 +163,23 @@ declare_clippy_lint! { /// ### What it does - /// Checks for `#[cfg_attr(clippy, allow(clippy::lint))]` - /// and suggests to replace it with `#[allow(clippy::lint)]`. + /// Checks for `#[deprecated]` annotations with a `since` + /// field that is not a valid semantic version. Also allows "TBD" to signal + /// future deprecation. /// /// ### Why is this bad? - /// There is no reason to put clippy attributes behind a clippy `cfg` as they are not - /// run by anything else than clippy. + /// For checking the version of the deprecation, it must be + /// a valid semver. Failing that, the contained information is useless. /// /// ### Example /// ```no_run - /// #![cfg_attr(clippy, allow(clippy::deprecated_cfg_attr))] + /// #[deprecated(since = "forever")] + /// fn something_else() { /* ... */ } /// ``` - /// - /// Use instead: - /// ```no_run - /// #![allow(clippy::deprecated_cfg_attr)] - /// ``` - #[clippy::version = "1.78.0"] - pub UNNECESSARY_CLIPPY_CFG, - suspicious, - "usage of `cfg_attr(clippy, allow(clippy::lint))` instead of `allow(clippy::lint)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for items that have the same kind of attributes with mixed styles (inner/outer). - /// - /// ### Why is this bad? - /// Having both style of said attributes makes it more complicated to read code. - /// - /// ### Known problems - /// This lint currently has false-negatives when mixing same attributes - /// but they have different path symbols, for example: - /// ```ignore - /// #[custom_attribute] - /// pub fn foo() { - /// #![my_crate::custom_attribute] - /// } - /// ``` - /// - /// ### Example - /// ```no_run - /// #[cfg(linux)] - /// pub fn foo() { - /// #![cfg(windows)] - /// } - /// ``` - /// Use instead: - /// ```no_run - /// #[cfg(linux)] - /// #[cfg(windows)] - /// pub fn foo() { - /// } - /// ``` - #[clippy::version = "1.78.0"] - pub MIXED_ATTRIBUTES_STYLE, - style, - "item has both inner and outer attributes" + #[clippy::version = "pre 1.29.0"] + pub DEPRECATED_SEMVER, + correctness, + "use of `#[deprecated(since = \"x\")]` where x is not semver" } declare_clippy_lint! { @@ -478,15 +237,272 @@ "ignored tests without messages" } +declare_clippy_lint! { + /// ### What it does + /// Checks for items annotated with `#[inline(always)]`, + /// unless the annotated function is empty or simply panics. + /// + /// ### Why is this bad? + /// While there are valid uses of this annotation (and once + /// you know when to use it, by all means `allow` this lint), it's a common + /// newbie-mistake to pepper one's code with it. + /// + /// As a rule of thumb, before slapping `#[inline(always)]` on a function, + /// measure if that additional function call really affects your runtime profile + /// sufficiently to make up for the increase in compile time. + /// + /// ### Known problems + /// False positives, big time. This lint is meant to be + /// deactivated by everyone doing serious performance work. This means having + /// done the measurement. + /// + /// ### Example + /// ```ignore + /// #[inline(always)] + /// fn not_quite_hot_code(..) { ... } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub INLINE_ALWAYS, + pedantic, + "use of `#[inline(always)]`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for items that have the same kind of attributes with mixed styles (inner/outer). + /// + /// ### Why is this bad? + /// Having both style of said attributes makes it more complicated to read code. + /// + /// ### Known problems + /// This lint currently has false-negatives when mixing same attributes + /// but they have different path symbols, for example: + /// ```ignore + /// #[custom_attribute] + /// pub fn foo() { + /// #![my_crate::custom_attribute] + /// } + /// ``` + /// + /// ### Example + /// ```no_run + /// #[cfg(linux)] + /// pub fn foo() { + /// #![cfg(windows)] + /// } + /// ``` + /// Use instead: + /// ```no_run + /// #[cfg(linux)] + /// #[cfg(windows)] + /// pub fn foo() { + /// } + /// ``` + #[clippy::version = "1.78.0"] + pub MIXED_ATTRIBUTES_STYLE, + style, + "item has both inner and outer attributes" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `any` and `all` combinators in `cfg` with only one condition. + /// + /// ### Why is this bad? + /// If there is only one condition, no need to wrap it into `any` or `all` combinators. + /// + /// ### Example + /// ```no_run + /// #[cfg(any(unix))] + /// pub struct Bar; + /// ``` + /// + /// Use instead: + /// ```no_run + /// #[cfg(unix)] + /// pub struct Bar; + /// ``` + #[clippy::version = "1.71.0"] + pub NON_MINIMAL_CFG, + style, + "ensure that all `cfg(any())` and `cfg(all())` have more than one condition" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for items with `#[repr(packed)]`-attribute without ABI qualification + /// + /// ### Why is this bad? + /// Without qualification, `repr(packed)` implies `repr(Rust)`. The Rust-ABI is inherently unstable. + /// While this is fine as long as the type is accessed correctly within Rust-code, most uses + /// of `#[repr(packed)]` involve FFI and/or data structures specified by network-protocols or + /// other external specifications. In such situations, the unstable Rust-ABI implied in + /// `#[repr(packed)]` may lead to future bugs should the Rust-ABI change. + /// + /// In case you are relying on a well defined and stable memory layout, qualify the type's + /// representation using the `C`-ABI. Otherwise, if the type in question is only ever + /// accessed from Rust-code according to Rust's rules, use the `Rust`-ABI explicitly. + /// + /// ### Example + /// ```no_run + /// #[repr(packed)] + /// struct NetworkPacketHeader { + /// header_length: u8, + /// header_version: u16 + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// #[repr(C, packed)] + /// struct NetworkPacketHeader { + /// header_length: u8, + /// header_version: u16 + /// } + /// ``` + #[clippy::version = "1.85.0"] + pub REPR_PACKED_WITHOUT_ABI, + suspicious, + "ensures that `repr(packed)` always comes with a qualified ABI" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `#[should_panic]` attributes without specifying the expected panic message. + /// + /// ### Why is this bad? + /// The expected panic message should be specified to ensure that the test is actually + /// panicking with the expected message, and not another unrelated panic. + /// + /// ### Example + /// ```no_run + /// fn random() -> i32 { 0 } + /// + /// #[should_panic] + /// #[test] + /// fn my_test() { + /// let _ = 1 / random(); + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// fn random() -> i32 { 0 } + /// + /// #[should_panic = "attempt to divide by zero"] + /// #[test] + /// fn my_test() { + /// let _ = 1 / random(); + /// } + /// ``` + #[clippy::version = "1.74.0"] + pub SHOULD_PANIC_WITHOUT_EXPECT, + pedantic, + "ensures that all `should_panic` attributes specify its expected panic message" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `#[cfg_attr(clippy, allow(clippy::lint))]` + /// and suggests to replace it with `#[allow(clippy::lint)]`. + /// + /// ### Why is this bad? + /// There is no reason to put clippy attributes behind a clippy `cfg` as they are not + /// run by anything else than clippy. + /// + /// ### Example + /// ```no_run + /// #![cfg_attr(clippy, allow(clippy::deprecated_cfg_attr))] + /// ``` + /// + /// Use instead: + /// ```no_run + /// #![allow(clippy::deprecated_cfg_attr)] + /// ``` + #[clippy::version = "1.78.0"] + pub UNNECESSARY_CLIPPY_CFG, + suspicious, + "usage of `cfg_attr(clippy, allow(clippy::lint))` instead of `allow(clippy::lint)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `extern crate` and `use` items annotated with + /// lint attributes. + /// + /// This lint permits lint attributes for lints emitted on the items themself. + /// For `use` items these lints are: + /// * ambiguous_glob_reexports + /// * dead_code + /// * deprecated + /// * hidden_glob_reexports + /// * unreachable_pub + /// * unused + /// * unused_braces + /// * unused_import_braces + /// * clippy::disallowed_types + /// * clippy::enum_glob_use + /// * clippy::macro_use_imports + /// * clippy::module_name_repetitions + /// * clippy::redundant_pub_crate + /// * clippy::single_component_path_imports + /// * clippy::unsafe_removed_from_name + /// * clippy::wildcard_imports + /// + /// For `extern crate` items these lints are: + /// * `unused_imports` on items with `#[macro_use]` + /// + /// ### Why is this bad? + /// Lint attributes have no effect on crate imports. Most + /// likely a `!` was forgotten. + /// + /// ### Example + /// ```ignore + /// #[deny(dead_code)] + /// extern crate foo; + /// #[forbid(dead_code)] + /// use foo::bar; + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// #[allow(unused_imports)] + /// use foo::baz; + /// #[allow(unused_imports)] + /// #[macro_use] + /// extern crate baz; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub USELESS_ATTRIBUTE, + correctness, + "use of lint attributes on `extern crate` items" +} + +impl_lint_pass!(Attributes => [INLINE_ALWAYS, REPR_PACKED_WITHOUT_ABI]); + +impl_lint_pass!(EarlyAttributes => [ + DEPRECATED_CFG_ATTR, + DEPRECATED_CLIPPY_CFG_ATTR, + NON_MINIMAL_CFG, + UNNECESSARY_CLIPPY_CFG, +]); + +impl_lint_pass!(PostExpansionEarlyAttributes => [ + ALLOW_ATTRIBUTES, + ALLOW_ATTRIBUTES_WITHOUT_REASON, + BLANKET_CLIPPY_RESTRICTION_LINTS, + DEPRECATED_SEMVER, + DUPLICATED_ATTRIBUTES, + IGNORE_WITHOUT_REASON, + MIXED_ATTRIBUTES_STYLE, + SHOULD_PANIC_WITHOUT_EXPECT, + USELESS_ATTRIBUTE, +]); + pub struct Attributes { msrv: Msrv, } -impl_lint_pass!(Attributes => [ - INLINE_ALWAYS, - REPR_PACKED_WITHOUT_ABI, -]); - impl Attributes { pub fn new(conf: &'static Conf) -> Self { Self { msrv: conf.msrv } @@ -529,13 +545,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(EarlyAttributes => [ - DEPRECATED_CFG_ATTR, - NON_MINIMAL_CFG, - DEPRECATED_CLIPPY_CFG_ATTR, - UNNECESSARY_CLIPPY_CFG, -]); - impl EarlyLintPass for EarlyAttributes { fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { deprecated_cfg_attr::check(cx, attr, &self.msrv); @@ -558,18 +567,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(PostExpansionEarlyAttributes => [ - ALLOW_ATTRIBUTES, - ALLOW_ATTRIBUTES_WITHOUT_REASON, - DEPRECATED_SEMVER, - IGNORE_WITHOUT_REASON, - USELESS_ATTRIBUTE, - BLANKET_CLIPPY_RESTRICTION_LINTS, - SHOULD_PANIC_WITHOUT_EXPECT, - MIXED_ATTRIBUTES_STYLE, - DUPLICATED_ATTRIBUTES, -]); - impl EarlyLintPass for PostExpansionEarlyAttributes { fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &ast::Crate) { blanket_clippy_restriction_lints::check_command_line(cx); diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 4ee955c035da..9e5b57a05580 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -11,6 +11,43 @@ use rustc_session::impl_lint_pass; use rustc_span::Span; +declare_clippy_lint! { + /// ### What it does + /// Allows users to configure types which should not be held across await + /// suspension points. + /// + /// ### Why is this bad? + /// There are some types which are perfectly safe to use concurrently from + /// a memory access perspective, but that will cause bugs at runtime if + /// they are held in such a way. + /// + /// ### Example + /// + /// ```toml + /// await-holding-invalid-types = [ + /// # You can specify a type name + /// "CustomLockType", + /// # You can (optionally) specify a reason + /// { path = "OtherCustomLockType", reason = "Relies on a thread local" } + /// ] + /// ``` + /// + /// ```no_run + /// # async fn baz() {} + /// struct CustomLockType; + /// struct OtherCustomLockType; + /// async fn foo() { + /// let _x = CustomLockType; + /// let _y = OtherCustomLockType; + /// baz().await; // Lint violation + /// } + /// ``` + #[clippy::version = "1.62.0"] + pub AWAIT_HOLDING_INVALID_TYPE, + suspicious, + "holding a type across an await point which is not allowed to be held as per the configuration" +} + declare_clippy_lint! { /// ### What it does /// Checks for calls to `await` while holding a non-async-aware @@ -135,44 +172,11 @@ "inside an async function, holding a `RefCell` ref while calling `await`" } -declare_clippy_lint! { - /// ### What it does - /// Allows users to configure types which should not be held across await - /// suspension points. - /// - /// ### Why is this bad? - /// There are some types which are perfectly safe to use concurrently from - /// a memory access perspective, but that will cause bugs at runtime if - /// they are held in such a way. - /// - /// ### Example - /// - /// ```toml - /// await-holding-invalid-types = [ - /// # You can specify a type name - /// "CustomLockType", - /// # You can (optionally) specify a reason - /// { path = "OtherCustomLockType", reason = "Relies on a thread local" } - /// ] - /// ``` - /// - /// ```no_run - /// # async fn baz() {} - /// struct CustomLockType; - /// struct OtherCustomLockType; - /// async fn foo() { - /// let _x = CustomLockType; - /// let _y = OtherCustomLockType; - /// baz().await; // Lint violation - /// } - /// ``` - #[clippy::version = "1.62.0"] - pub AWAIT_HOLDING_INVALID_TYPE, - suspicious, - "holding a type across an await point which is not allowed to be held as per the configuration" -} - -impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_INVALID_TYPE]); +impl_lint_pass!(AwaitHolding => [ + AWAIT_HOLDING_INVALID_TYPE, + AWAIT_HOLDING_LOCK, + AWAIT_HOLDING_REFCELL_REF, +]); pub struct AwaitHolding { def_ids: DefIdMap<(&'static str, &'static DisallowedPathWithoutReplacement)>, diff --git a/clippy_lints/src/bool_to_int_with_if.rs b/clippy_lints/src/bool_to_int_with_if.rs index 129e77478406..b98a20a90ccb 100644 --- a/clippy_lints/src/bool_to_int_with_if.rs +++ b/clippy_lints/src/bool_to_int_with_if.rs @@ -43,6 +43,7 @@ pedantic, "using if to convert bool to int" } + declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]); impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf { diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 0bd459d8b021..8b7619d11a83 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -74,6 +74,8 @@ "boolean expressions that contain terminals which can be eliminated" } +impl_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]); + // For each pairs, both orders are considered. const METHODS_WITH_NEGATION: [(Option, Symbol, Symbol); 3] = [ (None, sym::is_some, sym::is_none), @@ -91,8 +93,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]); - impl<'tcx> LateLintPass<'tcx> for NonminimalBool { fn check_fn( &mut self, diff --git a/clippy_lints/src/box_default.rs b/clippy_lints/src/box_default.rs index b78aa1f1b63d..34b96b1d9d7a 100644 --- a/clippy_lints/src/box_default.rs +++ b/clippy_lints/src/box_default.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::{is_default_equivalent, sym}; use clippy_utils::macros::macro_backtrace; use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::ty::expr_sig; +use clippy_utils::{is_default_equivalent, sym}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_ty}; diff --git a/clippy_lints/src/byte_char_slices.rs b/clippy_lints/src/byte_char_slices.rs index fc9931439e93..6c023189f69e 100644 --- a/clippy_lints/src/byte_char_slices.rs +++ b/clippy_lints/src/byte_char_slices.rs @@ -27,6 +27,7 @@ style, "hard to read byte char slice" } + declare_lint_pass!(ByteCharSlice => [BYTE_CHAR_SLICES]); impl EarlyLintPass for ByteCharSlice { diff --git a/clippy_lints/src/cargo/mod.rs b/clippy_lints/src/cargo/mod.rs index 08d92adbacef..0aa6e4c3e8e2 100644 --- a/clippy_lints/src/cargo/mod.rs +++ b/clippy_lints/src/cargo/mod.rs @@ -56,126 +56,6 @@ "common metadata is defined in `Cargo.toml`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for feature names with prefix `use-`, `with-` or suffix `-support` - /// - /// ### Why is this bad? - /// These prefixes and suffixes have no significant meaning. - /// - /// ### Example - /// ```toml - /// # The `Cargo.toml` with feature name redundancy - /// [features] - /// default = ["use-abc", "with-def", "ghi-support"] - /// use-abc = [] // redundant - /// with-def = [] // redundant - /// ghi-support = [] // redundant - /// ``` - /// - /// Use instead: - /// ```toml - /// [features] - /// default = ["abc", "def", "ghi"] - /// abc = [] - /// def = [] - /// ghi = [] - /// ``` - /// - #[clippy::version = "1.57.0"] - pub REDUNDANT_FEATURE_NAMES, - cargo, - "usage of a redundant feature name" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for negative feature names with prefix `no-` or `not-` - /// - /// ### Why is this bad? - /// Features are supposed to be additive, and negatively-named features violate it. - /// - /// ### Example - /// ```toml - /// # The `Cargo.toml` with negative feature names - /// [features] - /// default = [] - /// no-abc = [] - /// not-def = [] - /// - /// ``` - /// Use instead: - /// ```toml - /// [features] - /// default = ["abc", "def"] - /// abc = [] - /// def = [] - /// - /// ``` - #[clippy::version = "1.57.0"] - pub NEGATIVE_FEATURE_NAMES, - cargo, - "usage of a negative feature name" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks to see if multiple versions of a crate are being - /// used. - /// - /// ### Why is this bad? - /// This bloats the size of targets, and can lead to - /// confusing error messages when structs or traits are used interchangeably - /// between different versions of a crate. - /// - /// ### Known problems - /// Because this can be caused purely by the dependencies - /// themselves, it's not always possible to fix this issue. - /// In those cases, you can allow that specific crate using - /// the `allowed-duplicate-crates` configuration option. - /// - /// ### Example - /// ```toml - /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. - /// [dependencies] - /// ctrlc = "=3.1.0" - /// ansi_term = "=0.11.0" - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MULTIPLE_CRATE_VERSIONS, - cargo, - "multiple versions of the same crate being used" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for wildcard dependencies in the `Cargo.toml`. - /// - /// ### Why is this bad? - /// [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html), - /// it is highly unlikely that you work with any possible version of your dependency, - /// and wildcard dependencies would cause unnecessary breakage in the ecosystem. - /// - /// ### Example - /// ```toml - /// [dependencies] - /// regex = "*" - /// ``` - /// Use instead: - /// ```toml - /// [dependencies] - /// # allow patch updates, but not minor or major version changes - /// some_crate_1 = "~1.2.3" - /// - /// # pin the version to a specific version - /// some_crate_2 = "=1.2.3" - /// ``` - #[clippy::version = "1.32.0"] - pub WILDCARD_DEPENDENCIES, - cargo, - "wildcard dependencies being used" -} - declare_clippy_lint! { /// ### What it does /// Checks for lint groups with the same priority as lints in the `Cargo.toml` @@ -213,20 +93,140 @@ "a lint group in `Cargo.toml` at the same priority as a lint" } -pub struct Cargo { - allowed_duplicate_crates: FxHashSet, - ignore_publish: bool, +declare_clippy_lint! { + /// ### What it does + /// Checks to see if multiple versions of a crate are being + /// used. + /// + /// ### Why is this bad? + /// This bloats the size of targets, and can lead to + /// confusing error messages when structs or traits are used interchangeably + /// between different versions of a crate. + /// + /// ### Known problems + /// Because this can be caused purely by the dependencies + /// themselves, it's not always possible to fix this issue. + /// In those cases, you can allow that specific crate using + /// the `allowed-duplicate-crates` configuration option. + /// + /// ### Example + /// ```toml + /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. + /// [dependencies] + /// ctrlc = "=3.1.0" + /// ansi_term = "=0.11.0" + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MULTIPLE_CRATE_VERSIONS, + cargo, + "multiple versions of the same crate being used" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for negative feature names with prefix `no-` or `not-` + /// + /// ### Why is this bad? + /// Features are supposed to be additive, and negatively-named features violate it. + /// + /// ### Example + /// ```toml + /// # The `Cargo.toml` with negative feature names + /// [features] + /// default = [] + /// no-abc = [] + /// not-def = [] + /// + /// ``` + /// Use instead: + /// ```toml + /// [features] + /// default = ["abc", "def"] + /// abc = [] + /// def = [] + /// + /// ``` + #[clippy::version = "1.57.0"] + pub NEGATIVE_FEATURE_NAMES, + cargo, + "usage of a negative feature name" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for feature names with prefix `use-`, `with-` or suffix `-support` + /// + /// ### Why is this bad? + /// These prefixes and suffixes have no significant meaning. + /// + /// ### Example + /// ```toml + /// # The `Cargo.toml` with feature name redundancy + /// [features] + /// default = ["use-abc", "with-def", "ghi-support"] + /// use-abc = [] // redundant + /// with-def = [] // redundant + /// ghi-support = [] // redundant + /// ``` + /// + /// Use instead: + /// ```toml + /// [features] + /// default = ["abc", "def", "ghi"] + /// abc = [] + /// def = [] + /// ghi = [] + /// ``` + /// + #[clippy::version = "1.57.0"] + pub REDUNDANT_FEATURE_NAMES, + cargo, + "usage of a redundant feature name" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for wildcard dependencies in the `Cargo.toml`. + /// + /// ### Why is this bad? + /// [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html), + /// it is highly unlikely that you work with any possible version of your dependency, + /// and wildcard dependencies would cause unnecessary breakage in the ecosystem. + /// + /// ### Example + /// ```toml + /// [dependencies] + /// regex = "*" + /// ``` + /// Use instead: + /// ```toml + /// [dependencies] + /// # allow patch updates, but not minor or major version changes + /// some_crate_1 = "~1.2.3" + /// + /// # pin the version to a specific version + /// some_crate_2 = "=1.2.3" + /// ``` + #[clippy::version = "1.32.0"] + pub WILDCARD_DEPENDENCIES, + cargo, + "wildcard dependencies being used" } impl_lint_pass!(Cargo => [ CARGO_COMMON_METADATA, - REDUNDANT_FEATURE_NAMES, - NEGATIVE_FEATURE_NAMES, - MULTIPLE_CRATE_VERSIONS, - WILDCARD_DEPENDENCIES, LINT_GROUPS_PRIORITY, + MULTIPLE_CRATE_VERSIONS, + NEGATIVE_FEATURE_NAMES, + REDUNDANT_FEATURE_NAMES, + WILDCARD_DEPENDENCIES, ]); +pub struct Cargo { + allowed_duplicate_crates: FxHashSet, + ignore_publish: bool, +} + impl Cargo { pub fn new(conf: &'static Conf) -> Self { Self { diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 3c9ebef73f0d..75761de4ae73 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -36,50 +36,231 @@ declare_clippy_lint! { /// ### What it does - /// Checks for casts from any numeric type to a float type where - /// the receiving type cannot store all values from the original type without - /// rounding errors. This possible rounding is to be expected, so this lint is - /// `Allow` by default. + /// Checks for the usage of `as *const _` or `as *mut _` conversion using inferred type. /// - /// Basically, this warns on casting any integer with 32 or more bits to `f32` - /// or any 64-bit integer to `f64`. - /// - /// ### Why is this bad? - /// It's not bad at all. But in some applications it can be - /// helpful to know where precision loss can take place. This lint can help find - /// those places in the code. + /// ### Why restrict this? + /// The conversion might include a dangerous cast that might go undetected due to the type being inferred. /// /// ### Example /// ```no_run - /// let x = u64::MAX; - /// x as f64; + /// fn as_usize(t: &T) -> usize { + /// // BUG: `t` is already a reference, so we will here + /// // return a dangling pointer to a temporary value instead + /// &t as *const _ as usize + /// } /// ``` - #[clippy::version = "pre 1.29.0"] - pub CAST_PRECISION_LOSS, - pedantic, - "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" + /// Use instead: + /// ```no_run + /// fn as_usize(t: &T) -> usize { + /// t as *const T as usize + /// } + /// ``` + #[clippy::version = "1.85.0"] + pub AS_POINTER_UNDERSCORE, + restriction, + "detects `as *mut _` and `as *const _` conversion" } declare_clippy_lint! { /// ### What it does - /// Checks for casts from a signed to an unsigned numeric - /// type. In this case, negative values wrap around to large positive values, - /// which can be quite surprising in practice. However, since the cast works as - /// defined, this lint is `Allow` by default. + /// Checks for the result of a `&self`-taking `as_ptr` being cast to a mutable pointer. /// /// ### Why is this bad? - /// Possibly surprising results. You can activate this lint - /// as a one-time check to see where numeric wrapping can arise. + /// Since `as_ptr` takes a `&self`, the pointer won't have write permissions unless interior + /// mutability is used, making it unlikely that having it as a mutable pointer is correct. /// /// ### Example /// ```no_run - /// let y: i8 = -1; - /// y as u64; // will return 18446744073709551615 + /// let mut vec = Vec::::with_capacity(1); + /// let ptr = vec.as_ptr() as *mut u8; + /// unsafe { ptr.write(4) }; // UNDEFINED BEHAVIOUR + /// ``` + /// Use instead: + /// ```no_run + /// let mut vec = Vec::::with_capacity(1); + /// let ptr = vec.as_mut_ptr(); + /// unsafe { ptr.write(4) }; + /// ``` + #[clippy::version = "1.66.0"] + pub AS_PTR_CAST_MUT, + nursery, + "casting the result of the `&self`-taking `as_ptr` to a mutable pointer" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `as _` conversion using inferred type. + /// + /// ### Why restrict this? + /// The conversion might include lossy conversion or a dangerous cast that might go + /// undetected due to the type being inferred. + /// + /// The lint is allowed by default as using `_` is less wordy than always specifying the type. + /// + /// ### Example + /// ```no_run + /// fn foo(n: usize) {} + /// let n: u16 = 256; + /// foo(n as _); + /// ``` + /// Use instead: + /// ```no_run + /// fn foo(n: usize) {} + /// let n: u16 = 256; + /// foo(n as usize); + /// ``` + #[clippy::version = "1.63.0"] + pub AS_UNDERSCORE, + restriction, + "detects `as _` conversion" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `&expr as *const T` or + /// `&mut expr as *mut T`, and suggest using `&raw const` or + /// `&raw mut` instead. + /// + /// ### Why is this bad? + /// This would improve readability and avoid creating a reference + /// that points to an uninitialized value or unaligned place. + /// Read the `&raw` explanation in the Reference for more information. + /// + /// ### Example + /// ```no_run + /// let val = 1; + /// let p = &val as *const i32; + /// + /// let mut val_mut = 1; + /// let p_mut = &mut val_mut as *mut i32; + /// ``` + /// Use instead: + /// ```no_run + /// let val = 1; + /// let p = &raw const val; + /// + /// let mut val_mut = 1; + /// let p_mut = &raw mut val_mut; + /// ``` + #[clippy::version = "1.60.0"] + pub BORROW_AS_PTR, + pedantic, + "borrowing just to cast to a raw pointer" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of the `abs()` method that cast the result to unsigned. + /// + /// ### Why is this bad? + /// The `unsigned_abs()` method avoids panic when called on the MIN value. + /// + /// ### Example + /// ```no_run + /// let x: i32 = -42; + /// let y: u32 = x.abs() as u32; + /// ``` + /// Use instead: + /// ```no_run + /// let x: i32 = -42; + /// let y: u32 = x.unsigned_abs(); + /// ``` + #[clippy::version = "1.62.0"] + pub CAST_ABS_TO_UNSIGNED, + suspicious, + "casting the result of `abs()` to an unsigned integer can panic" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for casts from an enum tuple constructor to an integer. + /// + /// ### Why is this bad? + /// The cast is easily confused with casting a c-like enum value to an integer. + /// + /// ### Example + /// ```no_run + /// enum E { X(i32) }; + /// let _ = E::X as usize; + /// ``` + #[clippy::version = "1.61.0"] + pub CAST_ENUM_CONSTRUCTOR, + suspicious, + "casts from an enum tuple constructor to an integer" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for casts from an enum type to an integral type that will definitely truncate the + /// value. + /// + /// ### Why is this bad? + /// The resulting integral value will not match the value of the variant it came from. + /// + /// ### Example + /// ```no_run + /// enum E { X = 256 }; + /// let _ = E::X as u8; + /// ``` + #[clippy::version = "1.61.0"] + pub CAST_ENUM_TRUNCATION, + suspicious, + "casts from an enum type to an integral type that will truncate the value" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for casts between numeric types that can be replaced by safe + /// conversion functions. + /// + /// ### Why is this bad? + /// Rust's `as` keyword will perform many kinds of conversions, including + /// silently lossy conversions. Conversion functions such as `i32::from` + /// will only perform lossless conversions. Using the conversion functions + /// prevents conversions from becoming silently lossy if the input types + /// ever change, and makes it clear for people reading the code that the + /// conversion is lossless. + /// + /// ### Example + /// ```no_run + /// fn as_u64(x: u8) -> u64 { + /// x as u64 + /// } + /// ``` + /// + /// Using `::from` would look like this: + /// + /// ```no_run + /// fn as_u64(x: u8) -> u64 { + /// u64::from(x) + /// } /// ``` #[clippy::version = "pre 1.29.0"] - pub CAST_SIGN_LOSS, + pub CAST_LOSSLESS, pedantic, - "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`" + "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for a known NaN float being cast to an integer + /// + /// ### Why is this bad? + /// NaNs are cast into zero, so one could simply use this and make the + /// code more readable. The lint could also hint at a programmer error. + /// + /// ### Example + /// ```rust,ignore + /// let _ = (0.0_f32 / 0.0) as u64; + /// ``` + /// Use instead: + /// ```rust,ignore + /// let _ = 0_u64; + /// ``` + #[clippy::version = "1.66.0"] + pub CAST_NAN_TO_INT, + suspicious, + "casting a known floating-point NaN into an integer" } declare_clippy_lint! { @@ -159,71 +340,28 @@ declare_clippy_lint! { /// ### What it does - /// Checks for casts between numeric types that can be replaced by safe - /// conversion functions. + /// Checks for casts from any numeric type to a float type where + /// the receiving type cannot store all values from the original type without + /// rounding errors. This possible rounding is to be expected, so this lint is + /// `Allow` by default. + /// + /// Basically, this warns on casting any integer with 32 or more bits to `f32` + /// or any 64-bit integer to `f64`. /// /// ### Why is this bad? - /// Rust's `as` keyword will perform many kinds of conversions, including - /// silently lossy conversions. Conversion functions such as `i32::from` - /// will only perform lossless conversions. Using the conversion functions - /// prevents conversions from becoming silently lossy if the input types - /// ever change, and makes it clear for people reading the code that the - /// conversion is lossless. + /// It's not bad at all. But in some applications it can be + /// helpful to know where precision loss can take place. This lint can help find + /// those places in the code. /// /// ### Example /// ```no_run - /// fn as_u64(x: u8) -> u64 { - /// x as u64 - /// } - /// ``` - /// - /// Using `::from` would look like this: - /// - /// ```no_run - /// fn as_u64(x: u8) -> u64 { - /// u64::from(x) - /// } + /// let x = u64::MAX; + /// x as f64; /// ``` #[clippy::version = "pre 1.29.0"] - pub CAST_LOSSLESS, + pub CAST_PRECISION_LOSS, pedantic, - "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts to the same type, casts of int literals to integer - /// types, casts of float literals to float types, and casts between raw - /// pointers that don't change type or constness. - /// - /// ### Why is this bad? - /// It's just unnecessary. - /// - /// ### Known problems - /// When the expression on the left is a function call, the lint considers - /// the return type to be a type alias if it's aliased through a `use` - /// statement (like `use std::io::Result as IoResult`). It will not lint - /// such cases. - /// - /// This check will only work on primitive types without any intermediate - /// references: raw pointers and trait objects may or may not work. - /// - /// ### Example - /// ```no_run - /// let _ = 2i32 as i32; - /// let _ = 0.5 as f32; - /// ``` - /// - /// Better: - /// - /// ```no_run - /// let _ = 2_i32; - /// let _ = 0.5_f32; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub UNNECESSARY_CAST, - complexity, - "cast to the same type, e.g., `x as i32` where `x: i32`" + "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" } declare_clippy_lint! { @@ -254,6 +392,154 @@ "cast from a pointer to a more strictly aligned pointer" } +declare_clippy_lint! { + /// ### What it does + /// Checks for casts from a signed to an unsigned numeric + /// type. In this case, negative values wrap around to large positive values, + /// which can be quite surprising in practice. However, since the cast works as + /// defined, this lint is `Allow` by default. + /// + /// ### Why is this bad? + /// Possibly surprising results. You can activate this lint + /// as a one-time check to see where numeric wrapping can arise. + /// + /// ### Example + /// ```no_run + /// let y: i8 = -1; + /// y as u64; // will return 18446744073709551615 + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CAST_SIGN_LOSS, + pedantic, + "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `as` casts between raw pointers to slices with differently sized elements. + /// + /// ### Why is this bad? + /// The produced raw pointer to a slice does not update its length metadata. The produced + /// pointer will point to a different number of bytes than the original pointer because the + /// length metadata of a raw slice pointer is in elements rather than bytes. + /// Producing a slice reference from the raw pointer will either create a slice with + /// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior. + /// + /// ### Example + /// // Missing data + /// ```no_run + /// let a = [1_i32, 2, 3, 4]; + /// let p = &a as *const [i32] as *const [u8]; + /// unsafe { + /// println!("{:?}", &*p); + /// } + /// ``` + /// // Undefined Behavior (note: also potential alignment issues) + /// ```no_run + /// let a = [1_u8, 2, 3, 4]; + /// let p = &a as *const [u8] as *const [u32]; + /// unsafe { + /// println!("{:?}", &*p); + /// } + /// ``` + /// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length + /// ```no_run + /// let a = [1_i32, 2, 3, 4]; + /// let old_ptr = &a as *const [i32]; + /// // The data pointer is cast to a pointer to the target `u8` not `[u8]` + /// // The length comes from the known length of 4 i32s times the 4 bytes per i32 + /// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16); + /// unsafe { + /// println!("{:?}", &*new_ptr); + /// } + /// ``` + #[clippy::version = "1.61.0"] + pub CAST_SLICE_DIFFERENT_SIZES, + correctness, + "casting using `as` between raw pointers to slices of types with different sizes" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for a raw slice being cast to a slice pointer + /// + /// ### Why is this bad? + /// This can result in multiple `&mut` references to the same location when only a pointer is + /// required. + /// `ptr::slice_from_raw_parts` is a safe alternative that doesn't require + /// the same [safety requirements] to be upheld. + /// + /// ### Example + /// ```rust,ignore + /// let _: *const [u8] = std::slice::from_raw_parts(ptr, len) as *const _; + /// let _: *mut [u8] = std::slice::from_raw_parts_mut(ptr, len) as *mut _; + /// ``` + /// Use instead: + /// ```rust,ignore + /// let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, len); + /// let _: *mut [u8] = std::ptr::slice_from_raw_parts_mut(ptr, len); + /// ``` + /// [safety requirements]: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety + #[clippy::version = "1.65.0"] + pub CAST_SLICE_FROM_RAW_PARTS, + suspicious, + "casting a slice created from a pointer and length to a slice pointer" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for expressions where a character literal is cast + /// to `u8` and suggests using a byte literal instead. + /// + /// ### Why is this bad? + /// In general, casting values to smaller types is + /// error-prone and should be avoided where possible. In the particular case of + /// converting a character literal to `u8`, it is easy to avoid by just using a + /// byte literal instead. As an added bonus, `b'a'` is also slightly shorter + /// than `'a' as u8`. + /// + /// ### Example + /// ```rust,ignore + /// 'x' as u8 + /// ``` + /// + /// A better version, using the byte literal: + /// + /// ```rust,ignore + /// b'x' + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CHAR_LIT_AS_U8, + complexity, + "casting a character literal to `u8` truncates" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for casts of a primitive method pointer like `max`/`min` to any integer type. + /// + /// ### Why restrict this? + /// Casting a function pointer to an integer can have surprising results and can occur + /// accidentally if parentheses are omitted from a function call. If you aren't doing anything + /// low-level with function pointers then you can opt out of casting functions to integers in + /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function + /// pointer casts in your code. + /// + /// ### Example + /// ```no_run + /// let _ = u16::max as usize; + /// ``` + /// + /// Use instead: + /// ```no_run + /// let _ = u16::MAX as usize; + /// ``` + #[clippy::version = "1.89.0"] + pub CONFUSING_METHOD_TO_NUMERIC_CAST, + suspicious, + "casting a primitive method pointer to any integer type" +} + declare_clippy_lint! { /// ### What it does /// Checks for casts of function pointers to something other than `usize`. @@ -284,39 +570,6 @@ "casting a function pointer to a numeric type other than `usize`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of a function pointer to a numeric type not wide enough to - /// store an address. - /// - /// ### Why is this bad? - /// Such a cast discards some bits of the function's address. If this is intended, it would be more - /// clearly expressed by casting to `usize` first, then casting the `usize` to the intended type (with - /// a comment) to perform the truncation. - /// - /// ### Example - /// ```no_run - /// fn fn1() -> i16 { - /// 1 - /// }; - /// let _ = fn1 as i32; - /// ``` - /// - /// Use instead: - /// ```no_run - /// // Cast to usize first, then comment with the reason for the truncation - /// fn fn1() -> i16 { - /// 1 - /// }; - /// let fn_ptr = fn1 as usize; - /// let fn_ptr_truncated = fn_ptr as i32; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION, - style, - "casting a function pointer to a numeric type not wide enough to store the address" -} - declare_clippy_lint! { /// ### What it does /// Checks for casts of a function pointer to any integer type. @@ -361,30 +614,87 @@ declare_clippy_lint! { /// ### What it does - /// Checks for expressions where a character literal is cast - /// to `u8` and suggests using a byte literal instead. + /// Checks for casts of a function pointer to a numeric type not wide enough to + /// store an address. /// /// ### Why is this bad? - /// In general, casting values to smaller types is - /// error-prone and should be avoided where possible. In the particular case of - /// converting a character literal to `u8`, it is easy to avoid by just using a - /// byte literal instead. As an added bonus, `b'a'` is also slightly shorter - /// than `'a' as u8`. + /// Such a cast discards some bits of the function's address. If this is intended, it would be more + /// clearly expressed by casting to `usize` first, then casting the `usize` to the intended type (with + /// a comment) to perform the truncation. /// /// ### Example - /// ```rust,ignore - /// 'x' as u8 + /// ```no_run + /// fn fn1() -> i16 { + /// 1 + /// }; + /// let _ = fn1 as i32; /// ``` /// - /// A better version, using the byte literal: - /// - /// ```rust,ignore - /// b'x' + /// Use instead: + /// ```no_run + /// // Cast to usize first, then comment with the reason for the truncation + /// fn fn1() -> i16 { + /// 1 + /// }; + /// let fn_ptr = fn1 as usize; + /// let fn_ptr_truncated = fn_ptr as i32; /// ``` #[clippy::version = "pre 1.29.0"] - pub CHAR_LIT_AS_U8, - complexity, - "casting a character literal to `u8` truncates" + pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + style, + "casting a function pointer to a numeric type not wide enough to store the address" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for casts of small constant literals or `mem::align_of` results to raw pointers. + /// + /// ### Why is this bad? + /// This creates a dangling pointer and is better expressed as + /// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}. + /// + /// ### Example + /// ```no_run + /// let ptr = 4 as *const u32; + /// let aligned = std::mem::align_of::() as *const u32; + /// let mut_ptr: *mut i64 = 8 as *mut _; + /// ``` + /// Use instead: + /// ```no_run + /// let ptr = std::ptr::dangling::(); + /// let aligned = std::ptr::dangling::(); + /// let mut_ptr: *mut i64 = std::ptr::dangling_mut(); + /// ``` + #[clippy::version = "1.88.0"] + pub MANUAL_DANGLING_PTR, + style, + "casting small constant literals to pointers to create dangling pointers" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for bindings (constants, statics, or let bindings) that are defined + /// with one numeric type but are consistently cast to a different type in all usages. + /// + /// ### Why is this bad? + /// If a binding is always cast to a different type when used, it would be clearer + /// and more efficient to define it with the target type from the start. + /// + /// ### Example + /// ```no_run + /// const SIZE: u16 = 15; + /// let arr: [u8; SIZE as usize] = [0; SIZE as usize]; + /// ``` + /// + /// Use instead: + /// ```no_run + /// const SIZE: usize = 15; + /// let arr: [u8; SIZE] = [0; SIZE]; + /// ``` + #[clippy::version = "1.93.0"] + pub NEEDLESS_TYPE_CAST, + nursery, + "binding defined with one type but always cast to another" } declare_clippy_lint! { @@ -455,243 +765,62 @@ declare_clippy_lint! { /// ### What it does - /// Checks for casts from an enum type to an integral type that will definitely truncate the - /// value. + /// Checks for casts of references to pointer using `as` + /// and suggests `std::ptr::from_ref` and `std::ptr::from_mut` instead. /// /// ### Why is this bad? - /// The resulting integral value will not match the value of the variant it came from. + /// Using `as` casts may result in silently changing mutability or type. /// /// ### Example /// ```no_run - /// enum E { X = 256 }; - /// let _ = E::X as u8; - /// ``` - #[clippy::version = "1.61.0"] - pub CAST_ENUM_TRUNCATION, - suspicious, - "casts from an enum type to an integral type that will truncate the value" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `as` casts between raw pointers to slices with differently sized elements. - /// - /// ### Why is this bad? - /// The produced raw pointer to a slice does not update its length metadata. The produced - /// pointer will point to a different number of bytes than the original pointer because the - /// length metadata of a raw slice pointer is in elements rather than bytes. - /// Producing a slice reference from the raw pointer will either create a slice with - /// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior. - /// - /// ### Example - /// // Missing data - /// ```no_run - /// let a = [1_i32, 2, 3, 4]; - /// let p = &a as *const [i32] as *const [u8]; - /// unsafe { - /// println!("{:?}", &*p); - /// } - /// ``` - /// // Undefined Behavior (note: also potential alignment issues) - /// ```no_run - /// let a = [1_u8, 2, 3, 4]; - /// let p = &a as *const [u8] as *const [u32]; - /// unsafe { - /// println!("{:?}", &*p); - /// } - /// ``` - /// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length - /// ```no_run - /// let a = [1_i32, 2, 3, 4]; - /// let old_ptr = &a as *const [i32]; - /// // The data pointer is cast to a pointer to the target `u8` not `[u8]` - /// // The length comes from the known length of 4 i32s times the 4 bytes per i32 - /// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16); - /// unsafe { - /// println!("{:?}", &*new_ptr); - /// } - /// ``` - #[clippy::version = "1.61.0"] - pub CAST_SLICE_DIFFERENT_SIZES, - correctness, - "casting using `as` between raw pointers to slices of types with different sizes" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts from an enum tuple constructor to an integer. - /// - /// ### Why is this bad? - /// The cast is easily confused with casting a c-like enum value to an integer. - /// - /// ### Example - /// ```no_run - /// enum E { X(i32) }; - /// let _ = E::X as usize; - /// ``` - #[clippy::version = "1.61.0"] - pub CAST_ENUM_CONSTRUCTOR, - suspicious, - "casts from an enum tuple constructor to an integer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of the `abs()` method that cast the result to unsigned. - /// - /// ### Why is this bad? - /// The `unsigned_abs()` method avoids panic when called on the MIN value. - /// - /// ### Example - /// ```no_run - /// let x: i32 = -42; - /// let y: u32 = x.abs() as u32; + /// let a_ref = &1; + /// let a_ptr = a_ref as *const _; /// ``` /// Use instead: /// ```no_run - /// let x: i32 = -42; - /// let y: u32 = x.unsigned_abs(); + /// let a_ref = &1; + /// let a_ptr = std::ptr::from_ref(a_ref); /// ``` - #[clippy::version = "1.62.0"] - pub CAST_ABS_TO_UNSIGNED, - suspicious, - "casting the result of `abs()` to an unsigned integer can panic" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `as _` conversion using inferred type. - /// - /// ### Why restrict this? - /// The conversion might include lossy conversion or a dangerous cast that might go - /// undetected due to the type being inferred. - /// - /// The lint is allowed by default as using `_` is less wordy than always specifying the type. - /// - /// ### Example - /// ```no_run - /// fn foo(n: usize) {} - /// let n: u16 = 256; - /// foo(n as _); - /// ``` - /// Use instead: - /// ```no_run - /// fn foo(n: usize) {} - /// let n: u16 = 256; - /// foo(n as usize); - /// ``` - #[clippy::version = "1.63.0"] - pub AS_UNDERSCORE, - restriction, - "detects `as _` conversion" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `&expr as *const T` or - /// `&mut expr as *mut T`, and suggest using `&raw const` or - /// `&raw mut` instead. - /// - /// ### Why is this bad? - /// This would improve readability and avoid creating a reference - /// that points to an uninitialized value or unaligned place. - /// Read the `&raw` explanation in the Reference for more information. - /// - /// ### Example - /// ```no_run - /// let val = 1; - /// let p = &val as *const i32; - /// - /// let mut val_mut = 1; - /// let p_mut = &mut val_mut as *mut i32; - /// ``` - /// Use instead: - /// ```no_run - /// let val = 1; - /// let p = &raw const val; - /// - /// let mut val_mut = 1; - /// let p_mut = &raw mut val_mut; - /// ``` - #[clippy::version = "1.60.0"] - pub BORROW_AS_PTR, + #[clippy::version = "1.78.0"] + pub REF_AS_PTR, pedantic, - "borrowing just to cast to a raw pointer" + "using `as` to cast a reference to pointer" } declare_clippy_lint! { /// ### What it does - /// Checks for a raw slice being cast to a slice pointer + /// Checks for casts to the same type, casts of int literals to integer + /// types, casts of float literals to float types, and casts between raw + /// pointers that don't change type or constness. /// /// ### Why is this bad? - /// This can result in multiple `&mut` references to the same location when only a pointer is - /// required. - /// `ptr::slice_from_raw_parts` is a safe alternative that doesn't require - /// the same [safety requirements] to be upheld. + /// It's just unnecessary. /// - /// ### Example - /// ```rust,ignore - /// let _: *const [u8] = std::slice::from_raw_parts(ptr, len) as *const _; - /// let _: *mut [u8] = std::slice::from_raw_parts_mut(ptr, len) as *mut _; - /// ``` - /// Use instead: - /// ```rust,ignore - /// let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, len); - /// let _: *mut [u8] = std::ptr::slice_from_raw_parts_mut(ptr, len); - /// ``` - /// [safety requirements]: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety - #[clippy::version = "1.65.0"] - pub CAST_SLICE_FROM_RAW_PARTS, - suspicious, - "casting a slice created from a pointer and length to a slice pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the result of a `&self`-taking `as_ptr` being cast to a mutable pointer. + /// ### Known problems + /// When the expression on the left is a function call, the lint considers + /// the return type to be a type alias if it's aliased through a `use` + /// statement (like `use std::io::Result as IoResult`). It will not lint + /// such cases. /// - /// ### Why is this bad? - /// Since `as_ptr` takes a `&self`, the pointer won't have write permissions unless interior - /// mutability is used, making it unlikely that having it as a mutable pointer is correct. + /// This check will only work on primitive types without any intermediate + /// references: raw pointers and trait objects may or may not work. /// /// ### Example /// ```no_run - /// let mut vec = Vec::::with_capacity(1); - /// let ptr = vec.as_ptr() as *mut u8; - /// unsafe { ptr.write(4) }; // UNDEFINED BEHAVIOUR + /// let _ = 2i32 as i32; + /// let _ = 0.5 as f32; /// ``` - /// Use instead: + /// + /// Better: + /// /// ```no_run - /// let mut vec = Vec::::with_capacity(1); - /// let ptr = vec.as_mut_ptr(); - /// unsafe { ptr.write(4) }; + /// let _ = 2_i32; + /// let _ = 0.5_f32; /// ``` - #[clippy::version = "1.66.0"] - pub AS_PTR_CAST_MUT, - nursery, - "casting the result of the `&self`-taking `as_ptr` to a mutable pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for a known NaN float being cast to an integer - /// - /// ### Why is this bad? - /// NaNs are cast into zero, so one could simply use this and make the - /// code more readable. The lint could also hint at a programmer error. - /// - /// ### Example - /// ```rust,ignore - /// let _ = (0.0_f32 / 0.0) as u64; - /// ``` - /// Use instead: - /// ```rust,ignore - /// let _ = 0_u64; - /// ``` - #[clippy::version = "1.66.0"] - pub CAST_NAN_TO_INT, - suspicious, - "casting a known floating-point NaN into an integer" + #[clippy::version = "pre 1.29.0"] + pub UNNECESSARY_CAST, + complexity, + "cast to the same type, e.g., `x as i32` where `x: i32`" } declare_clippy_lint! { @@ -717,134 +846,36 @@ "using `0 as *{const, mut} T`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of references to pointer using `as` - /// and suggests `std::ptr::from_ref` and `std::ptr::from_mut` instead. - /// - /// ### Why is this bad? - /// Using `as` casts may result in silently changing mutability or type. - /// - /// ### Example - /// ```no_run - /// let a_ref = &1; - /// let a_ptr = a_ref as *const _; - /// ``` - /// Use instead: - /// ```no_run - /// let a_ref = &1; - /// let a_ptr = std::ptr::from_ref(a_ref); - /// ``` - #[clippy::version = "1.78.0"] - pub REF_AS_PTR, - pedantic, - "using `as` to cast a reference to pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `as *const _` or `as *mut _` conversion using inferred type. - /// - /// ### Why restrict this? - /// The conversion might include a dangerous cast that might go undetected due to the type being inferred. - /// - /// ### Example - /// ```no_run - /// fn as_usize(t: &T) -> usize { - /// // BUG: `t` is already a reference, so we will here - /// // return a dangling pointer to a temporary value instead - /// &t as *const _ as usize - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn as_usize(t: &T) -> usize { - /// t as *const T as usize - /// } - /// ``` - #[clippy::version = "1.85.0"] - pub AS_POINTER_UNDERSCORE, - restriction, - "detects `as *mut _` and `as *const _` conversion" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of small constant literals or `mem::align_of` results to raw pointers. - /// - /// ### Why is this bad? - /// This creates a dangling pointer and is better expressed as - /// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}. - /// - /// ### Example - /// ```no_run - /// let ptr = 4 as *const u32; - /// let aligned = std::mem::align_of::() as *const u32; - /// let mut_ptr: *mut i64 = 8 as *mut _; - /// ``` - /// Use instead: - /// ```no_run - /// let ptr = std::ptr::dangling::(); - /// let aligned = std::ptr::dangling::(); - /// let mut_ptr: *mut i64 = std::ptr::dangling_mut(); - /// ``` - #[clippy::version = "1.88.0"] - pub MANUAL_DANGLING_PTR, - style, - "casting small constant literals to pointers to create dangling pointers" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of a primitive method pointer like `max`/`min` to any integer type. - /// - /// ### Why restrict this? - /// Casting a function pointer to an integer can have surprising results and can occur - /// accidentally if parentheses are omitted from a function call. If you aren't doing anything - /// low-level with function pointers then you can opt out of casting functions to integers in - /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function - /// pointer casts in your code. - /// - /// ### Example - /// ```no_run - /// let _ = u16::max as usize; - /// ``` - /// - /// Use instead: - /// ```no_run - /// let _ = u16::MAX as usize; - /// ``` - #[clippy::version = "1.89.0"] - pub CONFUSING_METHOD_TO_NUMERIC_CAST, - suspicious, - "casting a primitive method pointer to any integer type" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for bindings (constants, statics, or let bindings) that are defined - /// with one numeric type but are consistently cast to a different type in all usages. - /// - /// ### Why is this bad? - /// If a binding is always cast to a different type when used, it would be clearer - /// and more efficient to define it with the target type from the start. - /// - /// ### Example - /// ```no_run - /// const SIZE: u16 = 15; - /// let arr: [u8; SIZE as usize] = [0; SIZE as usize]; - /// ``` - /// - /// Use instead: - /// ```no_run - /// const SIZE: usize = 15; - /// let arr: [u8; SIZE] = [0; SIZE]; - /// ``` - #[clippy::version = "1.93.0"] - pub NEEDLESS_TYPE_CAST, - nursery, - "binding defined with one type but always cast to another" -} +impl_lint_pass!(Casts => [ + AS_POINTER_UNDERSCORE, + AS_PTR_CAST_MUT, + AS_UNDERSCORE, + BORROW_AS_PTR, + CAST_ABS_TO_UNSIGNED, + CAST_ENUM_CONSTRUCTOR, + CAST_ENUM_TRUNCATION, + CAST_LOSSLESS, + CAST_NAN_TO_INT, + CAST_POSSIBLE_TRUNCATION, + CAST_POSSIBLE_WRAP, + CAST_PRECISION_LOSS, + CAST_PTR_ALIGNMENT, + CAST_SIGN_LOSS, + CAST_SLICE_DIFFERENT_SIZES, + CAST_SLICE_FROM_RAW_PARTS, + CHAR_LIT_AS_U8, + CONFUSING_METHOD_TO_NUMERIC_CAST, + FN_TO_NUMERIC_CAST, + FN_TO_NUMERIC_CAST_ANY, + FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + MANUAL_DANGLING_PTR, + NEEDLESS_TYPE_CAST, + PTR_AS_PTR, + PTR_CAST_CONSTNESS, + REF_AS_PTR, + UNNECESSARY_CAST, + ZERO_PTR, +]); pub struct Casts { msrv: Msrv, @@ -856,37 +887,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(Casts => [ - CAST_PRECISION_LOSS, - CAST_SIGN_LOSS, - CAST_POSSIBLE_TRUNCATION, - CAST_POSSIBLE_WRAP, - CAST_LOSSLESS, - CAST_PTR_ALIGNMENT, - CAST_SLICE_DIFFERENT_SIZES, - UNNECESSARY_CAST, - FN_TO_NUMERIC_CAST_ANY, - FN_TO_NUMERIC_CAST, - FN_TO_NUMERIC_CAST_WITH_TRUNCATION, - CHAR_LIT_AS_U8, - PTR_AS_PTR, - PTR_CAST_CONSTNESS, - CAST_ENUM_TRUNCATION, - CAST_ENUM_CONSTRUCTOR, - CAST_ABS_TO_UNSIGNED, - AS_UNDERSCORE, - BORROW_AS_PTR, - CAST_SLICE_FROM_RAW_PARTS, - AS_PTR_CAST_MUT, - CAST_NAN_TO_INT, - ZERO_PTR, - REF_AS_PTR, - AS_POINTER_UNDERSCORE, - MANUAL_DANGLING_PTR, - CONFUSING_METHOD_TO_NUMERIC_CAST, - NEEDLESS_TYPE_CAST, -]); - impl<'tcx> LateLintPass<'tcx> for Casts { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if expr.span.in_external_macro(cx.sess().source_map()) { diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 8303897d1294..5e9009c67197 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -34,6 +34,8 @@ "`try_from` could replace manual bounds checking when casting" } +impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]); + pub struct CheckedConversions { msrv: Msrv, } @@ -44,8 +46,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]); - impl LateLintPass<'_> for CheckedConversions { fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) { if let ExprKind::Binary(op, lhs, rhs) = item.kind diff --git a/clippy_lints/src/cloned_ref_to_slice_refs.rs b/clippy_lints/src/cloned_ref_to_slice_refs.rs index 35b799aefb04..c5eabe4c2b88 100644 --- a/clippy_lints/src/cloned_ref_to_slice_refs.rs +++ b/clippy_lints/src/cloned_ref_to_slice_refs.rs @@ -44,6 +44,8 @@ "cloning a reference for slice references" } +impl_lint_pass!(ClonedRefToSliceRefs<'_> => [CLONED_REF_TO_SLICE_REFS]); + pub struct ClonedRefToSliceRefs<'a> { msrv: &'a Msrv, } @@ -53,8 +55,6 @@ pub fn new(conf: &'a Conf) -> Self { } } -impl_lint_pass!(ClonedRefToSliceRefs<'_> => [CLONED_REF_TO_SLICE_REFS]); - impl<'tcx> LateLintPass<'tcx> for ClonedRefToSliceRefs<'_> { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { if self.msrv.meets(cx, { diff --git a/clippy_lints/src/coerce_container_to_any.rs b/clippy_lints/src/coerce_container_to_any.rs index 2e3acb7748e2..1a7e20b98271 100644 --- a/clippy_lints/src/coerce_container_to_any.rs +++ b/clippy_lints/src/coerce_container_to_any.rs @@ -46,6 +46,7 @@ nursery, "coercing to `&dyn Any` when dereferencing could produce a `dyn Any` without coercion is usually not intended" } + declare_lint_pass!(CoerceContainerToAny => [COERCE_CONTAINER_TO_ANY]); impl<'tcx> LateLintPass<'tcx> for CoerceContainerToAny { diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index c256ad9b06a4..911ca306aca4 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -40,6 +40,8 @@ @eval_always = true } +impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]); + pub struct CognitiveComplexity { limit: LimitStack, } @@ -52,8 +54,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]); - impl CognitiveComplexity { fn check<'tcx>( &self, diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index 17e11b8b281d..a76027caebc8 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -12,38 +12,6 @@ use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, Span, Symbol}; -declare_clippy_lint! { - /// ### What it does - /// Checks for nested `if` statements which can be collapsed - /// by `&&`-combining their conditions. - /// - /// ### Why is this bad? - /// Each `if`-statement adds one level of nesting, which - /// makes code look more complex than it really is. - /// - /// ### Example - /// ```no_run - /// # let (x, y) = (true, true); - /// if x { - /// if y { - /// // … - /// } - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let (x, y) = (true, true); - /// if x && y { - /// // … - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub COLLAPSIBLE_IF, - style, - "nested `if`s that can be collapsed (e.g., `if x { if y { ... } }`" -} - declare_clippy_lint! { /// ### What it does /// Checks for collapsible `else { if ... }` expressions @@ -80,6 +48,40 @@ "nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)" } +declare_clippy_lint! { + /// ### What it does + /// Checks for nested `if` statements which can be collapsed + /// by `&&`-combining their conditions. + /// + /// ### Why is this bad? + /// Each `if`-statement adds one level of nesting, which + /// makes code look more complex than it really is. + /// + /// ### Example + /// ```no_run + /// # let (x, y) = (true, true); + /// if x { + /// if y { + /// // … + /// } + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let (x, y) = (true, true); + /// if x && y { + /// // … + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub COLLAPSIBLE_IF, + style, + "nested `if`s that can be collapsed (e.g., `if x { if y { ... } }`" +} + +impl_lint_pass!(CollapsibleIf => [COLLAPSIBLE_ELSE_IF, COLLAPSIBLE_IF]); + pub struct CollapsibleIf { msrv: Msrv, lint_commented_code: bool, @@ -259,8 +261,6 @@ fn check_significant_tokens_and_expect_attrs( } } -impl_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]); - impl LateLintPass<'_> for CollapsibleIf { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if let ExprKind::If(cond, then, else_) = &expr.kind diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs index 5bf582f4da65..ddd3e8b805d1 100644 --- a/clippy_lints/src/collection_is_never_read.rs +++ b/clippy_lints/src/collection_is_never_read.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::{get_enclosing_block, sym}; use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::visitors::{Visitable, for_each_expr}; +use clippy_utils::{get_enclosing_block, sym}; use core::ops::ControlFlow; use rustc_hir::{Body, ExprKind, HirId, LangItem, LetStmt, Node, PatKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -40,6 +40,7 @@ nursery, "a collection is never queried" } + declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]); impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead { diff --git a/clippy_lints/src/crate_in_macro_def.rs b/clippy_lints/src/crate_in_macro_def.rs index 19f62e8bf79c..509b345048c1 100644 --- a/clippy_lints/src/crate_in_macro_def.rs +++ b/clippy_lints/src/crate_in_macro_def.rs @@ -49,6 +49,7 @@ suspicious, "using `crate` in a macro definition" } + declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]); impl EarlyLintPass for CrateInMacroDef { diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index f47d0d4835a9..eb14ef18c03d 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -1,8 +1,8 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{is_in_test, sym}; use clippy_utils::macros::{MacroCall, macro_backtrace}; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{is_in_test, sym}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::{Arm, Closure, ClosureKind, CoroutineKind, Expr, ExprKind, LetStmt, LocalSource, Node, Stmt, StmtKind}; @@ -33,6 +33,8 @@ "`dbg!` macro is intended as a debugging tool" } +impl_lint_pass!(DbgMacro => [DBG_MACRO]); + pub struct DbgMacro { allow_dbg_in_tests: bool, /// Tracks the `dbg!` macro callsites that are already checked. @@ -41,8 +43,6 @@ pub struct DbgMacro { prev_ctxt: SyntaxContext, } -impl_lint_pass!(DbgMacro => [DBG_MACRO]); - impl DbgMacro { pub fn new(conf: &'static Conf) -> Self { DbgMacro { diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index a48e4d2fbd57..2064d896861b 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -69,14 +69,14 @@ "binding initialized with Default should have its fields set in the initializer" } +impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]); + #[derive(Default)] pub struct Default { // Spans linted by `field_reassign_with_default`. reassigned_linted: FxHashSet, } -impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]); - impl<'tcx> LateLintPass<'tcx> for Default { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if !expr.span.from_expansion() diff --git a/clippy_lints/src/default_constructed_unit_structs.rs b/clippy_lints/src/default_constructed_unit_structs.rs index 641f8ae03b72..c831f96443c6 100644 --- a/clippy_lints/src/default_constructed_unit_structs.rs +++ b/clippy_lints/src/default_constructed_unit_structs.rs @@ -45,7 +45,10 @@ complexity, "unit structs can be constructed without calling `default`" } -declare_lint_pass!(DefaultConstructedUnitStructs => [DEFAULT_CONSTRUCTED_UNIT_STRUCTS]); + +declare_lint_pass!(DefaultConstructedUnitStructs => [ + DEFAULT_CONSTRUCTED_UNIT_STRUCTS, +]); fn is_alias(ty: hir::Ty<'_>) -> bool { if let hir::TyKind::Path(ref qpath) = ty.kind { diff --git a/clippy_lints/src/default_instead_of_iter_empty.rs b/clippy_lints/src/default_instead_of_iter_empty.rs index c7807f7b122a..fffdea863f51 100644 --- a/clippy_lints/src/default_instead_of_iter_empty.rs +++ b/clippy_lints/src/default_instead_of_iter_empty.rs @@ -28,6 +28,7 @@ style, "check `std::iter::Empty::default()` and replace with `std::iter::empty()`" } + declare_lint_pass!(DefaultIterEmpty => [DEFAULT_INSTEAD_OF_ITER_EMPTY]); impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty { diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index c1005688a921..61656d6cede5 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -48,6 +48,7 @@ restriction, "unions without a `#[repr(C)]` attribute" } + declare_lint_pass!(DefaultUnionRepresentation => [DEFAULT_UNION_REPRESENTATION]); impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation { diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 3433ab511b07..26dfa7593f22 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -3,7 +3,9 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop, peel_and_count_ty_refs}; -use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym}; +use clippy_utils::{ + DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym, +}; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; @@ -20,6 +22,29 @@ use rustc_span::{Span, Symbol}; use std::borrow::Cow; +declare_clippy_lint! { + /// ### What it does + /// Checks for dereferencing expressions which would be covered by auto-deref. + /// + /// ### Why is this bad? + /// This unnecessarily complicates the code. + /// + /// ### Example + /// ```no_run + /// let x = String::new(); + /// let y: &str = &*x; + /// ``` + /// Use instead: + /// ```no_run + /// let x = String::new(); + /// let y: &str = &x; + /// ``` + #[clippy::version = "1.64.0"] + pub EXPLICIT_AUTO_DEREF, + complexity, + "dereferencing when the compiler would automatically dereference" +} + declare_clippy_lint! { /// ### What it does /// Checks for explicit `deref()` or `deref_mut()` method calls. @@ -117,34 +142,11 @@ "`ref` binding to a reference" } -declare_clippy_lint! { - /// ### What it does - /// Checks for dereferencing expressions which would be covered by auto-deref. - /// - /// ### Why is this bad? - /// This unnecessarily complicates the code. - /// - /// ### Example - /// ```no_run - /// let x = String::new(); - /// let y: &str = &*x; - /// ``` - /// Use instead: - /// ```no_run - /// let x = String::new(); - /// let y: &str = &x; - /// ``` - #[clippy::version = "1.64.0"] - pub EXPLICIT_AUTO_DEREF, - complexity, - "dereferencing when the compiler would automatically dereference" -} - impl_lint_pass!(Dereferencing<'_> => [ + EXPLICIT_AUTO_DEREF, EXPLICIT_DEREF_METHODS, NEEDLESS_BORROW, REF_BINDING_TO_REFERENCE, - EXPLICIT_AUTO_DEREF, ]); #[derive(Default)] diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index 992ed320ce68..c04163b1c7a0 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -56,6 +56,8 @@ "manual implementation of the `Default` trait which is equal to a derive" } +impl_lint_pass!(DerivableImpls => [DERIVABLE_IMPLS]); + pub struct DerivableImpls { msrv: Msrv, } @@ -66,8 +68,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(DerivableImpls => [DERIVABLE_IMPLS]); - fn is_path_self(e: &Expr<'_>) -> bool { if let ExprKind::Path(QPath::Resolved(_, p)) = e.kind { matches!(p.res, Res::SelfCtor(..) | Res::Def(DefKind::Ctor(..), _)) diff --git a/clippy_lints/src/derive/mod.rs b/clippy_lints/src/derive/mod.rs index 86614201c406..fa1a7037154e 100644 --- a/clippy_lints/src/derive/mod.rs +++ b/clippy_lints/src/derive/mod.rs @@ -10,36 +10,6 @@ mod expl_impl_clone_on_copy; mod unsafe_derive_deserialize; -declare_clippy_lint! { - /// ### What it does - /// Lints against manual `PartialEq` implementations for types with a derived `Hash` - /// implementation. - /// - /// ### Why is this bad? - /// The implementation of these traits must agree (for - /// example for use with `HashMap`) so it’s probably a bad idea to use a - /// default-generated `Hash` implementation with an explicitly defined - /// `PartialEq`. In particular, the following must hold for any type: - /// - /// ```text - /// k1 == k2 ⇒ hash(k1) == hash(k2) - /// ``` - /// - /// ### Example - /// ```ignore - /// #[derive(Hash)] - /// struct Foo; - /// - /// impl PartialEq for Foo { - /// ... - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub DERIVED_HASH_WITH_MANUAL_EQ, - correctness, - "deriving `Hash` but implementing `PartialEq` explicitly" -} - declare_clippy_lint! { /// ### What it does /// Lints against manual `PartialOrd` and `Ord` implementations for types with a derived `Ord` @@ -91,6 +61,68 @@ "deriving `Ord` but implementing `PartialOrd` explicitly" } +declare_clippy_lint! { + /// ### What it does + /// Checks for types that derive `PartialEq` and could implement `Eq`. + /// + /// ### Why is this bad? + /// If a type `T` derives `PartialEq` and all of its members implement `Eq`, + /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used + /// in APIs that require `Eq` types. It also allows structs containing `T` to derive + /// `Eq` themselves. + /// + /// ### Example + /// ```no_run + /// #[derive(PartialEq)] + /// struct Foo { + /// i_am_eq: i32, + /// i_am_eq_too: Vec, + /// } + /// ``` + /// Use instead: + /// ```no_run + /// #[derive(PartialEq, Eq)] + /// struct Foo { + /// i_am_eq: i32, + /// i_am_eq_too: Vec, + /// } + /// ``` + #[clippy::version = "1.63.0"] + pub DERIVE_PARTIAL_EQ_WITHOUT_EQ, + nursery, + "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`" +} + +declare_clippy_lint! { + /// ### What it does + /// Lints against manual `PartialEq` implementations for types with a derived `Hash` + /// implementation. + /// + /// ### Why is this bad? + /// The implementation of these traits must agree (for + /// example for use with `HashMap`) so it’s probably a bad idea to use a + /// default-generated `Hash` implementation with an explicitly defined + /// `PartialEq`. In particular, the following must hold for any type: + /// + /// ```text + /// k1 == k2 ⇒ hash(k1) == hash(k2) + /// ``` + /// + /// ### Example + /// ```ignore + /// #[derive(Hash)] + /// struct Foo; + /// + /// impl PartialEq for Foo { + /// ... + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub DERIVED_HASH_WITH_MANUAL_EQ, + correctness, + "deriving `Hash` but implementing `PartialEq` explicitly" +} + declare_clippy_lint! { /// ### What it does /// Checks for explicit `Clone` implementations for `Copy` @@ -152,44 +184,12 @@ "deriving `serde::Deserialize` on a type that has methods using `unsafe`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for types that derive `PartialEq` and could implement `Eq`. - /// - /// ### Why is this bad? - /// If a type `T` derives `PartialEq` and all of its members implement `Eq`, - /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used - /// in APIs that require `Eq` types. It also allows structs containing `T` to derive - /// `Eq` themselves. - /// - /// ### Example - /// ```no_run - /// #[derive(PartialEq)] - /// struct Foo { - /// i_am_eq: i32, - /// i_am_eq_too: Vec, - /// } - /// ``` - /// Use instead: - /// ```no_run - /// #[derive(PartialEq, Eq)] - /// struct Foo { - /// i_am_eq: i32, - /// i_am_eq_too: Vec, - /// } - /// ``` - #[clippy::version = "1.63.0"] - pub DERIVE_PARTIAL_EQ_WITHOUT_EQ, - nursery, - "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`" -} - declare_lint_pass!(Derive => [ - EXPL_IMPL_CLONE_ON_COPY, DERIVED_HASH_WITH_MANUAL_EQ, DERIVE_ORD_XOR_PARTIAL_ORD, + DERIVE_PARTIAL_EQ_WITHOUT_EQ, + EXPL_IMPL_CLONE_ON_COPY, UNSAFE_DERIVE_DESERIALIZE, - DERIVE_PARTIAL_EQ_WITHOUT_EQ ]); impl<'tcx> LateLintPass<'tcx> for Derive { diff --git a/clippy_lints/src/disallowed_fields.rs b/clippy_lints/src/disallowed_fields.rs index f1136556e2ed..9873c32f427f 100644 --- a/clippy_lints/src/disallowed_fields.rs +++ b/clippy_lints/src/disallowed_fields.rs @@ -56,6 +56,8 @@ "declaration of a disallowed field use" } +impl_lint_pass!(DisallowedFields => [DISALLOWED_FIELDS]); + pub struct DisallowedFields { disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>, } @@ -74,8 +76,6 @@ pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { } } -impl_lint_pass!(DisallowedFields => [DISALLOWED_FIELDS]); - impl<'tcx> LateLintPass<'tcx> for DisallowedFields { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { let (id, span) = match &expr.kind { diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index 1c9c971730f6..7253b65e4337 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -63,6 +63,8 @@ "use of a disallowed macro" } +impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]); + pub struct DisallowedMacros { disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>, seen: FxHashSet, @@ -125,8 +127,6 @@ fn check(&mut self, cx: &LateContext<'_>, span: Span, derive_src: Option [DISALLOWED_MACROS]); - impl LateLintPass<'_> for DisallowedMacros { fn check_crate(&mut self, cx: &LateContext<'_>) { // once we check a crate in the late pass we can emit the early pass lints diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index 58403ad19235..e2fd71b7d990 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -61,6 +61,8 @@ "use of a disallowed method call" } +impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]); + pub struct DisallowedMethods { disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>, } @@ -84,8 +86,6 @@ pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { } } -impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]); - impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if expr.span.desugaring_kind().is_some() { diff --git a/clippy_lints/src/disallowed_names.rs b/clippy_lints/src/disallowed_names.rs index 566aa12b08f5..1f658b2aa068 100644 --- a/clippy_lints/src/disallowed_names.rs +++ b/clippy_lints/src/disallowed_names.rs @@ -26,6 +26,8 @@ "usage of a disallowed/placeholder name" } +impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]); + pub struct DisallowedNames { disallow: FxHashSet, } @@ -38,8 +40,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]); - impl<'tcx> LateLintPass<'tcx> for DisallowedNames { fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { if let PatKind::Binding(.., ident, _) = pat.kind diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index 5b4fe2a6b803..4596d9457c0b 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -45,6 +45,8 @@ "usage of non-allowed Unicode scripts" } +impl_lint_pass!(DisallowedScriptIdents => [DISALLOWED_SCRIPT_IDENTS]); + pub struct DisallowedScriptIdents { whitelist: FxHashSet