diff --git a/Cargo.lock b/Cargo.lock index 02459afa9072..2be69cf0b23b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3543,7 +3543,6 @@ dependencies = [ "rustc_ast_pretty", "rustc_errors", "rustc_feature", - "rustc_fluent_macro", "rustc_hir", "rustc_lexer", "rustc_macros", @@ -3684,7 +3683,6 @@ dependencies = [ "rustc_macros", "rustc_metadata", "rustc_middle", - "rustc_query_system", "rustc_serialize", "rustc_session", "rustc_span", @@ -3780,7 +3778,6 @@ dependencies = [ "rustc_ast_lowering", "rustc_ast_passes", "rustc_ast_pretty", - "rustc_attr_parsing", "rustc_borrowck", "rustc_builtin_macros", "rustc_codegen_ssa", @@ -3789,13 +3786,11 @@ dependencies = [ "rustc_errors", "rustc_expand", "rustc_feature", - "rustc_fluent_macro", "rustc_hir_analysis", "rustc_hir_pretty", "rustc_hir_typeck", "rustc_incremental", "rustc_index", - "rustc_infer", "rustc_interface", "rustc_lexer", "rustc_lint", @@ -3812,7 +3807,6 @@ dependencies = [ "rustc_pattern_analysis", "rustc_privacy", "rustc_public", - "rustc_query_system", "rustc_resolve", "rustc_session", "rustc_span", @@ -4093,7 +4087,6 @@ version = "0.0.0" dependencies = [ "rustc_data_structures", "rustc_errors", - "rustc_fluent_macro", "rustc_hir", "rustc_index", "rustc_macros", @@ -4229,6 +4222,8 @@ dependencies = [ name = "rustc_macros" version = "0.0.0" dependencies = [ + "fluent-bundle", + "fluent-syntax", "proc-macro2", "quote", "syn 2.0.110", @@ -4336,11 +4331,11 @@ dependencies = [ "polonius-engine", "regex", "rustc_abi", - "rustc_ast", "rustc_data_structures", "rustc_errors", "rustc_fluent_macro", "rustc_graphviz", + "rustc_hir", "rustc_index", "rustc_macros", "rustc_middle", @@ -4570,7 +4565,6 @@ dependencies = [ "rustc_data_structures", "rustc_errors", "rustc_feature", - "rustc_fluent_macro", "rustc_hashes", "rustc_hir", "rustc_index", diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index e346a56bcf40..8d8e8ffc562f 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -354,7 +354,13 @@ fn make_attr_token_stream( FrameData { open_delim_sp: Some((delim, span, spacing)), inner: vec![] }, )); } else if let Some(delim) = kind.close_delim() { - let frame_data = mem::replace(&mut stack_top, stack_rest.pop().unwrap()); + // If there's no matching opening delimiter, the token stream is malformed, + // likely due to a improper delimiter positions in the source code. + // It's not delimiter mismatch, and lexer can not detect it, so we just ignore it here. + let Some(frame) = stack_rest.pop() else { + return AttrTokenStream::new(stack_top.inner); + }; + let frame_data = mem::replace(&mut stack_top, frame); let (open_delim, open_sp, open_spacing) = frame_data.open_delim_sp.unwrap(); assert!( open_delim.eq_ignoring_invisible_origin(&delim), diff --git a/compiler/rustc_attr_parsing/Cargo.toml b/compiler/rustc_attr_parsing/Cargo.toml index 79193f394fe4..411f3f5ccbd1 100644 --- a/compiler/rustc_attr_parsing/Cargo.toml +++ b/compiler/rustc_attr_parsing/Cargo.toml @@ -10,7 +10,6 @@ rustc_ast = { path = "../rustc_ast" } rustc_ast_pretty = { path = "../rustc_ast_pretty" } rustc_errors = { path = "../rustc_errors" } rustc_feature = { path = "../rustc_feature" } -rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_hir = { path = "../rustc_hir" } rustc_lexer = { path = "../rustc_lexer" } rustc_macros = { path = "../rustc_macros" } diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl deleted file mode 100644 index 3e4c1a9dfad8..000000000000 --- a/compiler/rustc_attr_parsing/messages.ftl +++ /dev/null @@ -1,246 +0,0 @@ -attr_parsing_as_needed_compatibility = - linking modifier `as-needed` is only compatible with `dylib`, `framework` and `raw-dylib` linking kinds - -attr_parsing_bundle_needs_static = - linking modifier `bundle` is only compatible with `static` linking kind - -attr_parsing_cfg_attr_bad_delim = wrong `cfg_attr` delimiters - -attr_parsing_deprecated_item_suggestion = - suggestions on deprecated items are unstable - .help = add `#![feature(deprecated_suggestion)]` to the crate root - .note = see #94785 for more details - -attr_parsing_doc_alias_bad_char = - {$char_} character isn't allowed in {$attr_str} - -attr_parsing_doc_alias_empty = - {$attr_str} attribute cannot have empty value - -attr_parsing_doc_alias_malformed = - doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]` - -attr_parsing_doc_alias_start_end = - {$attr_str} cannot start or end with ' ' - -attr_parsing_doc_attr_not_crate_level = - `#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute - -attr_parsing_doc_attribute_not_attribute = - nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]` - .help = only existing builtin attributes are allowed in core/std - -attr_parsing_doc_keyword_not_keyword = - nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]` - .help = only existing keywords are allowed in core/std - -attr_parsing_empty_confusables = - expected at least one confusable name - -attr_parsing_empty_link_name = - link name must not be empty - .label = empty link name - -attr_parsing_expected_single_version_literal = - expected single version literal - -attr_parsing_expected_version_literal = - expected a version literal - -attr_parsing_expects_feature_list = - `{$name}` expects a list of feature names - -attr_parsing_expects_features = - `{$name}` expects feature names - -attr_parsing_import_name_type_raw = - import name type can only be used with link kind `raw-dylib` - -attr_parsing_import_name_type_x86 = - import name type is only supported on x86 - -attr_parsing_incompatible_wasm_link = - `wasm_import_module` is incompatible with other arguments in `#[link]` attributes - -attr_parsing_incorrect_repr_format_align_one_arg = - incorrect `repr(align)` attribute format: `align` takes exactly one argument in parentheses - -attr_parsing_incorrect_repr_format_expect_literal_integer = - incorrect `repr(align)` attribute format: `align` expects a literal integer as argument - -attr_parsing_incorrect_repr_format_generic = - incorrect `repr({$repr_arg})` attribute format - .suggestion = use parentheses instead - -attr_parsing_incorrect_repr_format_packed_expect_integer = - incorrect `repr(packed)` attribute format: `packed` expects a literal integer as argument - -attr_parsing_incorrect_repr_format_packed_one_or_zero_arg = - incorrect `repr(packed)` attribute format: `packed` takes exactly one parenthesized argument, or no parentheses at all - -attr_parsing_invalid_alignment_value = - invalid alignment value: {$error_part} - -attr_parsing_invalid_attr_unsafe = `{$name}` is not an unsafe attribute - .label = this is not an unsafe attribute - .suggestion = remove the `unsafe(...)` - .note = extraneous unsafe is not allowed in attributes - -attr_parsing_invalid_issue_string = - `issue` must be a non-zero numeric string or "none" - .must_not_be_zero = `issue` must not be "0", use "none" instead - .empty = cannot parse integer from empty string - .invalid_digit = invalid digit found in string - .pos_overflow = number too large to fit in target type - .neg_overflow = number too small to fit in target type - -attr_parsing_invalid_link_modifier = - invalid linking modifier syntax, expected '+' or '-' prefix before one of: bundle, verbatim, whole-archive, as-needed - -attr_parsing_invalid_meta_item = expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found {$descr} - .remove_neg_sugg = negative numbers are not literals, try removing the `-` sign - .quote_ident_sugg = surround the identifier with quotation marks to make it into a string literal - .label = {$descr}s are not allowed here - -attr_parsing_invalid_predicate = - invalid predicate `{$predicate}` - -attr_parsing_invalid_repr_align_need_arg = - invalid `repr(align)` attribute: `align` needs an argument - .suggestion = supply an argument here - -attr_parsing_invalid_repr_generic = - invalid `repr({$repr_arg})` attribute: {$error_part} - -attr_parsing_invalid_repr_hint_no_paren = - invalid representation hint: `{$name}` does not take a parenthesized argument list - -attr_parsing_invalid_repr_hint_no_value = - invalid representation hint: `{$name}` does not take a value - -attr_parsing_invalid_since = - 'since' must be a Rust version number, such as "1.31.0" - -attr_parsing_invalid_target = `#[{$name}]` attribute cannot be used on {$target} - .help = `#[{$name}]` can {$only}be applied to {$applied} - .suggestion = remove the attribute - -attr_parsing_limit_invalid = - `limit` must be a non-negative integer - .label = {$error_str} -attr_parsing_link_arg_unstable = - link kind `link-arg` is unstable - -attr_parsing_link_cfg_unstable = - link cfg is unstable - -attr_parsing_link_framework_apple = - link kind `framework` is only supported on Apple targets - -attr_parsing_link_ordinal_out_of_range = ordinal value in `link_ordinal` is too large: `{$ordinal}` - .note = the value may not exceed `u16::MAX` - -attr_parsing_link_requires_name = - `#[link]` attribute requires a `name = "string"` argument - .label = missing `name` argument - -attr_parsing_meta_bad_delim = wrong meta list delimiters -attr_parsing_meta_bad_delim_suggestion = the delimiters should be `(` and `)` - -attr_parsing_missing_feature = - missing 'feature' - -attr_parsing_missing_issue = - missing 'issue' - -attr_parsing_missing_note = - missing 'note' - -attr_parsing_missing_since = - missing 'since' - -attr_parsing_multiple_modifiers = - multiple `{$modifier}` modifiers in a single `modifiers` argument - -attr_parsing_multiple_stability_levels = - multiple stability levels - -attr_parsing_naked_functions_incompatible_attribute = - attribute incompatible with `#[unsafe(naked)]` - .label = the `{$attr}` attribute is incompatible with `#[unsafe(naked)]` - .naked_attribute = function marked with `#[unsafe(naked)]` here - -attr_parsing_non_ident_feature = - 'feature' is not an identifier - -attr_parsing_null_on_export = `export_name` may not contain null characters - -attr_parsing_null_on_link_section = `link_section` may not contain null characters - -attr_parsing_null_on_objc_class = `objc::class!` may not contain null characters - -attr_parsing_null_on_objc_selector = `objc::selector!` may not contain null characters - -attr_parsing_objc_class_expected_string_literal = `objc::class!` expected a string literal - -attr_parsing_objc_selector_expected_string_literal = `objc::selector!` expected a string literal - -attr_parsing_raw_dylib_elf_unstable = - link kind `raw-dylib` is unstable on ELF platforms - -attr_parsing_raw_dylib_no_nul = - link name must not contain NUL characters if link kind is `raw-dylib` - -attr_parsing_raw_dylib_only_windows = - link kind `raw-dylib` is only supported on Windows targets - -attr_parsing_repr_ident = - meta item in `repr` must be an identifier - -attr_parsing_rustc_allowed_unstable_pairing = - `rustc_allowed_through_unstable_modules` attribute must be paired with a `stable` attribute - -attr_parsing_rustc_promotable_pairing = - `rustc_promotable` attribute must be paired with either a `rustc_const_unstable` or a `rustc_const_stable` attribute - -attr_parsing_rustc_scalable_vector_count_out_of_range = element count in `rustc_scalable_vector` is too large: `{$n}` - .note = the value may not exceed `u16::MAX` - -attr_parsing_soft_no_args = - `soft` should not have any arguments - -attr_parsing_stability_outside_std = stability attributes may not be used outside of the standard library - -attr_parsing_suffixed_literal_in_attribute = suffixed literals are not allowed in attributes - .help = instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.) - -attr_parsing_unknown_version_literal = - unknown version literal format, assuming it refers to a future version - -attr_parsing_unrecognized_repr_hint = - unrecognized representation hint - .help = valid reprs are `Rust` (default), `C`, `align`, `packed`, `transparent`, `simd`, `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize` - .note = for more information, visit - -attr_parsing_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe - .label = usage of unsafe attribute -attr_parsing_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)` - -attr_parsing_unstable_cfg_target_compact = - compact `cfg(target(..))` is experimental and subject to change - -attr_parsing_unstable_feature_bound_incompatible_stability = item annotated with `#[unstable_feature_bound]` should not be stable - .help = if this item is meant to be stable, do not use any functions annotated with `#[unstable_feature_bound]`. Otherwise, mark this item as unstable with `#[unstable]` - -attr_parsing_unsupported_instruction_set = target `{$current_target}` does not support `#[instruction_set({$instruction_set}::*)]` - -attr_parsing_unsupported_literal_suggestion = - consider removing the prefix - -attr_parsing_unused_multiple = - multiple `{$name}` attributes - .suggestion = remove this attribute - .note = attribute also specified here - -attr_parsing_whole_archive_needs_static = - linking modifier `whole-archive` is only compatible with `static` linking kind diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index 3a540d80998d..686d98a9fc78 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -3,14 +3,14 @@ use std::convert::identity; use rustc_ast::token::Delimiter; use rustc_ast::tokenstream::DelimSpan; use rustc_ast::{AttrItem, Attribute, CRATE_NODE_ID, LitKind, ast, token}; -use rustc_errors::{Applicability, PResult}; +use rustc_errors::{Applicability, PResult, inline_fluent}; use rustc_feature::{ AttrSuggestionStyle, AttributeTemplate, Features, GatedCfg, find_gated_cfg, template, }; use rustc_hir::attrs::CfgEntry; use rustc_hir::lints::AttributeLintKind; use rustc_hir::{AttrPath, RustcVersion, Target}; -use rustc_parse::parser::{ForceCollect, Parser}; +use rustc_parse::parser::{ForceCollect, Parser, Recovery}; use rustc_parse::{exp, parse_in}; use rustc_session::Session; use rustc_session::config::ExpectedValues; @@ -25,7 +25,7 @@ use crate::session_diagnostics::{ AttributeParseError, AttributeParseErrorReason, CfgAttrBadDelim, MetaBadDelimSugg, ParsedDescription, }; -use crate::{AttributeParser, fluent_generated, parse_version, session_diagnostics}; +use crate::{AttributeParser, parse_version, session_diagnostics}; pub const CFG_TEMPLATE: AttributeTemplate = template!( List: &["predicate"], @@ -141,7 +141,7 @@ fn parse_cfg_entry_target( cx.sess(), sym::cfg_target_compact, meta_span, - fluent_generated::attr_parsing_unstable_cfg_target_compact, + inline_fluent!("compact `cfg(target(..))` is experimental and subject to change"), ) .emit(); } @@ -360,8 +360,10 @@ fn parse_cfg_attr_internal<'a>( ) -> PResult<'a, (CfgEntry, Vec<(ast::AttrItem, Span)>)> { // Parse cfg predicate let pred_start = parser.token.span; - let meta = - MetaItemOrLitParser::parse_single(parser, ShouldEmit::ErrorsAndLints { recover: true })?; + let meta = MetaItemOrLitParser::parse_single( + parser, + ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, + )?; let pred_span = pred_start.with_hi(parser.token.span.hi()); let cfg_predicate = AttributeParser::parse_single_args( @@ -376,7 +378,7 @@ fn parse_cfg_attr_internal<'a>( CRATE_NODE_ID, Target::Crate, features, - ShouldEmit::ErrorsAndLints { recover: true }, + ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, &meta, parse_cfg_entry, &CFG_ATTR_TEMPLATE, diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index ca844758daaa..4005ad2cba11 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -5,7 +5,7 @@ use rustc_feature::{AttributeTemplate, Features}; use rustc_hir::attrs::CfgEntry; use rustc_hir::{AttrPath, Target}; use rustc_parse::exp; -use rustc_parse::parser::Parser; +use rustc_parse::parser::{Parser, Recovery}; use rustc_session::Session; use rustc_span::{ErrorGuaranteed, Span, sym}; @@ -78,9 +78,11 @@ pub fn parse_cfg_select( } } } else { - let meta = - MetaItemOrLitParser::parse_single(p, ShouldEmit::ErrorsAndLints { recover: true }) - .map_err(|diag| diag.emit())?; + let meta = MetaItemOrLitParser::parse_single( + p, + ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, + ) + .map_err(|diag| diag.emit())?; let cfg_span = meta.span(); let cfg = AttributeParser::parse_single_args( sess, @@ -95,7 +97,7 @@ pub fn parse_cfg_select( // Doesn't matter what the target actually is here. Target::Crate, features, - ShouldEmit::ErrorsAndLints { recover: true }, + ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, &meta, parse_cfg_entry, &AttributeTemplate::default(), diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index a367e699fcb9..557dfe09853b 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -274,3 +274,12 @@ impl NoArgsAttributeParser for NoBuiltinsParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::NoBuiltins; } + +pub(crate) struct RustcPreserveUbChecksParser; + +impl NoArgsAttributeParser for RustcPreserveUbChecksParser { + const PATH: &[Symbol] = &[sym::rustc_preserve_ub_checks]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcPreserveUbChecks; +} diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index 548f53a986b8..c9da2f3b14bf 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -1,3 +1,4 @@ +use rustc_errors::inline_fluent; use rustc_feature::Features; use rustc_hir::attrs::AttributeKind::{LinkName, LinkOrdinal, LinkSection}; use rustc_hir::attrs::*; @@ -10,7 +11,6 @@ use rustc_target::spec::{Arch, BinaryFormat}; use super::prelude::*; use super::util::parse_single_integer; use crate::attributes::cfg::parse_cfg_entry; -use crate::fluent_generated; use crate::session_diagnostics::{ AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier, LinkFrameworkApple, LinkOrdinalOutOfRange, @@ -305,7 +305,7 @@ impl LinkParser { sess, sym::raw_dylib_elf, nv.value_span, - fluent_generated::attr_parsing_raw_dylib_elf_unstable, + inline_fluent!("link kind `raw-dylib` is unstable on ELF platforms"), ) .emit(); } else { @@ -320,7 +320,7 @@ impl LinkParser { sess, sym::link_arg_attribute, nv.value_span, - fluent_generated::attr_parsing_link_arg_unstable, + inline_fluent!("link kind `link-arg` is unstable"), ) .emit(); } @@ -385,13 +385,8 @@ impl LinkParser { return true; }; if !features.link_cfg() { - feature_err( - sess, - sym::link_cfg, - item.span(), - fluent_generated::attr_parsing_link_cfg_unstable, - ) - .emit(); + feature_err(sess, sym::link_cfg, item.span(), inline_fluent!("link cfg is unstable")) + .emit(); } *cfg = parse_cfg_entry(cx, link_cfg).ok(); true diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs index 53120dece916..71a8fb0dd47d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs @@ -7,36 +7,36 @@ use crate::attributes::{NoArgsAttributeParser, OnDuplicate}; use crate::context::Stage; use crate::target_checking::AllowedTargets; -pub(crate) struct RustcDumpUserArgs; +pub(crate) struct RustcDumpUserArgsParser; -impl NoArgsAttributeParser for RustcDumpUserArgs { +impl NoArgsAttributeParser for RustcDumpUserArgsParser { const PATH: &[Symbol] = &[sym::rustc_dump_user_args]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpUserArgs; } -pub(crate) struct RustcDumpDefParents; +pub(crate) struct RustcDumpDefParentsParser; -impl NoArgsAttributeParser for RustcDumpDefParents { +impl NoArgsAttributeParser for RustcDumpDefParentsParser { const PATH: &[Symbol] = &[sym::rustc_dump_def_parents]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpDefParents; } -pub(crate) struct RustcDumpItemBounds; +pub(crate) struct RustcDumpItemBoundsParser; -impl NoArgsAttributeParser for RustcDumpItemBounds { +impl NoArgsAttributeParser for RustcDumpItemBoundsParser { const PATH: &[Symbol] = &[sym::rustc_dump_item_bounds]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::AssocTy)]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpItemBounds; } -pub(crate) struct RustcDumpPredicates; +pub(crate) struct RustcDumpPredicatesParser; -impl NoArgsAttributeParser for RustcDumpPredicates { +impl NoArgsAttributeParser for RustcDumpPredicatesParser { const PATH: &[Symbol] = &[sym::rustc_dump_predicates]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ @@ -49,9 +49,9 @@ impl NoArgsAttributeParser for RustcDumpPredicates { const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpPredicates; } -pub(crate) struct RustcDumpVtable; +pub(crate) struct RustcDumpVtableParser; -impl NoArgsAttributeParser for RustcDumpVtable { +impl NoArgsAttributeParser for RustcDumpVtableParser { const PATH: &[Symbol] = &[sym::rustc_dump_vtable]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 38f728fa9f55..8961dc47706f 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -1,5 +1,7 @@ +use std::path::PathBuf; + use rustc_ast::{LitIntType, LitKind, MetaItemLit}; -use rustc_hir::attrs::RustcLayoutType; +use rustc_hir::attrs::{BorrowckGraphvizFormatKind, RustcLayoutType, RustcMirKind}; use rustc_session::errors; use super::prelude::*; @@ -307,6 +309,14 @@ impl NoArgsAttributeParser for RustcHasIncoherentInherentImplsParse const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcHasIncoherentInherentImpls; } +pub(crate) struct RustcHiddenTypeOfOpaquesParser; + +impl NoArgsAttributeParser for RustcHiddenTypeOfOpaquesParser { + const PATH: &[Symbol] = &[sym::rustc_hidden_type_of_opaques]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcHiddenTypeOfOpaques; +} pub(crate) struct RustcNounwindParser; impl NoArgsAttributeParser for RustcNounwindParser { @@ -349,7 +359,6 @@ impl CombineAttributeParser for RustcLayoutParser { const TEMPLATE: AttributeTemplate = template!(List: &["abi", "align", "size", "homogenous_aggregate", "debug"]); - fn extend( cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser, @@ -389,6 +398,94 @@ impl CombineAttributeParser for RustcLayoutParser { } } +pub(crate) struct RustcMirParser; + +impl CombineAttributeParser for RustcMirParser { + const PATH: &[rustc_span::Symbol] = &[sym::rustc_mir]; + + type Item = RustcMirKind; + + const CONVERT: ConvertFn = |items, _| AttributeKind::RustcMir(items); + + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::Method(MethodKind::Trait { body: true })), + ]); + + const TEMPLATE: AttributeTemplate = template!(List: &["arg1, arg2, ..."]); + + fn extend( + cx: &mut AcceptContext<'_, '_, S>, + args: &ArgParser, + ) -> impl IntoIterator { + let Some(list) = args.list() else { + cx.expected_list(cx.attr_span, args); + return ThinVec::new(); + }; + + list.mixed() + .filter_map(|arg| arg.meta_item()) + .filter_map(|mi| { + if let Some(ident) = mi.ident() { + match ident.name { + sym::rustc_peek_maybe_init => Some(RustcMirKind::PeekMaybeInit), + sym::rustc_peek_maybe_uninit => Some(RustcMirKind::PeekMaybeUninit), + sym::rustc_peek_liveness => Some(RustcMirKind::PeekLiveness), + sym::stop_after_dataflow => Some(RustcMirKind::StopAfterDataflow), + sym::borrowck_graphviz_postflow => { + let Some(nv) = mi.args().name_value() else { + cx.expected_name_value( + mi.span(), + Some(sym::borrowck_graphviz_postflow), + ); + return None; + }; + let Some(path) = nv.value_as_str() else { + cx.expected_string_literal(nv.value_span, None); + return None; + }; + let path = PathBuf::from(path.to_string()); + if path.file_name().is_some() { + Some(RustcMirKind::BorrowckGraphvizPostflow { path }) + } else { + cx.expected_filename_literal(nv.value_span); + None + } + } + sym::borrowck_graphviz_format => { + let Some(nv) = mi.args().name_value() else { + cx.expected_name_value( + mi.span(), + Some(sym::borrowck_graphviz_format), + ); + return None; + }; + let Some(format) = nv.value_as_ident() else { + cx.expected_identifier(nv.value_span); + return None; + }; + match format.name { + sym::two_phase => Some(RustcMirKind::BorrowckGraphvizFormat { + format: BorrowckGraphvizFormatKind::TwoPhase, + }), + _ => { + cx.expected_specific_argument(format.span, &[sym::two_phase]); + None + } + } + } + _ => None, + } + } else { + None + } + }) + .collect() + } +} pub(crate) struct RustcNonConstTraitMethodParser; impl NoArgsAttributeParser for RustcNonConstTraitMethodParser { diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 0cabc0895053..322e189e6d12 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -10,6 +10,7 @@ use rustc_feature::{AttrSuggestionStyle, AttributeTemplate}; use rustc_hir::attrs::AttributeKind; use rustc_hir::lints::AttributeLintKind; use rustc_hir::{AttrPath, HirId}; +use rustc_parse::parser::Recovery; use rustc_session::Session; use rustc_session::lint::{Lint, LintId}; use rustc_span::{ErrorGuaranteed, Span, Symbol}; @@ -31,7 +32,8 @@ use crate::attributes::crate_level::{ CrateNameParser, CrateTypeParser, MoveSizeLimitParser, NeedsPanicRuntimeParser, NoBuiltinsParser, NoCoreParser, NoMainParser, NoStdParser, PanicRuntimeParser, PatternComplexityLimitParser, ProfilerRuntimeParser, RecursionLimitParser, - RustcCoherenceIsCoreParser, TypeLengthLimitParser, WindowsSubsystemParser, + RustcCoherenceIsCoreParser, RustcPreserveUbChecksParser, TypeLengthLimitParser, + WindowsSubsystemParser, }; use crate::attributes::debugger::DebuggerViualizerParser; use crate::attributes::deprecation::DeprecationParser; @@ -71,17 +73,18 @@ use crate::attributes::rustc_allocator::{ RustcDeallocatorParser, RustcReallocatorParser, }; use crate::attributes::rustc_dump::{ - RustcDumpDefParents, RustcDumpItemBounds, RustcDumpPredicates, RustcDumpUserArgs, - RustcDumpVtable, + RustcDumpDefParentsParser, RustcDumpItemBoundsParser, RustcDumpPredicatesParser, + RustcDumpUserArgsParser, RustcDumpVtableParser, }; use crate::attributes::rustc_internal::{ - RustcHasIncoherentInherentImplsParser, RustcLayoutParser, RustcLayoutScalarValidRangeEndParser, - RustcLayoutScalarValidRangeStartParser, RustcLegacyConstGenericsParser, - RustcLintOptDenyFieldAccessParser, RustcLintOptTyParser, RustcLintQueryInstabilityParser, - RustcLintUntrackedQueryInformationParser, RustcMainParser, RustcMustImplementOneOfParser, - RustcNeverReturnsNullPointerParser, RustcNoImplicitAutorefsParser, - RustcNonConstTraitMethodParser, RustcNounwindParser, RustcObjectLifetimeDefaultParser, - RustcOffloadKernelParser, RustcScalableVectorParser, RustcSimdMonomorphizeLaneLimitParser, + RustcHasIncoherentInherentImplsParser, RustcHiddenTypeOfOpaquesParser, RustcLayoutParser, + RustcLayoutScalarValidRangeEndParser, RustcLayoutScalarValidRangeStartParser, + RustcLegacyConstGenericsParser, RustcLintOptDenyFieldAccessParser, RustcLintOptTyParser, + RustcLintQueryInstabilityParser, RustcLintUntrackedQueryInformationParser, RustcMainParser, + RustcMirParser, RustcMustImplementOneOfParser, RustcNeverReturnsNullPointerParser, + RustcNoImplicitAutorefsParser, RustcNonConstTraitMethodParser, RustcNounwindParser, + RustcObjectLifetimeDefaultParser, RustcOffloadKernelParser, RustcScalableVectorParser, + RustcSimdMonomorphizeLaneLimitParser, }; use crate::attributes::semantics::MayDangleParser; use crate::attributes::stability::{ @@ -199,6 +202,7 @@ attribute_parsers!( Combine, Combine, Combine, + Combine, Combine, Combine, // tidy-alphabetical-end @@ -293,12 +297,13 @@ attribute_parsers!( Single>, Single>, Single>, - Single>, - Single>, - Single>, - Single>, - Single>, + Single>, + Single>, + Single>, + Single>, + Single>, Single>, + Single>, Single>, Single>, Single>, @@ -309,6 +314,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, @@ -383,7 +389,7 @@ impl Stage for Late { } fn should_emit(&self) -> ShouldEmit { - ShouldEmit::ErrorsAndLints { recover: true } + ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed } } } @@ -512,6 +518,11 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { ) } + /// Error that a filename string literal was expected. + pub(crate) fn expected_filename_literal(&self, span: Span) { + self.emit_parse_error(span, AttributeParseErrorReason::ExpectedFilenameLiteral); + } + pub(crate) fn expected_integer_literal(&self, span: Span) -> ErrorGuaranteed { self.emit_parse_error(span, AttributeParseErrorReason::ExpectedIntegerLiteral) } @@ -770,10 +781,10 @@ pub enum ShouldEmit { ErrorsAndLints { /// Whether [`ArgParser`] will attempt to recover from errors. /// - /// If true, it will attempt to recover from bad input (like an invalid literal). Setting - /// this to false will instead return early, and not raise errors except at the top level - /// (in [`ArgParser::from_attr_args`]). - recover: bool, + /// Whether it is allowed to recover from bad input (like an invalid literal). Setting + /// this to `Forbidden` will instead return early, and not raise errors except at the top + /// level (in [`ArgParser::from_attr_args`]). + recovery: Recovery, }, /// The operation will *not* emit errors and lints. /// diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 349e6c234520..fe050250e354 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -113,5 +113,3 @@ pub use attributes::util::{is_builtin_attr, parse_version}; pub use context::{Early, Late, OmitDoc, ShouldEmit}; pub use interface::AttributeParser; pub use session_diagnostics::ParsedDescription; - -rustc_fluent_macro::fluent_messages! { "../messages.ftl" } diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 7f3c6d28005f..973635f432e8 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -15,7 +15,7 @@ use rustc_ast_pretty::pprust; use rustc_errors::{Diag, PResult}; use rustc_hir::{self as hir, AttrPath}; use rustc_parse::exp; -use rustc_parse::parser::{ForceCollect, Parser, PathStyle, token_descr}; +use rustc_parse::parser::{ForceCollect, Parser, PathStyle, Recovery, token_descr}; use rustc_session::errors::create_lit_error; use rustc_session::parse::ParseSess; use rustc_span::{Ident, Span, Symbol, sym}; @@ -121,7 +121,7 @@ impl ArgParser { &args.tokens, args.dspan.entire(), psess, - ShouldEmit::ErrorsAndLints { recover: false }, + ShouldEmit::ErrorsAndLints { recovery: Recovery::Forbidden }, ) { Ok(p) => return Some(ArgParser::List(p)), Err(e) => { @@ -373,7 +373,10 @@ fn expr_to_lit<'sess>( } Err(err) => { let err = create_lit_error(psess, err, token_lit, expr.span); - if matches!(should_emit, ShouldEmit::ErrorsAndLints { recover: false }) { + if matches!( + should_emit, + ShouldEmit::ErrorsAndLints { recovery: Recovery::Forbidden } + ) { Err(err) } else { let lit = MetaItemLit { @@ -431,7 +434,10 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { if !lit.kind.is_unsuffixed() { // Emit error and continue, we can still parse the attribute as if the suffix isn't there let err = self.parser.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }); - if matches!(self.should_emit, ShouldEmit::ErrorsAndLints { recover: false }) { + if matches!( + self.should_emit, + ShouldEmit::ErrorsAndLints { recovery: Recovery::Forbidden } + ) { return Err(err); } else { self.should_emit.emit_err(err) @@ -569,6 +575,10 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { should_emit: ShouldEmit, ) -> PResult<'sess, MetaItemListParser> { let mut parser = Parser::new(psess, tokens, None); + if let ShouldEmit::ErrorsAndLints { recovery } = should_emit { + parser = parser.recovery(recovery); + } + let mut this = MetaItemListParserContext { parser: &mut parser, should_emit }; // Presumably, the majority of the time there will only be one attr. diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index f9748542beb9..fb4d9c660a19 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -11,10 +11,8 @@ use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_span::{Span, Symbol}; use rustc_target::spec::TargetTuple; -use crate::fluent_generated as fluent; - #[derive(Diagnostic)] -#[diag(attr_parsing_invalid_predicate, code = E0537)] +#[diag("invalid predicate `{$predicate}`", code = E0537)] pub(crate) struct InvalidPredicate { #[primary_span] pub span: Span, @@ -23,7 +21,7 @@ pub(crate) struct InvalidPredicate { } #[derive(Diagnostic)] -#[diag(attr_parsing_doc_alias_empty)] +#[diag("{$attr_str} attribute cannot have empty value")] pub(crate) struct DocAliasEmpty<'a> { #[primary_span] pub span: Span, @@ -31,7 +29,7 @@ pub(crate) struct DocAliasEmpty<'a> { } #[derive(Diagnostic)] -#[diag(attr_parsing_doc_alias_bad_char)] +#[diag("{$char_} character isn't allowed in {$attr_str}")] pub(crate) struct DocAliasBadChar<'a> { #[primary_span] pub span: Span, @@ -40,7 +38,7 @@ pub(crate) struct DocAliasBadChar<'a> { } #[derive(Diagnostic)] -#[diag(attr_parsing_doc_alias_start_end)] +#[diag("{$attr_str} cannot start or end with ' '")] pub(crate) struct DocAliasStartEnd<'a> { #[primary_span] pub span: Span, @@ -48,7 +46,7 @@ pub(crate) struct DocAliasStartEnd<'a> { } #[derive(Diagnostic)] -#[diag(attr_parsing_doc_attr_not_crate_level)] +#[diag("`#![doc({$attr_name} = \"...\")]` isn't allowed as a crate-level attribute")] pub(crate) struct DocAttrNotCrateLevel { #[primary_span] pub span: Span, @@ -56,8 +54,8 @@ pub(crate) struct DocAttrNotCrateLevel { } #[derive(Diagnostic)] -#[diag(attr_parsing_doc_keyword_not_keyword)] -#[help] +#[diag("nonexistent keyword `{$keyword}` used in `#[doc(keyword = \"...\")]`")] +#[help("only existing keywords are allowed in core/std")] pub(crate) struct DocKeywordNotKeyword { #[primary_span] pub span: Span, @@ -65,8 +63,8 @@ pub(crate) struct DocKeywordNotKeyword { } #[derive(Diagnostic)] -#[diag(attr_parsing_doc_attribute_not_attribute)] -#[help] +#[diag("nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = \"...\")]`")] +#[help("only existing builtin attributes are allowed in core/std")] pub(crate) struct DocAttributeNotAttribute { #[primary_span] pub span: Span, @@ -74,28 +72,28 @@ pub(crate) struct DocAttributeNotAttribute { } #[derive(Diagnostic)] -#[diag(attr_parsing_missing_since, code = E0542)] +#[diag("missing 'since'", code = E0542)] pub(crate) struct MissingSince { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_missing_note, code = E0543)] +#[diag("missing 'note'", code = E0543)] pub(crate) struct MissingNote { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_multiple_stability_levels, code = E0544)] +#[diag("multiple stability levels", code = E0544)] pub(crate) struct MultipleStabilityLevels { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_invalid_issue_string, code = E0545)] +#[diag("`issue` must be a non-zero numeric string or \"none\"", code = E0545)] pub(crate) struct InvalidIssueString { #[primary_span] pub span: Span, @@ -108,31 +106,31 @@ pub(crate) struct InvalidIssueString { // translatable. #[derive(Subdiagnostic)] pub(crate) enum InvalidIssueStringCause { - #[label(attr_parsing_must_not_be_zero)] + #[label("`issue` must not be \"0\", use \"none\" instead")] MustNotBeZero { #[primary_span] span: Span, }, - #[label(attr_parsing_empty)] + #[label("cannot parse integer from empty string")] Empty { #[primary_span] span: Span, }, - #[label(attr_parsing_invalid_digit)] + #[label("invalid digit found in string")] InvalidDigit { #[primary_span] span: Span, }, - #[label(attr_parsing_pos_overflow)] + #[label("number too large to fit in target type")] PosOverflow { #[primary_span] span: Span, }, - #[label(attr_parsing_neg_overflow)] + #[label("number too small to fit in target type")] NegOverflow { #[primary_span] span: Span, @@ -153,21 +151,21 @@ impl InvalidIssueStringCause { } #[derive(Diagnostic)] -#[diag(attr_parsing_missing_feature, code = E0546)] +#[diag("missing 'feature'", code = E0546)] pub(crate) struct MissingFeature { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_non_ident_feature, code = E0546)] +#[diag("'feature' is not an identifier", code = E0546)] pub(crate) struct NonIdentFeature { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_missing_issue, code = E0547)] +#[diag("missing 'issue'", code = E0547)] pub(crate) struct MissingIssue { #[primary_span] pub span: Span, @@ -176,20 +174,20 @@ pub(crate) struct MissingIssue { // FIXME: Why is this the same error code as `InvalidReprHintNoParen` and `InvalidReprHintNoValue`? // It is more similar to `IncorrectReprFormatGeneric`. #[derive(Diagnostic)] -#[diag(attr_parsing_incorrect_repr_format_packed_one_or_zero_arg, code = E0552)] +#[diag("incorrect `repr(packed)` attribute format: `packed` takes exactly one parenthesized argument, or no parentheses at all", code = E0552)] pub(crate) struct IncorrectReprFormatPackedOneOrZeroArg { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_incorrect_repr_format_packed_expect_integer, code = E0552)] +#[diag("incorrect `repr(packed)` attribute format: `packed` expects a literal integer as argument", code = E0552)] pub(crate) struct IncorrectReprFormatPackedExpectInteger { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_invalid_repr_hint_no_paren, code = E0552)] +#[diag("invalid representation hint: `{$name}` does not take a parenthesized argument list", code = E0552)] pub(crate) struct InvalidReprHintNoParen { #[primary_span] pub span: Span, @@ -198,7 +196,7 @@ pub(crate) struct InvalidReprHintNoParen { } #[derive(Diagnostic)] -#[diag(attr_parsing_invalid_repr_hint_no_value, code = E0552)] +#[diag("invalid representation hint: `{$name}` does not take a value", code = E0552)] pub(crate) struct InvalidReprHintNoValue { #[primary_span] pub span: Span, @@ -207,15 +205,19 @@ pub(crate) struct InvalidReprHintNoValue { } #[derive(Diagnostic)] -#[diag(attr_parsing_invalid_repr_align_need_arg, code = E0589)] +#[diag("invalid `repr(align)` attribute: `align` needs an argument", code = E0589)] pub(crate) struct InvalidReprAlignNeedArg { #[primary_span] - #[suggestion(code = "align(...)", applicability = "has-placeholders")] + #[suggestion( + "supply an argument here", + code = "align(...)", + applicability = "has-placeholders" + )] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_invalid_repr_generic, code = E0589)] +#[diag("invalid `repr({$repr_arg})` attribute: {$error_part}", code = E0589)] pub(crate) struct InvalidReprGeneric<'a> { #[primary_span] pub span: Span, @@ -225,21 +227,21 @@ pub(crate) struct InvalidReprGeneric<'a> { } #[derive(Diagnostic)] -#[diag(attr_parsing_incorrect_repr_format_align_one_arg, code = E0693)] +#[diag("incorrect `repr(align)` attribute format: `align` takes exactly one argument in parentheses", code = E0693)] pub(crate) struct IncorrectReprFormatAlignOneArg { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_incorrect_repr_format_expect_literal_integer, code = E0693)] +#[diag("incorrect `repr(align)` attribute format: `align` expects a literal integer as argument", code = E0693)] pub(crate) struct IncorrectReprFormatExpectInteger { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_incorrect_repr_format_generic, code = E0693)] +#[diag("incorrect `repr({$repr_arg})` attribute format", code = E0693)] pub(crate) struct IncorrectReprFormatGeneric { #[primary_span] pub span: Span, @@ -253,7 +255,7 @@ pub(crate) struct IncorrectReprFormatGeneric { #[derive(Subdiagnostic)] pub(crate) enum IncorrectReprFormatGenericCause { #[suggestion( - attr_parsing_suggestion, + "use parentheses instead", code = "{name}({value})", applicability = "machine-applicable" )] @@ -269,7 +271,7 @@ pub(crate) enum IncorrectReprFormatGenericCause { }, #[suggestion( - attr_parsing_suggestion, + "use parentheses instead", code = "{name}({value})", applicability = "machine-applicable" )] @@ -298,48 +300,48 @@ impl IncorrectReprFormatGenericCause { } #[derive(Diagnostic)] -#[diag(attr_parsing_rustc_promotable_pairing, code = E0717)] +#[diag("`rustc_promotable` attribute must be paired with either a `rustc_const_unstable` or a `rustc_const_stable` attribute", code = E0717)] pub(crate) struct RustcPromotablePairing { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_rustc_allowed_unstable_pairing, code = E0789)] +#[diag("`rustc_allowed_through_unstable_modules` attribute must be paired with a `stable` attribute", code = E0789)] pub(crate) struct RustcAllowedUnstablePairing { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_deprecated_item_suggestion)] +#[diag("suggestions on deprecated items are unstable")] pub(crate) struct DeprecatedItemSuggestion { #[primary_span] pub span: Span, - #[help] + #[help("add `#![feature(deprecated_suggestion)]` to the crate root")] pub is_nightly: bool, - #[note] + #[note("see #94785 for more details")] pub details: (), } #[derive(Diagnostic)] -#[diag(attr_parsing_expected_single_version_literal)] +#[diag("expected single version literal")] pub(crate) struct ExpectedSingleVersionLiteral { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_expected_version_literal)] +#[diag("expected a version literal")] pub(crate) struct ExpectedVersionLiteral { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_expects_feature_list)] +#[diag("`{$name}` expects a list of feature names")] pub(crate) struct ExpectsFeatureList { #[primary_span] pub span: Span, @@ -348,7 +350,7 @@ pub(crate) struct ExpectsFeatureList { } #[derive(Diagnostic)] -#[diag(attr_parsing_expects_features)] +#[diag("`{$name}` expects feature names")] pub(crate) struct ExpectsFeatures { #[primary_span] pub span: Span, @@ -357,21 +359,21 @@ pub(crate) struct ExpectsFeatures { } #[derive(Diagnostic)] -#[diag(attr_parsing_invalid_since)] +#[diag("'since' must be a Rust version number, such as \"1.31.0\"")] pub(crate) struct InvalidSince { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_soft_no_args)] +#[diag("`soft` should not have any arguments")] pub(crate) struct SoftNoArgs { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_unknown_version_literal)] +#[diag("unknown version literal format, assuming it refers to a future version")] pub(crate) struct UnknownVersionLiteral { #[primary_span] pub span: Span, @@ -379,78 +381,83 @@ pub(crate) struct UnknownVersionLiteral { // FIXME(jdonszelmann) duplicated from `rustc_passes`, remove once `check_attr` is integrated. #[derive(Diagnostic)] -#[diag(attr_parsing_unused_multiple)] +#[diag("multiple `{$name}` attributes")] pub(crate) struct UnusedMultiple { #[primary_span] - #[suggestion(code = "", applicability = "machine-applicable")] + #[suggestion("remove this attribute", code = "", applicability = "machine-applicable")] pub this: Span, - #[note] + #[note("attribute also specified here")] pub other: Span, pub name: Symbol, } #[derive(Diagnostic)] -#[diag(attr_parsing_null_on_export, code = E0648)] +#[diag("`export_name` may not contain null characters", code = E0648)] pub(crate) struct NullOnExport { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_null_on_link_section, code = E0648)] +#[diag("`link_section` may not contain null characters", code = E0648)] pub(crate) struct NullOnLinkSection { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_null_on_objc_class)] +#[diag("`objc::class!` may not contain null characters")] pub(crate) struct NullOnObjcClass { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_null_on_objc_selector)] +#[diag("`objc::selector!` may not contain null characters")] pub(crate) struct NullOnObjcSelector { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_objc_class_expected_string_literal)] +#[diag("`objc::class!` expected a string literal")] pub(crate) struct ObjcClassExpectedStringLiteral { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_objc_selector_expected_string_literal)] +#[diag("`objc::selector!` expected a string literal")] pub(crate) struct ObjcSelectorExpectedStringLiteral { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_stability_outside_std, code = E0734)] +#[diag("stability attributes may not be used outside of the standard library", code = E0734)] pub(crate) struct StabilityOutsideStd { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_empty_confusables)] +#[diag("expected at least one confusable name")] pub(crate) struct EmptyConfusables { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[help] -#[diag(attr_parsing_invalid_target)] +#[help("`#[{$name}]` can {$only}be applied to {$applied}")] +#[diag("`#[{$name}]` attribute cannot be used on {$target}")] pub(crate) struct InvalidTarget { #[primary_span] - #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")] + #[suggestion( + "remove the attribute", + code = "", + applicability = "machine-applicable", + style = "tool-only" + )] pub span: Span, pub name: AttrPath, pub target: &'static str, @@ -459,7 +466,7 @@ pub(crate) struct InvalidTarget { } #[derive(Diagnostic)] -#[diag(attr_parsing_invalid_alignment_value, code = E0589)] +#[diag("invalid alignment value: {$error_part}", code = E0589)] pub(crate) struct InvalidAlignmentValue { #[primary_span] pub span: Span, @@ -467,43 +474,49 @@ pub(crate) struct InvalidAlignmentValue { } #[derive(Diagnostic)] -#[diag(attr_parsing_repr_ident, code = E0565)] +#[diag("meta item in `repr` must be an identifier", code = E0565)] pub(crate) struct ReprIdent { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_unrecognized_repr_hint, code = E0552)] -#[help] -#[note] +#[diag("unrecognized representation hint", code = E0552)] +#[help( + "valid reprs are `Rust` (default), `C`, `align`, `packed`, `transparent`, `simd`, `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize`" +)] +#[note( + "for more information, visit " +)] pub(crate) struct UnrecognizedReprHint { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_unstable_feature_bound_incompatible_stability)] -#[help] +#[diag("item annotated with `#[unstable_feature_bound]` should not be stable")] +#[help( + "if this item is meant to be stable, do not use any functions annotated with `#[unstable_feature_bound]`. Otherwise, mark this item as unstable with `#[unstable]`" +)] pub(crate) struct UnstableFeatureBoundIncompatibleStability { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_naked_functions_incompatible_attribute, code = E0736)] +#[diag("attribute incompatible with `#[unsafe(naked)]`", code = E0736)] pub(crate) struct NakedFunctionIncompatibleAttribute { #[primary_span] - #[label] + #[label("the `{$attr}` attribute is incompatible with `#[unsafe(naked)]`")] pub span: Span, - #[label(attr_parsing_naked_attribute)] + #[label("function marked with `#[unsafe(naked)]` here")] pub naked_span: Span, pub attr: String, } #[derive(Diagnostic)] -#[diag(attr_parsing_link_ordinal_out_of_range)] -#[note] +#[diag("ordinal value in `link_ordinal` is too large: `{$ordinal}`")] +#[note("the value may not exceed `u16::MAX`")] pub(crate) struct LinkOrdinalOutOfRange { #[primary_span] pub span: Span, @@ -511,8 +524,8 @@ pub(crate) struct LinkOrdinalOutOfRange { } #[derive(Diagnostic)] -#[diag(attr_parsing_rustc_scalable_vector_count_out_of_range)] -#[note] +#[diag("element count in `rustc_scalable_vector` is too large: `{$n}`")] +#[note("the value may not exceed `u16::MAX`")] pub(crate) struct RustcScalableVectorCountOutOfRange { #[primary_span] pub span: Span, @@ -524,6 +537,7 @@ pub(crate) enum AttributeParseErrorReason<'a> { ExpectedStringLiteral { byte_string: Option, }, + ExpectedFilenameLiteral, ExpectedIntegerLiteral, ExpectedIntegerLiteralInRange { lower_bound: isize, @@ -586,7 +600,7 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { if let Some(start_point_span) = byte_string { diag.span_suggestion( start_point_span, - fluent::attr_parsing_unsupported_literal_suggestion, + "consider removing the prefix", "", Applicability::MaybeIncorrect, ); @@ -597,6 +611,9 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { diag.span_label(self.span, "expected a string literal here"); } } + AttributeParseErrorReason::ExpectedFilenameLiteral => { + diag.span_label(self.span, "expected a filename string literal here"); + } AttributeParseErrorReason::ExpectedIntegerLiteral => { diag.span_label(self.span, "expected an integer literal here"); } @@ -751,30 +768,27 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { } #[derive(Diagnostic)] -#[diag(attr_parsing_invalid_attr_unsafe)] -#[note] +#[diag("`{$name}` is not an unsafe attribute")] +#[note("extraneous unsafe is not allowed in attributes")] pub(crate) struct InvalidAttrUnsafe { #[primary_span] - #[label] + #[label("this is not an unsafe attribute")] pub span: Span, pub name: AttrPath, } #[derive(Diagnostic)] -#[diag(attr_parsing_unsafe_attr_outside_unsafe)] +#[diag("unsafe attribute used without unsafe")] pub(crate) struct UnsafeAttrOutsideUnsafe { #[primary_span] - #[label] + #[label("usage of unsafe attribute")] pub span: Span, #[subdiagnostic] pub suggestion: Option, } #[derive(Subdiagnostic)] -#[multipart_suggestion( - attr_parsing_unsafe_attr_outside_unsafe_suggestion, - applicability = "machine-applicable" -)] +#[multipart_suggestion("wrap the attribute in `unsafe(...)`", applicability = "machine-applicable")] pub(crate) struct UnsafeAttrOutsideUnsafeSuggestion { #[suggestion_part(code = "unsafe(")] pub left: Span, @@ -783,7 +797,7 @@ pub(crate) struct UnsafeAttrOutsideUnsafeSuggestion { } #[derive(Diagnostic)] -#[diag(attr_parsing_meta_bad_delim)] +#[diag("wrong meta list delimiters")] pub(crate) struct MetaBadDelim { #[primary_span] pub span: Span, @@ -793,7 +807,7 @@ pub(crate) struct MetaBadDelim { #[derive(Subdiagnostic)] #[multipart_suggestion( - attr_parsing_meta_bad_delim_suggestion, + "the delimiters should be `(` and `)`", applicability = "machine-applicable" )] pub(crate) struct MetaBadDelimSugg { @@ -804,7 +818,7 @@ pub(crate) struct MetaBadDelimSugg { } #[derive(Diagnostic)] -#[diag(attr_parsing_invalid_meta_item)] +#[diag("expected a literal (`1u8`, `1.0f32`, `\"string\"`, etc.) here, found {$descr}")] pub(crate) struct InvalidMetaItem { #[primary_span] pub span: Span, @@ -813,12 +827,15 @@ pub(crate) struct InvalidMetaItem { pub quote_ident_sugg: Option, #[subdiagnostic] pub remove_neg_sugg: Option, - #[label] + #[label("{$descr}s are not allowed here")] pub label: Option, } #[derive(Subdiagnostic)] -#[multipart_suggestion(attr_parsing_quote_ident_sugg, applicability = "machine-applicable")] +#[multipart_suggestion( + "surround the identifier with quotation marks to make it into a string literal", + applicability = "machine-applicable" +)] pub(crate) struct InvalidMetaItemQuoteIdentSugg { #[suggestion_part(code = "\"")] pub before: Span, @@ -827,73 +844,80 @@ pub(crate) struct InvalidMetaItemQuoteIdentSugg { } #[derive(Subdiagnostic)] -#[multipart_suggestion(attr_parsing_remove_neg_sugg, applicability = "machine-applicable")] +#[multipart_suggestion( + "negative numbers are not literals, try removing the `-` sign", + applicability = "machine-applicable" +)] pub(crate) struct InvalidMetaItemRemoveNegSugg { #[suggestion_part(code = "")] pub negative_sign: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_suffixed_literal_in_attribute)] -#[help] +#[diag("suffixed literals are not allowed in attributes")] +#[help( + "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.)" +)] pub(crate) struct SuffixedLiteralInAttribute { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_empty_link_name, code = E0454)] +#[diag("link name must not be empty", code = E0454)] pub(crate) struct EmptyLinkName { #[primary_span] - #[label] + #[label("empty link name")] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_link_framework_apple, code = E0455)] +#[diag("link kind `framework` is only supported on Apple targets", code = E0455)] pub(crate) struct LinkFrameworkApple { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_incompatible_wasm_link)] +#[diag("`wasm_import_module` is incompatible with other arguments in `#[link]` attributes")] pub(crate) struct IncompatibleWasmLink { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_link_requires_name, code = E0459)] +#[diag("`#[link]` attribute requires a `name = \"string\"` argument", code = E0459)] pub(crate) struct LinkRequiresName { #[primary_span] - #[label] + #[label("missing `name` argument")] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_raw_dylib_no_nul)] +#[diag("link name must not contain NUL characters if link kind is `raw-dylib`")] pub(crate) struct RawDylibNoNul { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_raw_dylib_only_windows, code = E0455)] +#[diag("link kind `raw-dylib` is only supported on Windows targets", code = E0455)] pub(crate) struct RawDylibOnlyWindows { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_invalid_link_modifier)] +#[diag( + "invalid linking modifier syntax, expected '+' or '-' prefix before one of: bundle, verbatim, whole-archive, as-needed" +)] pub(crate) struct InvalidLinkModifier { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_multiple_modifiers)] +#[diag("multiple `{$modifier}` modifiers in a single `modifiers` argument")] pub(crate) struct MultipleModifiers { #[primary_span] pub span: Span, @@ -901,52 +925,54 @@ pub(crate) struct MultipleModifiers { } #[derive(Diagnostic)] -#[diag(attr_parsing_import_name_type_x86)] +#[diag("import name type is only supported on x86")] pub(crate) struct ImportNameTypeX86 { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_bundle_needs_static)] +#[diag("linking modifier `bundle` is only compatible with `static` linking kind")] pub(crate) struct BundleNeedsStatic { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_whole_archive_needs_static)] +#[diag("linking modifier `whole-archive` is only compatible with `static` linking kind")] pub(crate) struct WholeArchiveNeedsStatic { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_as_needed_compatibility)] +#[diag( + "linking modifier `as-needed` is only compatible with `dylib`, `framework` and `raw-dylib` linking kinds" +)] pub(crate) struct AsNeededCompatibility { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_import_name_type_raw)] +#[diag("import name type can only be used with link kind `raw-dylib`")] pub(crate) struct ImportNameTypeRaw { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_limit_invalid)] +#[diag("`limit` must be a non-negative integer")] pub(crate) struct LimitInvalid<'a> { #[primary_span] pub span: Span, - #[label] + #[label("{$error_str}")] pub value_span: Span, pub error_str: &'a str, } #[derive(Diagnostic)] -#[diag(attr_parsing_cfg_attr_bad_delim)] +#[diag("wrong `cfg_attr` delimiters")] pub(crate) struct CfgAttrBadDelim { #[primary_span] pub span: Span, @@ -955,14 +981,16 @@ pub(crate) struct CfgAttrBadDelim { } #[derive(Diagnostic)] -#[diag(attr_parsing_doc_alias_malformed)] +#[diag( + "doc alias attribute expects a string `#[doc(alias = \"a\")]` or a list of strings `#[doc(alias(\"a\", \"b\"))]`" +)] pub(crate) struct DocAliasMalformed { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(attr_parsing_unsupported_instruction_set)] +#[diag("target `{$current_target}` does not support `#[instruction_set({$instruction_set}::*)]`")] pub(crate) struct UnsupportedInstructionSet<'a> { #[primary_span] pub span: Span, diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index caf6a86af098..d8587ed81d6d 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -2309,12 +2309,12 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { tcx: TyCtxt<'hir>, issue_span: Span, expr_span: Span, - body_expr: Option<&'hir hir::Expr<'hir>>, - loop_bind: Option<&'hir Ident>, - loop_span: Option, - head_span: Option, - pat_span: Option, - head: Option<&'hir hir::Expr<'hir>>, + body_expr: Option<&'hir hir::Expr<'hir>> = None, + loop_bind: Option<&'hir Ident> = None, + loop_span: Option = None, + head_span: Option = None, + pat_span: Option = None, + head: Option<&'hir hir::Expr<'hir>> = None, } impl<'hir> Visitor<'hir> for ExprFinder<'hir> { fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) { @@ -2380,17 +2380,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { hir::intravisit::walk_expr(self, ex); } } - let mut finder = ExprFinder { - tcx, - expr_span: span, - issue_span, - loop_bind: None, - body_expr: None, - head_span: None, - loop_span: None, - pat_span: None, - head: None, - }; + let mut finder = ExprFinder { tcx, expr_span: span, issue_span, .. }; finder.visit_expr(tcx.hir_body(body_id).value); if let Some(body_expr) = finder.body_expr @@ -2625,13 +2615,13 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { struct ExpressionFinder<'tcx> { capture_span: Span, - closure_change_spans: Vec, - closure_arg_span: Option, - in_closure: bool, - suggest_arg: String, + closure_change_spans: Vec = vec![], + closure_arg_span: Option = None, + in_closure: bool = false, + suggest_arg: String = String::new(), tcx: TyCtxt<'tcx>, - closure_local_id: Option, - closure_call_changes: Vec<(Span, String)>, + closure_local_id: Option = None, + closure_call_changes: Vec<(Span, String)> = vec![], } impl<'hir> Visitor<'hir> for ExpressionFinder<'hir> { fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) { @@ -2712,16 +2702,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { }) = self.infcx.tcx.hir_node(self.mir_hir_id()) && let hir::Node::Expr(expr) = self.infcx.tcx.hir_node(body_id.hir_id) { - let mut finder = ExpressionFinder { - capture_span: *capture_kind_span, - closure_change_spans: vec![], - closure_arg_span: None, - in_closure: false, - suggest_arg: String::new(), - closure_local_id: None, - closure_call_changes: vec![], - tcx: self.infcx.tcx, - }; + let mut finder = + ExpressionFinder { capture_span: *capture_kind_span, tcx: self.infcx.tcx, .. }; finder.visit_expr(expr); if finder.closure_change_spans.is_empty() || finder.closure_call_changes.is_empty() { diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 4a059481c326..7c5cbc58e25c 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -4,6 +4,7 @@ #![allow(internal_features)] #![feature(assert_matches)] #![feature(box_patterns)] +#![feature(default_field_values)] #![feature(file_buffered)] #![feature(if_let_guard)] #![feature(negative_impls)] diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs index 8e925cfe09a2..3ebde949b99b 100644 --- a/compiler/rustc_builtin_macros/src/cfg.rs +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -13,6 +13,7 @@ use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpa use rustc_hir::attrs::CfgEntry; use rustc_hir::{AttrPath, Target}; use rustc_parse::exp; +use rustc_parse::parser::Recovery; use rustc_span::{ErrorGuaranteed, Span, sym}; use crate::errors; @@ -42,7 +43,7 @@ fn parse_cfg(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream) -> Result, span: Span, tts: TokenStream) -> Result`, because here +// we can treat `false` and `absent` the same. +#[derive(Clone, Debug, Encodable, Decodable)] pub struct NativeLib { pub kind: NativeLibKind, pub name: Symbol, diff --git a/compiler/rustc_data_structures/src/vec_cache.rs b/compiler/rustc_data_structures/src/vec_cache.rs index 599970663db8..f9f5871c8d61 100644 --- a/compiler/rustc_data_structures/src/vec_cache.rs +++ b/compiler/rustc_data_structures/src/vec_cache.rs @@ -29,8 +29,6 @@ struct Slot { struct SlotIndex { // the index of the bucket in VecCache (0 to 20) bucket_idx: usize, - // number of entries in that bucket - entries: usize, // the index of the slot within the bucket index_in_bucket: usize, } @@ -39,12 +37,12 @@ struct SlotIndex { // compile-time. Visiting all powers of two is enough to hit all the buckets. // // We confirm counts are accurate in the slot_index_exhaustive test. -const ENTRIES_BY_BUCKET: [usize; 21] = { - let mut entries = [0; 21]; +const ENTRIES_BY_BUCKET: [usize; BUCKETS] = { + let mut entries = [0; BUCKETS]; let mut key = 0; loop { let si = SlotIndex::from_index(key); - entries[si.bucket_idx] = si.entries; + entries[si.bucket_idx] = si.entries(); if key == 0 { key = 1; } else if key == (1 << 31) { @@ -56,7 +54,14 @@ const ENTRIES_BY_BUCKET: [usize; 21] = { entries }; +const BUCKETS: usize = 21; + impl SlotIndex { + /// The total possible number of entries in the bucket + const fn entries(&self) -> usize { + if self.bucket_idx == 0 { 1 << 12 } else { 1 << (self.bucket_idx + 11) } + } + // This unpacks a flat u32 index into identifying which bucket it belongs to and the offset // within that bucket. As noted in the VecCache docs, buckets double in size with each index. // Typically that would mean 31 buckets (2^0 + 2^1 ... + 2^31 = u32::MAX - 1), but to reduce @@ -70,18 +75,13 @@ impl SlotIndex { const fn from_index(idx: u32) -> Self { const FIRST_BUCKET_SHIFT: usize = 12; if idx < (1 << FIRST_BUCKET_SHIFT) { - return SlotIndex { - bucket_idx: 0, - entries: 1 << FIRST_BUCKET_SHIFT, - index_in_bucket: idx as usize, - }; + return SlotIndex { bucket_idx: 0, index_in_bucket: idx as usize }; } // We already ruled out idx 0, so this `ilog2` never panics (and the check optimizes away) let bucket = idx.ilog2() as usize; let entries = 1 << bucket; SlotIndex { bucket_idx: bucket - FIRST_BUCKET_SHIFT + 1, - entries, index_in_bucket: idx as usize - entries, } } @@ -98,7 +98,7 @@ impl SlotIndex { if ptr.is_null() { return None; } - assert!(self.index_in_bucket < self.entries); + debug_assert!(self.index_in_bucket < self.entries()); // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this // must be inbounds. let slot = unsafe { ptr.add(self.index_in_bucket) }; @@ -126,11 +126,12 @@ impl SlotIndex { fn bucket_ptr(&self, bucket: &AtomicPtr>) -> *mut Slot { let ptr = bucket.load(Ordering::Acquire); - if ptr.is_null() { self.initialize_bucket(bucket) } else { ptr } + if ptr.is_null() { Self::initialize_bucket(bucket, self.bucket_idx) } else { ptr } } #[cold] - fn initialize_bucket(&self, bucket: &AtomicPtr>) -> *mut Slot { + #[inline(never)] + fn initialize_bucket(bucket: &AtomicPtr>, bucket_idx: usize) -> *mut Slot { static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); // If we are initializing the bucket, then acquire a global lock. @@ -144,8 +145,8 @@ impl SlotIndex { // OK, now under the allocator lock, if we're still null then it's definitely us that will // initialize this bucket. if ptr.is_null() { - let bucket_layout = - std::alloc::Layout::array::>(self.entries as usize).unwrap(); + let bucket_len = SlotIndex { bucket_idx, index_in_bucket: 0 }.entries(); + let bucket_layout = std::alloc::Layout::array::>(bucket_len).unwrap(); // This is more of a sanity check -- this code is very cold, so it's safe to pay a // little extra cost here. assert!(bucket_layout.size() > 0); @@ -171,7 +172,7 @@ impl SlotIndex { let bucket = unsafe { buckets.get_unchecked(self.bucket_idx) }; let ptr = self.bucket_ptr(bucket); - assert!(self.index_in_bucket < self.entries); + debug_assert!(self.index_in_bucket < self.entries()); // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this // must be inbounds. let slot = unsafe { ptr.add(self.index_in_bucket) }; @@ -204,6 +205,31 @@ impl SlotIndex { Err(_) => false, } } + + /// Inserts into the map, given that the slot is unique, so it won't race with other threads. + #[inline] + unsafe fn put_unique(&self, buckets: &[AtomicPtr>; 21], value: V, extra: u32) { + // SAFETY: `bucket_idx` is ilog2(u32).saturating_sub(11), which is at most 21, i.e., + // in-bounds of buckets. + let bucket = unsafe { buckets.get_unchecked(self.bucket_idx) }; + let ptr = self.bucket_ptr(bucket); + + debug_assert!(self.index_in_bucket < self.entries()); + // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this + // must be inbounds. + let slot = unsafe { ptr.add(self.index_in_bucket) }; + + // SAFETY: We known our slot is unique as a precondition of this function, so this can't race. + unsafe { + (&raw mut (*slot).value).write(value); + } + + // SAFETY: initialized bucket has zeroed all memory within the bucket, so we are valid for + // AtomicU32 access. + let index_and_lock = unsafe { &(*slot).index_and_lock }; + + index_and_lock.store(extra.checked_add(2).unwrap(), Ordering::Release); + } } /// In-memory cache for queries whose keys are densely-numbered IDs @@ -229,11 +255,11 @@ pub struct VecCache { // Bucket 19: 1073741824 // Bucket 20: 2147483648 // The total number of entries if all buckets are initialized is u32::MAX-1. - buckets: [AtomicPtr>; 21], + buckets: [AtomicPtr>; BUCKETS], // In the compiler's current usage these are only *read* during incremental and self-profiling. // They are an optimization over iterating the full buckets array. - present: [AtomicPtr>; 21], + present: [AtomicPtr>; BUCKETS], len: AtomicUsize, key: PhantomData<(K, I)>, @@ -307,9 +333,11 @@ where let slot_idx = SlotIndex::from_index(key); if slot_idx.put(&self.buckets, value, index.index() as u32) { let present_idx = self.len.fetch_add(1, Ordering::Relaxed); - let slot = SlotIndex::from_index(present_idx as u32); - // We should always be uniquely putting due to `len` fetch_add returning unique values. - assert!(slot.put(&self.present, (), key)); + let slot = SlotIndex::from_index(u32::try_from(present_idx).unwrap()); + // SAFETY: We should always be uniquely putting due to `len` fetch_add returning unique values. + // We can't get here if `len` overflows because `put` will not succeed u32::MAX + 1 times + // as it will run out of slots. + unsafe { slot.put_unique(&self.present, (), key) }; } } @@ -331,6 +359,10 @@ where } } } + + pub fn len(&self) -> usize { + self.len.load(Ordering::Acquire) + } } #[cfg(test)] diff --git a/compiler/rustc_data_structures/src/vec_cache/tests.rs b/compiler/rustc_data_structures/src/vec_cache/tests.rs index 9b60913ec922..f588442eee62 100644 --- a/compiler/rustc_data_structures/src/vec_cache/tests.rs +++ b/compiler/rustc_data_structures/src/vec_cache/tests.rs @@ -68,6 +68,13 @@ fn slot_entries_table() { ); } +#[test] +fn bucket_entries_matches() { + for i in 0..BUCKETS { + assert_eq!(SlotIndex { bucket_idx: i, index_in_bucket: 0 }.entries(), ENTRIES_BY_BUCKET[i]); + } +} + #[test] #[cfg(not(miri))] fn slot_index_exhaustive() { @@ -81,14 +88,18 @@ fn slot_index_exhaustive() { let mut prev = slot_idx; for idx in 1..=u32::MAX { let slot_idx = SlotIndex::from_index(idx); + + // SAFETY: Ensure indices don't go out of bounds of buckets. + assert!(slot_idx.index_in_bucket < slot_idx.entries()); + if prev.bucket_idx == slot_idx.bucket_idx { assert_eq!(prev.index_in_bucket + 1, slot_idx.index_in_bucket); } else { assert_eq!(slot_idx.index_in_bucket, 0); } - assert_eq!(buckets[slot_idx.bucket_idx], slot_idx.entries as u32); - assert_eq!(ENTRIES_BY_BUCKET[slot_idx.bucket_idx], slot_idx.entries, "{}", idx); + assert_eq!(buckets[slot_idx.bucket_idx], slot_idx.entries() as u32); + assert_eq!(ENTRIES_BY_BUCKET[slot_idx.bucket_idx], slot_idx.entries(), "{}", idx); prev = slot_idx; } diff --git a/compiler/rustc_driver_impl/Cargo.toml b/compiler/rustc_driver_impl/Cargo.toml index c160240a18a7..bdc577622b24 100644 --- a/compiler/rustc_driver_impl/Cargo.toml +++ b/compiler/rustc_driver_impl/Cargo.toml @@ -12,7 +12,6 @@ rustc_ast = { path = "../rustc_ast" } rustc_ast_lowering = { path = "../rustc_ast_lowering" } rustc_ast_passes = { path = "../rustc_ast_passes" } rustc_ast_pretty = { path = "../rustc_ast_pretty" } -rustc_attr_parsing = { path = "../rustc_attr_parsing" } rustc_borrowck = { path = "../rustc_borrowck" } rustc_builtin_macros = { path = "../rustc_builtin_macros" } rustc_codegen_ssa = { path = "../rustc_codegen_ssa" } @@ -21,13 +20,11 @@ rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } rustc_expand = { path = "../rustc_expand" } rustc_feature = { path = "../rustc_feature" } -rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_hir_analysis = { path = "../rustc_hir_analysis" } rustc_hir_pretty = { path = "../rustc_hir_pretty" } rustc_hir_typeck = { path = "../rustc_hir_typeck" } rustc_incremental = { path = "../rustc_incremental" } rustc_index = { path = "../rustc_index" } -rustc_infer = { path = "../rustc_infer" } rustc_interface = { path = "../rustc_interface" } rustc_lexer = { path = "../rustc_lexer" } rustc_lint = { path = "../rustc_lint" } @@ -44,7 +41,6 @@ rustc_passes = { path = "../rustc_passes" } rustc_pattern_analysis = { path = "../rustc_pattern_analysis" } rustc_privacy = { path = "../rustc_privacy" } rustc_public = { path = "../rustc_public", features = ["rustc_internal"] } -rustc_query_system = { path = "../rustc_query_system" } rustc_resolve = { path = "../rustc_resolve" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } diff --git a/compiler/rustc_driver_impl/messages.ftl b/compiler/rustc_driver_impl/messages.ftl deleted file mode 100644 index b62cdc35f513..000000000000 --- a/compiler/rustc_driver_impl/messages.ftl +++ /dev/null @@ -1,29 +0,0 @@ -driver_impl_cant_emit_mir = could not emit MIR: {$error} - -driver_impl_ice = the compiler unexpectedly panicked. this is a bug. -driver_impl_ice_bug_report = we would appreciate a bug report: {$bug_report_url} -driver_impl_ice_bug_report_internal_feature = using internal features is not supported and expected to cause internal compiler errors when used incorrectly -driver_impl_ice_bug_report_update_note = please make sure that you have updated to the latest nightly -driver_impl_ice_exclude_cargo_defaults = some of the compiler flags provided by cargo are hidden - -driver_impl_ice_flags = compiler flags: {$flags} -driver_impl_ice_path = please attach the file at `{$path}` to your bug report -driver_impl_ice_path_error = the ICE couldn't be written to `{$path}`: {$error} -driver_impl_ice_path_error_env = the environment variable `RUSTC_ICE` is set to `{$env_var}` -driver_impl_ice_version = rustc {$version} running on {$triple} - -driver_impl_rlink_corrupt_file = corrupt metadata encountered in `{$file}` - -driver_impl_rlink_empty_version_number = the input does not contain version number - -driver_impl_rlink_encoding_version_mismatch = .rlink file was produced with encoding version `{$version_array}`, but the current version is `{$rlink_version}` - -driver_impl_rlink_no_a_file = rlink must be a file - -driver_impl_rlink_rustc_version_mismatch = .rlink file was produced by rustc version `{$rustc_version}`, but the current version is `{$current_version}` - -driver_impl_rlink_unable_to_read = failed to read rlink file: `{$err}` - -driver_impl_rlink_wrong_file_type = the input does not look like a .rlink file - -driver_impl_unstable_feature_usage = cannot dump feature usage metrics: {$error} diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 38ee87601614..cdc9959605c1 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -108,18 +108,14 @@ use crate::session_diagnostics::{ RLinkWrongFileType, RlinkCorruptFile, RlinkNotAFile, RlinkUnableToRead, UnstableFeatureUsage, }; -rustc_fluent_macro::fluent_messages! { "../messages.ftl" } - pub fn default_translator() -> Translator { Translator::with_fallback_bundle(DEFAULT_LOCALE_RESOURCES.to_vec(), false) } pub static DEFAULT_LOCALE_RESOURCES: &[&str] = &[ // tidy-alphabetical-start - crate::DEFAULT_LOCALE_RESOURCE, rustc_ast_lowering::DEFAULT_LOCALE_RESOURCE, rustc_ast_passes::DEFAULT_LOCALE_RESOURCE, - rustc_attr_parsing::DEFAULT_LOCALE_RESOURCE, rustc_borrowck::DEFAULT_LOCALE_RESOURCE, rustc_builtin_macros::DEFAULT_LOCALE_RESOURCE, rustc_codegen_ssa::DEFAULT_LOCALE_RESOURCE, @@ -129,7 +125,6 @@ pub static DEFAULT_LOCALE_RESOURCES: &[&str] = &[ rustc_hir_analysis::DEFAULT_LOCALE_RESOURCE, rustc_hir_typeck::DEFAULT_LOCALE_RESOURCE, rustc_incremental::DEFAULT_LOCALE_RESOURCE, - rustc_infer::DEFAULT_LOCALE_RESOURCE, rustc_interface::DEFAULT_LOCALE_RESOURCE, rustc_lint::DEFAULT_LOCALE_RESOURCE, rustc_metadata::DEFAULT_LOCALE_RESOURCE, @@ -142,7 +137,6 @@ pub static DEFAULT_LOCALE_RESOURCES: &[&str] = &[ rustc_passes::DEFAULT_LOCALE_RESOURCE, rustc_pattern_analysis::DEFAULT_LOCALE_RESOURCE, rustc_privacy::DEFAULT_LOCALE_RESOURCE, - rustc_query_system::DEFAULT_LOCALE_RESOURCE, rustc_resolve::DEFAULT_LOCALE_RESOURCE, rustc_session::DEFAULT_LOCALE_RESOURCE, rustc_trait_selection::DEFAULT_LOCALE_RESOURCE, @@ -491,10 +485,18 @@ fn handle_explain(early_dcx: &EarlyDiagCtxt, registry: Registry, code: &str, col } text.push('\n'); } + + // If output is a terminal, use a pager to display the content. if io::stdout().is_terminal() { show_md_content_with_pager(&text, color); } else { - safe_print!("{text}"); + // Otherwise, if the user has requested colored output + // print the content in color, else print the md content. + if color == ColorConfig::Always { + show_colored_md_content(&text); + } else { + safe_print!("{text}"); + } } } else { early_dcx.early_fatal(format!("{code} is not a valid error code")); @@ -564,6 +566,33 @@ fn show_md_content_with_pager(content: &str, color: ColorConfig) { safe_print!("{content}"); } +/// Prints the markdown content with colored output. +/// +/// This function is used when the output is not a terminal, +/// but the user has requested colored output with `--color=always`. +fn show_colored_md_content(content: &str) { + // Try to prettify the raw markdown text. + let mut pretty_data = { + let mdstream = markdown::MdStream::parse_str(content); + let bufwtr = markdown::create_stdout_bufwtr(); + let mut mdbuf = Vec::new(); + if mdstream.write_anstream_buf(&mut mdbuf, Some(&highlighter::highlight)).is_ok() { + Some((bufwtr, mdbuf)) + } else { + None + } + }; + + if let Some((bufwtr, mdbuf)) = &mut pretty_data + && bufwtr.write_all(&mdbuf).is_ok() + { + return; + } + + // Everything failed. Print the raw markdown text. + safe_print!("{content}"); +} + fn process_rlink(sess: &Session, compiler: &interface::Compiler) { assert!(sess.opts.unstable_opts.link_only); let dcx = sess.dcx(); diff --git a/compiler/rustc_driver_impl/src/session_diagnostics.rs b/compiler/rustc_driver_impl/src/session_diagnostics.rs index 774221fd396a..565c176645de 100644 --- a/compiler/rustc_driver_impl/src/session_diagnostics.rs +++ b/compiler/rustc_driver_impl/src/session_diagnostics.rs @@ -3,82 +3,88 @@ use std::error::Error; use rustc_macros::{Diagnostic, Subdiagnostic}; #[derive(Diagnostic)] -#[diag(driver_impl_cant_emit_mir)] +#[diag("could not emit MIR: {$error}")] pub struct CantEmitMIR { pub error: std::io::Error, } #[derive(Diagnostic)] -#[diag(driver_impl_rlink_unable_to_read)] +#[diag("failed to read rlink file: `{$err}`")] pub(crate) struct RlinkUnableToRead { pub err: std::io::Error, } #[derive(Diagnostic)] -#[diag(driver_impl_rlink_wrong_file_type)] +#[diag("the input does not look like a .rlink file")] pub(crate) struct RLinkWrongFileType; #[derive(Diagnostic)] -#[diag(driver_impl_rlink_empty_version_number)] +#[diag("the input does not contain version number")] pub(crate) struct RLinkEmptyVersionNumber; #[derive(Diagnostic)] -#[diag(driver_impl_rlink_encoding_version_mismatch)] +#[diag( + ".rlink file was produced with encoding version `{$version_array}`, but the current version is `{$rlink_version}`" +)] pub(crate) struct RLinkEncodingVersionMismatch { pub version_array: String, pub rlink_version: u32, } #[derive(Diagnostic)] -#[diag(driver_impl_rlink_rustc_version_mismatch)] +#[diag( + ".rlink file was produced by rustc version `{$rustc_version}`, but the current version is `{$current_version}`" +)] pub(crate) struct RLinkRustcVersionMismatch<'a> { pub rustc_version: String, pub current_version: &'a str, } #[derive(Diagnostic)] -#[diag(driver_impl_rlink_no_a_file)] +#[diag("rlink must be a file")] pub(crate) struct RlinkNotAFile; #[derive(Diagnostic)] -#[diag(driver_impl_rlink_corrupt_file)] +#[diag("corrupt metadata encountered in `{$file}`")] pub(crate) struct RlinkCorruptFile<'a> { pub file: &'a std::path::Path, } #[derive(Diagnostic)] -#[diag(driver_impl_ice)] +#[diag("the compiler unexpectedly panicked. this is a bug.")] pub(crate) struct Ice; #[derive(Diagnostic)] -#[diag(driver_impl_ice_bug_report)] +#[diag("we would appreciate a bug report: {$bug_report_url}")] pub(crate) struct IceBugReport<'a> { pub bug_report_url: &'a str, } #[derive(Diagnostic)] -#[diag(driver_impl_ice_bug_report_update_note)] +#[diag("please make sure that you have updated to the latest nightly")] pub(crate) struct UpdateNightlyNote; #[derive(Diagnostic)] -#[diag(driver_impl_ice_bug_report_internal_feature)] +#[diag( + "using internal features is not supported and expected to cause internal compiler errors when used incorrectly" +)] pub(crate) struct IceBugReportInternalFeature; #[derive(Diagnostic)] -#[diag(driver_impl_ice_version)] +#[diag("rustc {$version} running on {$triple}")] pub(crate) struct IceVersion<'a> { pub version: &'a str, pub triple: &'a str, } #[derive(Diagnostic)] -#[diag(driver_impl_ice_path)] +#[diag("please attach the file at `{$path}` to your bug report")] pub(crate) struct IcePath { pub path: std::path::PathBuf, } #[derive(Diagnostic)] -#[diag(driver_impl_ice_path_error)] +#[diag("the ICE couldn't be written to `{$path}`: {$error}")] pub(crate) struct IcePathError { pub path: std::path::PathBuf, pub error: String, @@ -87,23 +93,23 @@ pub(crate) struct IcePathError { } #[derive(Subdiagnostic)] -#[note(driver_impl_ice_path_error_env)] +#[note("the environment variable `RUSTC_ICE` is set to `{$env_var}`")] pub(crate) struct IcePathErrorEnv { pub env_var: std::path::PathBuf, } #[derive(Diagnostic)] -#[diag(driver_impl_ice_flags)] +#[diag("compiler flags: {$flags}")] pub(crate) struct IceFlags { pub flags: String, } #[derive(Diagnostic)] -#[diag(driver_impl_ice_exclude_cargo_defaults)] +#[diag("some of the compiler flags provided by cargo are hidden")] pub(crate) struct IceExcludeCargoDefaults; #[derive(Diagnostic)] -#[diag(driver_impl_unstable_feature_usage)] +#[diag("cannot dump feature usage metrics: {$error}")] pub(crate) struct UnstableFeatureUsage { pub error: Box, } diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs index c0737edd7d65..c9e887061305 100644 --- a/compiler/rustc_error_messages/src/lib.rs +++ b/compiler/rustc_error_messages/src/lib.rs @@ -247,6 +247,9 @@ pub enum SubdiagMessage { /// Identifier of a Fluent message. Instances of this variant are generated by the /// `Subdiagnostic` derive. FluentIdentifier(FluentId), + /// An inline Fluent message. Instances of this variant are generated by the + /// `Subdiagnostic` derive. + Inline(Cow<'static, str>), /// Attribute of a Fluent message. Needs to be combined with a Fluent identifier to produce an /// actual translated message. Instances of this variant are generated by the `fluent_messages` /// macro. @@ -291,6 +294,8 @@ pub enum DiagMessage { /// /// FluentIdentifier(FluentId, Option), + /// An inline Fluent message, containing the to be translated diagnostic message. + Inline(Cow<'static, str>), } impl DiagMessage { @@ -305,21 +310,22 @@ impl DiagMessage { SubdiagMessage::FluentIdentifier(id) => { return DiagMessage::FluentIdentifier(id, None); } + SubdiagMessage::Inline(s) => return DiagMessage::Inline(s), SubdiagMessage::FluentAttr(attr) => attr, }; match self { - DiagMessage::Str(s) => DiagMessage::Str(s.clone()), DiagMessage::FluentIdentifier(id, _) => { DiagMessage::FluentIdentifier(id.clone(), Some(attr)) } + _ => panic!("Tried to add a subdiagnostic to a message without a fluent identifier"), } } pub fn as_str(&self) -> Option<&str> { match self { DiagMessage::Str(s) => Some(s), - DiagMessage::FluentIdentifier(_, _) => None, + DiagMessage::FluentIdentifier(_, _) | DiagMessage::Inline(_) => None, } } } @@ -353,6 +359,7 @@ impl From for SubdiagMessage { // There isn't really a sensible behaviour for this because it loses information but // this is the most sensible of the behaviours. DiagMessage::FluentIdentifier(_, Some(attr)) => SubdiagMessage::FluentAttr(attr), + DiagMessage::Inline(s) => SubdiagMessage::Inline(s), } } } diff --git a/compiler/rustc_errors/src/translation.rs b/compiler/rustc_errors/src/translation.rs index 43d5fca301ec..aba79c30f3a3 100644 --- a/compiler/rustc_errors/src/translation.rs +++ b/compiler/rustc_errors/src/translation.rs @@ -3,11 +3,13 @@ use std::env; use std::error::Report; use std::sync::Arc; +use rustc_error_messages::langid; pub use rustc_error_messages::{FluentArgs, LazyFallbackBundle}; use tracing::{debug, trace}; use crate::error::{TranslateError, TranslateErrorKind}; -use crate::{DiagArg, DiagMessage, FluentBundle, Style}; +use crate::fluent_bundle::FluentResource; +use crate::{DiagArg, DiagMessage, FluentBundle, Style, fluent_bundle}; /// Convert diagnostic arguments (a rustc internal type that exists to implement /// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation. @@ -79,6 +81,28 @@ impl Translator { return Ok(Cow::Borrowed(msg)); } DiagMessage::FluentIdentifier(identifier, attr) => (identifier, attr), + // This translates an inline fluent diagnostic message + // It does this by creating a new `FluentBundle` with only one message, + // and then translating using this bundle. + DiagMessage::Inline(msg) => { + const GENERATED_MSG_ID: &str = "generated_msg"; + let resource = + FluentResource::try_new(format!("{GENERATED_MSG_ID} = {msg}\n")).unwrap(); + let mut bundle = fluent_bundle::FluentBundle::new(vec![langid!("en-US")]); + bundle.set_use_isolating(false); + bundle.add_resource(resource).unwrap(); + let message = bundle.get_message(GENERATED_MSG_ID).unwrap(); + let value = message.value().unwrap(); + + let mut errs = vec![]; + let translated = bundle.format_pattern(value, Some(args), &mut errs).to_string(); + debug!(?translated, ?errs); + return if errs.is_empty() { + Ok(Cow::Owned(translated)) + } else { + Err(TranslateError::fluent(&Cow::Borrowed(GENERATED_MSG_ID), args, errs)) + }; + } }; let translate_with_bundle = |bundle: &'a FluentBundle| -> Result, TranslateError<'_>> { @@ -142,3 +166,14 @@ impl Translator { } } } + +/// This macro creates a translatable `DiagMessage` from a literal string. +/// It should be used in places where a translatable message is needed, but struct diagnostics are undesired. +/// +/// This is a macro because in the future we may want to globally register these messages. +#[macro_export] +macro_rules! inline_fluent { + ($inline: literal) => { + rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed($inline)) + }; +} diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index 4887bfa44588..1e1f98aef1b1 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -20,6 +20,7 @@ use rustc_feature::{ UNSTABLE_LANG_FEATURES, }; use rustc_hir::Target; +use rustc_parse::parser::Recovery; use rustc_session::Session; use rustc_session::parse::feature_err; use rustc_span::{STDLIB_STABLE_CRATES, Span, Symbol, sym}; @@ -395,7 +396,9 @@ impl<'a> StripUnconfigured<'a> { fn in_cfg(&self, attrs: &[Attribute]) -> bool { attrs.iter().all(|attr| { !is_cfg(attr) - || self.cfg_true(attr, ShouldEmit::ErrorsAndLints { recover: true }).as_bool() + || self + .cfg_true(attr, ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }) + .as_bool() }) } diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index cfa7725c7400..449dc95ea372 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -26,7 +26,7 @@ use rustc_hir::def::MacroKinds; use rustc_hir::limit::Limit; use rustc_parse::parser::{ AllowConstBlockItems, AttemptLocalParseRecovery, CommaRecoveryMode, ForceCollect, Parser, - RecoverColon, RecoverComma, token_descr, + RecoverColon, RecoverComma, Recovery, token_descr, }; use rustc_session::Session; use rustc_session::lint::builtin::{UNUSED_ATTRIBUTES, UNUSED_DOC_COMMENTS}; @@ -508,6 +508,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { // Unresolved macros produce dummy outputs as a recovery measure. invocations.reverse(); let mut expanded_fragments = Vec::new(); + let mut expanded_fragments_len = 0; let mut undetermined_invocations = Vec::new(); let (mut progress, mut force) = (false, !self.monotonic); loop { @@ -602,6 +603,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { expanded_fragments.push(Vec::new()); } expanded_fragments[depth - 1].push((expn_id, expanded_fragment)); + expanded_fragments_len += 1; invocations.extend(derive_invocations.into_iter().rev()); } ExpandResult::Retry(invoc) => { @@ -622,7 +624,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { self.cx.force_mode = orig_force_mode; // Finally incorporate all the expanded macros into the input AST fragment. - let mut placeholder_expander = PlaceholderExpander::default(); + let mut placeholder_expander = PlaceholderExpander::with_capacity(expanded_fragments_len); while let Some(expanded_fragments) = expanded_fragments.pop() { for (expn_id, expanded_fragment) in expanded_fragments.into_iter().rev() { placeholder_expander @@ -2170,7 +2172,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { call.span(), self.cx.current_expansion.lint_node_id, Some(self.cx.ecfg.features), - ShouldEmit::ErrorsAndLints { recover: true }, + ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, ); let current_span = if let Some(sp) = span { sp.to(attr.span) } else { attr.span }; @@ -2220,7 +2222,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { // Target doesn't matter for `cfg` parsing. Target::Crate, self.cfg().features, - ShouldEmit::ErrorsAndLints { recover: true }, + ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, parse_cfg, &CFG_TEMPLATE, ) else { diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index 05f9a5aa43f7..2db18429a521 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -218,12 +218,17 @@ pub(crate) fn placeholder( } } -#[derive(Default)] pub(crate) struct PlaceholderExpander { expanded_fragments: FxHashMap, } impl PlaceholderExpander { + pub(crate) fn with_capacity(capacity: usize) -> Self { + PlaceholderExpander { + expanded_fragments: FxHashMap::with_capacity_and_hasher(capacity, Default::default()), + } + } + pub(crate) fn add(&mut self, id: ast::NodeId, mut fragment: AstFragment) { fragment.mut_visit_with(self); self.expanded_fragments.insert(id, fragment); diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 92dda79b0920..6138ffc8d954 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -699,6 +699,21 @@ pub enum RustcLayoutType { Debug, } +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute, PartialEq, Eq)] +pub enum RustcMirKind { + PeekMaybeInit, + PeekMaybeUninit, + PeekLiveness, + StopAfterDataflow, + BorrowckGraphvizPostflow { path: PathBuf }, + BorrowckGraphvizFormat { format: BorrowckGraphvizFormatKind }, +} + +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute, PartialEq, Eq)] +pub enum BorrowckGraphvizFormatKind { + TwoPhase, +} + /// Represents parsed *built-in* inert attributes. /// /// ## Overview @@ -1057,6 +1072,9 @@ pub enum AttributeKind { /// Represents `#[rustc_has_incoherent_inherent_impls]` RustcHasIncoherentInherentImpls, + /// Represents `#[rustc_hidden_type_of_opaques]` + RustcHiddenTypeOfOpaques, + /// Represents `#[rustc_layout]` RustcLayout(ThinVec), @@ -1087,6 +1105,9 @@ pub enum AttributeKind { /// Represents `#[rustc_main]`. RustcMain, + /// Represents `#[rustc_mir]`. + RustcMir(ThinVec), + /// Represents `#[rustc_must_implement_one_of]` RustcMustImplementOneOf { attr_span: Span, fn_names: ThinVec }, @@ -1123,6 +1144,9 @@ pub enum AttributeKind { /// Represents `#[rustc_pass_indirectly_in_non_rustic_abis]` RustcPassIndirectlyInNonRusticAbis(Span), + /// Represents `#[rustc_preserve_ub_checks]` + RustcPreserveUbChecks, + /// Represents `#[rustc_pub_transparent]` (used by the `repr_transparent_external_private_fields` lint). RustcPubTransparent(Span), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 816ed07c1dc4..7ec1920152a5 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -111,6 +111,7 @@ impl AttributeKind { RustcDumpVtable(..) => No, RustcDynIncompatibleTrait(..) => No, RustcHasIncoherentInherentImpls => Yes, + RustcHiddenTypeOfOpaques => No, RustcLayout(..) => No, RustcLayoutScalarValidRangeEnd(..) => Yes, RustcLayoutScalarValidRangeStart(..) => Yes, @@ -121,6 +122,7 @@ impl AttributeKind { RustcLintUntrackedQueryInformation => Yes, RustcMacroTransparency(..) => Yes, RustcMain => No, + RustcMir(..) => Yes, RustcMustImplementOneOf { .. } => No, RustcNeverReturnsNullPointer => Yes, RustcNoImplicitAutorefs => Yes, @@ -133,6 +135,7 @@ impl AttributeKind { RustcParenSugar(..) => No, RustcPassByValue(..) => Yes, RustcPassIndirectlyInNonRusticAbis(..) => No, + RustcPreserveUbChecks => No, RustcPubTransparent(..) => Yes, RustcReallocator => No, RustcScalableVector { .. } => Yes, diff --git a/compiler/rustc_hir/src/attrs/pretty_printing.rs b/compiler/rustc_hir/src/attrs/pretty_printing.rs index c2ad644688fc..bd268d2c423f 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -1,5 +1,6 @@ use std::num::NonZero; use std::ops::Deref; +use std::path::PathBuf; use rustc_abi::Align; use rustc_ast::attr::data_structures::CfgEntry; @@ -96,7 +97,15 @@ impl PrintAttribute for FxIndexMap { p.word("]"); } } +impl PrintAttribute for PathBuf { + fn should_render(&self) -> bool { + true + } + fn print_attribute(&self, p: &mut Printer) { + p.word(self.display().to_string()); + } +} macro_rules! print_skip { ($($t: ty),* $(,)?) => {$( impl PrintAttribute for $t { diff --git a/compiler/rustc_hir_analysis/src/collect/dump.rs b/compiler/rustc_hir_analysis/src/collect/dump.rs index 2dfd4ab6111f..bbf912cd4bde 100644 --- a/compiler/rustc_hir_analysis/src/collect/dump.rs +++ b/compiler/rustc_hir_analysis/src/collect/dump.rs @@ -7,10 +7,9 @@ use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt}; use rustc_span::sym; pub(crate) fn opaque_hidden_types(tcx: TyCtxt<'_>) { - if !tcx.has_attr(CRATE_DEF_ID, sym::rustc_hidden_type_of_opaques) { + if !find_attr!(tcx.get_all_attrs(CRATE_DEF_ID), AttributeKind::RustcHiddenTypeOfOpaques) { return; } - for id in tcx.hir_crate_items(()).opaques() { if let hir::OpaqueTyOrigin::FnReturn { parent: fn_def_id, .. } | hir::OpaqueTyOrigin::AsyncFn { parent: fn_def_id, .. } = diff --git a/compiler/rustc_hir_analysis/src/hir_wf_check.rs b/compiler/rustc_hir_analysis/src/hir_wf_check.rs index f879153c5765..d414f4dbcc24 100644 --- a/compiler/rustc_hir_analysis/src/hir_wf_check.rs +++ b/compiler/rustc_hir_analysis/src/hir_wf_check.rs @@ -51,12 +51,12 @@ pub(super) fn diagnostic_hir_wf_check<'tcx>( struct HirWfCheck<'tcx> { tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tcx>, - cause: Option>, - cause_depth: usize, + cause: Option> = None, + cause_depth: usize = 0, icx: ItemCtxt<'tcx>, def_id: LocalDefId, param_env: ty::ParamEnv<'tcx>, - depth: usize, + depth: usize = 0, } impl<'tcx> Visitor<'tcx> for HirWfCheck<'tcx> { @@ -124,16 +124,8 @@ pub(super) fn diagnostic_hir_wf_check<'tcx>( } } - let mut visitor = HirWfCheck { - tcx, - predicate, - cause: None, - cause_depth: 0, - icx, - def_id, - param_env: tcx.param_env(def_id.to_def_id()), - depth: 0, - }; + let param_env = tcx.param_env(def_id.to_def_id()); + let mut visitor = HirWfCheck { tcx, predicate, icx, def_id, param_env, .. }; // Get the starting `hir::Ty` using our `WellFormedLoc`. // We will walk 'into' this type to try to find diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs index 3a4d894239fc..f7a143435959 100644 --- a/compiler/rustc_hir_analysis/src/lib.rs +++ b/compiler/rustc_hir_analysis/src/lib.rs @@ -57,6 +57,7 @@ This API is completely unstable and subject to change. // tidy-alphabetical-start #![feature(assert_matches)] +#![feature(default_field_values)] #![feature(gen_blocks)] #![feature(if_let_guard)] #![feature(iter_intersperse)] diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index c07cbfae256d..2e85410c8960 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -2647,7 +2647,15 @@ impl<'a, 'b, 'tcx> FnCallDiagCtxt<'a, 'b, 'tcx> { // To suggest a multipart suggestion when encountering `foo(1, "")` where the def // was `fn foo(())`. let (_, expected_ty) = self.formal_and_expected_inputs[expected_idx]; - suggestions.push((*arg_span, self.ty_to_snippet(expected_ty, expected_idx))); + // Check if the new suggestion would overlap with any existing suggestion. + // This can happen when we have both removal suggestions (which may include + // adjacent commas) and type replacement suggestions for the same span. + let dominated = suggestions + .iter() + .any(|(span, _)| span.contains(*arg_span) || arg_span.overlaps(*span)); + if !dominated { + suggestions.push((*arg_span, self.ty_to_snippet(expected_ty, expected_idx))); + } } } } diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 39c28c4f4e99..6e126b3013c9 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -200,6 +200,14 @@ fn typeck_with_inspect<'tcx>( let wf_code = ObligationCauseCode::WellFormed(Some(WellFormedLoc::Ty(def_id))); fcx.register_wf_obligation(expected_type.into(), body.value.span, wf_code); + if let hir::Node::AnonConst(_) = node { + fcx.require_type_is_sized( + expected_type, + body.value.span, + ObligationCauseCode::SizedConstOrStatic, + ); + } + fcx.check_expr_coercible_to_type(body.value, expected_type, None); fcx.write_ty(id, expected_type); diff --git a/compiler/rustc_infer/Cargo.toml b/compiler/rustc_infer/Cargo.toml index 08c036148849..c4fbe89315db 100644 --- a/compiler/rustc_infer/Cargo.toml +++ b/compiler/rustc_infer/Cargo.toml @@ -10,7 +10,6 @@ doctest = false # tidy-alphabetical-start rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } -rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_hir = { path = "../rustc_hir" } rustc_index = { path = "../rustc_index" } rustc_macros = { path = "../rustc_macros" } diff --git a/compiler/rustc_infer/messages.ftl b/compiler/rustc_infer/messages.ftl deleted file mode 100644 index e51734ff7a77..000000000000 --- a/compiler/rustc_infer/messages.ftl +++ /dev/null @@ -1,5 +0,0 @@ -infer_opaque_hidden_type = - opaque type's hidden type cannot be another opaque type from the same scope - .label = one of the two opaque types used here has to be outside its defining scope - .opaque_type = opaque type whose hidden type is being assigned - .hidden_type = opaque type being used as hidden type diff --git a/compiler/rustc_infer/src/errors.rs b/compiler/rustc_infer/src/errors.rs index 76ea9c3433d3..7c6e3b4ef314 100644 --- a/compiler/rustc_infer/src/errors.rs +++ b/compiler/rustc_infer/src/errors.rs @@ -2,13 +2,13 @@ use rustc_macros::Diagnostic; use rustc_span::Span; #[derive(Diagnostic)] -#[diag(infer_opaque_hidden_type)] +#[diag("opaque type's hidden type cannot be another opaque type from the same scope")] pub(crate) struct OpaqueHiddenTypeDiag { #[primary_span] - #[label] + #[label("one of the two opaque types used here has to be outside its defining scope")] pub span: Span, - #[note(infer_opaque_type)] + #[note("opaque type whose hidden type is being assigned")] pub opaque_type: Span, - #[note(infer_hidden_type)] + #[note("opaque type being used as hidden type")] pub hidden_type: Span, } diff --git a/compiler/rustc_infer/src/lib.rs b/compiler/rustc_infer/src/lib.rs index 05ea0f813818..2a1733ef63cb 100644 --- a/compiler/rustc_infer/src/lib.rs +++ b/compiler/rustc_infer/src/lib.rs @@ -22,5 +22,3 @@ mod errors; pub mod infer; pub mod traits; - -rustc_fluent_macro::fluent_messages! { "../messages.ftl" } diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index b3889849430a..25f59f0e89df 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -231,7 +231,12 @@ pub(crate) fn run_in_thread_pool_with_globals< .name("rustc query cycle handler".to_string()) .spawn(move || { let on_panic = defer(|| { - eprintln!("internal compiler error: query cycle handler thread panicked, aborting process"); + // Split this long string so that it doesn't cause rustfmt to + // give up on the entire builder expression. + // + const MESSAGE: &str = "\ +internal compiler error: query cycle handler thread panicked, aborting process"; + eprintln!("{MESSAGE}"); // We need to abort here as we failed to resolve the deadlock, // otherwise the compiler could just hang, process::abort(); @@ -244,11 +249,16 @@ pub(crate) fn run_in_thread_pool_with_globals< tls::with(|tcx| { // Accessing session globals is sound as they outlive `GlobalCtxt`. // They are needed to hash query keys containing spans or symbols. - let query_map = rustc_span::set_session_globals_then(unsafe { &*(session_globals as *const SessionGlobals) }, || { - // Ensure there was no errors collecting all active jobs. - // We need the complete map to ensure we find a cycle to break. - QueryCtxt::new(tcx).collect_active_jobs(false).expect("failed to collect active queries in deadlock handler") - }); + let query_map = rustc_span::set_session_globals_then( + unsafe { &*(session_globals as *const SessionGlobals) }, + || { + // Ensure there were no errors collecting all active jobs. + // We need the complete map to ensure we find a cycle to break. + QueryCtxt::new(tcx).collect_active_jobs_from_all_queries(false).expect( + "failed to collect active queries in deadlock handler", + ) + }, + ); break_query_cycles(query_map, ®istry); }) }) diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index ff108031badc..d1d5d0a56ead 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -20,6 +20,7 @@ declare_lint_pass! { AMBIGUOUS_GLOB_IMPORTED_TRAITS, AMBIGUOUS_GLOB_IMPORTS, AMBIGUOUS_GLOB_REEXPORTS, + AMBIGUOUS_IMPORT_VISIBILITIES, AMBIGUOUS_PANIC_IMPORTS, ARITHMETIC_OVERFLOW, ASM_SUB_REGISTER, @@ -4564,6 +4565,55 @@ declare_lint! { }; } +declare_lint! { + /// The `ambiguous_import_visibilities` lint detects imports that should report ambiguity + /// errors, but previously didn't do that due to rustc bugs. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unknown_lints)] + /// #![deny(ambiguous_import_visibilities)] + /// mod reexport { + /// mod m { + /// pub struct S {} + /// } + /// + /// macro_rules! mac { + /// () => { use m::S; } + /// } + /// + /// pub use m::*; + /// mac!(); + /// + /// pub use S as Z; // ambiguous visibility + /// } + /// + /// fn main() { + /// reexport::Z {}; + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Previous versions of Rust compile it successfully because it + /// fetched the glob import's visibility for `pub use S as Z` import, and ignored the private + /// `use m::S` import that appeared later. + /// + /// This is a [future-incompatible] lint to transition this to a + /// hard error in the future. + /// + /// [future-incompatible]: ../index.md#future-incompatible-lints + pub AMBIGUOUS_IMPORT_VISIBILITIES, + Warn, + "detects certain glob imports that require reporting an ambiguity error", + @future_incompatible = FutureIncompatibleInfo { + reason: fcw!(FutureReleaseError #149145), + }; +} + declare_lint! { /// The `refining_impl_trait_reachable` lint detects `impl Trait` return /// types in method signatures that are refined by a publically reachable diff --git a/compiler/rustc_macros/Cargo.toml b/compiler/rustc_macros/Cargo.toml index f9d3b7583590..f097aee54abb 100644 --- a/compiler/rustc_macros/Cargo.toml +++ b/compiler/rustc_macros/Cargo.toml @@ -8,6 +8,8 @@ proc-macro = true [dependencies] # tidy-alphabetical-start +fluent-bundle = "0.16" +fluent-syntax = "0.12" proc-macro2 = "1" quote = "1" syn = { version = "2.0.9", features = ["full"] } diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs index 7e784e3464e9..e8356655dd9f 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs @@ -22,20 +22,22 @@ impl<'a> DiagnosticDerive<'a> { pub(crate) fn into_tokens(self) -> TokenStream { let DiagnosticDerive { mut structure } = self; let kind = DiagnosticDeriveKind::Diagnostic; - let slugs = RefCell::new(Vec::new()); + let messages = RefCell::new(Vec::new()); let implementation = kind.each_variant(&mut structure, |mut builder, variant| { let preamble = builder.preamble(variant); let body = builder.body(variant); - let Some(slug) = builder.primary_message() else { + let Some(message) = builder.primary_message() else { return DiagnosticDeriveError::ErrorHandled.to_compile_error(); }; - slugs.borrow_mut().push(slug.clone()); + messages.borrow_mut().push(message.clone()); + let message = message.diag_message(Some(variant)); + let init = quote! { let mut diag = rustc_errors::Diag::new( dcx, level, - crate::fluent_generated::#slug + #message ); }; @@ -66,7 +68,7 @@ impl<'a> DiagnosticDerive<'a> { } } }); - for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) { + for test in messages.borrow().iter().map(|s| s.generate_test(&structure)) { imp.extend(test); } imp @@ -86,17 +88,18 @@ impl<'a> LintDiagnosticDerive<'a> { pub(crate) fn into_tokens(self) -> TokenStream { let LintDiagnosticDerive { mut structure } = self; let kind = DiagnosticDeriveKind::LintDiagnostic; - let slugs = RefCell::new(Vec::new()); + let messages = RefCell::new(Vec::new()); let implementation = kind.each_variant(&mut structure, |mut builder, variant| { let preamble = builder.preamble(variant); let body = builder.body(variant); - let Some(slug) = builder.primary_message() else { + let Some(message) = builder.primary_message() else { return DiagnosticDeriveError::ErrorHandled.to_compile_error(); }; - slugs.borrow_mut().push(slug.clone()); + messages.borrow_mut().push(message.clone()); + let message = message.diag_message(Some(variant)); let primary_message = quote! { - diag.primary_message(crate::fluent_generated::#slug); + diag.primary_message(#message); }; let formatting_init = &builder.formatting_init; @@ -122,47 +125,10 @@ impl<'a> LintDiagnosticDerive<'a> { } } }); - for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) { + for test in messages.borrow().iter().map(|s| s.generate_test(&structure)) { imp.extend(test); } imp } } - -/// Generates a `#[test]` that verifies that all referenced variables -/// exist on this structure. -fn generate_test(slug: &syn::Path, structure: &Structure<'_>) -> TokenStream { - // FIXME: We can't identify variables in a subdiagnostic - for field in structure.variants().iter().flat_map(|v| v.ast().fields.iter()) { - for attr_name in field.attrs.iter().filter_map(|at| at.path().get_ident()) { - if attr_name == "subdiagnostic" { - return quote!(); - } - } - } - use std::sync::atomic::{AtomicUsize, Ordering}; - // We need to make sure that the same diagnostic slug can be used multiple times without - // causing an error, so just have a global counter here. - static COUNTER: AtomicUsize = AtomicUsize::new(0); - let slug = slug.get_ident().unwrap(); - let ident = quote::format_ident!("verify_{slug}_{}", COUNTER.fetch_add(1, Ordering::Relaxed)); - let ref_slug = quote::format_ident!("{slug}_refs"); - let struct_name = &structure.ast().ident; - let variables: Vec<_> = structure - .variants() - .iter() - .flat_map(|v| v.ast().fields.iter().filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))) - .collect(); - // tidy errors on `#[test]` outside of test files, so we use `#[test ]` to work around this - quote! { - #[cfg(test)] - #[test ] - fn #ident() { - let variables = [#(#variables),*]; - for vref in crate::fluent_generated::#ref_slug { - assert!(variables.contains(vref), "{}: variable `{vref}` not found ({})", stringify!(#struct_name), stringify!(#slug)); - } - } - } -} diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs index 25110fd4f908..6107b181eea2 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs @@ -4,13 +4,14 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; use syn::parse::ParseStream; use syn::spanned::Spanned; -use syn::{Attribute, Meta, Path, Token, Type, parse_quote}; +use syn::{Attribute, LitStr, Meta, Path, Token, Type, parse_quote}; use synstructure::{BindingInfo, Structure, VariantInfo}; use super::utils::SubdiagnosticVariant; use crate::diagnostics::error::{ DiagnosticDeriveError, span_err, throw_invalid_attr, throw_span_err, }; +use crate::diagnostics::message::Message; use crate::diagnostics::utils::{ FieldInfo, FieldInnerTy, FieldMap, SetOnce, SpannedOption, SubdiagnosticKind, build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error, @@ -41,9 +42,9 @@ pub(crate) struct DiagnosticDeriveVariantBuilder { /// derive builder. pub field_map: FieldMap, - /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that + /// Message is a mandatory part of the struct attribute as corresponds to the Fluent message that /// has the actual diagnostic message. - pub slug: Option, + pub message: Option, /// Error codes are a optional part of the struct attribute - this is only set to detect /// multiple specifications. @@ -90,7 +91,7 @@ impl DiagnosticDeriveKind { span, field_map: build_field_mapping(variant), formatting_init: TokenStream::new(), - slug: None, + message: None, code: None, }; f(builder, variant) @@ -105,8 +106,8 @@ impl DiagnosticDeriveKind { } impl DiagnosticDeriveVariantBuilder { - pub(crate) fn primary_message(&self) -> Option<&Path> { - match self.slug.as_ref() { + pub(crate) fn primary_message(&self) -> Option<&Message> { + match self.message.as_ref() { None => { span_err(self.span, "diagnostic slug not specified") .help( @@ -116,7 +117,7 @@ impl DiagnosticDeriveVariantBuilder { .emit(); None } - Some(slug) + Some(Message::Slug(slug)) if let Some(Mismatch { slug_name, crate_name, slug_prefix }) = Mismatch::check(slug) => { @@ -126,7 +127,7 @@ impl DiagnosticDeriveVariantBuilder { .emit(); None } - Some(slug) => Some(slug), + Some(msg) => Some(msg), } } @@ -136,7 +137,8 @@ impl DiagnosticDeriveVariantBuilder { let ast = variant.ast(); let attrs = &ast.attrs; let preamble = attrs.iter().map(|attr| { - self.generate_structure_code_for_attr(attr).unwrap_or_else(|v| v.to_compile_error()) + self.generate_structure_code_for_attr(attr, variant) + .unwrap_or_else(|v| v.to_compile_error()) }); quote! { @@ -154,7 +156,7 @@ impl DiagnosticDeriveVariantBuilder { } // ..and then subdiagnostic additions. for binding in variant.bindings().iter().filter(|bi| !should_generate_arg(bi.ast())) { - body.extend(self.generate_field_attrs_code(binding)); + body.extend(self.generate_field_attrs_code(binding, variant)); } body } @@ -163,7 +165,7 @@ impl DiagnosticDeriveVariantBuilder { fn parse_subdiag_attribute( &self, attr: &Attribute, - ) -> Result, DiagnosticDeriveError> { + ) -> Result, DiagnosticDeriveError> { let Some(subdiag) = SubdiagnosticVariant::from_attr(attr, &self.field_map)? else { // Some attributes aren't errors - like documentation comments - but also aren't // subdiagnostics. @@ -175,15 +177,18 @@ impl DiagnosticDeriveVariantBuilder { .help("consider creating a `Subdiagnostic` instead")); } - let slug = subdiag.slug.unwrap_or_else(|| match subdiag.kind { - SubdiagnosticKind::Label => parse_quote! { _subdiag::label }, - SubdiagnosticKind::Note => parse_quote! { _subdiag::note }, - SubdiagnosticKind::NoteOnce => parse_quote! { _subdiag::note_once }, - SubdiagnosticKind::Help => parse_quote! { _subdiag::help }, - SubdiagnosticKind::HelpOnce => parse_quote! { _subdiag::help_once }, - SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn }, - SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion }, - SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), + // For subdiagnostics without a message specified, insert a placeholder slug + let slug = subdiag.slug.unwrap_or_else(|| { + Message::Slug(match subdiag.kind { + SubdiagnosticKind::Label => parse_quote! { _subdiag::label }, + SubdiagnosticKind::Note => parse_quote! { _subdiag::note }, + SubdiagnosticKind::NoteOnce => parse_quote! { _subdiag::note_once }, + SubdiagnosticKind::Help => parse_quote! { _subdiag::help }, + SubdiagnosticKind::HelpOnce => parse_quote! { _subdiag::help_once }, + SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn }, + SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion }, + SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), + }) }); Ok(Some((subdiag.kind, slug, false))) @@ -195,6 +200,7 @@ impl DiagnosticDeriveVariantBuilder { fn generate_structure_code_for_attr( &mut self, attr: &Attribute, + variant: &VariantInfo<'_>, ) -> Result { // Always allow documentation comments. if is_doc_comment(attr) { @@ -210,13 +216,28 @@ impl DiagnosticDeriveVariantBuilder { let mut input = &*input; let slug_recovery_point = input.fork(); - let slug = input.parse::()?; - if input.is_empty() || input.peek(Token![,]) { - self.slug = Some(slug); + if input.peek(LitStr) { + // Parse an inline message + let message = input.parse::()?; + if !message.suffix().is_empty() { + span_err( + message.span().unwrap(), + "Inline message is not allowed to have a suffix", + ) + .emit(); + } + self.message = Some(Message::Inline(message.span(), message.value())); } else { - input = &slug_recovery_point; + // Parse a slug + let slug = input.parse::()?; + if input.is_empty() || input.peek(Token![,]) { + self.message = Some(Message::Slug(slug)); + } else { + input = &slug_recovery_point; + } } + // Parse arguments while !input.is_empty() { input.parse::()?; // Allow trailing comma @@ -266,7 +287,7 @@ impl DiagnosticDeriveVariantBuilder { | SubdiagnosticKind::NoteOnce | SubdiagnosticKind::Help | SubdiagnosticKind::HelpOnce - | SubdiagnosticKind::Warn => Ok(self.add_subdiagnostic(&fn_ident, slug)), + | SubdiagnosticKind::Warn => Ok(self.add_subdiagnostic(&fn_ident, slug, variant)), SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => { throw_invalid_attr!(attr, |diag| diag .help("`#[label]` and `#[suggestion]` can only be applied to fields")); @@ -294,7 +315,11 @@ impl DiagnosticDeriveVariantBuilder { } } - fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream { + fn generate_field_attrs_code( + &mut self, + binding_info: &BindingInfo<'_>, + variant: &VariantInfo<'_>, + ) -> TokenStream { let field = binding_info.ast(); let field_binding = &binding_info.binding; @@ -333,6 +358,7 @@ impl DiagnosticDeriveVariantBuilder { attr, FieldInfo { binding: binding_info, ty: inner_ty, span: &field.span() }, binding, + variant ) .unwrap_or_else(|v| v.to_compile_error()); @@ -350,6 +376,7 @@ impl DiagnosticDeriveVariantBuilder { attr: &Attribute, info: FieldInfo<'_>, binding: TokenStream, + variant: &VariantInfo<'_>, ) -> Result { let ident = &attr.path().segments.last().unwrap().ident; let name = ident.to_string(); @@ -388,7 +415,7 @@ impl DiagnosticDeriveVariantBuilder { match subdiag { SubdiagnosticKind::Label => { report_error_if_not_applied_to_span(attr, &info)?; - Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug)) + Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug, variant)) } SubdiagnosticKind::Note | SubdiagnosticKind::NoteOnce @@ -399,11 +426,11 @@ impl DiagnosticDeriveVariantBuilder { if type_matches_path(inner, &["rustc_span", "Span"]) || type_matches_path(inner, &["rustc_span", "MultiSpan"]) { - Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug)) + Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug, variant)) } else if type_is_unit(inner) || (matches!(info.ty, FieldInnerTy::Plain(_)) && type_is_bool(inner)) { - Ok(self.add_subdiagnostic(&fn_ident, slug)) + Ok(self.add_subdiagnostic(&fn_ident, slug, variant)) } else { report_type_error(attr, "`Span`, `MultiSpan`, `bool` or `()`")? } @@ -429,6 +456,7 @@ impl DiagnosticDeriveVariantBuilder { applicability.set_once(quote! { #static_applicability }, span); } + let message = slug.diag_message(Some(variant)); let applicability = applicability .value() .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified }); @@ -438,7 +466,7 @@ impl DiagnosticDeriveVariantBuilder { Ok(quote! { diag.span_suggestions_with_style( #span_field, - crate::fluent_generated::#slug, + #message, #code_field, #applicability, #style @@ -455,22 +483,30 @@ impl DiagnosticDeriveVariantBuilder { &self, field_binding: TokenStream, kind: &Ident, - fluent_attr_identifier: Path, + message: Message, + variant: &VariantInfo<'_>, ) -> TokenStream { let fn_name = format_ident!("span_{}", kind); + let message = message.diag_message(Some(variant)); quote! { diag.#fn_name( #field_binding, - crate::fluent_generated::#fluent_attr_identifier + #message ); } } /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug /// and `fluent_attr_identifier`. - fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream { + fn add_subdiagnostic( + &self, + kind: &Ident, + message: Message, + variant: &VariantInfo<'_>, + ) -> TokenStream { + let message = message.diag_message(Some(variant)); quote! { - diag.#kind(crate::fluent_generated::#fluent_attr_identifier); + diag.#kind(#message); } } diff --git a/compiler/rustc_macros/src/diagnostics/message.rs b/compiler/rustc_macros/src/diagnostics/message.rs new file mode 100644 index 000000000000..6c8aded89f16 --- /dev/null +++ b/compiler/rustc_macros/src/diagnostics/message.rs @@ -0,0 +1,138 @@ +use fluent_bundle::FluentResource; +use fluent_syntax::ast::{Expression, InlineExpression, Pattern, PatternElement}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::Path; +use synstructure::{Structure, VariantInfo}; + +use crate::diagnostics::error::span_err; + +#[derive(Clone)] +pub(crate) enum Message { + Slug(Path), + Inline(Span, String), +} + +impl Message { + /// Get the diagnostic message for this diagnostic + /// The passed `variant` is used to check whether all variables in the message are used. + /// For subdiagnostics, we cannot check this. + pub(crate) fn diag_message(&self, variant: Option<&VariantInfo<'_>>) -> TokenStream { + match self { + Message::Slug(slug) => { + quote! { crate::fluent_generated::#slug } + } + Message::Inline(message_span, message) => { + if let Some(variant) = variant { + verify_fluent_message(*message_span, &message, variant); + } + quote! { rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed(#message)) } + } + } + } + + /// Generates a `#[test]` that verifies that all referenced variables + /// exist on this structure. + pub(crate) fn generate_test(&self, structure: &Structure<'_>) -> TokenStream { + match self { + Message::Slug(slug) => { + // FIXME: We can't identify variables in a subdiagnostic + for field in structure.variants().iter().flat_map(|v| v.ast().fields.iter()) { + for attr_name in field.attrs.iter().filter_map(|at| at.path().get_ident()) { + if attr_name == "subdiagnostic" { + return quote!(); + } + } + } + use std::sync::atomic::{AtomicUsize, Ordering}; + // We need to make sure that the same diagnostic slug can be used multiple times without + // causing an error, so just have a global counter here. + static COUNTER: AtomicUsize = AtomicUsize::new(0); + let slug = slug.get_ident().unwrap(); + let ident = quote::format_ident!( + "verify_{slug}_{}", + COUNTER.fetch_add(1, Ordering::Relaxed) + ); + let ref_slug = quote::format_ident!("{slug}_refs"); + let struct_name = &structure.ast().ident; + let variables: Vec<_> = structure + .variants() + .iter() + .flat_map(|v| { + v.ast() + .fields + .iter() + .filter_map(|f| f.ident.as_ref().map(|i| i.to_string())) + }) + .collect(); + // tidy errors on `#[test]` outside of test files, so we use `#[test ]` to work around this + quote! { + #[cfg(test)] + #[test ] + fn #ident() { + let variables = [#(#variables),*]; + for vref in crate::fluent_generated::#ref_slug { + assert!(variables.contains(vref), "{}: variable `{vref}` not found ({})", stringify!(#struct_name), stringify!(#slug)); + } + } + } + } + Message::Inline(..) => { + // We don't generate a test for inline diagnostics, we can verify these at compile-time! + // This verification is done in the `diag_message` function above + quote! {} + } + } + } +} + +fn verify_fluent_message(msg_span: Span, message: &str, variant: &VariantInfo<'_>) { + // Parse the fluent message + const GENERATED_MSG_ID: &str = "generated_msg"; + let resource = FluentResource::try_new(format!("{GENERATED_MSG_ID} = {message}\n")).unwrap(); + assert_eq!(resource.entries().count(), 1); + let Some(fluent_syntax::ast::Entry::Message(message)) = resource.get_entry(0) else { + panic!("Did not parse into a message") + }; + + // Check if all variables are used + let fields: Vec = variant + .bindings() + .iter() + .flat_map(|b| b.ast().ident.as_ref()) + .map(|id| id.to_string()) + .collect(); + for variable in variable_references(&message) { + if !fields.iter().any(|f| f == variable) { + span_err(msg_span.unwrap(), format!("Variable `{variable}` not found in diagnostic ")) + .help(format!("Available fields: {:?}", fields.join(", "))) + .emit(); + } + // assert!(, ); + } +} + +fn variable_references<'a>(msg: &fluent_syntax::ast::Message<&'a str>) -> Vec<&'a str> { + let mut refs = vec![]; + if let Some(Pattern { elements }) = &msg.value { + for elt in elements { + if let PatternElement::Placeable { + expression: Expression::Inline(InlineExpression::VariableReference { id }), + } = elt + { + refs.push(id.name); + } + } + } + for attr in &msg.attributes { + for elt in &attr.value.elements { + if let PatternElement::Placeable { + expression: Expression::Inline(InlineExpression::VariableReference { id }), + } = elt + { + refs.push(id.name); + } + } + } + refs +} diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs index 55228248188e..09f05ce972f1 100644 --- a/compiler/rustc_macros/src/diagnostics/mod.rs +++ b/compiler/rustc_macros/src/diagnostics/mod.rs @@ -1,6 +1,7 @@ mod diagnostic; mod diagnostic_builder; mod error; +mod message; mod subdiagnostic; mod utils; diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index db2a19ab85ba..ac1fa984664c 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -11,6 +11,7 @@ use super::utils::SubdiagnosticVariant; use crate::diagnostics::error::{ DiagnosticDeriveError, invalid_attr, span_err, throw_invalid_attr, throw_span_err, }; +use crate::diagnostics::message::Message; use crate::diagnostics::utils::{ AllowMultipleAlternatives, FieldInfo, FieldInnerTy, FieldMap, SetOnce, SpannedOption, SubdiagnosticKind, build_field_mapping, build_suggestion_code, is_doc_comment, new_code_ident, @@ -182,7 +183,9 @@ impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics { } impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { - fn identify_kind(&mut self) -> Result, DiagnosticDeriveError> { + fn identify_kind( + &mut self, + ) -> Result, DiagnosticDeriveError> { let mut kind_slugs = vec![]; for attr in self.variant.ast().attrs { @@ -532,9 +535,8 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { let mut calls = TokenStream::new(); for (kind, slug) in kind_slugs { let message = format_ident!("__message"); - calls.extend( - quote! { let #message = #diag.eagerly_translate(crate::fluent_generated::#slug); }, - ); + let message_stream = slug.diag_message(None); + calls.extend(quote! { let #message = #diag.eagerly_translate(#message_stream); }); let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind); let call = match kind { diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index f084ba60ae3f..a5265a847a9c 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -16,6 +16,7 @@ use super::error::invalid_attr; use crate::diagnostics::error::{ DiagnosticDeriveError, span_err, throw_invalid_attr, throw_span_err, }; +use crate::diagnostics::message::Message; thread_local! { pub(crate) static CODE_IDENT_COUNT: RefCell = RefCell::new(0); @@ -587,7 +588,7 @@ pub(super) enum SubdiagnosticKind { pub(super) struct SubdiagnosticVariant { pub(super) kind: SubdiagnosticKind, - pub(super) slug: Option, + pub(super) slug: Option, } impl SubdiagnosticVariant { @@ -696,11 +697,31 @@ impl SubdiagnosticVariant { list.parse_args_with(|input: ParseStream<'_>| { let mut is_first = true; while !input.is_empty() { + // Try to parse an inline diagnostic message + if input.peek(LitStr) { + let message = input.parse::()?; + if !message.suffix().is_empty() { + span_err( + message.span().unwrap(), + "Inline message is not allowed to have a suffix", + ).emit(); + } + if !input.is_empty() { input.parse::()?; } + if is_first { + slug = Some(Message::Inline(message.span(), message.value())); + is_first = false; + } else { + span_err(message.span().unwrap(), "a diagnostic message must be the first argument to the attribute").emit(); + } + continue + } + + // Try to parse a slug instead let arg_name: Path = input.parse::()?; let arg_name_span = arg_name.span().unwrap(); if input.is_empty() || input.parse::().is_ok() { if is_first { - slug = Some(arg_name); + slug = Some(Message::Slug(arg_name)); is_first = false; } else { span_err(arg_name_span, "a diagnostic slug must be the first argument to the attribute").emit(); @@ -709,6 +730,7 @@ impl SubdiagnosticVariant { } is_first = false; + // Try to parse an argument match (arg_name.require_ident()?.to_string().as_str(), &mut kind) { ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => { let code_init = build_suggestion_code( diff --git a/compiler/rustc_macros/src/query.rs b/compiler/rustc_macros/src/query.rs index 0cac699e5b62..0e2b16d72eb1 100644 --- a/compiler/rustc_macros/src/query.rs +++ b/compiler/rustc_macros/src/query.rs @@ -303,9 +303,7 @@ fn add_query_desc_cached_impl( #[allow(unused_variables)] pub fn #name<'tcx>(tcx: TyCtxt<'tcx>, key: crate::query::queries::#name::Key<'tcx>) -> String { let (#tcx, #key) = (tcx, key); - ::rustc_middle::ty::print::with_no_trimmed_paths!( - format!(#desc) - ) + format!(#desc) } }; diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs index 393e9c59c355..973ceccc67f9 100644 --- a/compiler/rustc_middle/src/mir/statement.rs +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -441,6 +441,8 @@ impl<'tcx> Place<'tcx> { where D: ?Sized + HasLocalDecls<'tcx>, { + // If there's a field projection element in `projection`, we *could* skip everything + // before that, but on 2026-01-31 a perf experiment showed no benefit from doing so. PlaceTy::from_ty(local_decls.local_decls()[local].ty).multi_projection_ty(tcx, projection) } diff --git a/compiler/rustc_middle/src/query/inner.rs b/compiler/rustc_middle/src/query/inner.rs index 80e64e6a78ed..bc0b34b67f12 100644 --- a/compiler/rustc_middle/src/query/inner.rs +++ b/compiler/rustc_middle/src/query/inner.rs @@ -1,16 +1,13 @@ //! Helper functions that serve as the immediate implementation of //! `tcx.$query(..)` and its variations. -use std::fmt::Debug; - -use rustc_data_structures::fingerprint::Fingerprint; use rustc_query_system::dep_graph::{DepKind, DepNodeParams}; -use rustc_query_system::ich::StableHashingContext; use rustc_query_system::query::{QueryCache, QueryMode, try_get_cached}; use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; use crate::dep_graph; use crate::query::erase::{self, Erasable, Erased}; +use crate::query::plumbing::QueryVTable; use crate::ty::TyCtxt; /// Shared implementation of `tcx.$query(..)` and `tcx.at(span).$query(..)` @@ -80,35 +77,38 @@ where } /// Common implementation of query feeding, used by `define_feedable!`. -pub(crate) fn query_feed<'tcx, Cache, Value>( +pub(crate) fn query_feed<'tcx, Cache>( tcx: TyCtxt<'tcx>, dep_kind: DepKind, - hasher: Option, &Value) -> Fingerprint>, + query_vtable: &QueryVTable<'tcx, Cache>, cache: &Cache, key: Cache::Key, - erased: Erased, + value: Cache::Value, ) where - Cache: QueryCache>, + Cache: QueryCache, Cache::Key: DepNodeParams>, - Value: Erasable + Debug, { - let value = erase::restore_val::(erased); + let format_value = query_vtable.format_value; + // Check whether the in-memory cache already has a value for this key. match try_get_cached(tcx, cache, &key) { Some(old) => { - let old = erase::restore_val::(old); - if let Some(hasher) = hasher { - let (value_hash, old_hash): (Fingerprint, Fingerprint) = tcx - .with_stable_hashing_context(|mut hcx| { - (hasher(&mut hcx, &value), hasher(&mut hcx, &old)) - }); + // The query already has a cached value for this key. + // That's OK if both values are the same, i.e. they have the same hash, + // so now we check their hashes. + if let Some(hasher_fn) = query_vtable.hash_result { + let (old_hash, value_hash) = tcx.with_stable_hashing_context(|ref mut hcx| { + (hasher_fn(hcx, &old), hasher_fn(hcx, &value)) + }); if old_hash != value_hash { // We have an inconsistency. This can happen if one of the two // results is tainted by errors. In this case, delay a bug to // ensure compilation is doomed, and keep the `old` value. tcx.dcx().delayed_bug(format!( "Trying to feed an already recorded value for query {dep_kind:?} key={key:?}:\n\ - old value: {old:?}\nnew value: {value:?}", + old value: {old}\nnew value: {value}", + old = format_value(&old), + value = format_value(&value), )); } } else { @@ -117,14 +117,24 @@ pub(crate) fn query_feed<'tcx, Cache, Value>( // the query should not be marked `no_hash`. bug!( "Trying to feed an already recorded value for query {dep_kind:?} key={key:?}:\n\ - old value: {old:?}\nnew value: {value:?}", + old value: {old}\nnew value: {value}", + old = format_value(&old), + value = format_value(&value), ) } } None => { + // There is no cached value for this key, so feed the query by + // adding the provided value to the cache. let dep_node = dep_graph::DepNode::construct(tcx, dep_kind, &key); - let dep_node_index = tcx.dep_graph.with_feed_task(dep_node, tcx, &value, hasher); - cache.complete(key, erased, dep_node_index); + let dep_node_index = tcx.dep_graph.with_feed_task( + dep_node, + tcx, + &value, + query_vtable.hash_result, + query_vtable.format_value, + ); + cache.complete(key, value, dep_node_index); } } } diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index cea50f95df4b..c9291d89be8a 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -87,7 +87,6 @@ use rustc_hir::{Crate, ItemLocalId, ItemLocalMap, PreciseCapturingArgKind, Trait use rustc_index::IndexVec; use rustc_lint_defs::LintId; use rustc_macros::rustc_queries; -use rustc_query_system::ich::StableHashingContext; use rustc_query_system::query::{QueryMode, QueryState}; use rustc_session::Limits; use rustc_session::config::{EntryFnType, OptLevel, OutputFilenames, SymbolManglingVersion}; diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index b21db6eeeea0..78879dc9da9a 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -497,18 +497,6 @@ macro_rules! define_callbacks { }; } -macro_rules! hash_result { - ([]) => {{ - Some(dep_graph::hash_result) - }}; - ([(no_hash) $($rest:tt)*]) => {{ - None - }}; - ([$other:tt $($modifiers:tt)*]) => { - hash_result!([$($modifiers)*]) - }; -} - macro_rules! define_feedable { ($($(#[$attr:meta])* [$($modifiers:tt)*] fn $name:ident($($K:tt)*) -> $V:ty,)*) => { $(impl<'tcx, K: IntoQueryParam<$($K)*> + Copy> TyCtxtFeed<'tcx, K> { @@ -518,19 +506,17 @@ macro_rules! define_feedable { let key = self.key().into_query_param(); let tcx = self.tcx; - let erased = queries::$name::provided_to_erased(tcx, value); - let cache = &tcx.query_system.caches.$name; + let erased_value = queries::$name::provided_to_erased(tcx, value); let dep_kind: dep_graph::DepKind = dep_graph::dep_kinds::$name; - let hasher: Option, &_) -> _> = hash_result!([$($modifiers)*]); $crate::query::inner::query_feed( tcx, dep_kind, - hasher, - cache, + &tcx.query_system.query_vtables.$name, + &tcx.query_system.caches.$name, key, - erased, + erased_value, ); } })* diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 4e33b8ebb6de..0220531b09fa 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -408,6 +408,12 @@ impl> Visibility { } } +impl + Copy> Visibility { + pub fn min(self, vis: Visibility, tcx: TyCtxt<'_>) -> Visibility { + if self.is_at_least(vis, tcx) { vis } else { self } + } +} + impl Visibility { pub fn expect_local(self) -> Visibility { self.map_id(|id| id.expect_local()) diff --git a/compiler/rustc_middle/src/values.rs b/compiler/rustc_middle/src/values.rs index bc73d36216ef..fd08b5a972c6 100644 --- a/compiler/rustc_middle/src/values.rs +++ b/compiler/rustc_middle/src/values.rs @@ -50,9 +50,9 @@ impl<'tcx> Value> for ty::Binder<'_, ty::FnSig<'_>> { ) -> Self { let err = Ty::new_error(tcx, guar); - let arity = if let Some(frame) = cycle_error.cycle.get(0) - && frame.query.dep_kind == dep_kinds::fn_sig - && let Some(def_id) = frame.query.def_id + let arity = if let Some(info) = cycle_error.cycle.get(0) + && info.frame.dep_kind == dep_kinds::fn_sig + && let Some(def_id) = info.frame.def_id && let Some(node) = tcx.hir_get_if_local(def_id) && let Some(sig) = node.fn_sig() { @@ -85,10 +85,10 @@ impl<'tcx> Value> for Representability { let mut item_and_field_ids = Vec::new(); let mut representable_ids = FxHashSet::default(); for info in &cycle_error.cycle { - if info.query.dep_kind == dep_kinds::representability - && let Some(field_id) = info.query.def_id + if info.frame.dep_kind == dep_kinds::representability + && let Some(field_id) = info.frame.def_id && let Some(field_id) = field_id.as_local() - && let Some(DefKind::Field) = info.query.info.def_kind + && let Some(DefKind::Field) = info.frame.info.def_kind { let parent_id = tcx.parent(field_id.to_def_id()); let item_id = match tcx.def_kind(parent_id) { @@ -99,8 +99,8 @@ impl<'tcx> Value> for Representability { } } for info in &cycle_error.cycle { - if info.query.dep_kind == dep_kinds::representability_adt_ty - && let Some(def_id) = info.query.def_id_for_ty_in_cycle + if info.frame.dep_kind == dep_kinds::representability_adt_ty + && let Some(def_id) = info.frame.def_id_for_ty_in_cycle && let Some(def_id) = def_id.as_local() && !item_and_field_ids.iter().any(|&(id, _)| id == def_id) { @@ -141,9 +141,9 @@ impl<'tcx> Value> for &[ty::Variance] { search_for_cycle_permutation( &cycle_error.cycle, |cycle| { - if let Some(frame) = cycle.get(0) - && frame.query.dep_kind == dep_kinds::variances_of - && let Some(def_id) = frame.query.def_id + if let Some(info) = cycle.get(0) + && info.frame.dep_kind == dep_kinds::variances_of + && let Some(def_id) = info.frame.def_id { let n = tcx.generics_of(def_id).own_params.len(); ControlFlow::Break(vec![ty::Bivariant; n].leak()) @@ -189,8 +189,8 @@ impl<'tcx, T> Value> for Result> let diag = search_for_cycle_permutation( &cycle_error.cycle, |cycle| { - if cycle[0].query.dep_kind == dep_kinds::layout_of - && let Some(def_id) = cycle[0].query.def_id_for_ty_in_cycle + if cycle[0].frame.dep_kind == dep_kinds::layout_of + && let Some(def_id) = cycle[0].frame.def_id_for_ty_in_cycle && let Some(def_id) = def_id.as_local() && let def_kind = tcx.def_kind(def_id) && matches!(def_kind, DefKind::Closure) @@ -213,18 +213,18 @@ impl<'tcx, T> Value> for Result> tcx.def_kind_descr_article(def_kind, def_id.to_def_id()), tcx.def_kind_descr(def_kind, def_id.to_def_id()), ); - for (i, frame) in cycle.iter().enumerate() { - if frame.query.dep_kind != dep_kinds::layout_of { + for (i, info) in cycle.iter().enumerate() { + if info.frame.dep_kind != dep_kinds::layout_of { continue; } - let Some(frame_def_id) = frame.query.def_id_for_ty_in_cycle else { + let Some(frame_def_id) = info.frame.def_id_for_ty_in_cycle else { continue; }; let Some(frame_coroutine_kind) = tcx.coroutine_kind(frame_def_id) else { continue; }; let frame_span = - frame.query.info.default_span(cycle[(i + 1) % cycle.len()].span); + info.frame.info.default_span(cycle[(i + 1) % cycle.len()].span); if frame_span.is_dummy() { continue; } diff --git a/compiler/rustc_mir_dataflow/Cargo.toml b/compiler/rustc_mir_dataflow/Cargo.toml index 9621f9f20bdc..e41d5da71397 100644 --- a/compiler/rustc_mir_dataflow/Cargo.toml +++ b/compiler/rustc_mir_dataflow/Cargo.toml @@ -8,11 +8,11 @@ edition = "2024" polonius-engine = "0.13.0" regex = "1" rustc_abi = { path = "../rustc_abi" } -rustc_ast = { path = "../rustc_ast" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_graphviz = { path = "../rustc_graphviz" } +rustc_hir = { path = "../rustc_hir" } rustc_index = { path = "../rustc_index" } rustc_macros = { path = "../rustc_macros" } rustc_middle = { path = "../rustc_middle" } diff --git a/compiler/rustc_mir_dataflow/messages.ftl b/compiler/rustc_mir_dataflow/messages.ftl index 5698367e42ba..3783c647b03e 100644 --- a/compiler/rustc_mir_dataflow/messages.ftl +++ b/compiler/rustc_mir_dataflow/messages.ftl @@ -1,9 +1,3 @@ -mir_dataflow_duplicate_values_for = - duplicate values for `{$name}` - -mir_dataflow_path_must_end_in_filename = - path must end in a filename - mir_dataflow_peek_argument_not_a_local = rustc_peek: argument was not a local @@ -19,11 +13,5 @@ mir_dataflow_peek_must_be_not_temporary = mir_dataflow_peek_must_be_place_or_ref_place = rustc_peek: argument expression must be either `place` or `&place` -mir_dataflow_requires_an_argument = - `{$name}` requires an argument - mir_dataflow_stop_after_dataflow_ended_compilation = stop_after_dataflow ended compilation - -mir_dataflow_unknown_formatter = - unknown formatter diff --git a/compiler/rustc_mir_dataflow/src/errors.rs b/compiler/rustc_mir_dataflow/src/errors.rs index cfacc0ec370c..9d8c34c8a1f3 100644 --- a/compiler/rustc_mir_dataflow/src/errors.rs +++ b/compiler/rustc_mir_dataflow/src/errors.rs @@ -1,35 +1,5 @@ use rustc_macros::Diagnostic; -use rustc_span::{Span, Symbol}; - -#[derive(Diagnostic)] -#[diag(mir_dataflow_path_must_end_in_filename)] -pub(crate) struct PathMustEndInFilename { - #[primary_span] - pub span: Span, -} - -#[derive(Diagnostic)] -#[diag(mir_dataflow_unknown_formatter)] -pub(crate) struct UnknownFormatter { - #[primary_span] - pub span: Span, -} - -#[derive(Diagnostic)] -#[diag(mir_dataflow_duplicate_values_for)] -pub(crate) struct DuplicateValuesFor { - #[primary_span] - pub span: Span, - pub name: Symbol, -} - -#[derive(Diagnostic)] -#[diag(mir_dataflow_requires_an_argument)] -pub(crate) struct RequiresAnArgument { - #[primary_span] - pub span: Span, - pub name: Symbol, -} +use rustc_span::Span; #[derive(Diagnostic)] #[diag(mir_dataflow_stop_after_dataflow_ended_compilation)] diff --git a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs index 22bff3806b15..c4b9b4ce6416 100644 --- a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs +++ b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs @@ -7,6 +7,9 @@ use std::sync::OnceLock; use std::{io, ops, str}; use regex::Regex; +use rustc_graphviz as dot; +use rustc_hir::attrs::{AttributeKind, BorrowckGraphvizFormatKind, RustcMirKind}; +use rustc_hir::find_attr; use rustc_index::bit_set::DenseBitSet; use rustc_middle::mir::{ self, BasicBlock, Body, Location, MirDumper, graphviz_safe_def_name, traversal, @@ -14,17 +17,12 @@ use rustc_middle::mir::{ use rustc_middle::ty::TyCtxt; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_span::def_id::DefId; -use rustc_span::{Symbol, sym}; use tracing::debug; -use {rustc_ast as ast, rustc_graphviz as dot}; use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext}; use super::{ Analysis, CallReturnPlaces, Direction, Results, ResultsCursor, ResultsVisitor, visit_results, }; -use crate::errors::{ - DuplicateValuesFor, PathMustEndInFilename, RequiresAnArgument, UnknownFormatter, -}; /// Writes a DOT file containing the results of a dataflow analysis if the user requested it via /// `rustc_mir` attributes and `-Z dump-mir-dataflow`. The `Result` in and the `Results` out are @@ -43,10 +41,7 @@ where use std::io::Write; let def_id = body.source.def_id(); - let Ok(attrs) = RustcMirAttrs::parse(tcx, def_id) else { - // Invalid `rustc_mir` attrs are reported in `RustcMirAttrs::parse` - return Ok(()); - }; + let attrs = RustcMirAttrs::parse(tcx, def_id); let file = try { match attrs.output_path(A::NAME) { @@ -72,10 +67,7 @@ where Err(e) => return Err(e), }; - let style = match attrs.formatter { - Some(sym::two_phase) => OutputStyle::BeforeAndAfter, - _ => OutputStyle::AfterOnly, - }; + let style = attrs.formatter.unwrap_or(OutputStyle::AfterOnly); let mut buf = Vec::new(); @@ -98,71 +90,33 @@ where #[derive(Default)] struct RustcMirAttrs { basename_and_suffix: Option, - formatter: Option, + formatter: Option, } impl RustcMirAttrs { - fn parse(tcx: TyCtxt<'_>, def_id: DefId) -> Result { - let mut result = Ok(()); + fn parse(tcx: TyCtxt<'_>, def_id: DefId) -> Self { let mut ret = RustcMirAttrs::default(); - let rustc_mir_attrs = tcx - .get_attrs(def_id, sym::rustc_mir) - .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter())); - - for attr in rustc_mir_attrs { - let attr_result = match attr.name() { - Some(name @ sym::borrowck_graphviz_postflow) => { - Self::set_field(&mut ret.basename_and_suffix, tcx, name, &attr, |s| { - let path = PathBuf::from(s.to_string()); - match path.file_name() { - Some(_) => Ok(path), - None => { - tcx.dcx().emit_err(PathMustEndInFilename { span: attr.span() }); - Err(()) + let attrs = tcx.get_all_attrs(def_id); + if let Some(rustc_mir_attrs) = find_attr!(attrs, AttributeKind::RustcMir(kind) => kind) { + for attr in rustc_mir_attrs { + match attr { + RustcMirKind::BorrowckGraphvizPostflow { path } => { + ret.basename_and_suffix = Some(path.clone()); + } + RustcMirKind::BorrowckGraphvizFormat { format } => { + ret.formatter = match format { + BorrowckGraphvizFormatKind::TwoPhase => { + Some(OutputStyle::BeforeAndAfter) } - } - }) - } - Some(name @ sym::borrowck_graphviz_format) => { - Self::set_field(&mut ret.formatter, tcx, name, &attr, |s| match s { - sym::two_phase => Ok(s), - _ => { - tcx.dcx().emit_err(UnknownFormatter { span: attr.span() }); - Err(()) - } - }) - } - _ => Ok(()), - }; - - result = result.and(attr_result); + }; + } + _ => (), + }; + } } - result.map(|()| ret) - } - - fn set_field( - field: &mut Option, - tcx: TyCtxt<'_>, - name: Symbol, - attr: &ast::MetaItemInner, - mapper: impl FnOnce(Symbol) -> Result, - ) -> Result<(), ()> { - if field.is_some() { - tcx.dcx().emit_err(DuplicateValuesFor { span: attr.span(), name }); - - return Err(()); - } - - if let Some(s) = attr.value_str() { - *field = Some(mapper(s)?); - Ok(()) - } else { - tcx.dcx() - .emit_err(RequiresAnArgument { span: attr.span(), name: attr.name().unwrap() }); - Err(()) - } + ret } /// Returns the path where dataflow results should be written, or `None` diff --git a/compiler/rustc_mir_dataflow/src/rustc_peek.rs b/compiler/rustc_mir_dataflow/src/rustc_peek.rs index a899ec1fa884..1c5b38361669 100644 --- a/compiler/rustc_mir_dataflow/src/rustc_peek.rs +++ b/compiler/rustc_mir_dataflow/src/rustc_peek.rs @@ -1,8 +1,8 @@ -use rustc_ast::MetaItem; +use rustc_hir::attrs::{AttributeKind, RustcMirKind}; +use rustc_hir::find_attr; use rustc_middle::mir::{self, Body, Local, Location}; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_span::def_id::DefId; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{Span, sym}; use tracing::{debug, info}; use crate::errors::{ @@ -14,52 +14,37 @@ use crate::impls::{MaybeInitializedPlaces, MaybeLiveLocals, MaybeUninitializedPl use crate::move_paths::{HasMoveData, LookupResult, MoveData, MovePathIndex}; use crate::{Analysis, JoinSemiLattice, ResultsCursor}; -fn has_rustc_mir_with(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Option { - for attr in tcx.get_attrs(def_id, sym::rustc_mir) { - let items = attr.meta_item_list(); - for item in items.iter().flat_map(|l| l.iter()) { - match item.meta_item() { - Some(mi) if mi.has_name(name) => return Some(mi.clone()), - _ => continue, - } - } - } - None -} - pub fn sanity_check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) { let def_id = body.source.def_id(); - if !tcx.has_attr(def_id, sym::rustc_mir) { - debug!("skipping rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id)); - return; - } else { + let attrs = tcx.get_all_attrs(def_id); + if let Some(kind) = find_attr!(attrs, AttributeKind::RustcMir(kind) => kind) { + let move_data = MoveData::gather_moves(body, tcx, |_| true); debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id)); - } + if kind.contains(&RustcMirKind::PeekMaybeInit) { + let flow_inits = MaybeInitializedPlaces::new(tcx, body, &move_data) + .iterate_to_fixpoint(tcx, body, None) + .into_results_cursor(body); + sanity_check_via_rustc_peek(tcx, flow_inits); + } - let move_data = MoveData::gather_moves(body, tcx, |_| true); + if kind.contains(&RustcMirKind::PeekMaybeUninit) { + let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &move_data) + .iterate_to_fixpoint(tcx, body, None) + .into_results_cursor(body); + sanity_check_via_rustc_peek(tcx, flow_uninits); + } - if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_init).is_some() { - let flow_inits = MaybeInitializedPlaces::new(tcx, body, &move_data) - .iterate_to_fixpoint(tcx, body, None) - .into_results_cursor(body); - sanity_check_via_rustc_peek(tcx, flow_inits); - } + if kind.contains(&RustcMirKind::PeekLiveness) { + let flow_liveness = + MaybeLiveLocals.iterate_to_fixpoint(tcx, body, None).into_results_cursor(body); + sanity_check_via_rustc_peek(tcx, flow_liveness); + } - if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_uninit).is_some() { - let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &move_data) - .iterate_to_fixpoint(tcx, body, None) - .into_results_cursor(body); - sanity_check_via_rustc_peek(tcx, flow_uninits); - } - - if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_liveness).is_some() { - let flow_liveness = - MaybeLiveLocals.iterate_to_fixpoint(tcx, body, None).into_results_cursor(body); - sanity_check_via_rustc_peek(tcx, flow_liveness); - } - - if has_rustc_mir_with(tcx, def_id, sym::stop_after_dataflow).is_some() { - tcx.dcx().emit_fatal(StopAfterDataFlowEndedCompilation); + if kind.contains(&RustcMirKind::StopAfterDataflow) { + tcx.dcx().emit_fatal(StopAfterDataFlowEndedCompilation); + } + } else { + debug!("skipping rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id)); } } diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index b1ce0069b43a..bdc861e2cece 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -55,12 +55,10 @@ pub(super) fn extract_refined_covspans<'tcx>( } // Each pushed covspan should have the same context as the body span. - // If it somehow doesn't, discard the covspan, or panic in debug builds. + // If it somehow doesn't, discard the covspan. if !body_span.eq_ctxt(covspan_span) { - debug_assert!( - false, - "span context mismatch: body_span={body_span:?}, covspan.span={covspan_span:?}" - ); + // FIXME(Zalathar): Investigate how and why this is triggered + // by `tests/coverage/macros/context-mismatch-issue-147339.rs`. return false; } diff --git a/compiler/rustc_mir_transform/src/instsimplify.rs b/compiler/rustc_mir_transform/src/instsimplify.rs index fa9ceb018dd5..84d642ea4ebc 100644 --- a/compiler/rustc_mir_transform/src/instsimplify.rs +++ b/compiler/rustc_mir_transform/src/instsimplify.rs @@ -1,8 +1,8 @@ //! Performs various peephole optimizations. use rustc_abi::ExternAbi; -use rustc_ast::attr; -use rustc_hir::LangItem; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::{LangItem, find_attr}; use rustc_middle::bug; use rustc_middle::mir::visit::MutVisitor; use rustc_middle::mir::*; @@ -31,7 +31,7 @@ impl<'tcx> crate::MirPass<'tcx> for InstSimplify { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let preserve_ub_checks = - attr::contains_name(tcx.hir_krate_attrs(), sym::rustc_preserve_ub_checks); + find_attr!(tcx.hir_krate_attrs(), AttributeKind::RustcPreserveUbChecks); if !preserve_ub_checks { SimplifyUbCheck { tcx }.visit_body(body); } diff --git a/compiler/rustc_mir_transform/src/liveness.rs b/compiler/rustc_mir_transform/src/liveness.rs index cf977be4c3df..9bafee9f31c3 100644 --- a/compiler/rustc_mir_transform/src/liveness.rs +++ b/compiler/rustc_mir_transform/src/liveness.rs @@ -1243,9 +1243,12 @@ struct TransferFunction<'a, 'tcx> { impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> { fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { match statement.kind { - // `ForLet(None)` fake read erroneously marks the just-assigned local as live. - // This defeats the purpose of the analysis for `let` bindings. - StatementKind::FakeRead(box (FakeReadCause::ForLet(None), _)) => return, + // `ForLet(None)` and `ForGuardBinding` fake reads erroneously mark the just-assigned + // locals as live. This defeats the purpose of the analysis for such bindings. + StatementKind::FakeRead(box ( + FakeReadCause::ForLet(None) | FakeReadCause::ForGuardBinding, + _, + )) => return, // Handle self-assignment by restricting the read/write they do. StatementKind::Assign(box (ref dest, ref rvalue)) if self.self_assignment.contains(&location) => diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 8cf68b280850..dd16092c99f9 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -299,6 +299,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcDumpVtable(..) | AttributeKind::RustcDynIncompatibleTrait(..) | AttributeKind::RustcHasIncoherentInherentImpls + | AttributeKind::RustcHiddenTypeOfOpaques | AttributeKind::RustcLayout(..) | AttributeKind::RustcLayoutScalarValidRangeEnd(..) | AttributeKind::RustcLayoutScalarValidRangeStart(..) @@ -308,6 +309,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcLintUntrackedQueryInformation | AttributeKind::RustcMacroTransparency(_) | AttributeKind::RustcMain + | AttributeKind::RustcMir(_) | AttributeKind::RustcNeverReturnsNullPointer | AttributeKind::RustcNoImplicitAutorefs | AttributeKind::RustcNonConstTraitMethod @@ -318,6 +320,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcParenSugar(..) | AttributeKind::RustcPassByValue (..) | AttributeKind::RustcPassIndirectlyInNonRusticAbis(..) + | AttributeKind::RustcPreserveUbChecks | AttributeKind::RustcReallocator | AttributeKind::RustcScalableVector { .. } | AttributeKind::RustcShouldNotBeCalledOnConstItems(..) @@ -390,7 +393,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | sym::rustc_capture_analysis | sym::rustc_regions | sym::rustc_strict_coherence - | sym::rustc_hidden_type_of_opaques | sym::rustc_mir | sym::rustc_effective_visibility | sym::rustc_outlives @@ -406,8 +408,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | sym::register_tool | sym::rustc_no_implicit_bounds | sym::test_runner - | sym::reexport_test_harness_main - | sym::rustc_preserve_ub_checks, + | sym::reexport_test_harness_main, .. ] => {} [name, rest@..] => { diff --git a/compiler/rustc_query_impl/src/lib.rs b/compiler/rustc_query_impl/src/lib.rs index 4e79d0842da2..bd69c99a32dc 100644 --- a/compiler/rustc_query_impl/src/lib.rs +++ b/compiler/rustc_query_impl/src/lib.rs @@ -2,10 +2,13 @@ // tidy-alphabetical-start #![allow(internal_features)] +#![feature(adt_const_params)] #![feature(min_specialization)] #![feature(rustc_attrs)] // tidy-alphabetical-end +use std::marker::ConstParamTy; + use rustc_data_structures::stable_hasher::HashStable; use rustc_data_structures::sync::AtomicU64; use rustc_middle::arena::Arena; @@ -35,29 +38,34 @@ pub use crate::plumbing::{QueryCtxt, query_key_hash_verify_all}; mod profiling_support; pub use self::profiling_support::alloc_self_profile_query_strings; +#[derive(ConstParamTy)] // Allow this struct to be used for const-generic values. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct QueryFlags { + /// True if this query has the `anon` modifier. + is_anon: bool, + /// True if this query has the `depth_limit` modifier. + is_depth_limit: bool, + /// True if this query has the `feedable` modifier. + is_feedable: bool, +} + /// Combines a [`QueryVTable`] with some additional compile-time booleans /// to implement [`QueryDispatcher`], for use by code in [`rustc_query_system`]. /// /// Baking these boolean flags into the type gives a modest but measurable /// improvement to compiler perf and compiler code size; see /// . -struct SemiDynamicQueryDispatcher< - 'tcx, - C: QueryCache, - const ANON: bool, - const DEPTH_LIMIT: bool, - const FEEDABLE: bool, -> { +struct SemiDynamicQueryDispatcher<'tcx, C: QueryCache, const FLAGS: QueryFlags> { vtable: &'tcx QueryVTable<'tcx, C>, } // Manually implement Copy/Clone, because deriving would put trait bounds on the cache type. -impl<'tcx, C: QueryCache, const ANON: bool, const DEPTH_LIMIT: bool, const FEEDABLE: bool> Copy - for SemiDynamicQueryDispatcher<'tcx, C, ANON, DEPTH_LIMIT, FEEDABLE> +impl<'tcx, C: QueryCache, const FLAGS: QueryFlags> Copy + for SemiDynamicQueryDispatcher<'tcx, C, FLAGS> { } -impl<'tcx, C: QueryCache, const ANON: bool, const DEPTH_LIMIT: bool, const FEEDABLE: bool> Clone - for SemiDynamicQueryDispatcher<'tcx, C, ANON, DEPTH_LIMIT, FEEDABLE> +impl<'tcx, C: QueryCache, const FLAGS: QueryFlags> Clone + for SemiDynamicQueryDispatcher<'tcx, C, FLAGS> { fn clone(&self) -> Self { *self @@ -65,8 +73,8 @@ impl<'tcx, C: QueryCache, const ANON: bool, const DEPTH_LIMIT: bool, const FEEDA } // This is `impl QueryDispatcher for SemiDynamicQueryDispatcher`. -impl<'tcx, C: QueryCache, const ANON: bool, const DEPTH_LIMIT: bool, const FEEDABLE: bool> - QueryDispatcher<'tcx> for SemiDynamicQueryDispatcher<'tcx, C, ANON, DEPTH_LIMIT, FEEDABLE> +impl<'tcx, C: QueryCache, const FLAGS: QueryFlags> QueryDispatcher<'tcx> + for SemiDynamicQueryDispatcher<'tcx, C, FLAGS> where for<'a> C::Key: HashStable>, { @@ -86,10 +94,7 @@ where } #[inline(always)] - fn query_state<'a>(self, qcx: QueryCtxt<'tcx>) -> &'a QueryState<'tcx, Self::Key> - where - QueryCtxt<'tcx>: 'a, - { + fn query_state(self, qcx: QueryCtxt<'tcx>) -> &'tcx QueryState<'tcx, Self::Key> { // Safety: // This is just manually doing the subfield referencing through pointer math. unsafe { @@ -100,7 +105,7 @@ where } #[inline(always)] - fn query_cache<'a>(self, qcx: QueryCtxt<'tcx>) -> &'a Self::Cache { + fn query_cache(self, qcx: QueryCtxt<'tcx>) -> &'tcx Self::Cache { // Safety: // This is just manually doing the subfield referencing through pointer math. unsafe { @@ -158,7 +163,7 @@ where #[inline(always)] fn anon(self) -> bool { - ANON + FLAGS.is_anon } #[inline(always)] @@ -168,12 +173,12 @@ where #[inline(always)] fn depth_limit(self) -> bool { - DEPTH_LIMIT + FLAGS.is_depth_limit } #[inline(always)] fn feedable(self) -> bool { - FEEDABLE + FLAGS.is_feedable } #[inline(always)] @@ -216,12 +221,12 @@ trait QueryDispatcherUnerased<'tcx> { ) -> Self::UnerasedValue; } -pub fn query_system<'a>( +pub fn query_system<'tcx>( local_providers: Providers, extern_providers: ExternProviders, on_disk_cache: Option, incremental: bool, -) -> QuerySystem<'a> { +) -> QuerySystem<'tcx> { QuerySystem { states: Default::default(), arenas: Default::default(), diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs index 2d4e10a0380c..3835f3f557a6 100644 --- a/compiler/rustc_query_impl/src/plumbing.rs +++ b/compiler/rustc_query_impl/src/plumbing.rs @@ -24,7 +24,7 @@ use rustc_middle::ty::codec::TyEncoder; use rustc_middle::ty::print::with_reduced_queries; use rustc_middle::ty::tls::{self, ImplicitCtxt}; use rustc_middle::ty::{self, TyCtxt}; -use rustc_query_system::dep_graph::{DepNodeParams, HasDepContext}; +use rustc_query_system::dep_graph::{DepNodeParams, FingerprintStyle, HasDepContext}; use rustc_query_system::ich::StableHashingContext; use rustc_query_system::query::{ QueryCache, QueryContext, QueryDispatcher, QueryJobId, QueryMap, QuerySideEffect, @@ -48,6 +48,25 @@ impl<'tcx> QueryCtxt<'tcx> { pub fn new(tcx: TyCtxt<'tcx>) -> Self { QueryCtxt { tcx } } + + fn depth_limit_error(self, job: QueryJobId) { + let query_map = self + .collect_active_jobs_from_all_queries(true) + .expect("failed to collect active queries"); + let (info, depth) = job.find_dep_kind_root(query_map); + + let suggested_limit = match self.tcx.recursion_limit() { + Limit(0) => Limit(2), + limit => limit * 2, + }; + + self.tcx.sess.dcx().emit_fatal(QueryOverflow { + span: info.job.span, + note: QueryOverflowNote { desc: info.frame.info.extract().description, depth }, + suggested_limit, + crate_name: self.tcx.crate_name(LOCAL_CRATE), + }); + } } impl<'tcx> HasDepContext for QueryCtxt<'tcx> { @@ -81,7 +100,7 @@ impl<'tcx> QueryContext<'tcx> for QueryCtxt<'tcx> { tls::with_related_context(self.tcx, |icx| icx.query) } - /// Returns a map of currently active query jobs. + /// Returns a map of currently active query jobs, collected from all queries. /// /// If `require_complete` is `true`, this function locks all shards of the /// query results to produce a complete map, which always returns `Ok`. @@ -91,12 +110,15 @@ impl<'tcx> QueryContext<'tcx> for QueryCtxt<'tcx> { /// Prefer passing `false` to `require_complete` to avoid potential deadlocks, /// especially when called from within a deadlock handler, unless a /// complete map is needed and no deadlock is possible at this call site. - fn collect_active_jobs(self, require_complete: bool) -> Result, QueryMap<'tcx>> { + fn collect_active_jobs_from_all_queries( + self, + require_complete: bool, + ) -> Result, QueryMap<'tcx>> { let mut jobs = QueryMap::default(); let mut complete = true; - for collect in super::COLLECT_ACTIVE_JOBS.iter() { - if collect(self.tcx, &mut jobs, require_complete).is_none() { + for gather_fn in crate::PER_QUERY_GATHER_ACTIVE_JOBS_FNS.iter() { + if gather_fn(self.tcx, &mut jobs, require_complete).is_none() { complete = false; } } @@ -104,13 +126,6 @@ impl<'tcx> QueryContext<'tcx> for QueryCtxt<'tcx> { if complete { Ok(jobs) } else { Err(jobs) } } - fn lift_query_info( - self, - info: &QueryStackDeferred<'tcx>, - ) -> rustc_query_system::query::QueryStackFrameExtra { - info.extract() - } - // Interactions with on_disk_cache fn load_side_effect( self, @@ -162,26 +177,6 @@ impl<'tcx> QueryContext<'tcx> for QueryCtxt<'tcx> { tls::enter_context(&new_icx, compute) }) } - - fn depth_limit_error(self, job: QueryJobId) { - let query_map = self.collect_active_jobs(true).expect("failed to collect active queries"); - let (info, depth) = job.find_dep_kind_root(query_map); - - let suggested_limit = match self.tcx.recursion_limit() { - Limit(0) => Limit(2), - limit => limit * 2, - }; - - self.tcx.sess.dcx().emit_fatal(QueryOverflow { - span: info.job.span, - note: QueryOverflowNote { - desc: self.lift_query_info(&info.query.info).description, - depth, - }, - suggested_limit, - crate_name: self.tcx.crate_name(LOCAL_CRATE), - }); - } } pub(super) fn try_mark_green<'tcx>(tcx: TyCtxt<'tcx>, dep_node: &dep_graph::DepNode) -> bool { @@ -415,9 +410,8 @@ pub(crate) fn query_key_hash_verify<'tcx>( ) { let _timer = qcx.tcx.prof.generic_activity_with_arg("query_key_hash_verify_for", query.name()); - let mut map = UnordMap::default(); - let cache = query.query_cache(qcx); + let mut map = UnordMap::with_capacity(cache.len()); cache.iter(&mut |key, _, _| { let node = DepNode::construct(qcx.tcx, query.dep_kind(), key); if let Some(other_key) = map.insert(node, *key) { @@ -519,7 +513,11 @@ pub(crate) fn make_dep_kind_vtable_for_query<'tcx, Q>( where Q: QueryDispatcherUnerased<'tcx>, { - let fingerprint_style = ::Key::fingerprint_style(); + let fingerprint_style = if is_anon { + FingerprintStyle::Opaque + } else { + ::Key::fingerprint_style() + }; if is_anon || !fingerprint_style.reconstructible() { return DepKindVTable { @@ -708,14 +706,18 @@ macro_rules! define_queries { data: PhantomData<&'tcx ()> } + const FLAGS: QueryFlags = QueryFlags { + is_anon: is_anon!([$($modifiers)*]), + is_depth_limit: depth_limit!([$($modifiers)*]), + is_feedable: feedable!([$($modifiers)*]), + }; + impl<'tcx> QueryDispatcherUnerased<'tcx> for QueryType<'tcx> { type UnerasedValue = queries::$name::Value<'tcx>; type Dispatcher = SemiDynamicQueryDispatcher< 'tcx, queries::$name::Storage<'tcx>, - { is_anon!([$($modifiers)*]) }, - { depth_limit!([$($modifiers)*]) }, - { feedable!([$($modifiers)*]) }, + FLAGS, >; const NAME: &'static &'static str = &stringify!($name); @@ -733,22 +735,28 @@ macro_rules! define_queries { } } - pub(crate) fn collect_active_jobs<'tcx>( + /// Internal per-query plumbing for collecting the set of active jobs for this query. + /// + /// Should only be called through `PER_QUERY_GATHER_ACTIVE_JOBS_FNS`. + pub(crate) fn gather_active_jobs<'tcx>( tcx: TyCtxt<'tcx>, qmap: &mut QueryMap<'tcx>, require_complete: bool, ) -> Option<()> { - let make_query = |tcx, key| { + let make_frame = |tcx, key| { let kind = rustc_middle::dep_graph::dep_kinds::$name; let name = stringify!($name); $crate::plumbing::create_query_frame(tcx, rustc_middle::query::descs::$name, key, kind, name) }; - let res = tcx.query_system.states.$name.collect_active_jobs( + + // Call `gather_active_jobs_inner` to do the actual work. + let res = tcx.query_system.states.$name.gather_active_jobs_inner( tcx, - make_query, + make_frame, qmap, require_complete, ); + // this can be called during unwinding, and the function has a `try_`-prefix, so // don't `unwrap()` here, just manually check for `None` and do best-effort error // reporting. @@ -818,10 +826,17 @@ macro_rules! define_queries { // These arrays are used for iteration and can't be indexed by `DepKind`. - const COLLECT_ACTIVE_JOBS: &[ - for<'tcx> fn(TyCtxt<'tcx>, &mut QueryMap<'tcx>, bool) -> Option<()> - ] = - &[$(query_impl::$name::collect_active_jobs),*]; + /// Used by `collect_active_jobs_from_all_queries` to iterate over all + /// queries, and gather the active jobs for each query. + /// + /// (We arbitrarily use the word "gather" when collecting the jobs for + /// each individual query, so that we have distinct function names to + /// grep for.) + const PER_QUERY_GATHER_ACTIVE_JOBS_FNS: &[ + for<'tcx> fn(TyCtxt<'tcx>, &mut QueryMap<'tcx>, require_complete: bool) -> Option<()> + ] = &[ + $(query_impl::$name::gather_active_jobs),* + ]; const ALLOC_SELF_PROFILE_QUERY_STRINGS: &[ for<'tcx> fn(TyCtxt<'tcx>, &mut QueryKeyStringCache) diff --git a/compiler/rustc_query_system/Cargo.toml b/compiler/rustc_query_system/Cargo.toml index 0ad8143c5a4f..761a299eab77 100644 --- a/compiler/rustc_query_system/Cargo.toml +++ b/compiler/rustc_query_system/Cargo.toml @@ -11,7 +11,6 @@ rustc_ast = { path = "../rustc_ast" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } rustc_feature = { path = "../rustc_feature" } -rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_hashes = { path = "../rustc_hashes" } rustc_hir = { path = "../rustc_hir" } rustc_index = { path = "../rustc_index" } diff --git a/compiler/rustc_query_system/messages.ftl b/compiler/rustc_query_system/messages.ftl deleted file mode 100644 index d2ab2d34c5fc..000000000000 --- a/compiler/rustc_query_system/messages.ftl +++ /dev/null @@ -1,30 +0,0 @@ -query_system_cycle = cycle detected when {$stack_bottom} - .note = see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information - -query_system_cycle_recursive_trait_alias = trait aliases cannot be recursive - -query_system_cycle_recursive_ty_alias = type aliases cannot be recursive -query_system_cycle_recursive_ty_alias_help1 = consider using a struct, enum, or union instead to break the cycle -query_system_cycle_recursive_ty_alias_help2 = see for more information - -query_system_cycle_stack_middle = ...which requires {$desc}... - -query_system_cycle_stack_multiple = ...which again requires {$stack_bottom}, completing the cycle - -query_system_cycle_stack_single = ...which immediately requires {$stack_bottom} again - -query_system_cycle_usage = cycle used when {$usage} - -query_system_increment_compilation = internal compiler error: encountered incremental compilation error with {$dep_node} - -query_system_increment_compilation_note1 = please follow the instructions below to create a bug report with the provided information -query_system_increment_compilation_note2 = for incremental compilation bugs, having a reproduction is vital -query_system_increment_compilation_note3 = an ideal reproduction consists of the code before and some patch that then triggers the bug when applied and compiled again -query_system_increment_compilation_note4 = as a workaround, you can run {$run_cmd} to allow your project to compile - -query_system_overflow_note = query depth increased by {$depth} when {$desc} - -query_system_query_overflow = queries overflow the depth limit! - .help = consider increasing the recursion limit by adding a `#![recursion_limit = "{$suggested_limit}"]` attribute to your crate (`{$crate_name}`) - -query_system_reentrant = internal compiler error: reentrant incremental verify failure, suppressing message diff --git a/compiler/rustc_query_system/src/dep_graph/dep_node.rs b/compiler/rustc_query_system/src/dep_graph/dep_node.rs index 72bdcd2d534d..b9d35bc5a937 100644 --- a/compiler/rustc_query_system/src/dep_graph/dep_node.rs +++ b/compiler/rustc_query_system/src/dep_graph/dep_node.rs @@ -237,8 +237,9 @@ pub struct DepKindVTable { /// cached within one compiler invocation. pub is_eval_always: bool, - /// Whether the query key can be recovered from the hashed fingerprint. - /// See [DepNodeParams] trait for the behaviour of each key type. + /// Indicates whether and how the query key can be recovered from its hashed fingerprint. + /// + /// The [`DepNodeParams`] trait determines the fingerprint style for each key type. pub fingerprint_style: FingerprintStyle, /// The red/green evaluation system will try to mark a specific DepNode in the diff --git a/compiler/rustc_query_system/src/dep_graph/graph.rs b/compiler/rustc_query_system/src/dep_graph/graph.rs index cf3d211d1cc7..06e576baf22a 100644 --- a/compiler/rustc_query_system/src/dep_graph/graph.rs +++ b/compiler/rustc_query_system/src/dep_graph/graph.rs @@ -561,12 +561,13 @@ impl DepGraph { /// FIXME: If the code is changed enough for this node to be marked before requiring the /// caller's node, we suppose that those changes will be enough to mark this node red and /// force a recomputation using the "normal" way. - pub fn with_feed_task, R: Debug>( + pub fn with_feed_task, R>( &self, node: DepNode, cx: Ctxt, result: &R, hash_result: Option, &R) -> Fingerprint>, + format_value_fn: fn(&R) -> String, ) -> DepNodeIndex { if let Some(data) = self.data.as_ref() { // The caller query has more dependencies than the node we are creating. We may @@ -584,7 +585,7 @@ impl DepGraph { result, prev_index, hash_result, - |value| format!("{value:?}"), + format_value_fn, ); #[cfg(debug_assertions)] @@ -872,6 +873,8 @@ impl DepGraphData { // Return None if the dep node didn't exist in the previous session let prev_index = self.previous.node_to_index_opt(dep_node)?; + debug_assert_eq!(self.previous.index_to_node(prev_index), dep_node); + match self.colors.get(prev_index) { DepNodeColor::Green(dep_node_index) => Some((prev_index, dep_node_index)), DepNodeColor::Red => None, @@ -880,7 +883,7 @@ impl DepGraphData { // in the previous compilation session too, so we can try to // mark it as green by recursively marking all of its // dependencies green. - self.try_mark_previous_green(qcx, prev_index, dep_node, None) + self.try_mark_previous_green(qcx, prev_index, None) .map(|dep_node_index| (prev_index, dep_node_index)) } } @@ -928,8 +931,7 @@ impl DepGraphData { dep_dep_node, dep_dep_node.hash, ); - let node_index = - self.try_mark_previous_green(qcx, parent_dep_node_index, dep_dep_node, Some(frame)); + let node_index = self.try_mark_previous_green(qcx, parent_dep_node_index, Some(frame)); if node_index.is_some() { debug!("managed to MARK dependency {dep_dep_node:?} as green"); @@ -981,15 +983,15 @@ impl DepGraphData { &self, qcx: Qcx, prev_dep_node_index: SerializedDepNodeIndex, - dep_node: &DepNode, frame: Option<&MarkFrame<'_>>, ) -> Option { let frame = MarkFrame { index: prev_dep_node_index, parent: frame }; // We never try to mark eval_always nodes as green - debug_assert!(!qcx.dep_context().is_eval_always(dep_node.kind)); - - debug_assert_eq!(self.previous.index_to_node(prev_dep_node_index), dep_node); + debug_assert!( + !qcx.dep_context() + .is_eval_always(self.previous.index_to_node(prev_dep_node_index).kind) + ); let prev_deps = self.previous.edge_targets_from(prev_dep_node_index); @@ -1010,7 +1012,10 @@ impl DepGraphData { // ... and finally storing a "Green" entry in the color map. // Multiple threads can all write the same color here - debug!("successfully marked {dep_node:?} as green"); + debug!( + "successfully marked {:?} as green", + self.previous.index_to_node(prev_dep_node_index) + ); Some(dep_node_index) } } diff --git a/compiler/rustc_query_system/src/dep_graph/mod.rs b/compiler/rustc_query_system/src/dep_graph/mod.rs index 8f714a2c96d6..c3ac1c7bc2d5 100644 --- a/compiler/rustc_query_system/src/dep_graph/mod.rs +++ b/compiler/rustc_query_system/src/dep_graph/mod.rs @@ -39,11 +39,7 @@ pub trait DepContext: Copy { #[inline(always)] fn fingerprint_style(self, kind: DepKind) -> FingerprintStyle { - let vtable = self.dep_kind_vtable(kind); - if vtable.is_anon { - return FingerprintStyle::Opaque; - } - vtable.fingerprint_style + self.dep_kind_vtable(kind).fingerprint_style } #[inline(always)] @@ -148,6 +144,9 @@ impl HasDepContext for (T, Q) { } /// Describes the contents of the fingerprint generated by a given query. +/// +/// This is mainly for determining whether and how we can reconstruct a key +/// from the fingerprint. #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum FingerprintStyle { /// The fingerprint is actually a DefPathHash. @@ -156,7 +155,7 @@ pub enum FingerprintStyle { HirId, /// Query key was `()` or equivalent, so fingerprint is just zero. Unit, - /// Some opaque hash. + /// The fingerprint is an opaque hash, and a key cannot be reconstructed from it. Opaque, } diff --git a/compiler/rustc_query_system/src/error.rs b/compiler/rustc_query_system/src/error.rs index 4b1effe2b33d..55f2feba0d86 100644 --- a/compiler/rustc_query_system/src/error.rs +++ b/compiler/rustc_query_system/src/error.rs @@ -4,7 +4,7 @@ use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_span::{Span, Symbol}; #[derive(Subdiagnostic)] -#[note(query_system_cycle_stack_middle)] +#[note("...which requires {$desc}...")] pub(crate) struct CycleStack { #[primary_span] pub span: Span, @@ -13,24 +13,26 @@ pub(crate) struct CycleStack { #[derive(Subdiagnostic)] pub(crate) enum StackCount { - #[note(query_system_cycle_stack_single)] + #[note("...which immediately requires {$stack_bottom} again")] Single, - #[note(query_system_cycle_stack_multiple)] + #[note("...which again requires {$stack_bottom}, completing the cycle")] Multiple, } #[derive(Subdiagnostic)] pub(crate) enum Alias { - #[note(query_system_cycle_recursive_ty_alias)] - #[help(query_system_cycle_recursive_ty_alias_help1)] - #[help(query_system_cycle_recursive_ty_alias_help2)] + #[note("type aliases cannot be recursive")] + #[help("consider using a struct, enum, or union instead to break the cycle")] + #[help( + "see for more information" + )] Ty, - #[note(query_system_cycle_recursive_trait_alias)] + #[note("trait aliases cannot be recursive")] Trait, } #[derive(Subdiagnostic)] -#[note(query_system_cycle_usage)] +#[note("cycle used when {$usage}")] pub(crate) struct CycleUsage { #[primary_span] pub span: Span, @@ -38,7 +40,7 @@ pub(crate) struct CycleUsage { } #[derive(Diagnostic)] -#[diag(query_system_cycle, code = E0391)] +#[diag("cycle detected when {$stack_bottom}", code = E0391)] pub(crate) struct Cycle { #[primary_span] pub span: Span, @@ -51,28 +53,34 @@ pub(crate) struct Cycle { pub alias: Option, #[subdiagnostic] pub cycle_usage: Option, - #[note] + #[note( + "see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information" + )] pub note_span: (), } #[derive(Diagnostic)] -#[diag(query_system_reentrant)] +#[diag("internal compiler error: reentrant incremental verify failure, suppressing message")] pub(crate) struct Reentrant; #[derive(Diagnostic)] -#[diag(query_system_increment_compilation)] -#[note(query_system_increment_compilation_note1)] -#[note(query_system_increment_compilation_note2)] -#[note(query_system_increment_compilation_note3)] -#[note(query_system_increment_compilation_note4)] +#[diag("internal compiler error: encountered incremental compilation error with {$dep_node}")] +#[note("please follow the instructions below to create a bug report with the provided information")] +#[note("for incremental compilation bugs, having a reproduction is vital")] +#[note( + "an ideal reproduction consists of the code before and some patch that then triggers the bug when applied and compiled again" +)] +#[note("as a workaround, you can run {$run_cmd} to allow your project to compile")] pub(crate) struct IncrementCompilation { pub run_cmd: String, pub dep_node: String, } #[derive(Diagnostic)] -#[help] -#[diag(query_system_query_overflow)] +#[help( + "consider increasing the recursion limit by adding a `#![recursion_limit = \"{$suggested_limit}\"]` attribute to your crate (`{$crate_name}`)" +)] +#[diag("queries overflow the depth limit!")] pub struct QueryOverflow { #[primary_span] pub span: Span, @@ -83,7 +91,7 @@ pub struct QueryOverflow { } #[derive(Subdiagnostic)] -#[note(query_system_overflow_note)] +#[note("query depth increased by {$depth} when {$desc}")] pub struct QueryOverflowNote { pub desc: String, pub depth: usize, diff --git a/compiler/rustc_query_system/src/lib.rs b/compiler/rustc_query_system/src/lib.rs index cdfe3454061c..d1907a8c582d 100644 --- a/compiler/rustc_query_system/src/lib.rs +++ b/compiler/rustc_query_system/src/lib.rs @@ -14,5 +14,3 @@ mod values; pub use error::{QueryOverflow, QueryOverflowNote}; pub use values::Value; - -rustc_fluent_macro::fluent_messages! { "../messages.ftl" } diff --git a/compiler/rustc_query_system/src/query/caches.rs b/compiler/rustc_query_system/src/query/caches.rs index 30b5d7e59549..67ad767d4d31 100644 --- a/compiler/rustc_query_system/src/query/caches.rs +++ b/compiler/rustc_query_system/src/query/caches.rs @@ -30,6 +30,8 @@ pub trait QueryCache: Sized { fn complete(&self, key: Self::Key, value: Self::Value, index: DepNodeIndex); fn iter(&self, f: &mut dyn FnMut(&Self::Key, &Self::Value, DepNodeIndex)); + + fn len(&self) -> usize; } /// In-memory cache for queries whose keys aren't suitable for any of the @@ -71,6 +73,10 @@ where } } } + + fn len(&self) -> usize { + self.cache.len() + } } /// In-memory cache for queries whose key type only has one value (e.g. `()`). @@ -107,6 +113,10 @@ where f(&(), &value.0, value.1) } } + + fn len(&self) -> usize { + self.cache.get().is_some().into() + } } /// In-memory cache for queries whose key is a [`DefId`]. @@ -157,6 +167,10 @@ where }); self.foreign.iter(f); } + + fn len(&self) -> usize { + self.local.len() + self.foreign.len() + } } impl QueryCache for VecCache @@ -180,4 +194,8 @@ where fn iter(&self, f: &mut dyn FnMut(&Self::Key, &Self::Value, DepNodeIndex)) { self.iter(f) } + + fn len(&self) -> usize { + self.len() + } } diff --git a/compiler/rustc_query_system/src/query/dispatcher.rs b/compiler/rustc_query_system/src/query/dispatcher.rs index ac6c38dd7db5..bcd3d0322d07 100644 --- a/compiler/rustc_query_system/src/query/dispatcher.rs +++ b/compiler/rustc_query_system/src/query/dispatcher.rs @@ -25,7 +25,7 @@ type DepContextOf<'tcx, This: QueryDispatcher<'tcx>> = /// Those types are not visible from this `rustc_query_system` crate. /// /// "Dispatcher" should be understood as a near-synonym of "vtable". -pub trait QueryDispatcher<'tcx>: Copy { +pub trait QueryDispatcher<'tcx>: Copy + 'tcx { fn name(self) -> &'static str; /// Query context used by this dispatcher, i.e. `rustc_query_impl::QueryCtxt`. @@ -41,10 +41,10 @@ pub trait QueryDispatcher<'tcx>: Copy { fn format_value(self) -> fn(&Self::Value) -> String; // Don't use this method to access query results, instead use the methods on TyCtxt - fn query_state<'a>(self, tcx: Self::Qcx) -> &'a QueryState<'tcx, Self::Key>; + fn query_state(self, tcx: Self::Qcx) -> &'tcx QueryState<'tcx, Self::Key>; // Don't use this method to access query results, instead use the methods on TyCtxt - fn query_cache<'a>(self, tcx: Self::Qcx) -> &'a Self::Cache; + fn query_cache(self, tcx: Self::Qcx) -> &'tcx Self::Cache; fn will_cache_on_disk_for_key(self, tcx: DepContextOf<'tcx, Self>, key: &Self::Key) -> bool; diff --git a/compiler/rustc_query_system/src/query/job.rs b/compiler/rustc_query_system/src/query/job.rs index 177bcd63cbc6..50cb58f0b4d5 100644 --- a/compiler/rustc_query_system/src/query/job.rs +++ b/compiler/rustc_query_system/src/query/job.rs @@ -23,18 +23,17 @@ use crate::query::{QueryContext, QueryStackFrame}; pub struct QueryInfo { /// The span corresponding to the reason for which this query was required. pub span: Span, - pub query: QueryStackFrame, + pub frame: QueryStackFrame, } impl<'tcx> QueryInfo> { - pub(crate) fn lift>( - &self, - qcx: Qcx, - ) -> QueryInfo { - QueryInfo { span: self.span, query: self.query.lift(qcx) } + pub(crate) fn lift(&self) -> QueryInfo { + QueryInfo { span: self.span, frame: self.frame.lift() } } } +/// Map from query job IDs to job information collected by +/// [`QueryContext::collect_active_jobs_from_all_queries`]. pub type QueryMap<'tcx> = FxHashMap>; /// A value uniquely identifying an active query job. @@ -42,8 +41,8 @@ pub type QueryMap<'tcx> = FxHashMap>; pub struct QueryJobId(pub NonZero); impl QueryJobId { - fn query<'a, 'tcx>(self, map: &'a QueryMap<'tcx>) -> QueryStackFrame> { - map.get(&self).unwrap().query.clone() + fn frame<'a, 'tcx>(self, map: &'a QueryMap<'tcx>) -> QueryStackFrame> { + map.get(&self).unwrap().frame.clone() } fn span<'a, 'tcx>(self, map: &'a QueryMap<'tcx>) -> Span { @@ -61,7 +60,7 @@ impl QueryJobId { #[derive(Clone, Debug)] pub struct QueryJobInfo<'tcx> { - pub query: QueryStackFrame>, + pub frame: QueryStackFrame>, pub job: QueryJob<'tcx>, } @@ -125,7 +124,7 @@ impl QueryJobId { while let Some(job) = current_job { let info = query_map.get(&job).unwrap(); - cycle.push(QueryInfo { span: info.job.span, query: info.query.clone() }); + cycle.push(QueryInfo { span: info.job.span, frame: info.frame.clone() }); if job == *self { cycle.reverse(); @@ -140,7 +139,7 @@ impl QueryJobId { .job .parent .as_ref() - .map(|parent| (info.job.span, parent.query(&query_map))); + .map(|parent| (info.job.span, parent.frame(&query_map))); return CycleError { usage, cycle }; } @@ -158,13 +157,13 @@ impl QueryJobId { ) -> (QueryJobInfo<'tcx>, usize) { let mut depth = 1; let info = query_map.get(&self).unwrap(); - let dep_kind = info.query.dep_kind; + let dep_kind = info.frame.dep_kind; let mut current_id = info.job.parent; let mut last_layout = (info.clone(), depth); while let Some(id) = current_id { let info = query_map.get(&id).unwrap(); - if info.query.dep_kind == dep_kind { + if info.frame.dep_kind == dep_kind { depth += 1; last_layout = (info.clone(), depth); } @@ -389,7 +388,7 @@ where .iter() .min_by_key(|v| { let (span, query) = f(v); - let hash = query.query(query_map).hash; + let hash = query.frame(query_map).hash; // Prefer entry points which have valid spans for nicer error messages // We add an integer to the tuple ensuring that entry points // with valid spans are picked first @@ -473,14 +472,14 @@ fn remove_cycle<'tcx>( stack.rotate_left(pos); } - let usage = usage.as_ref().map(|(span, query)| (*span, query.query(query_map))); + let usage = usage.as_ref().map(|(span, query)| (*span, query.frame(query_map))); // Create the cycle error let error = CycleError { usage, cycle: stack .iter() - .map(|&(s, ref q)| QueryInfo { span: s, query: q.query(query_map) }) + .map(|&(s, ref q)| QueryInfo { span: s, frame: q.frame(query_map) }) .collect(), }; @@ -559,7 +558,7 @@ pub fn report_cycle<'a>( ) -> Diag<'a> { assert!(!stack.is_empty()); - let span = stack[0].query.info.default_span(stack[1 % stack.len()].span); + let span = stack[0].frame.info.default_span(stack[1 % stack.len()].span); let mut cycle_stack = Vec::new(); @@ -567,9 +566,9 @@ pub fn report_cycle<'a>( let stack_count = if stack.len() == 1 { StackCount::Single } else { StackCount::Multiple }; for i in 1..stack.len() { - let query = &stack[i].query; - let span = query.info.default_span(stack[(i + 1) % stack.len()].span); - cycle_stack.push(CycleStack { span, desc: query.info.description.to_owned() }); + let frame = &stack[i].frame; + let span = frame.info.default_span(stack[(i + 1) % stack.len()].span); + cycle_stack.push(CycleStack { span, desc: frame.info.description.to_owned() }); } let mut cycle_usage = None; @@ -581,9 +580,9 @@ pub fn report_cycle<'a>( } let alias = - if stack.iter().all(|entry| matches!(entry.query.info.def_kind, Some(DefKind::TyAlias))) { + if stack.iter().all(|entry| matches!(entry.frame.info.def_kind, Some(DefKind::TyAlias))) { Some(crate::error::Alias::Ty) - } else if stack.iter().all(|entry| entry.query.info.def_kind == Some(DefKind::TraitAlias)) { + } else if stack.iter().all(|entry| entry.frame.info.def_kind == Some(DefKind::TraitAlias)) { Some(crate::error::Alias::Trait) } else { None @@ -592,7 +591,7 @@ pub fn report_cycle<'a>( let cycle_diag = crate::error::Cycle { span, cycle_stack, - stack_bottom: stack[0].query.info.description.to_owned(), + stack_bottom: stack[0].frame.info.description.to_owned(), alias, cycle_usage, stack_count, @@ -616,7 +615,7 @@ pub fn print_query_stack<'tcx, Qcx: QueryContext<'tcx>>( let mut count_total = 0; // Make use of a partial query map if we fail to take locks collecting active queries. - let query_map = match qcx.collect_active_jobs(false) { + let query_map = match qcx.collect_active_jobs_from_all_queries(false) { Ok(query_map) => query_map, Err(query_map) => query_map, }; @@ -628,12 +627,12 @@ pub fn print_query_stack<'tcx, Qcx: QueryContext<'tcx>>( let Some(query_info) = query_map.get(&query) else { break; }; - let query_extra = qcx.lift_query_info(&query_info.query.info); + let query_extra = query_info.frame.info.extract(); if Some(count_printed) < limit_frames || limit_frames.is_none() { // Only print to stderr as many stack frames as `num_frames` when present. dcx.struct_failure_note(format!( "#{} [{:?}] {}", - count_printed, query_info.query.dep_kind, query_extra.description + count_printed, query_info.frame.dep_kind, query_extra.description )) .with_span(query_info.job.span) .emit(); @@ -645,7 +644,7 @@ pub fn print_query_stack<'tcx, Qcx: QueryContext<'tcx>>( file, "#{} [{}] {}", count_total, - qcx.dep_context().dep_kind_vtable(query_info.query.dep_kind).name, + qcx.dep_context().dep_kind_vtable(query_info.frame.dep_kind).name, query_extra.description ); } diff --git a/compiler/rustc_query_system/src/query/mod.rs b/compiler/rustc_query_system/src/query/mod.rs index 63202429679d..dbf7395bd61a 100644 --- a/compiler/rustc_query_system/src/query/mod.rs +++ b/compiler/rustc_query_system/src/query/mod.rs @@ -70,9 +70,9 @@ impl<'tcx> QueryStackFrame> { Self { info, def_id, dep_kind, hash, def_id_for_ty_in_cycle } } - fn lift>(&self, qcx: Qcx) -> QueryStackFrame { + fn lift(&self) -> QueryStackFrame { QueryStackFrame { - info: qcx.lift_query_info(&self.info), + info: self.info.extract(), dep_kind: self.dep_kind, hash: self.hash, def_id: self.def_id, @@ -166,9 +166,10 @@ pub trait QueryContext<'tcx>: HasDepContext { /// Get the query information from the TLS context. fn current_query_job(self) -> Option; - fn collect_active_jobs(self, require_complete: bool) -> Result, QueryMap<'tcx>>; - - fn lift_query_info(self, info: &QueryStackDeferred<'tcx>) -> QueryStackFrameExtra; + fn collect_active_jobs_from_all_queries( + self, + require_complete: bool, + ) -> Result, QueryMap<'tcx>>; /// Load a side effect associated to the node in the previous session. fn load_side_effect( @@ -183,6 +184,4 @@ pub trait QueryContext<'tcx>: HasDepContext { /// new query job while it executes. fn start_query(self, token: QueryJobId, depth_limit: bool, compute: impl FnOnce() -> R) -> R; - - fn depth_limit_error(self, job: QueryJobId); } diff --git a/compiler/rustc_query_system/src/query/plumbing.rs b/compiler/rustc_query_system/src/query/plumbing.rs index 98bbd3ebc4a0..fcd2e80a4fdc 100644 --- a/compiler/rustc_query_system/src/query/plumbing.rs +++ b/compiler/rustc_query_system/src/query/plumbing.rs @@ -11,7 +11,6 @@ use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::hash_table::{self, Entry, HashTable}; use rustc_data_structures::sharded::{self, Sharded}; use rustc_data_structures::stack::ensure_sufficient_stack; -use rustc_data_structures::sync::LockGuard; use rustc_data_structures::{outline, sync}; use rustc_errors::{Diag, FatalError, StashKey}; use rustc_span::{DUMMY_SP, Span}; @@ -79,40 +78,46 @@ where self.active.lock_shards().all(|shard| shard.is_empty()) } - pub fn collect_active_jobs( + /// Internal plumbing for collecting the set of active jobs for this query. + /// + /// Should only be called from `gather_active_jobs`. + pub fn gather_active_jobs_inner( &self, qcx: Qcx, - make_query: fn(Qcx, K) -> QueryStackFrame>, + make_frame: fn(Qcx, K) -> QueryStackFrame>, jobs: &mut QueryMap<'tcx>, require_complete: bool, ) -> Option<()> { let mut active = Vec::new(); - let mut collect = |iter: LockGuard<'_, HashTable<(K, ActiveKeyStatus<'tcx>)>>| { - for (k, v) in iter.iter() { + // Helper to gather active jobs from a single shard. + let mut gather_shard_jobs = |shard: &HashTable<(K, ActiveKeyStatus<'tcx>)>| { + for (k, v) in shard.iter() { if let ActiveKeyStatus::Started(ref job) = *v { active.push((*k, job.clone())); } } }; + // Lock shards and gather jobs from each shard. if require_complete { for shard in self.active.lock_shards() { - collect(shard); + gather_shard_jobs(&shard); } } else { // We use try_lock_shards here since we are called from the // deadlock handler, and this shouldn't be locked. for shard in self.active.try_lock_shards() { - collect(shard?); + let shard = shard?; + gather_shard_jobs(&shard); } } - // Call `make_query` while we're not holding a `self.active` lock as `make_query` may call + // Call `make_frame` while we're not holding a `self.active` lock as `make_frame` may call // queries leading to a deadlock. for (key, job) in active { - let query = make_query(qcx, key); - jobs.insert(job.id, QueryJobInfo { query, job }); + let frame = make_frame(qcx, key); + jobs.insert(job.id, QueryJobInfo { frame, job }); } Some(()) @@ -127,11 +132,11 @@ impl<'tcx, K> Default for QueryState<'tcx, K> { /// A type representing the responsibility to execute the job in the `job` field. /// This will poison the relevant query if dropped. -struct JobOwner<'a, 'tcx, K> +struct JobOwner<'tcx, K> where K: Eq + Hash + Copy, { - state: &'a QueryState<'tcx, K>, + state: &'tcx QueryState<'tcx, K>, key: K, } @@ -170,7 +175,7 @@ where } CycleErrorHandling::Stash => { let guar = if let Some(root) = cycle_error.cycle.first() - && let Some(span) = root.query.info.span + && let Some(span) = root.frame.info.span { error.stash(span, StashKey::Cycle).unwrap() } else { @@ -181,7 +186,7 @@ where } } -impl<'a, 'tcx, K> JobOwner<'a, 'tcx, K> +impl<'tcx, K> JobOwner<'tcx, K> where K: Eq + Hash + Copy, { @@ -218,7 +223,7 @@ where } } -impl<'a, 'tcx, K> Drop for JobOwner<'a, 'tcx, K> +impl<'tcx, K> Drop for JobOwner<'tcx, K> where K: Eq + Hash + Copy, { @@ -253,10 +258,10 @@ pub struct CycleError { } impl<'tcx> CycleError> { - fn lift>(&self, qcx: Qcx) -> CycleError { + fn lift(&self) -> CycleError { CycleError { - usage: self.usage.as_ref().map(|(span, frame)| (*span, frame.lift(qcx))), - cycle: self.cycle.iter().map(|info| info.lift(qcx)).collect(), + usage: self.usage.as_ref().map(|(span, frame)| (*span, frame.lift())), + cycle: self.cycle.iter().map(|info| info.lift()).collect(), } } } @@ -294,10 +299,13 @@ where { // Ensure there was no errors collecting all active jobs. // We need the complete map to ensure we find a cycle to break. - let query_map = qcx.collect_active_jobs(false).ok().expect("failed to collect active queries"); + let query_map = qcx + .collect_active_jobs_from_all_queries(false) + .ok() + .expect("failed to collect active queries"); let error = try_execute.find_cycle_in_stack(query_map, &qcx.current_query_job(), span); - (mk_cycle(query, qcx, error.lift(qcx)), None) + (mk_cycle(query, qcx, error.lift()), None) } #[inline(always)] @@ -345,7 +353,7 @@ where (v, Some(index)) } - Err(cycle) => (mk_cycle(query, qcx, cycle.lift(qcx)), None), + Err(cycle) => (mk_cycle(query, qcx, cycle.lift()), None), } } @@ -422,7 +430,7 @@ where fn execute_job<'tcx, Q, const INCR: bool>( query: Q, qcx: Q::Qcx, - state: &QueryState<'tcx, Q::Key>, + state: &'tcx QueryState<'tcx, Q::Key>, key: Q::Key, key_hash: u64, id: QueryJobId, diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 6f2d3e79d10a..f09b98715799 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -24,15 +24,15 @@ use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::builtin::{ - ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, AMBIGUOUS_GLOB_IMPORTS, AMBIGUOUS_PANIC_IMPORTS, - MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, + ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, AMBIGUOUS_GLOB_IMPORTS, AMBIGUOUS_IMPORT_VISIBILITIES, + AMBIGUOUS_PANIC_IMPORTS, MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, }; use rustc_session::utils::was_invoked_from_cargo; use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::edition::Edition; use rustc_span::hygiene::MacroKind; use rustc_span::source_map::{SourceMap, Spanned}; -use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol, SyntaxContext, kw, sym}; +use rustc_span::{BytePos, Ident, Span, Symbol, SyntaxContext, kw, sym}; use thin_vec::{ThinVec, thin_vec}; use tracing::{debug, instrument}; @@ -41,6 +41,7 @@ use crate::errors::{ ExplicitUnsafeTraits, MacroDefinedLater, MacroRulesNot, MacroSuggMovePosition, MaybeMissingMacroRulesName, }; +use crate::hygiene::Macros20NormalizedSyntaxContext; use crate::imports::{Import, ImportKind}; use crate::late::{DiagMetadata, PatternSource, Rib}; use crate::{ @@ -144,6 +145,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; let lint = match ambiguity_warning { + _ if ambiguity_error.ambig_vis.is_some() => AMBIGUOUS_IMPORT_VISIBILITIES, AmbiguityWarning::GlobImport => AMBIGUOUS_GLOB_IMPORTS, AmbiguityWarning::PanicImport => AMBIGUOUS_PANIC_IMPORTS, }; @@ -1163,11 +1165,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { suggestions: &mut Vec, scope_set: ScopeSet<'ra>, ps: &ParentScope<'ra>, - ctxt: SyntaxContext, + sp: Span, filter_fn: &impl Fn(Res) -> bool, ) { - let ctxt = DUMMY_SP.with_ctxt(ctxt); - self.cm().visit_scopes(scope_set, ps, ctxt, None, |this, scope, use_prelude, _| { + let ctxt = Macros20NormalizedSyntaxContext::new(sp.ctxt()); + self.cm().visit_scopes(scope_set, ps, ctxt, sp, None, |this, scope, use_prelude, _| { match scope { Scope::DeriveHelpers(expn_id) => { let res = Res::NonMacroAttr(NonMacroAttrKind::DeriveHelper); @@ -1269,8 +1271,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { filter_fn: &impl Fn(Res) -> bool, ) -> Option { let mut suggestions = Vec::new(); - let ctxt = ident.span.ctxt(); - self.add_scope_set_candidates(&mut suggestions, scope_set, parent_scope, ctxt, filter_fn); + self.add_scope_set_candidates( + &mut suggestions, + scope_set, + parent_scope, + ident.span, + filter_fn, + ); // Make sure error reporting is deterministic. suggestions.sort_by(|a, b| a.candidate.as_str().cmp(b.candidate.as_str())); @@ -1989,7 +1996,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } fn ambiguity_diagnostic(&self, ambiguity_error: &AmbiguityError<'ra>) -> errors::Ambiguity { - let AmbiguityError { kind, ident, b1, b2, scope1, scope2, .. } = *ambiguity_error; + let AmbiguityError { kind, ambig_vis, ident, b1, b2, scope1, scope2, .. } = + *ambiguity_error; let extern_prelude_ambiguity = || { // Note: b1 may come from a module scope, as an extern crate item in module. matches!(scope2, Scope::ExternPreludeFlags) @@ -2068,9 +2076,18 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { None }; + let ambig_vis = ambig_vis.map(|(vis1, vis2)| { + format!( + "{} or {}", + vis1.to_string(CRATE_DEF_ID, self.tcx), + vis2.to_string(CRATE_DEF_ID, self.tcx) + ) + }); + errors::Ambiguity { ident, help, + ambig_vis, kind: kind.descr(), b1_note, b1_help_msgs, diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 205f2c6aa539..3e5446403052 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1463,6 +1463,7 @@ pub(crate) struct UnknownDiagnosticAttributeTypoSugg { // FIXME: Make this properly translatable. pub(crate) struct Ambiguity { pub ident: Ident, + pub ambig_vis: Option, pub kind: &'static str, pub help: Option<&'static [&'static str]>, pub b1_note: Spanned, @@ -1473,8 +1474,12 @@ pub(crate) struct Ambiguity { impl Ambiguity { fn decorate<'a>(self, diag: &mut Diag<'a, impl EmissionGuarantee>) { - diag.primary_message(format!("`{}` is ambiguous", self.ident)); - diag.span_label(self.ident.span, "ambiguous name"); + if let Some(ambig_vis) = self.ambig_vis { + diag.primary_message(format!("ambiguous import visibility: {ambig_vis}")); + } else { + diag.primary_message(format!("`{}` is ambiguous", self.ident)); + diag.span_label(self.ident.span, "ambiguous name"); + } diag.note(format!("ambiguous because of {}", self.kind)); diag.span_note(self.b1_note.span, self.b1_note.node); if let Some(help) = self.help { diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 4fbde60d8679..d4d373d82064 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -5,6 +5,7 @@ use Namespace::*; use rustc_ast::{self as ast, NodeId}; use rustc_errors::ErrorGuaranteed; use rustc_hir::def::{DefKind, MacroKinds, Namespace, NonMacroAttrKind, PartialRes, PerNS}; +use rustc_middle::ty::Visibility; use rustc_middle::{bug, span_bug}; use rustc_session::lint::builtin::PROC_MACRO_DERIVE_RESOLUTION_FALLBACK; use rustc_session::parse::feature_err; @@ -54,9 +55,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { mut self: CmResolver<'r, 'ra, 'tcx>, scope_set: ScopeSet<'ra>, parent_scope: &ParentScope<'ra>, - // Location of the span is not significant, but pass a `Span` instead of `SyntaxContext` - // to avoid extracting and re-packaging the syntax context unnecessarily. - orig_ctxt: Span, + mut ctxt: Macros20NormalizedSyntaxContext, + orig_ident_span: Span, derive_fallback_lint_id: Option, mut visitor: impl FnMut( CmResolver<'_, 'ra, 'tcx>, @@ -128,7 +128,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { TypeNS | ValueNS => Scope::ModuleNonGlobs(module, None), MacroNS => Scope::DeriveHelpers(parent_scope.expansion), }; - let mut ctxt = Macros20NormalizedSyntaxContext::new(orig_ctxt.ctxt()); let mut use_prelude = !module.no_implicit_prelude; loop { @@ -153,7 +152,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { true } Scope::ModuleNonGlobs(..) | Scope::ModuleGlobs(..) => true, - Scope::MacroUsePrelude => use_prelude || orig_ctxt.edition().is_rust_2015(), + Scope::MacroUsePrelude => use_prelude || orig_ident_span.is_rust_2015(), Scope::BuiltinAttrs => true, Scope::ExternPreludeItems | Scope::ExternPreludeFlags => { use_prelude || module_and_extern_prelude || extern_prelude @@ -396,9 +395,30 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { finalize: Option, ignore_decl: Option>, ignore_import: Option>, + ) -> Result, Determinacy> { + self.resolve_ident_in_scope_set_inner( + IdentKey::new(orig_ident), + orig_ident.span, + scope_set, + parent_scope, + finalize, + ignore_decl, + ignore_import, + ) + } + + fn resolve_ident_in_scope_set_inner<'r>( + self: CmResolver<'r, 'ra, 'tcx>, + ident: IdentKey, + orig_ident_span: Span, + scope_set: ScopeSet<'ra>, + parent_scope: &ParentScope<'ra>, + finalize: Option, + ignore_decl: Option>, + ignore_import: Option>, ) -> Result, Determinacy> { // Make sure `self`, `super` etc produce an error when passed to here. - if !matches!(scope_set, ScopeSet::Module(..)) && orig_ident.is_path_segment_keyword() { + if !matches!(scope_set, ScopeSet::Module(..)) && ident.name.is_path_segment_keyword() { return Err(Determinacy::Determined); } @@ -432,13 +452,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let break_result = self.visit_scopes( scope_set, parent_scope, - orig_ident.span, + ident.ctxt, + orig_ident_span, derive_fallback_lint_id, |mut this, scope, use_prelude, ctxt| { - let ident = IdentKey { name: orig_ident.name, ctxt }; + let ident = IdentKey { name: ident.name, ctxt }; let res = match this.reborrow().resolve_ident_in_scope( ident, - orig_ident.span, + orig_ident_span, ns, scope, use_prelude, @@ -465,20 +486,25 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // We do not need to report them if we are either in speculative resolution, // or in late resolution when everything is already imported and expanded // and no ambiguities exist. - if matches!(finalize, None | Some(Finalize { stage: Stage::Late, .. })) { - return ControlFlow::Break(Ok(decl)); - } + let import_vis = match finalize { + None | Some(Finalize { stage: Stage::Late, .. }) => { + return ControlFlow::Break(Ok(decl)); + } + Some(Finalize { import_vis, .. }) => import_vis, + }; if let Some(&(innermost_decl, _)) = innermost_results.first() { // Found another solution, if the first one was "weak", report an error. if this.get_mut().maybe_push_ambiguity( - orig_ident, + ident, + orig_ident_span, ns, scope_set, parent_scope, decl, scope, &innermost_results, + import_vis, ) { // No need to search for more potential ambiguities, one is enough. return ControlFlow::Break(Ok(innermost_decl)); @@ -695,8 +721,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Scope::StdLibPrelude => { let mut result = Err(Determinacy::Determined); if let Some(prelude) = self.prelude - && let Ok(decl) = self.reborrow().resolve_ident_in_scope_set( - ident.orig(orig_ident_span.with_ctxt(*ident.ctxt)), + && let Ok(decl) = self.reborrow().resolve_ident_in_scope_set_inner( + ident, + orig_ident_span, ScopeSet::Module(ns, prelude), parent_scope, None, @@ -749,19 +776,30 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { fn maybe_push_ambiguity( &mut self, - orig_ident: Ident, + ident: IdentKey, + orig_ident_span: Span, ns: Namespace, scope_set: ScopeSet<'ra>, parent_scope: &ParentScope<'ra>, decl: Decl<'ra>, scope: Scope<'ra>, innermost_results: &[(Decl<'ra>, Scope<'ra>)], + import_vis: Option, ) -> bool { let (innermost_decl, innermost_scope) = innermost_results[0]; let (res, innermost_res) = (decl.res(), innermost_decl.res()); - if res == innermost_res { + let ambig_vis = if res != innermost_res { + None + } else if let Some(import_vis) = import_vis + && let min = + (|d: Decl<'_>| d.vis().min(import_vis.to_def_id(), self.tcx).expect_local()) + && let (min1, min2) = (min(decl), min(innermost_decl)) + && min1 != min2 + { + Some((min1, min2)) + } else { return false; - } + }; // FIXME: Use `scope` instead of `res` to detect built-in attrs and derive helpers, // it will exclude imports, make slightly more code legal, and will require lang approval. @@ -775,7 +813,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } else if innermost_res == derive_helper_compat { Some(AmbiguityKind::DeriveHelper) } else if res == derive_helper_compat && innermost_res != derive_helper { - span_bug!(orig_ident.span, "impossible inner resolution kind") + span_bug!(orig_ident_span, "impossible inner resolution kind") } else if matches!(innermost_scope, Scope::MacroRules(_)) && matches!(scope, Scope::ModuleNonGlobs(..) | Scope::ModuleGlobs(..)) && !self.disambiguate_macro_rules_vs_modularized(innermost_decl, decl) @@ -790,7 +828,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // we visit all macro_rules scopes (e.g. textual scope macros) // before we visit any modules (e.g. path-based scope macros) span_bug!( - orig_ident.span, + orig_ident_span, "ambiguous scoped macro resolutions with path-based \ scope resolution as first candidate" ) @@ -839,8 +877,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } else { // Turn ambiguity errors for core vs std panic into warnings. // FIXME: Remove with lang team approval. - let is_issue_147319_hack = orig_ident.span.edition() <= Edition::Edition2024 - && matches!(orig_ident.name, sym::panic) + let is_issue_147319_hack = orig_ident_span.edition() <= Edition::Edition2024 + && matches!(ident.name, sym::panic) && matches!(scope, Scope::StdLibPrelude) && matches!(innermost_scope, Scope::ModuleGlobs(_, _)) && ((self.is_specific_builtin_macro(res, sym::std_panic) @@ -848,11 +886,18 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { || (self.is_specific_builtin_macro(res, sym::core_panic) && self.is_specific_builtin_macro(innermost_res, sym::std_panic))); - let warning = is_issue_147319_hack.then_some(AmbiguityWarning::PanicImport); + let warning = if ambig_vis.is_some() { + Some(AmbiguityWarning::GlobImport) + } else if is_issue_147319_hack { + Some(AmbiguityWarning::PanicImport) + } else { + None + }; self.ambiguity_errors.push(AmbiguityError { kind, - ident: orig_ident, + ambig_vis, + ident: ident.orig(orig_ident_span), b1: innermost_decl, b2: decl, scope1: innermost_scope, @@ -880,46 +925,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { #[instrument(level = "debug", skip(self))] pub(crate) fn resolve_ident_in_module<'r>( - self: CmResolver<'r, 'ra, 'tcx>, - module: ModuleOrUniformRoot<'ra>, - mut ident: Ident, - ns: Namespace, - parent_scope: &ParentScope<'ra>, - finalize: Option, - ignore_decl: Option>, - ignore_import: Option>, - ) -> Result, Determinacy> { - let tmp_parent_scope; - let mut adjusted_parent_scope = parent_scope; - match module { - ModuleOrUniformRoot::Module(m) => { - if let Some(def) = ident.span.normalize_to_macros_2_0_and_adjust(m.expansion) { - tmp_parent_scope = - ParentScope { module: self.expn_def_scope(def), ..*parent_scope }; - adjusted_parent_scope = &tmp_parent_scope; - } - } - ModuleOrUniformRoot::ExternPrelude => { - ident.span.normalize_to_macros_2_0_and_adjust(ExpnId::root()); - } - ModuleOrUniformRoot::ModuleAndExternPrelude(..) | ModuleOrUniformRoot::CurrentScope => { - // No adjustments - } - } - self.resolve_ident_in_virt_module_unadjusted( - module, - ident, - ns, - adjusted_parent_scope, - finalize, - ignore_decl, - ignore_import, - ) - } - - /// Attempts to resolve `ident` in namespace `ns` of `module`. - #[instrument(level = "debug", skip(self))] - fn resolve_ident_in_virt_module_unadjusted<'r>( self: CmResolver<'r, 'ra, 'tcx>, module: ModuleOrUniformRoot<'ra>, ident: Ident, @@ -930,14 +935,22 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ignore_import: Option>, ) -> Result, Determinacy> { match module { - ModuleOrUniformRoot::Module(module) => self.resolve_ident_in_scope_set( - ident, - ScopeSet::Module(ns, module), - parent_scope, - finalize, - ignore_decl, - ignore_import, - ), + ModuleOrUniformRoot::Module(module) => { + let (ident_key, def) = IdentKey::new_adjusted(ident, module.expansion); + let adjusted_parent_scope = match def { + Some(def) => ParentScope { module: self.expn_def_scope(def), ..*parent_scope }, + None => *parent_scope, + }; + self.resolve_ident_in_scope_set_inner( + ident_key, + ident.span, + ScopeSet::Module(ns, module), + &adjusted_parent_scope, + finalize, + ignore_decl, + ignore_import, + ) + } ModuleOrUniformRoot::ModuleAndExternPrelude(module) => self.resolve_ident_in_scope_set( ident, ScopeSet::ModuleAndExternPrelude(ns, module), @@ -950,8 +963,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if ns != TypeNS { Err(Determined) } else { - self.resolve_ident_in_scope_set( - ident, + self.resolve_ident_in_scope_set_inner( + IdentKey::new_adjusted(ident, ExpnId::root()).0, + ident.span, ScopeSet::ExternPrelude, parent_scope, finalize, @@ -1145,8 +1159,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { None => return Err(ControlFlow::Continue(Undetermined)), }; let tmp_parent_scope; - let (mut adjusted_parent_scope, mut ctxt) = (parent_scope, *ident.ctxt); - match ctxt.glob_adjust(module.expansion, glob_import.span) { + let (mut adjusted_parent_scope, mut adjusted_ident) = (parent_scope, ident); + match adjusted_ident + .ctxt + .update_unchecked(|ctxt| ctxt.glob_adjust(module.expansion, glob_import.span)) + { Some(Some(def)) => { tmp_parent_scope = ParentScope { module: self.expn_def_scope(def), ..*parent_scope }; @@ -1155,8 +1172,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Some(None) => {} None => continue, }; - let result = self.reborrow().resolve_ident_in_scope_set( - ident.orig(orig_ident_span.with_ctxt(ctxt)), + let result = self.reborrow().resolve_ident_in_scope_set_inner( + adjusted_ident, + orig_ident_span, ScopeSet::Module(ns, module), adjusted_parent_scope, None, diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 057340028085..78ad139cff79 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -1189,7 +1189,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ident, ns, &import.parent_scope, - Some(Finalize { report_private: false, ..finalize }), + Some(Finalize { + report_private: false, + import_vis: Some(import.vis), + ..finalize + }), bindings[ns].get().decl(), Some(import), ); diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index a3ee17ec4a9a..8794c4ff8b02 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -2677,7 +2677,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { &mut names, ScopeSet::All(ns), parent_scope, - ctxt, + segment.ident.span.with_ctxt(ctxt), filter_fn, ); break; diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 5fe1be039a88..659b74b74df7 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -8,6 +8,7 @@ // tidy-alphabetical-start #![allow(internal_features)] +#![cfg_attr(bootstrap, feature(ptr_as_ref_unchecked))] #![feature(arbitrary_self_types)] #![feature(assert_matches)] #![feature(box_patterns)] @@ -18,7 +19,6 @@ #![feature(default_field_values)] #![feature(if_let_guard)] #![feature(iter_intersperse)] -#![feature(ptr_as_ref_unchecked)] #![feature(rustc_attrs)] #![feature(trim_prefix_suffix)] #![recursion_limit = "256"] @@ -575,6 +575,12 @@ impl IdentKey { IdentKey { name: ident.name, ctxt: Macros20NormalizedSyntaxContext::new(ident.span.ctxt()) } } + #[inline] + fn new_adjusted(ident: Ident, expn_id: ExpnId) -> (IdentKey, Option) { + let (ctxt, def) = Macros20NormalizedSyntaxContext::new_adjusted(ident.span.ctxt(), expn_id); + (IdentKey { name: ident.name, ctxt }, def) + } + #[inline] fn with_root_ctxt(name: Symbol) -> Self { let ctxt = Macros20NormalizedSyntaxContext::new_unchecked(SyntaxContext::root()); @@ -963,6 +969,7 @@ enum AmbiguityWarning { struct AmbiguityError<'ra> { kind: AmbiguityKind, + ambig_vis: Option<(Visibility, Visibility)>, ident: Ident, b1: Decl<'ra>, b2: Decl<'ra>, @@ -1923,7 +1930,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { &mut self, current_trait: Option>, parent_scope: &ParentScope<'ra>, - ctxt: Span, + sp: Span, assoc_item: Option<(Symbol, Namespace)>, ) -> Vec { let mut found_traits = Vec::new(); @@ -1940,7 +1947,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } let scope_set = ScopeSet::All(TypeNS); - self.cm().visit_scopes(scope_set, parent_scope, ctxt, None, |mut this, scope, _, _| { + let ctxt = Macros20NormalizedSyntaxContext::new(sp.ctxt()); + self.cm().visit_scopes(scope_set, parent_scope, ctxt, sp, None, |mut this, scope, _, _| { match scope { Scope::ModuleNonGlobs(module, _) => { this.get_mut().traits_in_module(module, assoc_item, &mut found_traits); @@ -2080,6 +2088,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if let Some(b2) = used_decl.ambiguity.get() { let ambiguity_error = AmbiguityError { kind: AmbiguityKind::GlobVsGlob, + ambig_vis: None, ident, b1: used_decl, b2, @@ -2549,6 +2558,8 @@ struct Finalize { used: Used = Used::Other, /// Finalizing early or late resolution. stage: Stage = Stage::Early, + /// Nominal visibility of the import item, in case we are resolving an import's final segment. + import_vis: Option = None, } impl Finalize { @@ -2723,7 +2734,7 @@ mod ref_mut { } mod hygiene { - use rustc_span::SyntaxContext; + use rustc_span::{ExpnId, SyntaxContext}; /// A newtype around `SyntaxContext` that can only keep contexts produced by /// [SyntaxContext::normalize_to_macros_2_0]. @@ -2736,6 +2747,15 @@ mod hygiene { Macros20NormalizedSyntaxContext(ctxt.normalize_to_macros_2_0()) } + #[inline] + pub(crate) fn new_adjusted( + mut ctxt: SyntaxContext, + expn_id: ExpnId, + ) -> (Macros20NormalizedSyntaxContext, Option) { + let def = ctxt.normalize_to_macros_2_0_and_adjust(expn_id); + (Macros20NormalizedSyntaxContext(ctxt), def) + } + #[inline] pub(crate) fn new_unchecked(ctxt: SyntaxContext) -> Macros20NormalizedSyntaxContext { debug_assert_eq!(ctxt, ctxt.normalize_to_macros_2_0()); diff --git a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs index 9cea681fcb57..971ac9348fc4 100644 --- a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs +++ b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs @@ -243,24 +243,24 @@ fn trait_object_ty<'tcx>(tcx: TyCtxt<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tc .flat_map(|super_poly_trait_ref| { tcx.associated_items(super_poly_trait_ref.def_id()) .in_definition_order() - .filter(|item| item.is_type()) + .filter(|item| item.is_type() || item.is_const()) .filter(|item| !tcx.generics_require_sized_self(item.def_id)) - .map(move |assoc_ty| { + .map(move |assoc_item| { super_poly_trait_ref.map_bound(|super_trait_ref| { - let alias_ty = - ty::AliasTy::new_from_args(tcx, assoc_ty.def_id, super_trait_ref.args); - let resolved = tcx.normalize_erasing_regions( - ty::TypingEnv::fully_monomorphized(), - alias_ty.to_ty(tcx), + let projection_term = ty::AliasTerm::new_from_args( + tcx, + assoc_item.def_id, + super_trait_ref.args, ); - debug!("Resolved {:?} -> {resolved}", alias_ty.to_ty(tcx)); + let term = tcx.normalize_erasing_regions( + ty::TypingEnv::fully_monomorphized(), + projection_term.to_term(tcx), + ); + debug!("Projection {:?} -> {term}", projection_term.to_term(tcx),); ty::ExistentialPredicate::Projection( ty::ExistentialProjection::erase_self_ty( tcx, - ty::ProjectionPredicate { - projection_term: alias_ty.into(), - term: resolved.into(), - }, + ty::ProjectionPredicate { projection_term, term }, ), ) }) diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 2e8037b3f9e4..05f4c8e268a9 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -804,7 +804,7 @@ impl SyntaxContext { /// Like `SyntaxContext::adjust`, but also normalizes `self` to macros 2.0. #[inline] - pub(crate) fn normalize_to_macros_2_0_and_adjust(&mut self, expn_id: ExpnId) -> Option { + pub fn normalize_to_macros_2_0_and_adjust(&mut self, expn_id: ExpnId) -> Option { HygieneData::with(|data| { *self = data.normalize_to_macros_2_0(*self); data.adjust(self, expn_id) diff --git a/compiler/rustc_target/src/spec/targets/hexagon_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/targets/hexagon_unknown_linux_musl.rs index 82811cda00ce..92c477f53f8f 100644 --- a/compiler/rustc_target/src/spec/targets/hexagon_unknown_linux_musl.rs +++ b/compiler/rustc_target/src/spec/targets/hexagon_unknown_linux_musl.rs @@ -1,4 +1,4 @@ -use crate::spec::{Arch, Cc, LinkerFlavor, Target, TargetMetadata, base}; +use crate::spec::{Arch, Cc, LinkerFlavor, Lld, Target, TargetMetadata, base}; pub(crate) fn target() -> Target { let mut base = base::linux_musl::opts(); @@ -8,8 +8,8 @@ pub(crate) fn target() -> Target { base.features = "-small-data,+hvx-length128b".into(); base.has_rpath = true; - base.linker = Some("rust-lld".into()); - base.linker_flavor = LinkerFlavor::Unix(Cc::Yes); + base.linker = Some("hexagon-unknown-linux-musl-clang".into()); + base.linker_flavor = LinkerFlavor::Gnu(Cc::Yes, Lld::No); base.c_enum_min_bits = Some(8); diff --git a/compiler/rustc_target/src/spec/targets/hexagon_unknown_none_elf.rs b/compiler/rustc_target/src/spec/targets/hexagon_unknown_none_elf.rs index 55ec3697a15e..3809057e255c 100644 --- a/compiler/rustc_target/src/spec/targets/hexagon_unknown_none_elf.rs +++ b/compiler/rustc_target/src/spec/targets/hexagon_unknown_none_elf.rs @@ -1,4 +1,6 @@ -use crate::spec::{Arch, PanicStrategy, Target, TargetMetadata, TargetOptions}; +use crate::spec::{ + Arch, Cc, LinkerFlavor, Lld, PanicStrategy, Target, TargetMetadata, TargetOptions, +}; pub(crate) fn target() -> Target { Target { @@ -28,6 +30,7 @@ pub(crate) fn target() -> Target { emit_debug_gdb_scripts: false, c_enum_min_bits: Some(8), linker: Some("rust-lld".into()), + linker_flavor: LinkerFlavor::Gnu(Cc::No, Lld::Yes), ..Default::default() }, } diff --git a/compiler/rustc_target/src/spec/targets/hexagon_unknown_qurt.rs b/compiler/rustc_target/src/spec/targets/hexagon_unknown_qurt.rs index 746e0cb11dcb..dcc92b4bdcc4 100644 --- a/compiler/rustc_target/src/spec/targets/hexagon_unknown_qurt.rs +++ b/compiler/rustc_target/src/spec/targets/hexagon_unknown_qurt.rs @@ -24,8 +24,8 @@ pub(crate) fn target() -> Target { os: Os::Qurt, vendor: "unknown".into(), cpu: "hexagonv69".into(), - linker: Some("rust-lld".into()), - linker_flavor: LinkerFlavor::Gnu(Cc::No, Lld::Yes), + linker: Some("hexagon-clang".into()), + linker_flavor: LinkerFlavor::Gnu(Cc::Yes, Lld::No), exe_suffix: ".elf".into(), dynamic_linking: true, executables: true, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index d888c767203a..136f084fd2e9 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -1629,6 +1629,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }; if let Some(lhs) = lhs.to_alias_term() + && let ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst = lhs.kind(self.tcx) && let Some((better_type_err, expected_term)) = derive_better_type_error(lhs, rhs) { @@ -1637,6 +1638,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { better_type_err, ) } else if let Some(rhs) = rhs.to_alias_term() + && let ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst = rhs.kind(self.tcx) && let Some((better_type_err, expected_term)) = derive_better_type_error(rhs, lhs) { diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index be70612653ce..9371b55b6363 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -8,6 +8,7 @@ use std::ops::ControlFlow; use rustc_errors::FatalError; use rustc_hir::attrs::AttributeKind; +use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, LangItem, find_attr}; use rustc_middle::query::Providers; @@ -833,8 +834,10 @@ impl<'tcx> TypeVisitor> for IllegalSelfTypeVisitor<'tcx> { match ct.kind() { ty::ConstKind::Unevaluated(proj) if self.tcx.features().min_generic_const_args() => { match self.allow_self_projections { - AllowSelfProjections::Yes => { - let trait_def_id = self.tcx.parent(proj.def); + AllowSelfProjections::Yes + if let trait_def_id = self.tcx.parent(proj.def) + && self.tcx.def_kind(trait_def_id) == DefKind::Trait => + { let trait_ref = ty::TraitRef::from_assoc(self.tcx, trait_def_id, proj.args); // Only walk contained consts if the parent trait is not a supertrait. @@ -844,7 +847,7 @@ impl<'tcx> TypeVisitor> for IllegalSelfTypeVisitor<'tcx> { ct.super_visit_with(self) } } - AllowSelfProjections::No => ct.super_visit_with(self), + _ => ct.super_visit_with(self), } } _ => ct.super_visit_with(self), diff --git a/library/alloctests/tests/sort/tests.rs b/library/alloctests/tests/sort/tests.rs index d321f8df5189..09b76773d6b2 100644 --- a/library/alloctests/tests/sort/tests.rs +++ b/library/alloctests/tests/sort/tests.rs @@ -362,6 +362,13 @@ fn sort_vs_sort_by_impl() { assert_eq!(input_sort_by, expected); } +pub fn box_value_impl() { + for len in [3, 9, 35, 56, 132] { + test_is_sorted::, S>(len, Box::new, patterns::random); + test_is_sorted::, S>(len, Box::new, |len| patterns::random_sorted(len, 80.0)); + } +} + gen_sort_test_fns_with_default_patterns!( correct_i32, |len, pattern_fn| test_is_sorted::(len, |val| val, pattern_fn), @@ -967,6 +974,7 @@ define_instantiate_sort_tests!( [miri_yes, fixed_seed_rand_vec_prefix], [miri_yes, int_edge], [miri_yes, sort_vs_sort_by], + [miri_yes, box_value], [miri_yes, correct_i32_random], [miri_yes, correct_i32_random_z1], [miri_yes, correct_i32_random_d2], diff --git a/library/compiler-builtins/compiler-builtins/src/lib.rs b/library/compiler-builtins/compiler-builtins/src/lib.rs index c993209699be..342427aca9c7 100644 --- a/library/compiler-builtins/compiler-builtins/src/lib.rs +++ b/library/compiler-builtins/compiler-builtins/src/lib.rs @@ -11,6 +11,7 @@ #![feature(repr_simd)] #![feature(macro_metavar_expr_concat)] #![feature(rustc_attrs)] +#![feature(float_bits_const)] #![cfg_attr(f16_enabled, feature(f16))] #![cfg_attr(f128_enabled, feature(f128))] #![no_builtins] diff --git a/library/core/src/convert/num.rs b/library/core/src/convert/num.rs index c69026c5c9f3..6e82e3356410 100644 --- a/library/core/src/convert/num.rs +++ b/library/core/src/convert/num.rs @@ -39,62 +39,51 @@ impl_float_to_int!(f32 => u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i12 impl_float_to_int!(f64 => u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); impl_float_to_int!(f128 => u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); -// Conversion traits for primitive integer and float types -// Conversions T -> T are covered by a blanket impl and therefore excluded -// Some conversions from and to usize/isize are not implemented due to portability concerns -macro_rules! impl_from { - (bool => $Int:ty $(,)?) => { - impl_from!( - bool => $Int, - #[stable(feature = "from_bool", since = "1.28.0")], - concat!( - "Converts a [`bool`] to [`", stringify!($Int), "`] losslessly.\n", - "The resulting value is `0` for `false` and `1` for `true` values.\n", - "\n", - "# Examples\n", - "\n", - "```\n", - "assert_eq!(", stringify!($Int), "::from(true), 1);\n", - "assert_eq!(", stringify!($Int), "::from(false), 0);\n", - "```\n", - ), - ); - }; - ($Small:ty => $Large:ty, #[$attr:meta] $(,)?) => { - impl_from!( - $Small => $Large, - #[$attr], - concat!("Converts [`", stringify!($Small), "`] to [`", stringify!($Large), "`] losslessly."), - ); - }; - ($Small:ty => $Large:ty, #[$attr:meta], $doc:expr $(,)?) => { - #[$attr] +/// Implement `From` for integers +macro_rules! impl_from_bool { + ($($int:ty)*) => {$( + #[stable(feature = "from_bool", since = "1.28.0")] #[rustc_const_unstable(feature = "const_convert", issue = "143773")] - impl const From<$Small> for $Large { - // Rustdocs on the impl block show a "[+] show undocumented items" toggle. - // Rustdocs on functions do not. - #[doc = $doc] + impl const From for $int { + /// Converts from [`bool`] to + #[doc = concat!("[`", stringify!($int), "`]")] + /// , by turning `false` into `0` and `true` into `1`. + /// + /// # Examples + /// + /// ``` + #[doc = concat!("assert_eq!(", stringify!($int), "::from(false), 0);")] + /// + #[doc = concat!("assert_eq!(", stringify!($int), "::from(true), 1);")] + /// ``` #[inline(always)] - fn from(small: $Small) -> Self { - small as Self + fn from(b: bool) -> Self { + b as Self } } - }; + )*} } // boolean -> integer -impl_from!(bool => u8); -impl_from!(bool => u16); -impl_from!(bool => u32); -impl_from!(bool => u64); -impl_from!(bool => u128); -impl_from!(bool => usize); -impl_from!(bool => i8); -impl_from!(bool => i16); -impl_from!(bool => i32); -impl_from!(bool => i64); -impl_from!(bool => i128); -impl_from!(bool => isize); +impl_from_bool!(u8 u16 u32 u64 u128 usize); +impl_from_bool!(i8 i16 i32 i64 i128 isize); + +/// Implement `From<$small>` for `$large` +macro_rules! impl_from { + ($small:ty => $large:ty, #[$attr:meta]) => { + #[$attr] + #[rustc_const_unstable(feature = "const_convert", issue = "143773")] + impl const From<$small> for $large { + #[doc = concat!("Converts from [`", stringify!($small), "`] to [`", stringify!($large), "`] losslessly.")] + #[inline(always)] + fn from(small: $small) -> Self { + debug_assert!(<$large>::MIN as i128 <= <$small>::MIN as i128); + debug_assert!(<$small>::MAX as u128 <= <$large>::MAX as u128); + small as Self + } + } + } +} // unsigned integer -> unsigned integer impl_from!(u8 => u16, #[stable(feature = "lossless_int_conv", since = "1.5.0")]); @@ -338,12 +327,48 @@ macro_rules! impl_try_from_both_bounded { )*} } +/// Implement `TryFrom` for `bool` +macro_rules! impl_try_from_integer_for_bool { + ($($int:ty)+) => {$( + #[stable(feature = "try_from", since = "1.34.0")] + #[rustc_const_unstable(feature = "const_convert", issue = "143773")] + impl const TryFrom<$int> for bool { + type Error = TryFromIntError; + + /// Tries to create a bool from an integer type. + /// Returns an error if the integer is not 0 or 1. + /// + /// # Examples + /// + /// ``` + #[doc = concat!("assert_eq!(0_", stringify!($int), ".try_into(), Ok(false));")] + /// + #[doc = concat!("assert_eq!(1_", stringify!($int), ".try_into(), Ok(true));")] + /// + #[doc = concat!("assert!(<", stringify!($int), " as TryInto>::try_into(2).is_err());")] + /// ``` + #[inline] + fn try_from(i: $int) -> Result { + match i { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(TryFromIntError(())), + } + } + } + )*} +} + macro_rules! rev { ($mac:ident, $source:ty => $($target:ty),+) => {$( $mac!($target => $source); )*} } +// integer -> bool +impl_try_from_integer_for_bool!(u128 u64 u32 u16 u8); +impl_try_from_integer_for_bool!(i128 i64 i32 i16 i8); + // unsigned integer -> unsigned integer impl_try_from_upper_bounded!(u16 => u8); impl_try_from_upper_bounded!(u32 => u8, u16); diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index e8c9d26fb3b5..432ca50b3361 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -97,7 +97,6 @@ // tidy-alphabetical-start #![feature(array_ptr_get)] #![feature(asm_experimental_arch)] -#![feature(bigint_helper_methods)] #![feature(bstr)] #![feature(bstr_internals)] #![feature(cfg_select)] @@ -107,6 +106,7 @@ #![feature(const_destruct)] #![feature(const_eval_select)] #![feature(const_select_unpredictable)] +#![feature(const_unsigned_bigint_helpers)] #![feature(core_intrinsics)] #![feature(coverage_attribute)] #![feature(disjoint_bitor)] @@ -120,6 +120,7 @@ #![feature(ptr_alignment_type)] #![feature(ptr_metadata)] #![feature(set_ptr_value)] +#![feature(signed_bigint_helpers)] #![feature(slice_ptr_get)] #![feature(str_internals)] #![feature(str_split_inclusive_remainder)] @@ -129,6 +130,7 @@ #![feature(unsafe_pinned)] #![feature(utf16_extra)] #![feature(variant_count)] +#![feature(widening_mul)] // tidy-alphabetical-end // // Language features: diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index cc142fab8e82..140b955259ab 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -108,6 +108,17 @@ pub mod consts { pub const FRAC_1_SQRT_3: f128 = 0.577350269189625764509148780501957455647601751270126876018602_f128; + /// sqrt(5) + #[unstable(feature = "more_float_constants", issue = "146939")] + // Also, #[unstable(feature = "f128", issue = "116909")] + pub const SQRT_5: f128 = 2.23606797749978969640917366873127623544061835961152572427089_f128; + + /// 1/sqrt(5) + #[unstable(feature = "more_float_constants", issue = "146939")] + // Also, #[unstable(feature = "f128", issue = "116909")] + pub const FRAC_1_SQRT_5: f128 = + 0.447213595499957939281834733746255247088123671922305144854179_f128; + /// Euler's number (e) #[unstable(feature = "f128", issue = "116909")] pub const E: f128 = 2.71828182845904523536028747135266249775724709369995957496697_f128; @@ -143,6 +154,11 @@ impl f128 { #[unstable(feature = "f128", issue = "116909")] pub const RADIX: u32 = 2; + /// The size of this float type in bits. + // #[unstable(feature = "f128", issue = "116909")] + #[unstable(feature = "float_bits_const", issue = "151073")] + pub const BITS: u32 = 128; + /// Number of significant digits in base 2. /// /// Note that the size of the mantissa in the bitwise representation is one diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index e97a44e991f6..463f07da91b2 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -103,6 +103,16 @@ pub mod consts { // Also, #[unstable(feature = "more_float_constants", issue = "146939")] pub const FRAC_1_SQRT_3: f16 = 0.577350269189625764509148780501957456_f16; + /// sqrt(5) + #[unstable(feature = "more_float_constants", issue = "146939")] + // Also, #[unstable(feature = "f16", issue = "116909")] + pub const SQRT_5: f16 = 2.23606797749978969640917366873127623_f16; + + /// 1/sqrt(5) + #[unstable(feature = "more_float_constants", issue = "146939")] + // Also, #[unstable(feature = "f16", issue = "116909")] + pub const FRAC_1_SQRT_5: f16 = 0.44721359549995793928183473374625524_f16; + /// Euler's number (e) #[unstable(feature = "f16", issue = "116909")] pub const E: f16 = 2.71828182845904523536028747135266250_f16; @@ -138,6 +148,11 @@ impl f16 { #[unstable(feature = "f16", issue = "116909")] pub const RADIX: u32 = 2; + /// The size of this float type in bits. + // #[unstable(feature = "f16", issue = "116909")] + #[unstable(feature = "float_bits_const", issue = "151073")] + pub const BITS: u32 = 16; + /// Number of significant digits in base 2. /// /// Note that the size of the mantissa in the bitwise representation is one diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 3d8249631037..be908cb3894b 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -356,6 +356,14 @@ pub mod consts { #[unstable(feature = "more_float_constants", issue = "146939")] pub const FRAC_1_SQRT_3: f32 = 0.577350269189625764509148780501957456_f32; + /// sqrt(5) + #[unstable(feature = "more_float_constants", issue = "146939")] + pub const SQRT_5: f32 = 2.23606797749978969640917366873127623_f32; + + /// 1/sqrt(5) + #[unstable(feature = "more_float_constants", issue = "146939")] + pub const FRAC_1_SQRT_5: f32 = 0.44721359549995793928183473374625524_f32; + /// Euler's number (e) #[stable(feature = "rust1", since = "1.0.0")] pub const E: f32 = 2.71828182845904523536028747135266250_f32; @@ -390,6 +398,10 @@ impl f32 { #[stable(feature = "assoc_int_consts", since = "1.43.0")] pub const RADIX: u32 = 2; + /// The size of this float type in bits. + #[unstable(feature = "float_bits_const", issue = "151073")] + pub const BITS: u32 = 32; + /// Number of significant digits in base 2. /// /// Note that the size of the mantissa in the bitwise representation is one diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 566a6a7ec947..73b20a50ff8e 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -356,6 +356,14 @@ pub mod consts { #[unstable(feature = "more_float_constants", issue = "146939")] pub const FRAC_1_SQRT_3: f64 = 0.577350269189625764509148780501957456_f64; + /// sqrt(5) + #[unstable(feature = "more_float_constants", issue = "146939")] + pub const SQRT_5: f64 = 2.23606797749978969640917366873127623_f64; + + /// 1/sqrt(5) + #[unstable(feature = "more_float_constants", issue = "146939")] + pub const FRAC_1_SQRT_5: f64 = 0.44721359549995793928183473374625524_f64; + /// Euler's number (e) #[stable(feature = "rust1", since = "1.0.0")] pub const E: f64 = 2.71828182845904523536028747135266250_f64; @@ -390,6 +398,10 @@ impl f64 { #[stable(feature = "assoc_int_consts", since = "1.43.0")] pub const RADIX: u32 = 2; + /// The size of this float type in bits. + #[unstable(feature = "float_bits_const", issue = "151073")] + pub const BITS: u32 = 64; + /// Number of significant digits in base 2. /// /// Note that the size of the mantissa in the bitwise representation is one diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 9a27f8a0b5e6..b21865a9ae54 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2522,7 +2522,7 @@ macro_rules! int_impl { /// # Examples /// /// ``` - /// #![feature(bigint_helper_methods)] + /// #![feature(signed_bigint_helpers)] /// // Only the most significant word is signed. /// // #[doc = concat!("// 10 MAX (a = 10 × 2^", stringify!($BITS), " + 2^", stringify!($BITS), " - 1)")] @@ -2544,7 +2544,7 @@ macro_rules! int_impl { /// /// assert_eq!((sum1, sum0), (6, 8)); /// ``` - #[unstable(feature = "bigint_helper_methods", issue = "85532")] + #[unstable(feature = "signed_bigint_helpers", issue = "151989")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -2625,7 +2625,7 @@ macro_rules! int_impl { /// # Examples /// /// ``` - /// #![feature(bigint_helper_methods)] + /// #![feature(signed_bigint_helpers)] /// // Only the most significant word is signed. /// // #[doc = concat!("// 6 8 (a = 6 × 2^", stringify!($BITS), " + 8)")] @@ -2647,7 +2647,7 @@ macro_rules! int_impl { /// #[doc = concat!("assert_eq!((diff1, diff0), (10, ", stringify!($UnsignedT), "::MAX));")] /// ``` - #[unstable(feature = "bigint_helper_methods", issue = "85532")] + #[unstable(feature = "signed_bigint_helpers", issue = "151989")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -2717,12 +2717,12 @@ macro_rules! int_impl { /// Please note that this example is shared among integer types, which is why `i32` is used. /// /// ``` - /// #![feature(bigint_helper_methods)] + /// #![feature(widening_mul)] /// assert_eq!(5i32.widening_mul(-2), (4294967286, -1)); /// assert_eq!(1_000_000_000i32.widening_mul(-10), (2884901888, -3)); /// ``` - #[unstable(feature = "bigint_helper_methods", issue = "85532")] - #[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")] + #[unstable(feature = "widening_mul", issue = "152016")] + #[rustc_const_unstable(feature = "widening_mul", issue = "152016")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -2747,7 +2747,7 @@ macro_rules! int_impl { /// Please note that this example is shared among integer types, which is why `i32` is used. /// /// ``` - /// #![feature(bigint_helper_methods)] + /// #![feature(signed_bigint_helpers)] /// assert_eq!(5i32.carrying_mul(-2, 0), (4294967286, -1)); /// assert_eq!(5i32.carrying_mul(-2, 10), (0, 0)); /// assert_eq!(1_000_000_000i32.carrying_mul(-10, 0), (2884901888, -3)); @@ -2757,8 +2757,8 @@ macro_rules! int_impl { "(", stringify!($SelfT), "::MAX.unsigned_abs() + 1, ", stringify!($SelfT), "::MAX / 2));" )] /// ``` - #[unstable(feature = "bigint_helper_methods", issue = "85532")] - #[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")] + #[unstable(feature = "signed_bigint_helpers", issue = "151989")] + #[rustc_const_unstable(feature = "signed_bigint_helpers", issue = "151989")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -2784,7 +2784,7 @@ macro_rules! int_impl { /// Please note that this example is shared among integer types, which is why `i32` is used. /// /// ``` - /// #![feature(bigint_helper_methods)] + /// #![feature(signed_bigint_helpers)] /// assert_eq!(5i32.carrying_mul_add(-2, 0, 0), (4294967286, -1)); /// assert_eq!(5i32.carrying_mul_add(-2, 10, 10), (10, 0)); /// assert_eq!(1_000_000_000i32.carrying_mul_add(-10, 0, 0), (2884901888, -3)); @@ -2794,8 +2794,8 @@ macro_rules! int_impl { "(", stringify!($UnsignedT), "::MAX, ", stringify!($SelfT), "::MAX / 2));" )] /// ``` - #[unstable(feature = "bigint_helper_methods", issue = "85532")] - #[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")] + #[unstable(feature = "signed_bigint_helpers", issue = "151989")] + #[rustc_const_unstable(feature = "signed_bigint_helpers", issue = "151989")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index c48320c0eab3..5c263ea845cc 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -2807,7 +2807,7 @@ macro_rules! uint_impl { /// assert_eq!((sum1, sum0), (9, 6)); /// ``` #[stable(feature = "unsigned_bigint_helpers", since = "1.91.0")] - #[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")] + #[rustc_const_unstable(feature = "const_unsigned_bigint_helpers", issue = "152015")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -2899,7 +2899,7 @@ macro_rules! uint_impl { #[doc = concat!("assert_eq!((diff1, diff0), (3, ", stringify!($SelfT), "::MAX));")] /// ``` #[stable(feature = "unsigned_bigint_helpers", since = "1.91.0")] - #[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")] + #[rustc_const_unstable(feature = "const_unsigned_bigint_helpers", issue = "152015")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -3011,14 +3011,14 @@ macro_rules! uint_impl { /// # Examples /// /// ``` - /// #![feature(bigint_helper_methods)] + /// #![feature(widening_mul)] #[doc = concat!("assert_eq!(5_", stringify!($SelfT), ".widening_mul(7), (35, 0));")] #[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX.widening_mul(", stringify!($SelfT), "::MAX), (1, ", stringify!($SelfT), "::MAX - 1));")] /// ``` /// /// Compared to other `*_mul` methods: /// ``` - /// #![feature(bigint_helper_methods)] + /// #![feature(widening_mul)] #[doc = concat!("assert_eq!(", stringify!($SelfT), "::widening_mul(1 << ", stringify!($BITS_MINUS_ONE), ", 6), (0, 3));")] #[doc = concat!("assert_eq!(", stringify!($SelfT), "::overflowing_mul(1 << ", stringify!($BITS_MINUS_ONE), ", 6), (0, true));")] #[doc = concat!("assert_eq!(", stringify!($SelfT), "::wrapping_mul(1 << ", stringify!($BITS_MINUS_ONE), ", 6), 0);")] @@ -3028,12 +3028,12 @@ macro_rules! uint_impl { /// Please note that this example is shared among integer types, which is why `u32` is used. /// /// ``` - /// #![feature(bigint_helper_methods)] + /// #![feature(widening_mul)] /// assert_eq!(5u32.widening_mul(2), (10, 0)); /// assert_eq!(1_000_000_000u32.widening_mul(10), (1410065408, 2)); /// ``` - #[unstable(feature = "bigint_helper_methods", issue = "85532")] - #[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")] + #[unstable(feature = "widening_mul", issue = "152016")] + #[rustc_const_unstable(feature = "widening_mul", issue = "152016")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -3072,7 +3072,7 @@ macro_rules! uint_impl { /// implementing it for wider-than-native types. /// /// ``` - /// #![feature(bigint_helper_methods)] + /// #![feature(const_unsigned_bigint_helpers)] /// fn scalar_mul_eq(little_endian_digits: &mut Vec, multiplicand: u16) { /// let mut carry = 0; /// for d in little_endian_digits.iter_mut() { @@ -3097,7 +3097,7 @@ macro_rules! uint_impl { /// except that it gives the value of the overflow instead of just whether one happened: /// /// ``` - /// #![feature(bigint_helper_methods)] + /// #![feature(const_unsigned_bigint_helpers)] /// let r = u8::carrying_mul(7, 13, 0); /// assert_eq!((r.0, r.1 != 0), u8::overflowing_mul(7, 13)); /// let r = u8::carrying_mul(13, 42, 0); @@ -3109,14 +3109,14 @@ macro_rules! uint_impl { /// [`wrapping_add`](Self::wrapping_add) methods: /// /// ``` - /// #![feature(bigint_helper_methods)] + /// #![feature(const_unsigned_bigint_helpers)] /// assert_eq!( /// 789_u16.carrying_mul(456, 123).0, /// 789_u16.wrapping_mul(456).wrapping_add(123), /// ); /// ``` #[stable(feature = "unsigned_bigint_helpers", since = "1.91.0")] - #[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")] + #[rustc_const_unstable(feature = "const_unsigned_bigint_helpers", issue = "152015")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -3182,7 +3182,7 @@ macro_rules! uint_impl { /// ); /// ``` #[stable(feature = "unsigned_bigint_helpers", since = "1.91.0")] - #[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")] + #[rustc_const_unstable(feature = "const_unsigned_bigint_helpers", issue = "152015")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index 2566d1471ab7..75777144dfb8 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -239,7 +239,7 @@ impl *const T { /// let ptr: *const u8 = &10u8 as *const u8; /// /// unsafe { - /// let val_back = &*ptr; + /// let val_back = ptr.as_ref_unchecked(); /// assert_eq!(val_back, &10); /// } /// ``` @@ -259,6 +259,7 @@ impl *const T { /// /// [`is_null`]: #method.is_null /// [`as_uninit_ref`]: #method.as_uninit_ref + /// [`as_ref_unchecked`]: #method.as_ref_unchecked #[stable(feature = "ptr_as_ref", since = "1.9.0")] #[rustc_const_stable(feature = "const_ptr_is_null", since = "1.84.0")] #[inline] @@ -283,15 +284,14 @@ impl *const T { /// # Examples /// /// ``` - /// #![feature(ptr_as_ref_unchecked)] /// let ptr: *const u8 = &10u8 as *const u8; /// /// unsafe { /// assert_eq!(ptr.as_ref_unchecked(), &10); /// } /// ``` - // FIXME: mention it in the docs for `as_ref` and `as_uninit_ref` once stabilized. - #[unstable(feature = "ptr_as_ref_unchecked", issue = "122034")] + #[stable(feature = "ptr_as_ref_unchecked", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "ptr_as_ref_unchecked", since = "CURRENT_RUSTC_VERSION")] #[inline] #[must_use] pub const unsafe fn as_ref_unchecked<'a>(self) -> &'a T { diff --git a/library/core/src/ptr/docs/as_ref.md b/library/core/src/ptr/docs/as_ref.md index 0c0d2768c748..2c7d6e149b76 100644 --- a/library/core/src/ptr/docs/as_ref.md +++ b/library/core/src/ptr/docs/as_ref.md @@ -1,6 +1,7 @@ Returns `None` if the pointer is null, or else returns a shared reference to the value wrapped in `Some`. If the value may be uninitialized, [`as_uninit_ref`] -must be used instead. +must be used instead. If the value is known to be non-null, [`as_ref_unchecked`] +can be used instead. # Safety @@ -14,6 +15,5 @@ determined to be null or not. See [`is_null`] for more information. # Null-unchecked version -If you are sure the pointer can never be null and are looking for some kind of -`as_ref_unchecked` that returns the `&T` instead of `Option<&T>`, know that you can -dereference the pointer directly. +If you are sure the pointer can never be null, you can use `as_ref_unchecked` which returns +`&mut T` instead of `Option<&mut T>`. diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index 20e71bc2a1a5..02e12d56fa65 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -230,7 +230,7 @@ impl *mut T { /// let ptr: *mut u8 = &mut 10u8 as *mut u8; /// /// unsafe { - /// let val_back = &*ptr; + /// let val_back = ptr.as_ref_unchecked(); /// println!("We got back the value: {val_back}!"); /// } /// ``` @@ -252,7 +252,8 @@ impl *mut T { /// For the mutable counterpart see [`as_mut`]. /// /// [`is_null`]: #method.is_null-1 - /// [`as_uninit_ref`]: pointer#method.as_uninit_ref-1 + /// [`as_uninit_ref`]: #method.as_uninit_ref-1 + /// [`as_ref_unchecked`]: #method.as_ref_unchecked-1 /// [`as_mut`]: #method.as_mut #[stable(feature = "ptr_as_ref", since = "1.9.0")] @@ -281,15 +282,14 @@ impl *mut T { /// # Examples /// /// ``` - /// #![feature(ptr_as_ref_unchecked)] /// let ptr: *mut u8 = &mut 10u8 as *mut u8; /// /// unsafe { /// println!("We got back the value: {}!", ptr.as_ref_unchecked()); /// } /// ``` - // FIXME: mention it in the docs for `as_ref` and `as_uninit_ref` once stabilized. - #[unstable(feature = "ptr_as_ref_unchecked", issue = "122034")] + #[stable(feature = "ptr_as_ref_unchecked", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "ptr_as_ref_unchecked", since = "CURRENT_RUSTC_VERSION")] #[inline] #[must_use] pub const unsafe fn as_ref_unchecked<'a>(self) -> &'a T { @@ -531,11 +531,13 @@ impl *mut T { /// Returns `None` if the pointer is null, or else returns a unique reference to /// the value wrapped in `Some`. If the value may be uninitialized, [`as_uninit_mut`] - /// must be used instead. + /// must be used instead. If the value is known to be non-null, [`as_mut_unchecked`] + /// can be used instead. /// /// For the shared counterpart see [`as_ref`]. /// /// [`as_uninit_mut`]: #method.as_uninit_mut + /// [`as_mut_unchecked`]: #method.as_mut_unchecked /// [`as_ref`]: pointer#method.as_ref-1 /// /// # Safety @@ -564,14 +566,13 @@ impl *mut T { /// /// # Null-unchecked version /// - /// If you are sure the pointer can never be null and are looking for some kind of - /// `as_mut_unchecked` that returns the `&mut T` instead of `Option<&mut T>`, know that - /// you can dereference the pointer directly. + /// If you are sure the pointer can never be null, you can use `as_mut_unchecked` which returns + /// `&mut T` instead of `Option<&mut T>`. /// /// ``` /// let mut s = [1, 2, 3]; /// let ptr: *mut u32 = s.as_mut_ptr(); - /// let first_value = unsafe { &mut *ptr }; + /// let first_value = unsafe { ptr.as_mut_unchecked() }; /// *first_value = 4; /// # assert_eq!(s, [4, 2, 3]); /// println!("{s:?}"); // It'll print: "[4, 2, 3]". @@ -603,7 +604,6 @@ impl *mut T { /// # Examples /// /// ``` - /// #![feature(ptr_as_ref_unchecked)] /// let mut s = [1, 2, 3]; /// let ptr: *mut u32 = s.as_mut_ptr(); /// let first_value = unsafe { ptr.as_mut_unchecked() }; @@ -611,8 +611,8 @@ impl *mut T { /// # assert_eq!(s, [4, 2, 3]); /// println!("{s:?}"); // It'll print: "[4, 2, 3]". /// ``` - // FIXME: mention it in the docs for `as_mut` and `as_uninit_mut` once stabilized. - #[unstable(feature = "ptr_as_ref_unchecked", issue = "122034")] + #[stable(feature = "ptr_as_ref_unchecked", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "ptr_as_ref_unchecked", since = "CURRENT_RUSTC_VERSION")] #[inline] #[must_use] pub const unsafe fn as_mut_unchecked<'a>(self) -> &'a mut T { diff --git a/library/core/src/result.rs b/library/core/src/result.rs index 52f3d43dfd6d..5f438d72ac13 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -690,7 +690,7 @@ impl Result { /// Converts from `Result` to [`Option`]. /// /// Converts `self` into an [`Option`], consuming `self`, - /// and discarding the error, if any. + /// and converting the error to `None`, if any. /// /// # Examples /// diff --git a/library/core/src/slice/sort/stable/quicksort.rs b/library/core/src/slice/sort/stable/quicksort.rs index 734a495ce225..acc8a5e838e1 100644 --- a/library/core/src/slice/sort/stable/quicksort.rs +++ b/library/core/src/slice/sort/stable/quicksort.rs @@ -1,6 +1,6 @@ //! This module contains a stable quicksort and partition implementation. -use crate::mem::{ManuallyDrop, MaybeUninit}; +use crate::mem::MaybeUninit; use crate::slice::sort::shared::FreezeMarker; use crate::slice::sort::shared::pivot::choose_pivot; use crate::slice::sort::shared::smallsort::StableSmallSortTypeImpl; @@ -41,8 +41,11 @@ pub fn quicksort bool>( // SAFETY: We only access the temporary copy for Freeze types, otherwise // self-modifications via `is_less` would not be observed and this would // be unsound. Our temporary copy does not escape this scope. - let pivot_copy = unsafe { ManuallyDrop::new(ptr::read(&v[pivot_pos])) }; - let pivot_ref = (!has_direct_interior_mutability::()).then_some(&*pivot_copy); + // We use `MaybeUninit` to avoid re-tag issues. FIXME: use `MaybeDangling`. + let pivot_copy = unsafe { ptr::read((&raw const v[pivot_pos]).cast::>()) }; + let pivot_ref = + // SAFETY: We created the value in an init state. + (!has_direct_interior_mutability::()).then_some(unsafe { &*pivot_copy.as_ptr() }); // We choose a pivot, and check if this pivot is equal to our left // ancestor. If true, we do a partition putting equal elements on the diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 9054eada12fb..d085e4ad1a8f 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -8,7 +8,6 @@ #![feature(ascii_char_variants)] #![feature(async_iter_from_iter)] #![feature(async_iterator)] -#![feature(bigint_helper_methods)] #![feature(bool_to_result)] #![feature(bstr)] #![feature(cfg_target_has_reliable_f16_f128)] @@ -34,6 +33,7 @@ #![feature(const_result_trait_fn)] #![feature(const_select_unpredictable)] #![feature(const_trait_impl)] +#![feature(const_unsigned_bigint_helpers)] #![feature(control_flow_ok)] #![feature(core_float_math)] #![feature(core_intrinsics)] @@ -98,6 +98,7 @@ #![feature(portable_simd)] #![feature(ptr_metadata)] #![feature(result_option_map_or_default)] +#![feature(signed_bigint_helpers)] #![feature(slice_from_ptr_range)] #![feature(slice_index_methods)] #![feature(slice_internals)] @@ -122,6 +123,7 @@ #![feature(uint_gather_scatter_bits)] #![feature(unsize)] #![feature(unwrap_infallible)] +#![feature(widening_mul)] // tidy-alphabetical-end #![allow(internal_features)] #![deny(fuzzy_provenance_casts)] diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs index 95a7ea7d7b3b..49b6f2ae41f8 100644 --- a/library/proc_macro/src/lib.rs +++ b/library/proc_macro/src/lib.rs @@ -54,7 +54,9 @@ use std::{error, fmt}; pub use diagnostic::{Diagnostic, Level, MultiSpan}; #[unstable(feature = "proc_macro_value", issue = "136652")] pub use rustc_literal_escaper::EscapeError; -use rustc_literal_escaper::{MixedUnit, unescape_byte_str, unescape_c_str, unescape_str}; +use rustc_literal_escaper::{ + MixedUnit, unescape_byte, unescape_byte_str, unescape_c_str, unescape_char, unescape_str, +}; #[unstable(feature = "proc_macro_totokens", issue = "130977")] pub use to_tokens::ToTokens; @@ -1451,6 +1453,28 @@ impl Literal { }) } + /// Returns the unescaped character value if the current literal is a byte character literal. + #[unstable(feature = "proc_macro_value", issue = "136652")] + pub fn byte_character_value(&self) -> Result { + self.0.symbol.with(|symbol| match self.0.kind { + bridge::LitKind::Char => { + unescape_byte(symbol).map_err(ConversionErrorKind::FailedToUnescape) + } + _ => Err(ConversionErrorKind::InvalidLiteralKind), + }) + } + + /// Returns the unescaped character value if the current literal is a character literal. + #[unstable(feature = "proc_macro_value", issue = "136652")] + pub fn character_value(&self) -> Result { + self.0.symbol.with(|symbol| match self.0.kind { + bridge::LitKind::Char => { + unescape_char(symbol).map_err(ConversionErrorKind::FailedToUnescape) + } + _ => Err(ConversionErrorKind::InvalidLiteralKind), + }) + } + /// Returns the unescaped string value if the current literal is a string or a string literal. #[unstable(feature = "proc_macro_value", issue = "136652")] pub fn str_value(&self) -> Result { diff --git a/library/std/src/env.rs b/library/std/src/env.rs index 615b767a4ea5..1f0ced5d0fd0 100644 --- a/library/std/src/env.rs +++ b/library/std/src/env.rs @@ -518,8 +518,8 @@ pub struct JoinPathsError { /// /// Returns an [`Err`] (containing an error message) if one of the input /// [`Path`]s contains an invalid character for constructing the `PATH` -/// variable (a double quote on Windows or a colon on Unix), or if the system -/// does not have a `PATH`-like variable (e.g. UEFI or WASI). +/// variable (a double quote on Windows or a colon on Unix or semicolon on +/// UEFI), or if the system does not have a `PATH`-like variable (e.g. WASI). /// /// # Examples /// diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index b213fa749177..dcde208fac77 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -260,6 +260,7 @@ all(target_vendor = "fortanix", target_env = "sgx"), feature(slice_index_methods, coerce_unsized, sgx_platform) )] +#![cfg_attr(all(test, target_os = "uefi"), feature(uefi_std))] #![cfg_attr(target_family = "wasm", feature(stdarch_wasm_atomic_wait))] #![cfg_attr(target_arch = "wasm64", feature(simd_wasm64))] // diff --git a/library/std/src/os/uefi/env.rs b/library/std/src/os/uefi/env.rs index ab5406e605c6..82e3fc9775cb 100644 --- a/library/std/src/os/uefi/env.rs +++ b/library/std/src/os/uefi/env.rs @@ -4,13 +4,25 @@ use crate::ffi::c_void; use crate::ptr::NonNull; -use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, Ordering}; +use crate::sync::atomic::Ordering; -static SYSTEM_TABLE: Atomic<*mut c_void> = AtomicPtr::new(crate::ptr::null_mut()); -static IMAGE_HANDLE: Atomic<*mut c_void> = AtomicPtr::new(crate::ptr::null_mut()); -// Flag to check if BootServices are still valid. -// Start with assuming that they are not available -static BOOT_SERVICES_FLAG: Atomic = AtomicBool::new(false); +#[doc(hidden)] +#[cfg(not(test))] +pub mod globals { + use crate::ffi::c_void; + use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr}; + + pub static SYSTEM_TABLE: Atomic<*mut c_void> = AtomicPtr::new(crate::ptr::null_mut()); + pub static IMAGE_HANDLE: Atomic<*mut c_void> = AtomicPtr::new(crate::ptr::null_mut()); + // Flag to check if BootServices are still valid. + // Start with assuming that they are not available + pub static BOOT_SERVICES_FLAG: Atomic = AtomicBool::new(false); +} + +#[cfg(not(test))] +use globals::*; +#[cfg(test)] +use realstd::os::uefi::env::globals::*; /// Initializes the global System Table and Image Handle pointers. /// diff --git a/library/std/src/os/windows/mod.rs b/library/std/src/os/windows/mod.rs index f452403ee842..53c33d17a9f6 100644 --- a/library/std/src/os/windows/mod.rs +++ b/library/std/src/os/windows/mod.rs @@ -29,6 +29,8 @@ pub mod ffi; pub mod fs; pub mod io; +#[unstable(feature = "windows_unix_domain_sockets", issue = "150487")] +pub mod net; pub mod process; pub mod raw; pub mod thread; diff --git a/library/std/src/os/windows/net/addr.rs b/library/std/src/os/windows/net/addr.rs new file mode 100644 index 000000000000..ef2263edcf61 --- /dev/null +++ b/library/std/src/os/windows/net/addr.rs @@ -0,0 +1,173 @@ +#![unstable(feature = "windows_unix_domain_sockets", issue = "150487")] +use crate::bstr::ByteStr; +use crate::ffi::OsStr; +use crate::path::Path; +#[cfg(not(doc))] +use crate::sys::c::{AF_UNIX, SOCKADDR, SOCKADDR_UN}; +use crate::sys::cvt_nz; +use crate::{fmt, io, mem, ptr}; + +#[cfg(not(doc))] +pub fn sockaddr_un(path: &Path) -> io::Result<(SOCKADDR_UN, usize)> { + // SAFETY: All zeros is a valid representation for `sockaddr_un`. + let mut addr: SOCKADDR_UN = unsafe { mem::zeroed() }; + addr.sun_family = AF_UNIX; + + // path to UTF-8 bytes + let bytes = path + .to_str() + .ok_or(io::const_error!(io::ErrorKind::InvalidInput, "path must be valid UTF-8"))? + .as_bytes(); + if bytes.len() >= addr.sun_path.len() { + return Err(io::const_error!(io::ErrorKind::InvalidInput, "path too long")); + } + // SAFETY: `bytes` and `addr.sun_path` are not overlapping and + // both point to valid memory. + // NOTE: We zeroed the memory above, so the path is already null + // terminated. + unsafe { + ptr::copy_nonoverlapping(bytes.as_ptr(), addr.sun_path.as_mut_ptr().cast(), bytes.len()) + }; + + let len = SUN_PATH_OFFSET + bytes.len() + 1; + Ok((addr, len)) +} +#[cfg(not(doc))] +const SUN_PATH_OFFSET: usize = mem::offset_of!(SOCKADDR_UN, sun_path); +pub struct SocketAddr { + #[cfg(not(doc))] + pub(super) addr: SOCKADDR_UN, + pub(super) len: u32, // Use u32 here as same as libc::socklen_t +} +impl fmt::Debug for SocketAddr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.address() { + AddressKind::Unnamed => write!(fmt, "(unnamed)"), + AddressKind::Abstract(name) => write!(fmt, "{name:?} (abstract)"), + AddressKind::Pathname(path) => write!(fmt, "{path:?} (pathname)"), + } + } +} + +impl SocketAddr { + #[cfg(not(doc))] + pub(super) fn new(f: F) -> io::Result + where + F: FnOnce(*mut SOCKADDR, *mut i32) -> i32, + { + unsafe { + let mut addr: SOCKADDR_UN = mem::zeroed(); + let mut len = mem::size_of::() as i32; + cvt_nz(f(&raw mut addr as *mut _, &mut len))?; + SocketAddr::from_parts(addr, len) + } + } + #[cfg(not(doc))] + pub(super) fn from_parts(addr: SOCKADDR_UN, len: i32) -> io::Result { + if addr.sun_family != AF_UNIX { + Err(io::const_error!(io::ErrorKind::InvalidInput, "invalid address family")) + } else if len < SUN_PATH_OFFSET as _ || len > mem::size_of::() as _ { + Err(io::const_error!(io::ErrorKind::InvalidInput, "invalid address length")) + } else { + Ok(SocketAddr { addr, len: len as _ }) + } + } + + /// Returns the contents of this address if it is a `pathname` address. + /// + /// # Examples + /// + /// With a pathname: + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixListener; + /// use std::path::Path; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixListener::bind("/tmp/sock")?; + /// let addr = socket.local_addr().expect("Couldn't get local address"); + /// assert_eq!(addr.as_pathname(), Some(Path::new("/tmp/sock"))); + /// Ok(()) + /// } + /// ``` + pub fn as_pathname(&self) -> Option<&Path> { + if let AddressKind::Pathname(path) = self.address() { Some(path) } else { None } + } + + /// Constructs a `SockAddr` with the family `AF_UNIX` and the provided path. + /// + /// # Errors + /// + /// Returns an error if the path is longer than `SUN_LEN` or if it contains + /// NULL bytes. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::SocketAddr; + /// use std::path::Path; + /// + /// # fn main() -> std::io::Result<()> { + /// let address = SocketAddr::from_pathname("/path/to/socket")?; + /// assert_eq!(address.as_pathname(), Some(Path::new("/path/to/socket"))); + /// # Ok(()) + /// # } + /// ``` + /// + /// Creating a `SocketAddr` with a NULL byte results in an error. + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::SocketAddr; + /// + /// assert!(SocketAddr::from_pathname("/path/with/\0/bytes").is_err()); + /// ``` + pub fn from_pathname

(path: P) -> io::Result + where + P: AsRef, + { + sockaddr_un(path.as_ref()).map(|(addr, len)| SocketAddr { addr, len: len as _ }) + } + fn address(&self) -> AddressKind<'_> { + let len = self.len as usize - SUN_PATH_OFFSET; + let path = unsafe { mem::transmute::<&[i8], &[u8]>(&self.addr.sun_path) }; + + if len == 0 { + AddressKind::Unnamed + } else if self.addr.sun_path[0] == 0 { + AddressKind::Abstract(ByteStr::from_bytes(&path[1..len])) + } else { + AddressKind::Pathname(unsafe { + OsStr::from_encoded_bytes_unchecked(&path[..len - 1]).as_ref() + }) + } + } + + /// Returns `true` if the address is unnamed. + /// + /// # Examples + /// + /// A named address: + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixListener; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixListener::bind("/tmp/sock")?; + /// let addr = socket.local_addr().expect("Couldn't get local address"); + /// assert_eq!(addr.is_unnamed(), false); + /// Ok(()) + /// } + /// ``` + pub fn is_unnamed(&self) -> bool { + matches!(self.address(), AddressKind::Unnamed) + } +} +enum AddressKind<'a> { + Unnamed, + Pathname(&'a Path), + Abstract(&'a ByteStr), +} diff --git a/library/std/src/os/windows/net/listener.rs b/library/std/src/os/windows/net/listener.rs new file mode 100644 index 000000000000..332b116ee1a3 --- /dev/null +++ b/library/std/src/os/windows/net/listener.rs @@ -0,0 +1,342 @@ +#![unstable(feature = "windows_unix_domain_sockets", issue = "150487")] +use crate::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket}; +use crate::os::windows::net::{SocketAddr, UnixStream}; +use crate::path::Path; +#[cfg(not(doc))] +use crate::sys::c::{AF_UNIX, SOCK_STREAM, SOCKADDR_UN, bind, getsockname, listen}; +use crate::sys::net::Socket; +#[cfg(not(doc))] +use crate::sys::winsock::startup; +use crate::sys::{AsInner, cvt_nz}; +use crate::{fmt, io}; + +/// A structure representing a Unix domain socket server. +/// +/// # Examples +/// +/// ```no_run +/// #![feature(windows_unix_domain_sockets)] +/// use std::thread; +/// use std::os::windows::net::{UnixStream, UnixListener}; +/// +/// fn handle_client(stream: UnixStream) { +/// // ... +/// } +/// +/// fn main() -> std::io::Result<()> { +/// let listener = UnixListener::bind("/path/to/the/socket")?; +/// +/// // accept connections and process them, spawning a new thread for each one +/// for stream in listener.incoming() { +/// match stream { +/// Ok(stream) => { +/// /* connection succeeded */ +/// thread::spawn(|| handle_client(stream)); +/// } +/// Err(err) => { +/// /* connection failed */ +/// break; +/// } +/// } +/// } +/// Ok(()) +/// } +/// ``` +pub struct UnixListener(Socket); + +impl fmt::Debug for UnixListener { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = fmt.debug_struct("UnixListener"); + builder.field("sock", self.0.as_inner()); + if let Ok(addr) = self.local_addr() { + builder.field("local", &addr); + } + builder.finish() + } +} +impl UnixListener { + /// Creates a new `UnixListener` bound to the specified socket. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixListener; + /// + /// let listener = match UnixListener::bind("/path/to/the/socket") { + /// Ok(sock) => sock, + /// Err(e) => { + /// println!("Couldn't connect: {e:?}"); + /// return + /// } + /// }; + /// ``` + pub fn bind>(path: P) -> io::Result { + let socket_addr = SocketAddr::from_pathname(path)?; + Self::bind_addr(&socket_addr) + } + + /// Creates a new `UnixListener` bound to the specified [`socket address`]. + /// + /// [`socket address`]: crate::os::windows::net::SocketAddr + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::{UnixListener}; + /// + /// fn main() -> std::io::Result<()> { + /// let listener1 = UnixListener::bind("path/to/socket")?; + /// let addr = listener1.local_addr()?; + /// + /// let listener2 = match UnixListener::bind_addr(&addr) { + /// Ok(sock) => sock, + /// Err(err) => { + /// println!("Couldn't bind: {err:?}"); + /// return Err(err); + /// } + /// }; + /// Ok(()) + /// } + /// ``` + pub fn bind_addr(socket_addr: &SocketAddr) -> io::Result { + startup(); + let inner = Socket::new(AF_UNIX as _, SOCK_STREAM)?; + unsafe { + cvt_nz(bind(inner.as_raw(), &raw const socket_addr.addr as _, socket_addr.len as _))?; + cvt_nz(listen(inner.as_raw(), 128))?; + } + Ok(UnixListener(inner)) + } + + /// Accepts a new incoming connection to this listener. + /// + /// This function will block the calling thread until a new Unix connection + /// is established. When established, the corresponding [`UnixStream`] and + /// the remote peer's address will be returned. + /// + /// [`UnixStream`]: crate::os::windows::net::UnixStream + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixListener; + /// + /// fn main() -> std::io::Result<()> { + /// let listener = UnixListener::bind("/path/to/the/socket")?; + /// + /// match listener.accept() { + /// Ok((socket, addr)) => println!("Got a client: {addr:?}"), + /// Err(e) => println!("accept function failed: {e:?}"), + /// } + /// Ok(()) + /// } + /// ``` + pub fn accept(&self) -> io::Result<(UnixStream, SocketAddr)> { + let mut storage = SOCKADDR_UN::default(); + let mut len = size_of::() as _; + let inner = self.0.accept(&raw mut storage as *mut _, &raw mut len)?; + let addr = SocketAddr::from_parts(storage, len)?; + Ok((UnixStream(inner), addr)) + } + + /// Returns the local socket address of this listener. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixListener; + /// + /// fn main() -> std::io::Result<()> { + /// let listener = UnixListener::bind("/path/to/the/socket")?; + /// let addr = listener.local_addr().expect("Couldn't get local address"); + /// Ok(()) + /// } + /// ``` + pub fn local_addr(&self) -> io::Result { + SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) + } + + /// Creates a new independently owned handle to the underlying socket. + /// + /// The returned `UnixListener` is a reference to the same socket that this + /// object references. Both handles can be used to accept incoming + /// connections and options set on one listener will affect the other. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixListener; + /// + /// fn main() -> std::io::Result<()> { + /// let listener = UnixListener::bind("/path/to/the/socket")?; + /// let listener_copy = listener.try_clone().expect("try_clone failed"); + /// Ok(()) + /// } + /// ``` + pub fn try_clone(&self) -> io::Result { + self.0.duplicate().map(UnixListener) + } + + /// Moves the socket into or out of nonblocking mode. + /// + /// This will result in the `accept` operation becoming nonblocking, + /// i.e., immediately returning from their calls. If the IO operation is + /// successful, `Ok` is returned and no further action is required. If the + /// IO operation could not be completed and needs to be retried, an error + /// with kind [`io::ErrorKind::WouldBlock`] is returned. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixListener; + /// + /// fn main() -> std::io::Result<()> { + /// let listener = UnixListener::bind("/path/to/the/socket")?; + /// listener.set_nonblocking(true).expect("Couldn't set non blocking"); + /// Ok(()) + /// } + /// ``` + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + self.0.set_nonblocking(nonblocking) + } + + /// Returns the value of the `SO_ERROR` option. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixListener; + /// + /// fn main() -> std::io::Result<()> { + /// let listener = UnixListener::bind("/tmp/sock")?; + /// + /// if let Ok(Some(err)) = listener.take_error() { + /// println!("Got error: {err:?}"); + /// } + /// Ok(()) + /// } + /// ``` + pub fn take_error(&self) -> io::Result> { + self.0.take_error() + } + + /// Returns an iterator over incoming connections. + /// + /// The iterator will never return [`None`] and will also not yield the + /// peer's [`SocketAddr`] structure. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::thread; + /// use std::os::windows::net::{UnixStream, UnixListener}; + /// + /// fn handle_client(stream: UnixStream) { + /// // ... + /// } + /// + /// fn main() -> std::io::Result<()> { + /// let listener = UnixListener::bind("/path/to/the/socket")?; + /// + /// for stream in listener.incoming() { + /// match stream { + /// Ok(stream) => { + /// thread::spawn(|| handle_client(stream)); + /// } + /// Err(err) => { + /// break; + /// } + /// } + /// } + /// Ok(()) + /// } + /// ``` + pub fn incoming(&self) -> Incoming<'_> { + Incoming { listener: self } + } +} + +/// An iterator over incoming connections to a [`UnixListener`]. +/// +/// It will never return [`None`]. +/// +/// # Examples +/// +/// ```no_run +/// #![feature(windows_unix_domain_sockets)] +/// use std::thread; +/// use std::os::windows::net::{UnixStream, UnixListener}; +/// +/// fn handle_client(stream: UnixStream) { +/// // ... +/// } +/// +/// fn main() -> std::io::Result<()> { +/// let listener = UnixListener::bind("/path/to/the/socket")?; +/// +/// for stream in listener.incoming() { +/// match stream { +/// Ok(stream) => { +/// thread::spawn(|| handle_client(stream)); +/// } +/// Err(err) => { +/// break; +/// } +/// } +/// } +/// Ok(()) +/// } +/// ``` +pub struct Incoming<'a> { + listener: &'a UnixListener, +} + +impl<'a> Iterator for Incoming<'a> { + type Item = io::Result; + + fn next(&mut self) -> Option> { + Some(self.listener.accept().map(|s| s.0)) + } + + fn size_hint(&self) -> (usize, Option) { + (usize::MAX, None) + } +} + +impl AsRawSocket for UnixListener { + #[inline] + fn as_raw_socket(&self) -> RawSocket { + self.0.as_raw_socket() + } +} + +impl FromRawSocket for UnixListener { + #[inline] + unsafe fn from_raw_socket(sock: RawSocket) -> Self { + UnixListener(unsafe { Socket::from_raw_socket(sock) }) + } +} + +impl IntoRawSocket for UnixListener { + #[inline] + fn into_raw_socket(self) -> RawSocket { + self.0.into_raw_socket() + } +} + +impl<'a> IntoIterator for &'a UnixListener { + type Item = io::Result; + type IntoIter = Incoming<'a>; + + fn into_iter(self) -> Incoming<'a> { + self.incoming() + } +} diff --git a/library/std/src/os/windows/net/mod.rs b/library/std/src/os/windows/net/mod.rs new file mode 100644 index 000000000000..6b3f062b8ab4 --- /dev/null +++ b/library/std/src/os/windows/net/mod.rs @@ -0,0 +1,6 @@ +mod addr; +mod listener; +mod stream; +pub use addr::*; +pub use listener::*; +pub use stream::*; diff --git a/library/std/src/os/windows/net/stream.rs b/library/std/src/os/windows/net/stream.rs new file mode 100644 index 000000000000..c31f03fdf53f --- /dev/null +++ b/library/std/src/os/windows/net/stream.rs @@ -0,0 +1,421 @@ +#![unstable(feature = "windows_unix_domain_sockets", issue = "150487")] +use crate::net::Shutdown; +use crate::os::windows::io::{ + AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, RawSocket, +}; +use crate::os::windows::net::SocketAddr; +use crate::path::Path; +#[cfg(not(doc))] +use crate::sys::c::{ + AF_UNIX, SO_RCVTIMEO, SO_SNDTIMEO, SOCK_STREAM, connect, getpeername, getsockname, +}; +use crate::sys::net::Socket; +#[cfg(not(doc))] +use crate::sys::winsock::startup; +use crate::sys::{AsInner, cvt_nz}; +use crate::time::Duration; +use crate::{fmt, io}; +/// A Unix stream socket. +/// +/// # Examples +/// +/// ```no_run +/// #![feature(windows_unix_domain_sockets)] +/// use std::os::windows::net::UnixStream; +/// use std::io::prelude::*; +/// +/// fn main() -> std::io::Result<()> { +/// let mut stream = UnixStream::connect("/path/to/my/socket")?; +/// stream.write_all(b"hello world")?; +/// let mut response = String::new(); +/// stream.read_to_string(&mut response)?; +/// println!("{response}"); +/// Ok(()) +/// } +/// ``` +pub struct UnixStream(pub(super) Socket); +impl fmt::Debug for UnixStream { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = fmt.debug_struct("UnixStream"); + builder.field("sock", self.0.as_inner()); + if let Ok(addr) = self.local_addr() { + builder.field("local", &addr); + } + if let Ok(addr) = self.peer_addr() { + builder.field("peer", &addr); + } + builder.finish() + } +} +impl UnixStream { + /// Connects to the socket named by `path`. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixStream; + /// + /// let socket = match UnixStream::connect("/tmp/sock") { + /// Ok(sock) => sock, + /// Err(e) => { + /// println!("Couldn't connect: {e:?}"); + /// return + /// } + /// }; + /// ``` + pub fn connect>(path: P) -> io::Result { + let socket_addr = SocketAddr::from_pathname(path)?; + Self::connect_addr(&socket_addr) + } + + /// Connects to the socket specified by [`address`]. + /// + /// [`address`]: crate::os::windows::net::SocketAddr + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::{UnixListener, UnixStream}; + /// + /// fn main() -> std::io::Result<()> { + /// let listener = UnixListener::bind("/path/to/the/socket")?; + /// let addr = listener.local_addr()?; + /// + /// let sock = match UnixStream::connect_addr(&addr) { + /// Ok(sock) => sock, + /// Err(e) => { + /// println!("Couldn't connect: {e:?}"); + /// return Err(e) + /// } + /// }; + /// Ok(()) + /// } + /// ```` + pub fn connect_addr(socket_addr: &SocketAddr) -> io::Result { + startup(); + let inner = Socket::new(AF_UNIX as _, SOCK_STREAM)?; + unsafe { + cvt_nz(connect( + inner.as_raw(), + &raw const socket_addr.addr as *const _, + socket_addr.len as _, + ))?; + } + Ok(UnixStream(inner)) + } + + /// Returns the socket address of the local half of this connection. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixStream; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// let addr = socket.local_addr().expect("Couldn't get local address"); + /// Ok(()) + /// } + /// ``` + pub fn local_addr(&self) -> io::Result { + SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) + } + + /// Returns the socket address of the remote half of this connection. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixStream; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// let addr = socket.peer_addr().expect("Couldn't get peer address"); + /// Ok(()) + /// } + /// ``` + pub fn peer_addr(&self) -> io::Result { + SocketAddr::new(|addr, len| unsafe { getpeername(self.0.as_raw(), addr, len) }) + } + + /// Returns the read timeout of this socket. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixStream; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// socket.set_read_timeout(Some(Duration::new(1, 0))).expect("Couldn't set read timeout"); + /// assert_eq!(socket.read_timeout()?, Some(Duration::new(1, 0))); + /// Ok(()) + /// } + /// ``` + pub fn read_timeout(&self) -> io::Result> { + self.0.timeout(SO_RCVTIMEO) + } + + /// Moves the socket into or out of nonblocking mode. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixStream; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// socket.set_nonblocking(true).expect("Couldn't set nonblocking"); + /// Ok(()) + /// } + /// ``` + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + self.0.set_nonblocking(nonblocking) + } + + /// Sets the read timeout for the socket. + /// + /// If the provided value is [`None`], then [`read`] calls will block + /// indefinitely. An [`Err`] is returned if the zero [`Duration`] is passed to this + /// method. + /// + /// [`read`]: io::Read::read + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixStream; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// socket.set_read_timeout(Some(Duration::new(1, 0))).expect("Couldn't set read timeout"); + /// Ok(()) + /// } + /// ``` + /// + /// An [`Err`] is returned if the zero [`Duration`] is passed to this + /// method: + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::io; + /// use std::os::windows::net::UnixStream; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// let result = socket.set_read_timeout(Some(Duration::new(0, 0))); + /// let err = result.unwrap_err(); + /// assert_eq!(err.kind(), io::ErrorKind::InvalidInput); + /// Ok(()) + /// } + /// ``` + pub fn set_read_timeout(&self, dur: Option) -> io::Result<()> { + self.0.set_timeout(dur, SO_RCVTIMEO) + } + + /// Sets the write timeout for the socket. + /// + /// If the provided value is [`None`], then [`write`] calls will block + /// indefinitely. An [`Err`] is returned if the zero [`Duration`] is + /// passed to this method. + /// + /// [`read`]: io::Read::read + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixStream; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// socket.set_write_timeout(Some(Duration::new(1, 0))) + /// .expect("Couldn't set write timeout"); + /// Ok(()) + /// } + /// ``` + /// + /// An [`Err`] is returned if the zero [`Duration`] is passed to this + /// method: + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::io; + /// use std::os::windows::net::UnixStream; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// let result = socket.set_write_timeout(Some(Duration::new(0, 0))); + /// let err = result.unwrap_err(); + /// assert_eq!(err.kind(), io::ErrorKind::InvalidInput); + /// Ok(()) + /// } + /// ``` + pub fn set_write_timeout(&self, dur: Option) -> io::Result<()> { + self.0.set_timeout(dur, SO_SNDTIMEO) + } + + /// Shuts down the read, write, or both halves of this connection. + /// + /// This function will cause all pending and future I/O calls on the + /// specified portions to immediately return with an appropriate value + /// (see the documentation of [`Shutdown`]). + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixStream; + /// use std::net::Shutdown; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// socket.shutdown(Shutdown::Both).expect("shutdown function failed"); + /// Ok(()) + /// } + /// ``` + pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { + self.0.shutdown(how) + } + + /// Returns the value of the `SO_ERROR` option. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixStream; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// if let Ok(Some(err)) = socket.take_error() { + /// println!("Got error: {err:?}"); + /// } + /// Ok(()) + /// } + /// ``` + pub fn take_error(&self) -> io::Result> { + self.0.take_error() + } + + /// Creates a new independently owned handle to the underlying socket. + /// + /// The returned `UnixStream` is a reference to the same stream that this + /// object references. Both handles will read and write the same stream of + /// data, and options set on one stream will be propagated to the other + /// stream. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixStream; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// let sock_copy = socket.try_clone().expect("Couldn't clone socket"); + /// Ok(()) + /// } + /// ``` + pub fn try_clone(&self) -> io::Result { + self.0.duplicate().map(UnixStream) + } + + /// Returns the write timeout of this socket. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(windows_unix_domain_sockets)] + /// use std::os::windows::net::UnixStream; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixStream::connect("/tmp/sock")?; + /// socket.set_write_timeout(Some(Duration::new(1, 0))) + /// .expect("Couldn't set write timeout"); + /// assert_eq!(socket.write_timeout()?, Some(Duration::new(1, 0))); + /// Ok(()) + /// } + /// ``` + pub fn write_timeout(&self) -> io::Result> { + self.0.timeout(SO_SNDTIMEO) + } +} + +impl io::Read for UnixStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + io::Read::read(&mut &*self, buf) + } +} + +impl<'a> io::Read for &'a UnixStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } +} + +impl io::Write for UnixStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + io::Write::write(&mut &*self, buf) + } + + fn flush(&mut self) -> io::Result<()> { + io::Write::flush(&mut &*self) + } +} +impl<'a> io::Write for &'a UnixStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.write_vectored(&[io::IoSlice::new(buf)]) + } + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.0.write_vectored(bufs) + } + #[inline] + fn is_write_vectored(&self) -> bool { + self.0.is_write_vectored() + } +} + +impl AsSocket for UnixStream { + #[inline] + fn as_socket(&self) -> BorrowedSocket<'_> { + self.0.as_socket() + } +} + +impl AsRawSocket for UnixStream { + #[inline] + fn as_raw_socket(&self) -> RawSocket { + self.0.as_raw_socket() + } +} + +impl FromRawSocket for UnixStream { + #[inline] + unsafe fn from_raw_socket(sock: RawSocket) -> Self { + unsafe { UnixStream(Socket::from_raw_socket(sock)) } + } +} + +impl IntoRawSocket for UnixStream { + fn into_raw_socket(self) -> RawSocket { + self.0.into_raw_socket() + } +} diff --git a/library/std/src/sys/pal/uefi/os.rs b/library/std/src/sys/pal/uefi/os.rs index 178f7f506341..5b9785c8371e 100644 --- a/library/std/src/sys/pal/uefi/os.rs +++ b/library/std/src/sys/pal/uefi/os.rs @@ -5,10 +5,13 @@ use super::{helpers, unsupported_err}; use crate::ffi::{OsStr, OsString}; use crate::marker::PhantomData; use crate::os::uefi; +use crate::os::uefi::ffi::{OsStrExt, OsStringExt}; use crate::path::{self, PathBuf}; use crate::ptr::NonNull; use crate::{fmt, io}; +const PATHS_SEP: u16 = b';' as u16; + pub fn getcwd() -> io::Result { match helpers::open_shell() { Some(shell) => { @@ -54,17 +57,34 @@ impl<'a> Iterator for SplitPaths<'a> { #[derive(Debug)] pub struct JoinPathsError; -pub fn join_paths(_paths: I) -> Result +// UEFI Shell Path variable is defined in Section 3.6.1 +// [UEFI Shell Specification](https://uefi.org/sites/default/files/resources/UEFI_Shell_2_2.pdf). +pub fn join_paths(paths: I) -> Result where I: Iterator, T: AsRef, { - Err(JoinPathsError) + let mut joined = Vec::new(); + + for (i, path) in paths.enumerate() { + if i > 0 { + joined.push(PATHS_SEP) + } + + let v = path.as_ref().encode_wide().collect::>(); + if v.contains(&PATHS_SEP) { + return Err(JoinPathsError); + } + + joined.extend_from_slice(&v); + } + + Ok(OsString::from_wide(&joined)) } impl fmt::Display for JoinPathsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - "not supported on this platform yet".fmt(f) + "path segment contains `;`".fmt(f) } } diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs index 17e3cdbecd5c..0cd915261471 100644 --- a/library/std/src/sys/pal/windows/mod.rs +++ b/library/std/src/sys/pal/windows/mod.rs @@ -233,6 +233,11 @@ pub fn cvt(i: I) -> io::Result { if i.is_zero() { Err(io::Error::last_os_error()) } else { Ok(i) } } +#[allow(dead_code)] +pub fn cvt_nz(i: I) -> crate::io::Result<()> { + if i.is_zero() { Ok(()) } else { Err(crate::io::Error::last_os_error()) } +} + pub fn dur2timeout(dur: Duration) -> u32 { // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the // timeouts in windows APIs are typically u32 milliseconds. To translate, we diff --git a/library/std/tests/windows_unix_socket.rs b/library/std/tests/windows_unix_socket.rs new file mode 100644 index 000000000000..1f20cf586ca2 --- /dev/null +++ b/library/std/tests/windows_unix_socket.rs @@ -0,0 +1,87 @@ +#![cfg(windows)] +#![cfg(not(miri))] // no socket support in Miri +#![feature(windows_unix_domain_sockets)] +// Now only test windows_unix_domain_sockets feature +// in the future, will test both unix and windows uds +use std::io::{Read, Write}; +use std::os::windows::net::{UnixListener, UnixStream}; +use std::thread; + +#[test] +fn win_uds_smoke_bind_connect() { + let tmp = std::env::temp_dir(); + let sock_path = tmp.join("rust-test-uds-smoke.sock"); + let _ = std::fs::remove_file(&sock_path); + let listener = UnixListener::bind(&sock_path).expect("bind failed"); + let sock_path_clone = sock_path.clone(); + let tx = thread::spawn(move || { + let mut stream = UnixStream::connect(&sock_path_clone).expect("connect failed"); + stream.write_all(b"hello").expect("write failed"); + }); + + let (mut stream, _) = listener.accept().expect("accept failed"); + let mut buf = [0; 5]; + stream.read_exact(&mut buf).expect("read failed"); + assert_eq!(&buf, b"hello"); + + tx.join().unwrap(); + + drop(listener); + let _ = std::fs::remove_file(&sock_path); +} + +#[test] +fn win_uds_echo() { + let tmp = std::env::temp_dir(); + let sock_path = tmp.join("rust-test-uds-echo.sock"); + let _ = std::fs::remove_file(&sock_path); + + let listener = UnixListener::bind(&sock_path).expect("bind failed"); + let srv = thread::spawn(move || { + let (mut stream, _) = listener.accept().expect("accept failed"); + let mut buf = [0u8; 128]; + loop { + let n = match stream.read(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(e) => panic!("read error: {}", e), + }; + stream.write_all(&buf[..n]).expect("write_all failed"); + } + }); + + let sock_path_clone = sock_path.clone(); + let cli = thread::spawn(move || { + let mut stream = UnixStream::connect(&sock_path_clone).expect("connect failed"); + let req = b"hello windows uds"; + stream.write_all(req).expect("write failed"); + let mut resp = vec![0u8; req.len()]; + stream.read_exact(&mut resp).expect("read failed"); + assert_eq!(resp, req); + }); + + cli.join().unwrap(); + srv.join().unwrap(); + + let _ = std::fs::remove_file(&sock_path); +} + +#[test] +fn win_uds_path_too_long() { + let tmp = std::env::temp_dir(); + let long_path = tmp.join("a".repeat(200)); + let result = UnixListener::bind(&long_path); + assert!(result.is_err()); + let _ = std::fs::remove_file(&long_path); +} +#[test] +fn win_uds_existing_bind() { + let tmp = std::env::temp_dir(); + let sock_path = tmp.join("rust-test-uds-existing.sock"); + let _ = std::fs::remove_file(&sock_path); + let listener = UnixListener::bind(&sock_path).expect("bind failed"); + let result = UnixListener::bind(&sock_path); + assert!(result.is_err()); + drop(listener); + let _ = std::fs::remove_file(&sock_path); +} diff --git a/library/stdarch/crates/core_arch/src/lib.rs b/library/stdarch/crates/core_arch/src/lib.rs index 3992eaee30fd..039a4c4411f2 100644 --- a/library/stdarch/crates/core_arch/src/lib.rs +++ b/library/stdarch/crates/core_arch/src/lib.rs @@ -33,7 +33,6 @@ f16, aarch64_unstable_target_feature, target_feature_inline_always, - bigint_helper_methods, funnel_shifts, avx10_target_feature, const_trait_impl, diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 61015a141e46..0add77f0b09a 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -513,6 +513,7 @@ impl Step for RustAnalyzer { // This builds a proc macro against the bootstrap libproc_macro, which is not ABI // compatible with the ABI proc-macro-srv expects to load. cargo.arg("--exclude=proc-macro-srv"); + cargo.arg("--exclude=proc-macro-srv-cli"); } let mut skip_tests = vec![]; diff --git a/src/ci/citool/Cargo.lock b/src/ci/citool/Cargo.lock index 9e1ded4d3f7c..fe1c92f049e0 100644 --- a/src/ci/citool/Cargo.lock +++ b/src/ci/citool/Cargo.lock @@ -66,9 +66,9 @@ checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "askama" -version = "0.15.1" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7125972258312e79827b60c9eb93938334100245081cf701a2dee981b17427" +checksum = "08e1676b346cadfec169374f949d7490fd80a24193d37d2afce0c047cf695e57" dependencies = [ "askama_macros", "itoa", @@ -79,9 +79,9 @@ dependencies = [ [[package]] name = "askama_derive" -version = "0.15.1" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba5e7259a1580c61571e3116ebaaa01e3c001b2132b17c4cc5c70780ca3e994" +checksum = "7661ff56517787343f376f75db037426facd7c8d3049cef8911f1e75016f3a37" dependencies = [ "askama_parser", "basic-toml", @@ -96,18 +96,18 @@ dependencies = [ [[package]] name = "askama_macros" -version = "0.15.1" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236ce20b77cb13506eaf5024899f4af6e12e8825f390bd943c4c37fd8f322e46" +checksum = "713ee4dbfd1eb719c2dab859465b01fa1d21cb566684614a713a6b7a99a4e47b" dependencies = [ "askama_derive", ] [[package]] name = "askama_parser" -version = "0.15.1" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c63392767bb2df6aa65a6e1e3b80fd89bb7af6d58359b924c0695620f1512e" +checksum = "1d62d674238a526418b30c0def480d5beadb9d8964e7f38d635b03bf639c704c" dependencies = [ "rustc-hash", "serde", diff --git a/src/ci/citool/src/analysis.rs b/src/ci/citool/src/analysis.rs index 39b115154f9f..e91d27a36092 100644 --- a/src/ci/citool/src/analysis.rs +++ b/src/ci/citool/src/analysis.rs @@ -194,8 +194,8 @@ pub fn output_test_diffs( report_test_diffs(aggregated_test_diffs, job_metrics, job_info_resolver); } -/// Prints the ten largest differences in bootstrap durations. -pub fn output_largest_duration_changes( +/// Prints the ten largest differences in job durations. +pub fn output_largest_job_duration_changes( job_metrics: &HashMap, job_info_resolver: &mut JobInfoResolver, ) { @@ -237,11 +237,11 @@ pub fn output_largest_duration_changes( println!("# Job duration changes"); for (index, entry) in changes.into_iter().take(10).enumerate() { println!( - "{}. {}: {:.1}s -> {:.1}s ({:+.1}%)", + "{}. {}: {} -> {} ({:+.1}%)", index + 1, format_job_link(job_info_resolver, job_metrics, entry.job), - entry.before.as_secs_f64(), - entry.after.as_secs_f64(), + format_duration(entry.before), + format_duration(entry.after), entry.change ); } @@ -256,6 +256,27 @@ mostly for t-infra members, for simpler debugging of potential CI slow-downs."# }); } +fn format_duration(duration: Duration) -> String { + let total_secs = duration.as_secs(); + let hours = total_secs / 3600; + let minutes = (total_secs % 3600) / 60; + let seconds = total_secs % 60; + + let mut res = String::new(); + + if hours > 0 { + res.push_str(&format!("{hours}h ")); + } + if minutes > 0 { + res.push_str(&format!("{minutes}m ")); + } + if hours == 0 && seconds > 0 { + res.push_str(&format!("{seconds}s")); + } + + res.trim().to_string() +} + #[derive(Default)] struct TestSuiteRecord { passed: u64, diff --git a/src/ci/citool/src/main.rs b/src/ci/citool/src/main.rs index 01c0650b3c98..9b9cbe3862e3 100644 --- a/src/ci/citool/src/main.rs +++ b/src/ci/citool/src/main.rs @@ -17,7 +17,7 @@ use clap::Parser; use jobs::JobDatabase; use serde_yaml::Value; -use crate::analysis::{output_largest_duration_changes, output_test_diffs}; +use crate::analysis::{output_largest_job_duration_changes, output_test_diffs}; use crate::cpu_usage::load_cpu_usage; use crate::datadog::upload_datadog_metric; use crate::github::JobInfoResolver; @@ -205,7 +205,7 @@ And then open `test-dashboard/index.html` in your browser to see an overview of ); }); - output_largest_duration_changes(&metrics, &mut job_info_resolver); + output_largest_job_duration_changes(&metrics, &mut job_info_resolver); Ok(()) } diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 795271ee0ef0..209f4226eae7 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -370143facfb348ad3b29749c0393402d76b280c3 +44e34e1ac6d7e69b40856cf1403d3da145319c30 diff --git a/src/doc/rustc-dev-guide/src/autodiff/debugging.md b/src/doc/rustc-dev-guide/src/autodiff/debugging.md index 7c7af8589868..3b2278f1a075 100644 --- a/src/doc/rustc-dev-guide/src/autodiff/debugging.md +++ b/src/doc/rustc-dev-guide/src/autodiff/debugging.md @@ -16,7 +16,7 @@ Before generating the llvm-ir, keep in mind two techniques that can help ensure ## 1) Generate an llvm-ir reproducer ```sh -RUSTFLAGS="-Z autodiff=Enable,PrintModbefore" cargo +enzyme build --release &> out.ll +RUSTFLAGS="-Z autodiff=Enable,PrintModBefore" cargo +enzyme build --release &> out.ll ``` This also captures a few warnings and info messages above and below your module. open out.ll and remove every line above `; moduleid = `. Now look at the end of the file and remove everything that's not part of llvm-ir, i.e. remove errors and warnings. The last line of your llvm-ir should now start with `! = `, i.e. `!40831 = !{i32 0, i32 1037508, i32 1037538, i32 1037559}` or `!43760 = !dilocation(line: 297, column: 5, scope: !43746)`. diff --git a/src/doc/rustc-dev-guide/src/backend/debugging.md b/src/doc/rustc-dev-guide/src/backend/debugging.md index 84efa72f4c46..eaa9e399a052 100644 --- a/src/doc/rustc-dev-guide/src/backend/debugging.md +++ b/src/doc/rustc-dev-guide/src/backend/debugging.md @@ -6,16 +6,15 @@ [codegen]: ./codegen.md This section is about debugging compiler bugs in code generation (e.g. why the -compiler generated some piece of code or crashed in LLVM). LLVM is a big -project on its own that probably needs to have its own debugging document (not -that I could find one). But here are some tips that are important in a rustc -context: +compiler generated some piece of code or crashed in LLVM). +LLVM is a big project that probably needs to have its own debugging document, +but following are some tips that are important in a rustc context. ### Minimize the example As a general rule, compilers generate lots of information from analyzing code. -Thus, a useful first step is usually to find a minimal example. One way to do -this is to +Thus, a useful first step is usually to find a minimal example. +One way to do this is to 1. create a new crate that reproduces the issue (e.g. adding whatever crate is at fault as a dependency, and using it from there) @@ -35,14 +34,13 @@ For more discussion on methodology for steps 2 and 3 above, there is an The official compilers (including nightlies) have LLVM assertions disabled, which means that LLVM assertion failures can show up as compiler crashes (not -ICEs but "real" crashes) and other sorts of weird behavior. If you are -encountering these, it is a good idea to try using a compiler with LLVM +ICEs but "real" crashes) and other sorts of weird behavior. +If you are encountering these, it is a good idea to try using a compiler with LLVM assertions enabled - either an "alt" nightly or a compiler you build yourself -by setting `[llvm] assertions=true` in your bootstrap.toml - and see whether -anything turns up. +by setting `llvm.assertions = true` in your bootstrap.toml - and see whether anything turns up. -The rustc build process builds the LLVM tools into -`./build//llvm/bin`. They can be called directly. +The rustc build process builds the LLVM tools into `build/host/llvm/bin`. +They can be called directly. These tools include: * [`llc`], which compiles bitcode (`.bc` files) to executable code; this can be used to replicate LLVM backend bugs. @@ -55,9 +53,10 @@ These tools include: [`bugpoint`]: https://llvm.org/docs/Bugpoint.html By default, the Rust build system does not check for changes to the LLVM source code or -its build configuration settings. So, if you need to rebuild the LLVM that is linked +its build configuration settings. +So, if you need to rebuild the LLVM that is linked into `rustc`, first delete the file `.llvm-stamp`, which should be located -in `build//llvm/`. +in `build/host/llvm/`. The default rustc compilation pipeline has multiple codegen units, which is hard to replicate manually and means that LLVM is called multiple times in @@ -66,26 +65,27 @@ disappear), passing `-C codegen-units=1` to rustc will make debugging easier. ### Get your hands on raw LLVM input -For rustc to generate LLVM IR, you need to pass the `--emit=llvm-ir` flag. If -you are building via cargo, use the `RUSTFLAGS` environment variable (e.g. -`RUSTFLAGS='--emit=llvm-ir'`). This causes rustc to spit out LLVM IR into the -target directory. +For rustc to generate LLVM IR, you need to pass the `--emit=llvm-ir` flag. +If you are building via cargo, +use the `RUSTFLAGS` environment variable (e.g. `RUSTFLAGS='--emit=llvm-ir'`). +This causes rustc to spit out LLVM IR into the target directory. -`cargo llvm-ir [options] path` spits out the LLVM IR for a particular function -at `path`. (`cargo install cargo-asm` installs `cargo asm` and `cargo -llvm-ir`). `--build-type=debug` emits code for debug builds. There are also -other useful options. Also, debug info in LLVM IR can clutter the output a lot: +`cargo llvm-ir [options] path` spits out the LLVM IR for a particular function at `path`. +(`cargo install cargo-asm` installs `cargo asm` and `cargo llvm-ir`). +`--build-type=debug` emits code for debug builds. +There are also other useful options. +Also, debug info in LLVM IR can clutter the output a lot: `RUSTFLAGS="-C debuginfo=0"` is really useful. `RUSTFLAGS="-C save-temps"` outputs LLVM bitcode at -different stages during compilation, which is sometimes useful. The output LLVM -bitcode will be in `.bc` files in the compiler's output directory, set via the +different stages during compilation, which is sometimes useful. +The output LLVM bitcode will be in `.bc` files in the compiler's output directory, set via the `--out-dir DIR` argument to `rustc`. * If you are hitting an assertion failure or segmentation fault from the LLVM backend when invoking `rustc` itself, it is a good idea to try passing each - of these `.bc` files to the `llc` command, and see if you get the same - failure. (LLVM developers often prefer a bug reduced to a `.bc` file over one + of these `.bc` files to the `llc` command, and see if you get the same failure. + (LLVM developers often prefer a bug reduced to a `.bc` file over one that uses a Rust crate for its minimized reproduction.) * To get human readable versions of the LLVM bitcode, one just needs to convert @@ -100,7 +100,7 @@ you should: ```bash $ rustc +local my-file.rs --emit=llvm-ir -O -C no-prepopulate-passes \ -C codegen-units=1 -$ OPT=./build/$TRIPLE/llvm/bin/opt +$ OPT=build/$TRIPLE/llvm/bin/opt $ $OPT -S -O2 < my-file.ll > my ``` @@ -112,8 +112,8 @@ llvm-args='-filter-print-funcs=EXACT_FUNCTION_NAME` (e.g. `-C llvm-args='-filter-print-funcs=_ZN11collections3str21_$LT$impl$u20$str$GT$\ 7replace17hbe10ea2e7c809b0bE'`). -That produces a lot of output into standard error, so you'll want to pipe that -to some file. Also, if you are using neither `-filter-print-funcs` nor `-C +That produces a lot of output into standard error, so you'll want to pipe that to some file. +Also, if you are using neither `-filter-print-funcs` nor `-C codegen-units=1`, then, because the multiple codegen units run in parallel, the printouts will mix together and you won't be able to read anything. @@ -125,8 +125,8 @@ printouts will mix together and you won't be able to read anything. * Within LLVM itself, calling `F.getParent()->dump()` at the beginning of `SafeStackLegacyPass::runOnFunction` will dump the whole module, which - may provide better basis for reproduction. (However, you - should be able to get that same dump from the `.bc` files dumped by + may provide better basis for reproduction. + (However, you should be able to get that same dump from the `.bc` files dumped by `-C save-temps`.) If you want just the IR for a specific function (say, you want to see why it @@ -145,29 +145,29 @@ $ ./build/$TRIPLE/llvm/bin/llvm-extract \ If you are seeing incorrect behavior due to an optimization pass, a very handy LLVM option is `-opt-bisect-limit`, which takes an integer denoting the index -value of the highest pass to run. Index values for taken passes are stable +value of the highest pass to run. +Index values for taken passes are stable from run to run; by coupling this with software that automates bisecting the -search space based on the resulting program, an errant pass can be quickly -determined. When an `-opt-bisect-limit` is specified, all runs are displayed +search space based on the resulting program, an errant pass can be quickly determined. +When an `-opt-bisect-limit` is specified, all runs are displayed to standard error, along with their index and output indicating if the pass was run or skipped. Setting the limit to an index of -1 (e.g., `RUSTFLAGS="-C llvm-args=-opt-bisect-limit=-1"`) will show all passes and their corresponding index values. If you want to play with the optimization pipeline, you can use the [`opt`] tool -from `./build//llvm/bin/` with the LLVM IR emitted by rustc. +from `./build/host/llvm/bin/` with the LLVM IR emitted by rustc. When investigating the implementation of LLVM itself, you should be aware of its [internal debug infrastructure][llvm-debug]. This is provided in LLVM Debug builds, which you enable for rustc LLVM builds by changing this setting in the bootstrap.toml: ``` -[llvm] # Indicates whether the LLVM assertions are enabled or not -assertions = true +llvm.assertions = true # Indicates whether the LLVM build is a Release or Debug build -optimize = false +llvm.optimize = false ``` The quick summary is: * Setting `assertions=true` enables coarse-grain debug messaging. @@ -190,8 +190,8 @@ specifically the `#t-compiler/wg-llvm` channel. ### Compiler options to know and love The `-C help` and `-Z help` compiler switches will list out a variety -of interesting options you may find useful. Here are a few of the most -common that pertain to LLVM development (some of them are employed in the +of interesting options you may find useful. +Here are a few of the most common that pertain to LLVM development (some of them are employed in the tutorial above): - The `--emit llvm-ir` option emits a `.ll` file with LLVM IR in textual format @@ -201,7 +201,8 @@ tutorial above): e.g. `-C llvm-args=-print-before-all` to print IR before every LLVM pass. - The `-C no-prepopulate-passes` will avoid pre-populate the LLVM pass - manager with a list of passes. This will allow you to view the LLVM + manager with a list of passes. + This will allow you to view the LLVM IR that rustc generates, not the LLVM IR after optimizations. - The `-C passes=val` option allows you to supply a space separated list of extra LLVM passes to run - The `-C save-temps` option saves all temporary output files during compilation @@ -211,18 +212,17 @@ tutorial above): - The `-Z no-parallel-backend` will disable parallel compilation of distinct compilation units - The `-Z llvm-time-trace` option will output a Chrome profiler compatible JSON file which contains details and timings for LLVM passes. -- The `-C llvm-args=-opt-bisect-limit=` option allows for bisecting LLVM - optimizations. +- The `-C llvm-args=-opt-bisect-limit=` option allows for bisecting LLVM optimizations. ### Filing LLVM bug reports When filing an LLVM bug report, you will probably want some sort of minimal -working example that demonstrates the problem. The Godbolt compiler explorer is -really helpful for this. +working example that demonstrates the problem. +The Godbolt compiler explorer is really helpful for this. 1. Once you have some LLVM IR for the problematic code (see above), you can -create a minimal working example with Godbolt. Go to -[llvm.godbolt.org](https://llvm.godbolt.org). +create a minimal working example with Godbolt. +Go to [llvm.godbolt.org](https://llvm.godbolt.org). 2. Choose `LLVM-IR` as programming language. @@ -230,8 +230,7 @@ create a minimal working example with Godbolt. Go to - There are some useful flags: `-mattr` enables target features, `-march=` selects the target, `-mcpu=` selects the CPU, etc. - Commands like `llc -march=help` output all architectures available, which - is useful because sometimes the Rust arch names and the LLVM names do not - match. + is useful because sometimes the Rust arch names and the LLVM names do not match. - If you have compiled rustc yourself somewhere, in the target directory you have binaries for `llc`, `opt`, etc. @@ -239,7 +238,8 @@ create a minimal working example with Godbolt. Go to optimizations transform it. 5. Once you have a godbolt link demonstrating the issue, it is pretty easy to - fill in an LLVM bug. Just visit their [github issues page][llvm-issues]. + fill in an LLVM bug. + Just visit their [github issues page][llvm-issues]. [llvm-issues]: https://github.com/llvm/llvm-project/issues @@ -251,8 +251,8 @@ gotten the fix yet (or perhaps you are familiar enough with LLVM to fix it yours In that case, we can sometimes opt to port the fix for the bug directly to our own LLVM fork, so that rustc can use it more easily. -Our fork of LLVM is maintained in [rust-lang/llvm-project]. Once -you've landed the fix there, you'll also need to land a PR modifying +Our fork of LLVM is maintained in [rust-lang/llvm-project]. +Once you've landed the fix there, you'll also need to land a PR modifying our submodule commits -- ask around on Zulip for help. [rust-lang/llvm-project]: https://github.com/rust-lang/llvm-project/ diff --git a/src/doc/rustc-dev-guide/src/backend/updating-llvm.md b/src/doc/rustc-dev-guide/src/backend/updating-llvm.md index 70deeebf8bec..3d0e130b6aaa 100644 --- a/src/doc/rustc-dev-guide/src/backend/updating-llvm.md +++ b/src/doc/rustc-dev-guide/src/backend/updating-llvm.md @@ -3,16 +3,16 @@ Rust supports building against multiple LLVM versions: -* Tip-of-tree for the current LLVM development branch is usually supported - within a few days. PRs for such fixes are tagged with `llvm-main`. +* Tip-of-tree for the current LLVM development branch is usually supported within a few days. + PRs for such fixes are tagged with `llvm-main`. * The latest released major version is always supported. * The one or two preceding major versions are usually supported. By default, Rust uses its own fork in the [rust-lang/llvm-project repository]. This fork is based on a `release/$N.x` branch of the upstream project, where `$N` is either the latest released major version, or the current major version -in release candidate phase. The fork is never based on the `main` development -branch. +in release candidate phase. +The fork is never based on the `main` development branch. Our LLVM fork only accepts: @@ -32,16 +32,15 @@ There are three types of LLVM updates, with different procedures: ## Backports (upstream supported) While the current major LLVM version is supported upstream, fixes should be -backported upstream first, and the release branch then merged back into the -Rust fork. +backported upstream first, and the release branch then merged back into the Rust fork. 1. Make sure the bugfix is in upstream LLVM. -2. If this hasn't happened already, request a backport to the upstream release - branch. If you have LLVM commit access, follow the [backport process]. - Otherwise, open an issue requesting the backport. Continue once the - backport has been approved and merged. -3. Identify the branch that rustc is currently using. The `src/llvm-project` - submodule is always pinned to a branch of the +2. If this hasn't happened already, request a backport to the upstream release branch. + If you have LLVM commit access, follow the [backport process]. + Otherwise, open an issue requesting the backport. + Continue once the backport has been approved and merged. +3. Identify the branch that rustc is currently using. + The `src/llvm-project` submodule is always pinned to a branch of the [rust-lang/llvm-project repository]. 4. Fork the rust-lang/llvm-project repository. 5. Check out the appropriate branch (typically named `rustc/a.b-yyyy-mm-dd`). @@ -51,26 +50,23 @@ Rust fork. 7. Merge the `upstream/release/$N.x` branch. 8. Push this branch to your fork. 9. Send a Pull Request to rust-lang/llvm-project to the same branch as before. - Be sure to reference the Rust and/or LLVM issue that you're fixing in the PR - description. + Be sure to reference the Rust and/or LLVM issue that you're fixing in the PR description. 10. Wait for the PR to be merged. -11. Send a PR to rust-lang/rust updating the `src/llvm-project` submodule with - your bugfix. This can be done locally with `git submodule update --remote - src/llvm-project` typically. +11. Send a PR to rust-lang/rust updating the `src/llvm-project` submodule with your bugfix. + This can be done locally with `git submodule update --remote src/llvm-project` typically. 12. Wait for PR to be merged. -An example PR: -[#59089](https://github.com/rust-lang/rust/pull/59089) +An example PR: [#59089](https://github.com/rust-lang/rust/pull/59089) ## Backports (upstream not supported) -Upstream LLVM releases are only supported for two to three months after the -GA release. Once upstream backports are no longer accepted, changes should be +Upstream LLVM releases are only supported for two to three months after the GA release. +Once upstream backports are no longer accepted, changes should be cherry-picked directly to our fork. 1. Make sure the bugfix is in upstream LLVM. -2. Identify the branch that rustc is currently using. The `src/llvm-project` - submodule is always pinned to a branch of the +2. Identify the branch that rustc is currently using. + The `src/llvm-project` submodule is always pinned to a branch of the [rust-lang/llvm-project repository]. 3. Fork the rust-lang/llvm-project repository. 4. Check out the appropriate branch (typically named `rustc/a.b-yyyy-mm-dd`). @@ -80,16 +76,13 @@ cherry-picked directly to our fork. 6. Cherry-pick the relevant commit(s) using `git cherry-pick -x`. 7. Push this branch to your fork. 8. Send a Pull Request to rust-lang/llvm-project to the same branch as before. - Be sure to reference the Rust and/or LLVM issue that you're fixing in the PR - description. + Be sure to reference the Rust and/or LLVM issue that you're fixing in the PR description. 9. Wait for the PR to be merged. -10. Send a PR to rust-lang/rust updating the `src/llvm-project` submodule with - your bugfix. This can be done locally with `git submodule update --remote - src/llvm-project` typically. +10. Send a PR to rust-lang/rust updating the `src/llvm-project` submodule with your bugfix. + This can be done locally with `git submodule update --remote src/llvm-project` typically. 11. Wait for PR to be merged. -An example PR: -[#59089](https://github.com/rust-lang/rust/pull/59089) +An example PR: [#59089](https://github.com/rust-lang/rust/pull/59089) ## New LLVM Release Updates @@ -110,14 +103,12 @@ so let's go through each in detail. 1. Create a new branch in the [rust-lang/llvm-project repository] from this `release/$N.x` branch, and name it `rustc/a.b-yyyy-mm-dd`, - where `a.b` is the current version number of LLVM in-tree - at the time of the branch, + where `a.b` is the current version number of LLVM in-tree at the time of the branch, and the remaining part is the current date. 1. Apply Rust-specific patches to the llvm-project repository. All features and bugfixes are upstream, - but there's often some weird build-related patches - that don't make sense to upstream. + but there's often some weird build-related patches that don't make sense to upstream. These patches are typically the latest patches in the rust-lang/llvm-project branch that rustc is currently using. @@ -145,13 +136,14 @@ so let's go through each in detail. This is done by having the following setting in `bootstrap.toml`: ```toml - [llvm] - download-ci-llvm = false + llvm.download-ci-llvm = false ``` -1. Test for regressions across other platforms. LLVM often has at least one bug +1. Test for regressions across other platforms. + LLVM often has at least one bug for non-tier-1 architectures, so it's good to do some more testing before - sending this to bors! If you're low on resources you can send the PR as-is + sending this to bors! + If you're low on resources you can send the PR as-is now to bors, though, and it'll get tested anyway. Ideally, build LLVM and test it on a few platforms: @@ -168,9 +160,11 @@ so let's go through each in detail. * `./src/ci/docker/run.sh dist-various-2` * `./src/ci/docker/run.sh armhf-gnu` -1. Prepare a PR to `rust-lang/rust`. Work with maintainers of +1. Prepare a PR to `rust-lang/rust`. + Work with maintainers of `rust-lang/llvm-project` to get your commit in a branch of that repository, - and then you can send a PR to `rust-lang/rust`. You'll change at least + and then you can send a PR to `rust-lang/rust`. + You'll change at least `src/llvm-project` and will likely also change [`llvm-wrapper`] as well. @@ -192,14 +186,12 @@ so let's go through each in detail. We will often want to have those bug fixes as well. The merge process for that is to use `git merge` itself to merge LLVM's `release/a.b` branch with the branch created in step 2. - This is typically - done multiple times when necessary while LLVM's release branch is baking. + This is typically done multiple times when necessary while LLVM's release branch is baking. 1. LLVM then announces the release of version `a.b`. 1. After LLVM's official release, - we follow the process of creating a new branch on the - rust-lang/llvm-project repository again, + we follow the process of creating a new branch on the rust-lang/llvm-project repository again, this time with a new date. It is only then that the PR to update Rust to use that version is merged. diff --git a/src/doc/rustc-dev-guide/src/building/compiler-documenting.md b/src/doc/rustc-dev-guide/src/building/compiler-documenting.md index b031462ea15f..026a42907493 100644 --- a/src/doc/rustc-dev-guide/src/building/compiler-documenting.md +++ b/src/doc/rustc-dev-guide/src/building/compiler-documenting.md @@ -39,8 +39,7 @@ like the standard library (std) or the compiler (rustc). To create it by default with `x doc`, modify `bootstrap.toml`: ```toml - [build] - compiler-docs = true + build.compiler-docs = true ``` Note that when enabled, diff --git a/src/doc/rustc-dev-guide/src/building/how-to-build-and-run.md b/src/doc/rustc-dev-guide/src/building/how-to-build-and-run.md index d0a38b12c550..fc8fe402bfa9 100644 --- a/src/doc/rustc-dev-guide/src/building/how-to-build-and-run.md +++ b/src/doc/rustc-dev-guide/src/building/how-to-build-and-run.md @@ -4,22 +4,25 @@ For `profile = "library"` users, or users who use `download-rustc = true | "if-unchanged"`, please be advised that the `./x test library/std` flow where `download-rustc` is active (i.e. no compiler changes) is currently broken. -This is tracked in . Only the `./x test` flow is affected in this +This is tracked in . +Only the `./x test` flow is affected in this case, `./x {check,build} library/std` should still work. -In the short-term, you may need to disable `download-rustc` for `./x test library/std`. This can be done either by: +In the short-term, you may need to disable `download-rustc` for `./x test library/std`. +This can be done either by: 1. `./x test library/std --set rust.download-rustc=false` -2. Or set `rust.download-rustc=false` in `bootstrap.toml`. +2. Or set `rust.download-rustc = false` in `bootstrap.toml`. -Unfortunately that will require building the stage 1 compiler. The bootstrap team is working on this, but +Unfortunately that will require building the stage 1 compiler. +The bootstrap team is working on this, but implementing a maintainable fix is taking some time. -The compiler is built using a tool called `x.py`. You will need to -have Python installed to run it. +The compiler is built using a tool called `x.py`. +You will need to have Python installed to run it. ## Quick Start @@ -28,7 +31,8 @@ For a less in-depth quick-start of getting the compiler running, see [quickstart ## Get the source code -The main repository is [`rust-lang/rust`][repo]. This contains the compiler, +The main repository is [`rust-lang/rust`][repo]. +This contains the compiler, the standard library (including `core`, `alloc`, `test`, `proc_macro`, etc), and a bunch of tools (e.g. `rustdoc`, the bootstrapping infrastructure, etc). @@ -86,8 +90,8 @@ cd rust ## What is `x.py`? -`x.py` is the build tool for the `rust` repository. It can build docs, run tests, and compile the -compiler and standard library. +`x.py` is the build tool for the `rust` repository. +It can build docs, run tests, and build the compiler and standard library. This chapter focuses on the basics to be productive, but if you want to learn more about `x.py`, [read this chapter][bootstrap]. @@ -103,11 +107,13 @@ Also, using `x` rather than `x.py` is recommended as: (You can find the platform related scripts around the `x.py`, like `x.ps1`) -Notice that this is not absolute. For instance, using Nushell in VSCode on Win10, -typing `x` or `./x` still opens `x.py` in an editor rather than invoking the program. :) +Notice that this is not absolute. +For instance, using Nushell in VSCode on Win10, +typing `x` or `./x` still opens `x.py` in an editor rather than invoking the program. +:) -In the rest of this guide, we use `x` rather than `x.py` directly. The following -command: +In the rest of this guide, we use `x` rather than `x.py` directly. +The following command: ```bash ./x check @@ -164,9 +170,10 @@ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser #### Running `x.py` slightly more conveniently -There is a binary that wraps `x.py` called `x` in `src/tools/x`. All it does is -run `x.py`, but it can be installed system-wide and run from any subdirectory -of a checkout. It also looks up the appropriate version of `python` to use. +There is a binary that wraps `x.py` called `x` in `src/tools/x`. +All it does is run `x.py`, but it can be installed system-wide and run from any subdirectory +of a checkout. +It also looks up the appropriate version of `python` to use. You can install it with `cargo install --path src/tools/x`. @@ -177,18 +184,20 @@ shell to run the platform related scripts. ## Create a `bootstrap.toml` -To start, run `./x setup` and select the `compiler` defaults. This will do some initialization -and create a `bootstrap.toml` for you with reasonable defaults. If you use a different default (which +To start, run `./x setup` and select the `compiler` defaults. +This will do some initialization and create a `bootstrap.toml` for you with reasonable defaults. +If you use a different default (which you'll likely want to do if you want to contribute to an area of rust other than the compiler, such as rustdoc), make sure to read information about that default (located in `src/bootstrap/defaults`) as the build process may be different for other defaults. -Alternatively, you can write `bootstrap.toml` by hand. See `bootstrap.example.toml` for all the available -settings and explanations of them. See `src/bootstrap/defaults` for common settings to change. +Alternatively, you can write `bootstrap.toml` by hand. +See `bootstrap.example.toml` for all the available settings and what they do. +See `src/bootstrap/defaults` for common settings to change. If you have already built `rustc` and you change settings related to LLVM, then you may have to -execute `./x clean --all` for subsequent configuration changes to take effect. Note that `./x -clean` will not cause a rebuild of LLVM. +execute `./x clean --all` for subsequent configuration changes to take effect. +Note that `./x clean` will not cause a rebuild of LLVM. ## Common `x` commands @@ -202,28 +211,28 @@ working on `rustc`, `std`, `rustdoc`, and other tools. | `./x test` | Runs all tests | | `./x fmt` | Formats all code | -As written, these commands are reasonable starting points. However, there are -additional options and arguments for each of them that are worth learning for -serious development work. In particular, `./x build` and `./x test` -provide many ways to compile or test a subset of the code, which can save a lot -of time. +As written, these commands are reasonable starting points. +However, there are additional options and arguments for each of them that are worth learning for +serious development work. +In particular, `./x build` and `./x test` +provide many ways to compile or test a subset of the code, which can save a lot of time. Also, note that `x` supports all kinds of path suffixes for `compiler`, `library`, -and `src/tools` directories. So, you can simply run `x test tidy` instead of -`x test src/tools/tidy`. Or, `x build std` instead of `x build library/std`. +and `src/tools` directories. +So, you can simply run `x test tidy` instead of `x test src/tools/tidy`. +Or, `x build std` instead of `x build library/std`. [rust-analyzer]: suggested.html#configuring-rust-analyzer-for-rustc -See the chapters on -[testing](../tests/running.md) and [rustdoc](../rustdoc.md) for more details. +See the chapters on [testing](../tests/running.md) and [rustdoc](../rustdoc.md) for more details. ### Building the compiler Note that building will require a relatively large amount of storage space. You may want to have upwards of 10 or 15 gigabytes available to build the compiler. -Once you've created a `bootstrap.toml`, you are now ready to run -`x`. There are a lot of options here, but let's start with what is +Once you've created a `bootstrap.toml`, you are now ready to run `x`. +There are a lot of options here, but let's start with what is probably the best "go to" command for building a local compiler: ```console @@ -236,8 +245,7 @@ What this command does is: - Assemble a working stage1 sysroot, containing the stage1 compiler and stage1 standard libraries. This final product (stage1 compiler + libs built using that compiler) -is what you need to build other Rust programs (unless you use `#![no_std]` or -`#![no_core]`). +is what you need to build other Rust programs (unless you use `#![no_std]` or `#![no_core]`). You will probably find that building the stage1 `std` is a bottleneck for you, but fear not, there is a (hacky) workaround... @@ -245,12 +253,12 @@ see [the section on avoiding rebuilds for std][keep-stage]. [keep-stage]: ./suggested.md#faster-rebuilds-with---keep-stage-std -Sometimes you don't need a full build. When doing some kind of -"type-based refactoring", like renaming a method, or changing the +Sometimes you don't need a full build. +When doing some kind of "type-based refactoring", like renaming a method, or changing the signature of some function, you can use `./x check` instead for a much faster build. -Note that this whole command just gives you a subset of the full `rustc` -build. The **full** `rustc` build (what you get with `./x build +Note that this whole command just gives you a subset of the full `rustc` build. +The **full** `rustc` build (what you get with `./x build --stage 2 rustc`) has quite a few more steps: - Build `rustc` with the stage1 compiler. @@ -262,8 +270,8 @@ You almost never need to do this. ### Build specific components If you are working on the standard library, you probably don't need to build -every other default component. Instead, you can build a specific component by -providing its name, like this: +every other default component. +Instead, you can build a specific component by providing its name, like this: ```bash ./x build --stage 1 library @@ -275,12 +283,13 @@ default). ## Creating a rustup toolchain Once you have successfully built `rustc`, you will have created a bunch -of files in your `build` directory. In order to actually run the -resulting `rustc`, we recommend creating rustup toolchains. The first -command listed below creates the stage1 toolchain, which was built in the -steps above, with the name `stage1`. The second command creates the stage2 -toolchain using the stage1 compiler. This will be needed in the future -if running the entire test suite, but will not be built in this page. +of files in your `build` directory. +In order to actually run the resulting `rustc`, we recommend creating rustup toolchains. +The first command listed below creates the stage1 toolchain, which was built in the +steps above, with the name `stage1`. +The second command creates the stage2 toolchain using the stage1 compiler. +This will be needed in the future +if running the entire test suite, but will not be built in this page. Building stage2 is done with the same `./x build` command as for stage1, specifying that the stage is 2 instead. @@ -289,8 +298,8 @@ rustup toolchain link stage1 build/host/stage1 rustup toolchain link stage2 build/host/stage2 ``` -Now you can run the `rustc` you built with via the toolchain. If you run with -`-vV`, you should see a version number ending in `-dev`, indicating a build from +Now you can run the `rustc` you built with via the toolchain. +If you run with `-vV`, you should see a version number ending in `-dev`, indicating a build from your local environment: ```bash @@ -308,15 +317,19 @@ The rustup toolchain points to the specified toolchain compiled in your `build` so the rustup toolchain will be updated whenever `x build` or `x test` are run for that toolchain/stage. -**Note:** the toolchain we've built does not include `cargo`. In this case, `rustup` will +**Note:** the toolchain we've built does not include `cargo`. + In this case, `rustup` will fall back to using `cargo` from the installed `nightly`, `beta`, or `stable` toolchain -(in that order). If you need to use unstable `cargo` flags, be sure to run -`rustup install nightly` if you haven't already. See the +(in that order). + If you need to use unstable `cargo` flags, be sure to run +`rustup install nightly` if you haven't already. + See the [rustup documentation on custom toolchains](https://rust-lang.github.io/rustup/concepts/toolchains.html#custom-toolchains). **Note:** rust-analyzer and IntelliJ Rust plugin use a component called -`rust-analyzer-proc-macro-srv` to work with proc macros. If you intend to use a -custom toolchain for a project (e.g. via `rustup override set stage1`) you may +`rust-analyzer-proc-macro-srv` to work with proc macros. +If you intend to use a +custom toolchain for a project (e.g. via `rustup override set stage1`), you may want to build this component: ```bash @@ -342,8 +355,7 @@ If you want to always build for other targets without needing to pass flags to ` you can configure this in the `[build]` section of your `bootstrap.toml` like so: ```toml -[build] -target = ["x86_64-unknown-linux-gnu", "wasm32-wasip1"] +build.target = ["x86_64-unknown-linux-gnu", "wasm32-wasip1"] ``` Note that building for some targets requires having external dependencies installed @@ -369,16 +381,14 @@ cargo +stage1 build --target wasm32-wasip1 ## Other `x` commands -Here are a few other useful `x` commands. We'll cover some of them in detail -in other sections: +Here are a few other useful `x` commands. +We'll cover some of them in detail in other sections: - Building things: - `./x build` – builds everything using the stage 1 compiler, not just up to `std` - - `./x build --stage 2` – builds everything with the stage 2 compiler including - `rustdoc` -- Running tests (see the [section on running tests](../tests/running.html) for - more details): + - `./x build --stage 2` – builds everything with the stage 2 compiler including `rustdoc` +- Running tests (see the [section on running tests](../tests/running.html) for more details): - `./x test library/std` – runs the unit tests and integration tests from `std` - `./x test tests/ui` – runs the `ui` test suite - `./x test tests/ui/const-generics` - runs all the tests in @@ -390,8 +400,8 @@ in other sections: Sometimes you need to start fresh, but this is normally not the case. If you need to run this then bootstrap is most likely not acting right and -you should file a bug as to what is going wrong. If you do need to clean -everything up then you only need to run one command! +you should file a bug as to what is going wrong. +If you do need to clean everything up then you only need to run one command! ```bash ./x clean @@ -403,15 +413,17 @@ a long time even on fast computers. ## Remarks on disk space Building the compiler (especially if beyond stage 1) can require significant amounts of free disk -space, possibly around 100GB. This is compounded if you have a separate build directory for +space, possibly around 100GB. +This is compounded if you have a separate build directory for rust-analyzer (e.g. `build-rust-analyzer`). This is easy to hit with dev-desktops which have a [set disk quota](https://github.com/rust-lang/simpleinfra/blob/8a59e4faeb75a09b072671c74a7cb70160ebef50/ansible/roles/dev-desktop/defaults/main.yml#L7) -for each user, but this also applies to local development as well. Occasionally, you may need to: +for each user, but this also applies to local development as well. +Occasionally, you may need to: - Remove `build/` directory. - Remove `build-rust-analyzer/` directory (if you have a separate rust-analyzer build directory). -- Uninstall unnecessary toolchains if you use `cargo-bisect-rustc`. You can check which toolchains - are installed with `rustup toolchain list`. +- Uninstall unnecessary toolchains if you use `cargo-bisect-rustc`. + You can check which toolchains are installed with `rustup toolchain list`. [^1]: issue[#1707](https://github.com/rust-lang/rustc-dev-guide/issues/1707) diff --git a/src/doc/rustc-dev-guide/src/building/new-target.md b/src/doc/rustc-dev-guide/src/building/new-target.md index d0ed787f520a..73174aaa0adf 100644 --- a/src/doc/rustc-dev-guide/src/building/new-target.md +++ b/src/doc/rustc-dev-guide/src/building/new-target.md @@ -52,7 +52,7 @@ own preinstalled LLVM, you will need to provide `FileCheck` in some other way. On Debian-based systems, you can install the `llvm-N-tools` package (where `N` is the LLVM version number, e.g. `llvm-8-tools`). Alternately, you can specify the path to `FileCheck` with the `llvm-filecheck` config item in `bootstrap.toml` -or you can disable codegen test with the `codegen-tests` item in `bootstrap.toml`. +or you can disable codegen test with the `rust.codegen-tests` item in `bootstrap.toml`. ## Creating a target specification @@ -72,8 +72,7 @@ somewhat successfully, you can copy the specification into the compiler itself. You will need to add a line to the big table inside of the `supported_targets` macro in the `rustc_target::spec` module. -You will then add a corresponding file for your new target containing a -`target` function. +You will then add a corresponding file for your new target containing a `target` function. Look for existing targets to use as examples. @@ -118,16 +117,10 @@ After this, run `cargo update -p libc` to update the lockfiles. Beware that if you patch to a local `path` dependency, this will enable warnings for that dependency. Some dependencies are not warning-free, and due -to the `deny-warnings` setting in `bootstrap.toml`, the build may suddenly start to fail. +to the `rust.deny-warnings` setting in `bootstrap.toml`, the build may suddenly start to fail. To work around warnings, you may want to: - Modify the dependency to remove the warnings -- Or for local development purposes, suppress the warnings by setting deny-warnings = false in bootstrap.toml. - -```toml -# bootstrap.toml -[rust] -deny-warnings = false -``` +- Or for local development purposes, suppress the warnings by setting `rust.deny-warnings = false` in bootstrap.toml. [`libc`]: https://crates.io/crates/libc [`cc`]: https://crates.io/crates/cc diff --git a/src/doc/rustc-dev-guide/src/building/optimized-build.md b/src/doc/rustc-dev-guide/src/building/optimized-build.md index 46def66d1782..0a6b82eba46a 100644 --- a/src/doc/rustc-dev-guide/src/building/optimized-build.md +++ b/src/doc/rustc-dev-guide/src/building/optimized-build.md @@ -2,20 +2,20 @@ There are multiple additional build configuration options and techniques that can be used to compile a build of `rustc` that is as optimized as possible (for example when building `rustc` for a Linux -distribution). The status of these configuration options for various Rust targets is tracked [here]. +distribution). +The status of these configuration options for various Rust targets is tracked [here]. This page describes how you can use these approaches when building `rustc` yourself. [here]: https://github.com/rust-lang/rust/issues/103595 ## Link-time optimization -Link-time optimization is a powerful compiler technique that can increase program performance. To -enable (Thin-)LTO when building `rustc`, set the `rust.lto` config option to `"thin"` +Link-time optimization is a powerful compiler technique that can increase program performance. +To enable (Thin-)LTO when building `rustc`, set the `rust.lto` config option to `"thin"` in `bootstrap.toml`: ```toml -[rust] -lto = "thin" +rust.lto = "thin" ``` > Note that LTO for `rustc` is currently supported and tested only for @@ -30,13 +30,12 @@ Enabling LTO on Linux has [produced] speed-ups by up to 10%. ## Memory allocator -Using a different memory allocator for `rustc` can provide significant performance benefits. If you -want to enable the `jemalloc` allocator, you can set the `rust.jemalloc` option to `true` +Using a different memory allocator for `rustc` can provide significant performance benefits. +If you want to enable the `jemalloc` allocator, you can set the `rust.jemalloc` option to `true` in `bootstrap.toml`: ```toml -[rust] -jemalloc = true +rust.jemalloc = true ``` > Note that this option is currently only supported for Linux and macOS targets. @@ -48,17 +47,16 @@ You can modify the number of codegen units for `rustc` and `libstd` in `bootstra following options: ```toml -[rust] -codegen-units = 1 -codegen-units-std = 1 +rust.codegen-units = 1 +rust.codegen-units-std = 1 ``` ## Instruction set By default, `rustc` is compiled for a generic (and conservative) instruction set architecture -(depending on the selected target), to make it support as many CPUs as possible. If you want to -compile `rustc` for a specific instruction set architecture, you can set the `target_cpu` compiler -option in `RUSTFLAGS`: +(depending on the selected target), to make it support as many CPUs as possible. +If you want to compile `rustc` for a specific instruction set architecture, +you can set the `target_cpu` compiler option in `RUSTFLAGS`: ```bash RUSTFLAGS="-C target_cpu=x86-64-v3" ./x build ... @@ -68,22 +66,23 @@ If you also want to compile LLVM for a specific instruction set, you can set `ll in `bootstrap.toml`: ```toml -[llvm] -cxxflags = "-march=x86-64-v3" -cflags = "-march=x86-64-v3" +llvm.cxxflags = "-march=x86-64-v3" +llvm.cflags = "-march=x86-64-v3" ``` ## Profile-guided optimization Applying profile-guided optimizations (or more generally, feedback-directed optimizations) can -produce a large increase to `rustc` performance, by up to 15% ([1], [2]). However, these techniques +produce a large increase to `rustc` performance, by up to 15% ([1], [2]). +However, these techniques are not simply enabled by a configuration option, but rather they require a complex build workflow that compiles `rustc` multiple times and profiles it on selected benchmarks. There is a tool called `opt-dist` that is used to optimize `rustc` with [PGO] (profile-guided -optimizations) and [BOLT] (a post-link binary optimizer) for builds distributed to end users. You -can examine the tool, which is located in `src/tools/opt-dist`, and build a custom PGO build -workflow based on it, or try to use it directly. Note that the tool is currently quite hardcoded to +optimizations) and [BOLT] (a post-link binary optimizer) for builds distributed to end users. +You can examine the tool, which is located in `src/tools/opt-dist`, and build a custom PGO build +workflow based on it, or try to use it directly. +Note that the tool is currently quite hardcoded to the way we use it in Rust's continuous integration workflows, and it might require some custom changes to make it work in a different environment. @@ -97,9 +96,9 @@ changes to make it work in a different environment. To use the tool, you will need to provide some external dependencies: - A Python3 interpreter (for executing `x.py`). -- Compiled LLVM toolchain, with the `llvm-profdata` binary. Optionally, if you want to use BOLT, - the `llvm-bolt` and - `merge-fdata` binaries have to be available in the toolchain. +- Compiled LLVM toolchain, with the `llvm-profdata` binary. + Optionally, if you want to use BOLT, + the `llvm-bolt` and `merge-fdata` binaries have to be available in the toolchain. These dependencies are provided to `opt-dist` by an implementation of the [`Environment`] struct. It specifies directories where will the PGO/BOLT pipeline take place, and also external dependencies @@ -108,9 +107,8 @@ like Python or LLVM. Here is an example of how can `opt-dist` be used locally (outside of CI): 1. Enable metrics in your `bootstrap.toml` file, because `opt-dist` expects it to be enabled: - ```toml - [build] - metrics = true + ```toml + build.metrics = true ``` 2. Build the tool with the following command: ```bash diff --git a/src/doc/rustc-dev-guide/src/building/suggested.md b/src/doc/rustc-dev-guide/src/building/suggested.md index b5c9b9b4e3d1..3ed4f129d144 100644 --- a/src/doc/rustc-dev-guide/src/building/suggested.md +++ b/src/doc/rustc-dev-guide/src/building/suggested.md @@ -118,8 +118,7 @@ requires extra disk space. Selecting `vscode` in `./x setup editor` will prompt you to create a `.vscode/settings.json` file which will configure Visual Studio code. -The recommended `rust-analyzer` settings live at -[`src/etc/rust_analyzer_settings.json`]. +The recommended `rust-analyzer` settings live at [`src/etc/rust_analyzer_settings.json`]. If running `./x check` on save is inconvenient, in VS Code you can use a [Build Task] instead: @@ -253,8 +252,7 @@ It can be configured through `.zed/settings.json`, as described [here](https://zed.dev/docs/configuring-languages). Selecting `zed` in `./x setup editor` will prompt you to create a `.zed/settings.json` file which will configure Zed with the recommended configuration. -The recommended `rust-analyzer` settings live -at [`src/etc/rust_analyzer_zed.json`]. +The recommended `rust-analyzer` settings live at [`src/etc/rust_analyzer_zed.json`]. ## Check, check, and check again @@ -277,14 +275,14 @@ This often helps reviewing. ## Configuring `rustup` to use nightly Some parts of the bootstrap process uses pinned, nightly versions of tools like rustfmt. -To make things like `cargo fmt` work correctly in your repo, run +To make things like `cargo fmt` work correctly in your repo, +[install a nightly toolchain] with rustup, then run this command: ```console cd rustup override set nightly ``` -After [installing a nightly toolchain] with `rustup`. Don't forget to do this for all directories you have [setup a worktree for]. You may need to use the pinned nightly version from `src/stage0`, but often the normal `nightly` channel will work. @@ -297,7 +295,7 @@ toolchain for your bootstrapped compiler You still have to use `x` to work on the compiler or standard library, this just lets you use `cargo fmt`. -[installing a nightly toolchain]: https://rust-lang.github.io/rustup/concepts/channels.html?highlight=nightl#working-with-nightly-rust +[install a nightly toolchain]: https://rust-lang.github.io/rustup/concepts/channels.html?highlight=nightl#working-with-nightly-rust [setup a worktree for]: ./suggested.md#working-on-multiple-branches-at-the-same-time [the section on vscode]: suggested.md#configuring-rust-analyzer-for-rustc [the section on rustup]: how-to-build-and-run.md?highlight=rustup#creating-a-rustup-toolchain @@ -441,7 +439,7 @@ ln -s ./src/tools/nix-dev-shell/envrc-shell ./.envrc # Use nix-shell ### Note Note that when using nix on a not-NixOS distribution, it may be necessary to set -**`patch-binaries-for-nix = true` in `bootstrap.toml`**. Bootstrap tries to detect +**`build.patch-binaries-for-nix = true` in `bootstrap.toml`**. Bootstrap tries to detect whether it's running in nix and enable patching automatically, but this detection can have false negatives. diff --git a/src/doc/rustc-dev-guide/src/compiler-debugging.md b/src/doc/rustc-dev-guide/src/compiler-debugging.md index 1944bf53378d..d4c599a6edf7 100644 --- a/src/doc/rustc-dev-guide/src/compiler-debugging.md +++ b/src/doc/rustc-dev-guide/src/compiler-debugging.md @@ -1,26 +1,27 @@ # Debugging the compiler -This chapter contains a few tips to debug the compiler. These tips aim to be -useful no matter what you are working on. Some of the other chapters have +This chapter contains a few tips to debug the compiler. +These tips aim to be useful no matter what you are working on. +Some of the other chapters have advice about specific parts of the compiler (e.g. the [Queries Debugging and Testing chapter](./incrcomp-debugging.html) or the [LLVM Debugging chapter](./backend/debugging.md)). ## Configuring the compiler -By default, rustc is built without most debug information. To enable debug info, -set `debug = true` in your bootstrap.toml. +By default, rustc is built without most debug information. +To enable debug info, +set `rust.debug = true` in your bootstrap.toml. -Setting `debug = true` turns on many different debug options (e.g., `debug-assertions`, +Setting `rust.debug = true` turns on many different debug options (e.g., `debug-assertions`, `debug-logging`, etc.) which can be individually tweaked if you want to, but many people -simply set `debug = true`. +simply set `rust.debug = true`. If you want to use GDB to debug rustc, please set `bootstrap.toml` with options: ```toml -[rust] -debug = true -debuginfo-level = 2 +rust.debug = true +rust.debuginfo-level = 2 ``` > NOTE: @@ -36,8 +37,7 @@ This requires at least GDB v10.2, otherwise you need to disable new symbol-mangling-version in `bootstrap.toml`. ```toml -[rust] -new-symbol-mangling = false +rust.new-symbol-mangling = false ``` > See the comments in `bootstrap.example.toml` for more info. @@ -47,16 +47,16 @@ You will need to rebuild the compiler after changing any configuration option. ## Suppressing the ICE file By default, if rustc encounters an Internal Compiler Error (ICE) it will dump the ICE contents to an -ICE file within the current working directory named `rustc-ice--.txt`. If this is -not desirable, you can prevent the ICE file from being created with `RUSTC_ICE=0`. +ICE file within the current working directory named `rustc-ice--.txt`. +If this is not desirable, you can prevent the ICE file from being created with `RUSTC_ICE=0`. ## Getting a backtrace [getting-a-backtrace]: #getting-a-backtrace When you have an ICE (panic in the compiler), you can set -`RUST_BACKTRACE=1` to get the stack trace of the `panic!` like in -normal Rust programs. IIRC backtraces **don't work** on MinGW, -sorry. If you have trouble or the backtraces are full of `unknown`, +`RUST_BACKTRACE=1` to get the stack trace of the `panic!` like in normal Rust programs. +IIRC backtraces **don't work** on MinGW, sorry. +If you have trouble or the backtraces are full of `unknown`, you might want to find some way to use Linux, Mac, or MSVC on Windows. In the default configuration (without `debug` set to `true`), you don't have line numbers @@ -100,9 +100,10 @@ stack backtrace: ## `-Z` flags -The compiler has a bunch of `-Z *` flags. These are unstable flags that are only -enabled on nightly. Many of them are useful for debugging. To get a full listing -of `-Z` flags, use `-Z help`. +The compiler has a bunch of `-Z *` flags. +These are unstable flags that are only enabled on nightly. +Many of them are useful for debugging. +To get a full listing of `-Z` flags, use `-Z help`. One useful flag is `-Z verbose-internals`, which generally enables printing more info that could be useful for debugging. @@ -114,7 +115,8 @@ Right below you can find elaborate explainers on a selected few. If you want to get a backtrace to the point where the compiler emits an error message, you can pass the `-Z treat-err-as-bug=n`, which will make -the compiler panic on the `nth` error. If you leave off `=n`, the compiler will +the compiler panic on the `nth` error. +If you leave off `=n`, the compiler will assume `1` for `n` and thus panic on the first error it encounters. For example: @@ -190,13 +192,12 @@ Cool, now I have a backtrace for the error! The `-Z eagerly-emit-delayed-bugs` option makes it easy to debug delayed bugs. It turns them into normal errors, i.e. makes them visible. This can be used in -combination with `-Z treat-err-as-bug` to stop at a particular delayed bug and -get a backtrace. +combination with `-Z treat-err-as-bug` to stop at a particular delayed bug and get a backtrace. ### Getting the error creation location -`-Z track-diagnostics` can help figure out where errors are emitted. It uses `#[track_caller]` -for this and prints its location alongside the error: +`-Z track-diagnostics` can help figure out where errors are emitted. +It uses `#[track_caller]` for this and prints its location alongside the error: ``` $ RUST_BACKTRACE=1 rustc +stage1 error.rs -Z track-diagnostics @@ -238,11 +239,11 @@ For details see [the guide section on tracing](./tracing.md) ## Narrowing (Bisecting) Regressions The [cargo-bisect-rustc][bisect] tool can be used as a quick and easy way to -find exactly which PR caused a change in `rustc` behavior. It automatically -downloads `rustc` PR artifacts and tests them against a project you provide -until it finds the regression. You can then look at the PR to get more context -on *why* it was changed. See [this tutorial][bisect-tutorial] on how to use -it. +find exactly which PR caused a change in `rustc` behavior. +It automatically downloads `rustc` PR artifacts and tests them against a project you provide +until it finds the regression. +You can then look at the PR to get more context on *why* it was changed. + See [this tutorial][bisect-tutorial] on how to use it. [bisect]: https://github.com/rust-lang/cargo-bisect-rustc [bisect-tutorial]: https://rust-lang.github.io/cargo-bisect-rustc/tutorial.html @@ -252,8 +253,9 @@ it. The [rustup-toolchain-install-master][rtim] tool by kennytm can be used to download the artifacts produced by Rust's CI for a specific SHA1 -- this basically corresponds to the successful landing of some PR -- and then sets -them up for your local use. This also works for artifacts produced by `@bors -try`. This is helpful when you want to examine the resulting build of a PR +them up for your local use. +This also works for artifacts produced by `@bors try`. +This is helpful when you want to examine the resulting build of a PR without doing the build yourself. [rtim]: https://github.com/kennytm/rustup-toolchain-install-master @@ -261,10 +263,12 @@ without doing the build yourself. ## `#[rustc_*]` TEST attributes The compiler defines a whole lot of internal (perma-unstable) attributes some of which are useful -for debugging by dumping extra compiler-internal information. These are prefixed with `rustc_` and +for debugging by dumping extra compiler-internal information. +These are prefixed with `rustc_` and are gated behind the internal feature `rustc_attrs` (enabled via e.g. `#![feature(rustc_attrs)]`). -For a complete and up to date list, see [`builtin_attrs`]. More specifically, the ones marked `TEST`. +For a complete and up to date list, see [`builtin_attrs`]. +More specifically, the ones marked `TEST`. Here are some notable ones: | Attribute | Description | @@ -313,7 +317,8 @@ $ firefox maybe_init_suffix.pdf # Or your favorite pdf viewer ### Debugging type layouts The internal attribute `#[rustc_layout]` can be used to dump the [`Layout`] of -the type it is attached to. For example: +the type it is attached to. +For example: ```rust #![feature(rustc_attrs)] diff --git a/src/doc/rustc-dev-guide/src/effects.md b/src/doc/rustc-dev-guide/src/effects.md index 732ba7153116..26530a30d066 100644 --- a/src/doc/rustc-dev-guide/src/effects.md +++ b/src/doc/rustc-dev-guide/src/effects.md @@ -2,16 +2,17 @@ ## The `HostEffect` predicate -[`HostEffectPredicate`]s are a kind of predicate from `~const Tr` or `const Tr` -bounds. It has a trait reference, and a `constness` which could be `Maybe` or -`Const` depending on the bound. Because `~const Tr`, or rather `Maybe` bounds +[`HostEffectPredicate`]s are a kind of predicate from `~const Tr` or `const Tr` bounds. +It has a trait reference, and a `constness` which could be `Maybe` or +`Const` depending on the bound. +Because `~const Tr`, or rather `Maybe` bounds apply differently based on whichever contexts they are in, they have different -behavior than normal bounds. Where normal trait bounds on a function such as +behavior than normal bounds. +Where normal trait bounds on a function such as `T: Tr` are collected within the [`predicates_of`] query to be proven when a function is called and to be assumed within the function, bounds such as `T: ~const Tr` will behave as a normal trait bound and add `T: Tr` to the result -from `predicates_of`, but also adds a `HostEffectPredicate` to the -[`const_conditions`] query. +from `predicates_of`, but also adds a `HostEffectPredicate` to the [`const_conditions`] query. On the other hand, `T: const Tr` bounds do not change meaning across contexts, therefore they will result in `HostEffect(T: Tr, const)` being added to @@ -23,17 +24,17 @@ therefore they will result in `HostEffect(T: Tr, const)` being added to ## The `const_conditions` query -`predicates_of` represents a set of predicates that need to be proven to use an -item. For example, to use `foo` in the example below: +`predicates_of` represents a set of predicates that need to be proven to use an item. +For example, to use `foo` in the example below: ```rust fn foo() where T: Default {} ``` -We must be able to prove that `T` implements `Default`. In a similar vein, +We must be able to prove that `T` implements `Default`. +In a similar vein, `const_conditions` represents a set of predicates that need to be proven to use -an item *in const contexts*. If we adjust the example above to use `const` trait -bounds: +an item *in const contexts*. If we adjust the example above to use `const` trait bounds: ```rust const fn foo() where T: ~const Default {} @@ -45,13 +46,13 @@ prove that `T` has a const implementation of `Default`. ## Enforcement of `const_conditions` -`const_conditions` are currently checked in various places. +`const_conditions` are currently checked in various places. Every call in HIR from a const context (which includes `const fn` and `const` items) will check that `const_conditions` of the function we are calling hold. -This is done in [`FnCtxt::enforce_context_effects`]. Note that we don't check -if the function is only referred to but not called, as the following code needs -to compile: +This is done in [`FnCtxt::enforce_context_effects`]. +Note that we don't check +if the function is only referred to but not called, as the following code needs to compile: ```rust const fn hi() -> T { @@ -61,8 +62,8 @@ const X: fn() -> u32 = hi::; ``` For a trait `impl` to be well-formed, we must be able to prove the -`const_conditions` of the trait from the `impl`'s environment. This is checked -in [`wfcheck::check_impl`]. +`const_conditions` of the trait from the `impl`'s environment. +This is checked in [`wfcheck::check_impl`]. Here's an example: @@ -77,10 +78,11 @@ impl const Foo for () {} ``` Methods of trait impls must not have stricter bounds than the method of the -trait that they are implementing. To check that the methods are compatible, a +trait that they are implementing. +To check that the methods are compatible, a hybrid environment is constructed with the predicates of the `impl` plus the -predicates of the trait method, and we attempt to prove the predicates of the -impl method. We do the same for `const_conditions`: +predicates of the trait method, and we attempt to prove the predicates of the impl method. +We do the same for `const_conditions`: ```rust const trait Foo { @@ -95,10 +97,10 @@ impl Foo for Vec { } ``` -These checks are done in [`compare_method_predicate_entailment`]. A similar -function that does the same check for associated types is called -[`compare_type_predicate_entailment`]. Both of these need to consider -`const_conditions` when in const contexts. +These checks are done in [`compare_method_predicate_entailment`]. +A similar function that does the same check for associated types is called +[`compare_type_predicate_entailment`]. +Both of these need to consider `const_conditions` when in const contexts. In MIR, as part of const checking, `const_conditions` of items that are called are revalidated again in [`Checker::revalidate_conditional_constness`]. @@ -111,7 +113,9 @@ are revalidated again in [`Checker::revalidate_conditional_constness`]. ## `explicit_implied_const_bounds` on associated types and traits -Bounds on associated types, opaque types, and supertraits such as +Bounds on associated types, opaque types, and supertraits such as the following +have their bounds represented differently: + ```rust trait Foo: ~const PartialEq { type X: ~const PartialEq; @@ -122,11 +126,11 @@ fn foo() -> impl ~const PartialEq { } ``` -Have their bounds represented differently. Unlike `const_conditions` which need -to be proved for callers, and can be assumed inside the definition (e.g. trait +Unlike `const_conditions`, which need to be proved for callers, +and can be assumed inside the definition (e.g. trait bounds on functions), these bounds need to be proved at definition (at the impl, -or when returning the opaque) but can be assumed for callers. The non-const -equivalent of these bounds are called [`explicit_item_bounds`]. +or when returning the opaque) but can be assumed for callers. +The non-const equivalent of these bounds are called [`explicit_item_bounds`]. These bounds are checked in [`compare_impl_item::check_type_bounds`] for HIR typeck, [`evaluate_host_effect_from_item_bounds`] in the old solver and @@ -139,18 +143,16 @@ typeck, [`evaluate_host_effect_from_item_bounds`] in the old solver and ## Proving `HostEffectPredicate`s -`HostEffectPredicate`s are implemented both in the [old solver] and the [new -trait solver]. In general, we can prove a `HostEffect` predicate when either of -these conditions are met: +`HostEffectPredicate`s are implemented both in the [old solver] and the [new trait solver]. +In general, we can prove a `HostEffect` predicate when either of these conditions are met: * The predicate can be assumed from caller bounds; * The type has a `const` `impl` for the trait, *and* that const conditions on -the impl holds, *and* that the `explicit_implied_const_bounds` on the trait -holds; or -* The type has a built-in implementation for the trait in const contexts. For -example, `Fn` may be implemented by function items if their const conditions -are satisfied, or `Destruct` is implemented in const contexts if the type can -be dropped at compile time. + the impl holds, *and* that the `explicit_implied_const_bounds` on the trait holds; or +* The type has a built-in implementation for the trait in const contexts. + For example, `Fn` may be implemented by function items if their const conditions + are satisfied, or `Destruct` is implemented in const contexts if the type can + be dropped at compile time. [old solver]: https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_trait_selection/traits/effects.rs.html [new trait solver]: https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_next_trait_solver/solve/effect_goals.rs.html @@ -161,10 +163,11 @@ To be expanded later. ### The `#[rustc_non_const_trait_method]` attribute -This is intended for internal (standard library) usage only. With this attribute -applied to a trait method, the compiler will not check the default body of this -method for ability to run in compile time. Users of the trait will also not be -allowed to use this trait method in const contexts. This attribute is primarily +This is intended for internal (standard library) usage only. +With this attribute applied to a trait method, the compiler will not check the default body of this +method for ability to run in compile time. +Users of the trait will also not be allowed to use this trait method in const contexts. +This attribute is primarily used for constifying large traits such as `Iterator` without having to make all its methods `const` at the same time. diff --git a/src/doc/rustc-dev-guide/src/external-repos.md b/src/doc/rustc-dev-guide/src/external-repos.md index 2e32fcfe78c1..7ae1c881be8f 100644 --- a/src/doc/rustc-dev-guide/src/external-repos.md +++ b/src/doc/rustc-dev-guide/src/external-repos.md @@ -1,4 +1,4 @@ -# Using External Repositories +# Using external repositories The `rust-lang/rust` git repository depends on several other repos in the `rust-lang` organization. There are three main ways we use dependencies: @@ -12,7 +12,7 @@ As a general rule: changes - Use submodules for tools that are independent of the compiler -## External Dependencies (subtrees) +## External dependencies (subtrees) The following external projects are managed using some form of a `subtree`: @@ -153,7 +153,7 @@ Now you're done, the `src/tools/clippy` directory behaves as if Clippy were part of the rustc monorepo, so no one but you (or others that synchronize subtrees) actually needs to use `git subtree`. -## External Dependencies (submodules) +## External dependencies (submodules) Building Rust will also use external git repositories tracked using [git submodules]. The complete list may be found in the [`.gitmodules`] file. diff --git a/src/doc/rustc-dev-guide/src/llvm-coverage-instrumentation.md b/src/doc/rustc-dev-guide/src/llvm-coverage-instrumentation.md index d71e51d5f61b..8e94928c8a1a 100644 --- a/src/doc/rustc-dev-guide/src/llvm-coverage-instrumentation.md +++ b/src/doc/rustc-dev-guide/src/llvm-coverage-instrumentation.md @@ -7,13 +7,13 @@ libraries and binaries with additional instructions and data, at compile time. The coverage instrumentation injects calls to the LLVM intrinsic instruction [`llvm.instrprof.increment`][llvm-instrprof-increment] at code branches (based on a MIR-based control flow analysis), and LLVM converts these to -instructions that increment static counters, when executed. The LLVM coverage -instrumentation also requires a [Coverage Map] that encodes source metadata, +instructions that increment static counters, when executed. +The LLVM coverage instrumentation also requires a [Coverage Map] that encodes source metadata, mapping counter IDs--directly and indirectly--to the file locations (with start and end line and column). -Rust libraries, with or without coverage instrumentation, can be linked into -instrumented binaries. When the program is executed and cleanly terminates, +Rust libraries, with or without coverage instrumentation, can be linked into instrumented binaries. +When the program is executed and cleanly terminates, LLVM libraries write the final counter values to a file (`default.profraw` or a custom file set through environment variable `LLVM_PROFILE_FILE`). @@ -21,9 +21,7 @@ Developers use existing LLVM coverage analysis tools to decode `.profraw` files, with corresponding Coverage Maps (from matching binaries that produced them), and generate various reports for analysis, for example: -Screenshot of sample `llvm-cov show` result, for function add_quoted_string -
+![Screenshot of sample `llvm-cov show` result, for function add_quoted_string](img/llvm-cov-show-01.png) Detailed instructions and examples are documented in the [rustc book][rustc-book-instrument-coverage]. @@ -39,31 +37,28 @@ When working on the coverage instrumentation code, it is usually necessary to This allows the compiler to produce instrumented binaries, and makes it possible to run the full coverage test suite. -Enabling debug assertions in the compiler and in LLVM is recommended, but not -mandatory. +Enabling debug assertions in the compiler and in LLVM is recommended, but not mandatory. ```toml # Similar to the "compiler" profile, but also enables debug assertions in LLVM. # These assertions can detect malformed coverage mappings in some cases. profile = "codegen" -[build] # IMPORTANT: This tells the build system to build the LLVM profiler runtime. # Without it, the compiler can't produce coverage-instrumented binaries, # and many of the coverage tests will be skipped. -profiler = true +build.profiler = true -[rust] # Enable debug assertions in the compiler. -debug-assertions = true +rust.debug-assertions = true ``` ## Rust symbol mangling `-C instrument-coverage` automatically enables Rust symbol mangling `v0` (as if the user specified `-C symbol-mangling-version=v0` option when invoking -`rustc`) to ensure consistent and reversible name mangling. This has two -important benefits: +`rustc`) to ensure consistent and reversible name mangling. +This has two important benefits: 1. LLVM coverage tools can analyze coverage over multiple runs, including some changes to source code; so mangled names must be consistent across compilations. @@ -73,8 +68,8 @@ important benefits: ## The LLVM profiler runtime -Coverage data is only generated by running the executable Rust program. `rustc` -statically links coverage-instrumented binaries with LLVM runtime code +Coverage data is only generated by running the executable Rust program. +`rustc` statically links coverage-instrumented binaries with LLVM runtime code ([compiler-rt][compiler-rt-profile]) that implements program hooks (such as an `exit` hook) to write the counter values to the `.profraw` file. diff --git a/src/doc/rustc-dev-guide/src/offload/internals.md b/src/doc/rustc-dev-guide/src/offload/internals.md index 77a4cadbcb98..520c48d95896 100644 --- a/src/doc/rustc-dev-guide/src/offload/internals.md +++ b/src/doc/rustc-dev-guide/src/offload/internals.md @@ -13,4 +13,4 @@ We use a single-source, two-pass compilation approach. First we compile all functions that should be offloaded for the device (e.g nvptx64, amdgcn-amd-amdhsa, intel in the future). Currently we require cumbersome `#cfg(target_os="")` annotations, but we intend to recognize those in the future based on our offload intrinsic. -We then compile the code for the host (e.g. x86-64), where most of the offloading logic happens. On the host side, we generate calls to the openmp offload runtime, to inform it about the layout of the types (a simplified version of the autodiff TypeTrees). We also use the type system to figure out whether kernel arguments have to be moved only to the device (e.g. `&[f32;1024]`), from the device, or both (e.g. `&mut [f64]`). We then launched the kernel, after which we inform the runtime to end this environment and move data back (as far as needed). +We then compile the code for the host (e.g. x86-64), where most of the offloading logic happens. On the host side, we generate calls to the openmp offload runtime, to inform it about the layout of the types (a simplified version of the autodiff TypeTrees). We also use the type system to figure out whether kernel arguments have to be moved only to the device (e.g. `&[f32;1024]`), from the device, or both (e.g. `&mut [f64]`). We then launch the kernel, after which we inform the runtime to end this environment and move data back (as far as needed). diff --git a/src/doc/rustc-dev-guide/src/profiling.md b/src/doc/rustc-dev-guide/src/profiling.md index 519d9b5488cb..66eabe7d8c93 100644 --- a/src/doc/rustc-dev-guide/src/profiling.md +++ b/src/doc/rustc-dev-guide/src/profiling.md @@ -33,10 +33,11 @@ Since most of the time compiling rustc is spent in LLVM, the idea is that by reducing the amount of code passed to LLVM, compiling rustc gets faster. To use `cargo-llvm-lines` together with somewhat custom rustc build process, you can use -`-C save-temps` to obtain required LLVM IR. The option preserves temporary work products -created during compilation. Among those is LLVM IR that represents an input to the -optimization pipeline; ideal for our purposes. It is stored in files with `*.no-opt.bc` -extension in LLVM bitcode format. +`-C save-temps` to obtain required LLVM IR. +The option preserves temporary work products created during compilation. +Among those is LLVM IR that represents an input to the +optimization pipeline; ideal for our purposes. +It is stored in files with `*.no-opt.bc` extension in LLVM bitcode format. Example usage: ``` @@ -89,24 +90,24 @@ Since this doesn't seem to work with incremental compilation or `./x check`, you will be compiling rustc _a lot_. I recommend changing a few settings in `bootstrap.toml` to make it bearable: ``` -[rust] # A debug build takes _a third_ as long on my machine, # but compiling more than stage0 rustc becomes unbearably slow. -optimize = false +rust.optimize = false # We can't use incremental anyway, so we disable it for a little speed boost. -incremental = false +rust.incremental = false # We won't be running it, so no point in compiling debug checks. -debug = false +rust.debug = false # Using a single codegen unit gives less output, but is slower to compile. -codegen-units = 0 # num_cpus +rust.codegen-units = 0 # num_cpus ``` The llvm-lines output is affected by several options. -`optimize = false` increases it from 2.1GB to 3.5GB and `codegen-units = 0` to 4.1GB. +`rust.optimize = false` increases it from 2.1GB to 3.5GB and `codegen-units = 0` to 4.1GB. -MIR optimizations have little impact. Compared to the default `RUSTFLAGS="-Z +MIR optimizations have little impact. +Compared to the default `RUSTFLAGS="-Z mir-opt-level=1"`, level 0 adds 0.3GB and level 2 removes 0.2GB. As of July 2022, inlining happens in LLVM and GCC codegen backends, diff --git a/src/doc/rustc-dev-guide/src/profiling/wpa-profiling.md b/src/doc/rustc-dev-guide/src/profiling/wpa-profiling.md index d2680d40853f..a0a5bee63e9e 100644 --- a/src/doc/rustc-dev-guide/src/profiling/wpa-profiling.md +++ b/src/doc/rustc-dev-guide/src/profiling/wpa-profiling.md @@ -3,13 +3,13 @@ ## Introducing WPR and WPA High-level performance analysis (including memory usage) can be performed with the Windows -Performance Recorder (WPR) and Windows Performance Analyzer (WPA). As the names suggest, WPR is for -recording system statistics (in the form of event trace log a.k.a. ETL files), while WPA is for -analyzing these ETL files. +Performance Recorder (WPR) and Windows Performance Analyzer (WPA). +As the names suggest, WPR is for recording system statistics (in the form of event trace log a.k.a. +ETL files), while WPA is for analyzing these ETL files. WPR collects system wide statistics, so it won't just record things relevant to rustc but also -everything else that's running on the machine. During analysis, we can filter to just the things we -find interesting. +everything else that's running on the machine. +During analysis, we can filter to just the things we find interesting. These tools are quite powerful but also require a bit of learning before we can successfully profile the Rust compiler. @@ -21,36 +21,43 @@ specifically designed to make analyzing rustc easier. ### Installing WPR and WPA You can install WPR and WPA as part of the Windows Performance Toolkit which itself is an option as -part of downloading the Windows Assessment and Deployment Kit (ADK). You can download the ADK -installer [here](https://go.microsoft.com/fwlink/?linkid=2086042). Make sure to select the Windows -Performance Toolkit (you don't need to select anything else). +part of downloading the Windows Assessment and Deployment Kit (ADK). +You can download the ADK +installer [here](https://learn.microsoft.com/en-us/windows-hardware/get-started/adk-install). +Make sure to select the Windows Performance Toolkit (you don't need to select anything else). ## Recording -In order to perform system analysis, you'll first need to record your system with WPR. Open WPR and -at the bottom of the window select the "profiles" of the things you want to record. For looking +In order to perform system analysis, you'll first need to record your system with WPR. +Open WPR and at the bottom of the window select the "profiles" of the things you want to record. +For looking into memory usage of the rustc bootstrap process, we'll want to select the following items: * CPU usage * VirtualAlloc usage You might be tempted to record "Heap usage" as well, but this records every single heap allocation -and can be very, very expensive. For high-level analysis, it might be best to leave that turned -off. +and can be very, very expensive. +For high-level analysis, it might be best to leave that turned off. -Now we need to get our setup ready to record. For memory usage analysis, it is best to record the -stage 2 compiler build with a stage 1 compiler build with debug symbols. Having symbols in the +Now we need to get our setup ready to record. +For memory usage analysis, it is best to record the +stage 2 compiler build with a stage 1 compiler build with debug symbols. +Having symbols in the compiler we're using to build rustc will aid our analysis greatly by allowing WPA to resolve Rust -symbols correctly. Unfortunately, the stage 0 compiler does not have symbols turned on which is why -we'll need to build a stage 1 compiler and then a stage 2 compiler ourselves. +symbols correctly. +Unfortunately, the stage 0 compiler does not have symbols turned on, +which is why we'll need to build a stage 1 compiler, +and then a stage 2 compiler ourselves. -To do this, make sure you have set `debuginfo-level = 1` in your `bootstrap.toml` file. This tells -rustc to generate debug information which includes stack frames when bootstrapping. +To do this, make sure you have set `rust.debuginfo-level = 1` in your `bootstrap.toml` file. +This tells rustc to generate debug information which includes stack frames when bootstrapping. Now you can build the stage 1 compiler: `x build --stage 1 -i library` or however else you want to build the stage 1 compiler. -Now that the stage 1 compiler is built, we can record the stage 2 build. Go back to WPR, click the +Now that the stage 1 compiler is built, we can record the stage 2 build. +Go back to WPR, click the "start" button and build the stage 2 compiler (e.g., `x build --stage=2 -i library`). When this process finishes, stop the recording. @@ -61,8 +68,10 @@ appears. ## Analysis -Now that our ETL file is open in WPA, we can analyze the results. First, we'll want to apply the -pre-made "profile" which will put WPA into a state conducive to analyzing rustc bootstrap. Download +Now that our ETL file is open in WPA, we can analyze the results. +First, we'll want to apply the +pre-made "profile" which will put WPA into a state conducive to analyzing rustc bootstrap. +Download the profile [here](https://github.com/wesleywiser/rustc-bootstrap-wpa-analysis/releases/download/1/rustc.generic.wpaProfile). Select the "Profiles" menu at the top, then "apply" and then choose the downloaded profile. @@ -71,8 +80,9 @@ You should see something resembling the following: ![WPA with profile applied](../img/wpa-initial-memory.png) Next, we will need to tell WPA to load and process debug symbols so that it can properly demangle -the Rust stack traces. To do this, click "Trace" and then choose "Load Symbols". This step can take -a while. +the Rust stack traces. +To do this, click "Trace" and then choose "Load Symbols". +This step can take a while. Once WPA has loaded symbols for rustc, we can expand the rustc.exe node and begin drilling down into the stack with the largest allocations. @@ -81,8 +91,8 @@ To do that, we'll expand the `[Root]` node in the "Commit Stack" column and cont until we find interesting stack frames. > Tip: After selecting the node you want to expand, press the right arrow key. This will expand the -node and put the selection on the next largest node in the expanded set. You can continue pressing -the right arrow key until you reach an interesting frame. +node and put the selection on the next largest node in the expanded set. +You can continue pressing the right arrow key until you reach an interesting frame. ![WPA with expanded stack](../img/wpa-stack.png) diff --git a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md index 1049d7a82ddd..2d409ad201b1 100644 --- a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md +++ b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md @@ -42,29 +42,28 @@ For custom-built toolchains or environments not using rustup, additional configu #### Troubleshooting Steps -1. **Check LLVM installation**: Verify LLVM is installed and accessible -2. **Configure library paths**: You may need to set environment variables: - ```text +1. Verify LLVM is installed and accessible +2. Ensure that library paths are set: + ```sh export LD_LIBRARY_PATH=/path/to/llvm/lib:$LD_LIBRARY_PATH ``` -3. **Check version compatibility**: Ensure your LLVM version is compatible with your Rust toolchain +3. Ensure your LLVM version is compatible with your Rust toolchain -### Configuring `rust-analyzer` for Out-of-Tree Projects +### Configuring `rust-analyzer` for out-of-tree projects When developing out-of-tree projects that use `rustc_private` crates, you can configure `rust-analyzer` to recognize these crates. #### Configuration Steps -1. **Set rust-analyzer configuration** - Configure `rust-analyzer.rustc.source` to `"discover"` in your editor settings. +1. Configure `rust-analyzer.rustc.source` to `"discover"` in your editor settings. For VS Code, add to `rust_analyzer_settings.json`: ```json { "rust-analyzer.rustc.source": "discover" } ``` -2. **Add metadata to Cargo.toml** - Add the following to the `Cargo.toml` of every crate that uses `rustc_private`: + +2. Add the following to the `Cargo.toml` of every crate that uses `rustc_private`: ```toml [package.metadata.rust-analyzer] rustc_private = true @@ -74,4 +73,6 @@ This configuration allows `rust-analyzer` to properly recognize and provide IDE ### Additional Resources -- [GitHub Issue #137421](https://github.com/rust-lang/rust/issues/137421): Explains that `rustc_private` linker failures often occur because `llvm-tools` is not installed +- [GitHub Issue #137421] explains that `rustc_private` linker failures often occur because `llvm-tools` is not installed + +[GitHub Issue #137421]: https://github.com/rust-lang/rust/issues/137421 diff --git a/src/doc/rustc-dev-guide/src/sanitizers.md b/src/doc/rustc-dev-guide/src/sanitizers.md index 673d650e6050..65e1c0c70505 100644 --- a/src/doc/rustc-dev-guide/src/sanitizers.md +++ b/src/doc/rustc-dev-guide/src/sanitizers.md @@ -36,8 +36,7 @@ Highlight of the most important aspects of the implementation: when enabled in `bootstrap.toml`: ```toml - [build] - sanitizers = true + build.sanitizers = true ``` The runtimes are [placed into target libdir][sanitizer-copy]. @@ -84,7 +83,7 @@ Sanitizers are validated by code generation tests in [`tests/ui/sanitizer/`][test-ui] directory. Testing sanitizer functionality requires the sanitizer runtimes (built when -`sanitizer = true` in `bootstrap.toml`) and target providing support for particular a sanitizer. +`build.sanitizer = true` in `bootstrap.toml`) and target providing support for particular a sanitizer. When a sanitizer is unsupported on a given target, sanitizer tests will be ignored. This behaviour is controlled by compiletest `needs-sanitizer-*` directives. diff --git a/src/doc/rustc-dev-guide/src/tests/compiletest.md b/src/doc/rustc-dev-guide/src/tests/compiletest.md index 5c4dfb6e0dd1..959a73a5a9ad 100644 --- a/src/doc/rustc-dev-guide/src/tests/compiletest.md +++ b/src/doc/rustc-dev-guide/src/tests/compiletest.md @@ -277,8 +277,7 @@ the debugger currently being used: gdb is in a range (inclusive) - `min-lldb-version: 310` — ignores the test if the version of lldb is below the given version - `rust-lldb` — ignores the test if lldb is not contain the Rust plugin. - NOTE: The "Rust" version of LLDB doesn't exist anymore, so this will always be - ignored. + NOTE: The "Rust" version of LLDB doesn't exist anymore, so this will always be ignored. This should probably be removed. By passing the `--debugger` option to compiletest, you can specify a single debugger to run tests with. @@ -570,9 +569,7 @@ Instrumented binaries need to be linked against the LLVM profiler runtime, so is enabled in `bootstrap.toml`: ```toml -# bootstrap.toml -[build] -profiler = true +build.profiler = true ``` This also means that they typically don't run in PR CI jobs, though they do run diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md index 08371a779e11..ac76a2e15d9a 100644 --- a/src/doc/rustc-dev-guide/src/tests/directives.md +++ b/src/doc/rustc-dev-guide/src/tests/directives.md @@ -164,9 +164,9 @@ The following directives will check rustc build settings and target settings: via `--target`, use `needs-llvm-components` instead to ensure the appropriate backend is available. - `needs-profiler-runtime` — ignores the test if the profiler runtime was not - enabled for the target (`build.profiler = true` in rustc's `bootstrap.toml`) + enabled for the target (`build.profiler = true` in `bootstrap.toml`) - `needs-sanitizer-support` — ignores if the sanitizer support was not enabled - for the target (`sanitizers = true` in rustc's `bootstrap.toml`) + for the target (`build.sanitizers = true` in `bootstrap.toml`) - `needs-sanitizer-{address,hwaddress,leak,memory,thread}` — ignores if the corresponding sanitizer is not enabled for the target (AddressSanitizer, hardware-assisted AddressSanitizer, LeakSanitizer, MemorySanitizer or diff --git a/src/doc/rustc-dev-guide/src/tests/docker.md b/src/doc/rustc-dev-guide/src/tests/docker.md index af8e0c36d050..51beee8a866c 100644 --- a/src/doc/rustc-dev-guide/src/tests/docker.md +++ b/src/doc/rustc-dev-guide/src/tests/docker.md @@ -1,12 +1,14 @@ # Testing with Docker -The [`src/ci/docker`] directory includes [Docker] image definitions for Linux-based jobs executed on GitHub Actions (non-Linux jobs run outside Docker). You can run these jobs on your local development machine, which can be -helpful to test environments different from your local system. You will -need to install Docker on a Linux, Windows, or macOS system (typically Linux +The [`src/ci/docker`] directory includes [Docker] image definitions for Linux-based jobs executed on GitHub Actions (non-Linux jobs run outside Docker). +You can run these jobs on your local development machine, which can be +helpful to test environments different from your local system. +You will need to install Docker on a Linux, Windows, or macOS system (typically Linux will be much faster than Windows or macOS because the latter use virtual machines to emulate a Linux environment). -Jobs running in CI are configured through a set of bash scripts, and it is not always trivial to reproduce their behavior locally. If you want to run a CI job locally in the simplest way possible, you can use a provided helper `citool` that tries to replicate what happens on CI as closely as possible: +Jobs running in CI are configured through a set of bash scripts, and it is not always trivial to reproduce their behavior locally. +If you want to run a CI job locally in the simplest way possible, you can use a provided helper `citool` that tries to replicate what happens on CI as closely as possible: ```bash cargo run --manifest-path src/ci/citool/Cargo.toml run-local @@ -18,39 +20,53 @@ If the above script does not work for you, you would like to have more control o ## The `run.sh` script The [`src/ci/docker/run.sh`] script is used to build a specific Docker image, run it, -build Rust within the image, and either run tests or prepare a set of archives designed for distribution. The script will mount your local Rust source tree in read-only mode, and an `obj` directory in read-write mode. All the compiler artifacts will be stored in the `obj` directory. The shell will start out in the `obj`directory. From there, it will execute `../src/ci/run.sh` which starts the build as defined by the Docker image. +build Rust within the image, and either run tests or prepare a set of archives designed for distribution. +The script will mount your local Rust source tree in read-only mode, and an `obj` directory in read-write mode. +All the compiler artifacts will be stored in the `obj` directory. +The shell will start out in the `obj`directory. +From there, it will execute `../src/ci/run.sh` which starts the build as defined by the Docker image. -You can run `src/ci/docker/run.sh ` directly. A few important notes regarding the `run.sh` script: -- When executed on CI, the script expects that all submodules are checked out. If some submodule that is accessed by the job is not available, the build will result in an error. You should thus make sure that you have all required submodules checked out locally. You can either do that manually through git, or set `submodules = true` in your `bootstrap.toml` and run a command such as `x build` to let bootstrap download the most important submodules (this might not be enough for the given CI job that you are trying to execute though). -- `` corresponds to a single directory located in one of the `src/ci/docker/host-*` directories. Note that image name does not necessarily correspond to a job name, as some jobs execute the same image, but with different environment variables or Docker build arguments (this is a part of the complexity that makes it difficult to run CI jobs locally). +You can run `src/ci/docker/run.sh ` directly. +A few important notes regarding the `run.sh` script: +- When executed on CI, the script expects that all submodules are checked out. + If some submodule that is accessed by the job is not available, the build will result in an error. + You should thus make sure that you have all required submodules checked out locally. + You can either do that manually through git, or set `build.submodules = true` in your `bootstrap.toml` and run a command such as `x build` to let bootstrap download the most important submodules + Note that this might not be enough for the given CI job that you are trying to execute though. +- `` corresponds to a single directory located in one of the `src/ci/docker/host-*` directories. + Note that image name does not necessarily correspond to a job name, as some jobs execute the same image, but with different environment variables or Docker build arguments + This is a part of the complexity that makes it difficult to run CI jobs locally. - If you are executing a "dist" job (job beginning with `dist-`), you should set the `DEPLOY=1` environment variable. - If you are executing an "alternative dist" job (job beginning with `dist-` and ending with `-alt`), you should set the `DEPLOY_ALT=1` environment variable. -- Some of the std tests require IPv6 support. Docker on Linux seems to have it - disabled by default. Run the commands in [`enable-docker-ipv6.sh`] to enable - IPv6 before creating the container. This only needs to be done once. +- Some of the std tests require IPv6 support. + Docker on Linux seems to have it disabled by default. + Run the commands in [`enable-docker-ipv6.sh`] to enable IPv6 before creating the container. + This only needs to be done once. ### Interactive mode -Sometimes, it can be useful to build a specific Docker image, and then run custom commands inside it, so that you can experiment with how the given system behaves. You can do that using an interactive mode, which will +Sometimes, it can be useful to build a specific Docker image, and then run custom commands inside it, so that you can experiment with how the given system behaves. +You can do that using an interactive mode, which will start a bash shell in the container, using `src/ci/docker/run.sh --dev `. -When inside the Docker container, you can run individual commands to do specific tasks. For -example, you can run `../x test tests/ui` to just run UI tests. +When inside the Docker container, you can run individual commands to do specific tasks. +For example, you can run `../x test tests/ui` to just run UI tests. Some additional notes about using the interactive mode: - The container will be deleted automatically when you exit the shell, however - the build artifacts persist in the `obj` directory. If you are switching - between different Docker images, the artifacts from previous environments - stored in the `obj` directory may confuse the build system. Sometimes you - will need to delete parts or all of the `obj` directory before building + the build artifacts persist in the `obj` directory. + If you are switching between different Docker images, the artifacts from previous environments + stored in the `obj` directory may confuse the build system. + Sometimes you will need to delete parts or all of the `obj` directory before building inside the container. -- The container is bare-bones, with only a minimal set of packages. You may - want to install some things like `apt install less vim`. -- You can open multiple shells in the container. First you need the container +- The container is bare-bones, with only a minimal set of packages. + You may want to install some things like `apt install less vim`. +- You can open multiple shells in the container. + First you need the container name (a short hash), which is displayed in the shell prompt, or you can run - `docker container ls` outside of the container to list the available - containers. With the container name, run `docker exec -it + `docker container ls` outside of the container to list the available containers. + With the container name, run `docker exec -it /bin/bash` where `` is the container name like `4ba195e95cef`. [Docker]: https://www.docker.com/ diff --git a/src/doc/rustc-dev-guide/src/tracing.md b/src/doc/rustc-dev-guide/src/tracing.md index 28c0bcc737ca..2ad035bbafbc 100644 --- a/src/doc/rustc-dev-guide/src/tracing.md +++ b/src/doc/rustc-dev-guide/src/tracing.md @@ -74,8 +74,7 @@ RUSTC_LOG=[typeck] The query arguments are included as a tracing field which means that you can filter on the debug display of the arguments. For example, the `typeck` query has an argument `key: LocalDefId` of what is being checked. -You can use a regex to match on that `LocalDefId` to log type checking for a specific -function: +You can use a regex to match on that `LocalDefId` to log type checking for a specific function: ``` RUSTC_LOG=[typeck{key=.*name_of_item.*}] @@ -194,7 +193,7 @@ calls to `debug!` and `trace!` are only included in the program if `rust.debug-logging=true` is turned on in bootstrap.toml (it is turned off by default), so if you don't see `DEBUG` logs, especially if you run the compiler with `RUSTC_LOG=rustc rustc some.rs` and only see -`INFO` logs, make sure that `debug-logging=true` is turned on in your bootstrap.toml. +`INFO` logs, make sure that `rust.debug-logging=true` is turned on in your bootstrap.toml. ## Logging etiquette and conventions diff --git a/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md b/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md index d74dd843eb25..eefe82133906 100644 --- a/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md +++ b/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md @@ -36,14 +36,19 @@ Also included in that toolchain is the C library that can be used when creating dynamically linked executables. ```text -# /opt/clang+llvm-18.1.0-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/bin/qemu-hexagon -L /opt/clang+llvm-18.1.0-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/target/hexagon-unknown-linux-musl/usr/ ./hello +# /opt/clang+llvm-VERSION-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/bin/qemu-hexagon -L /opt/clang+llvm-VERSION-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/target/hexagon-unknown-linux-musl/usr/ ./hello ``` ## Linking -This target selects `rust-lld` by default. Another option to use is -[eld](https://github.com/qualcomm/eld), which is also provided with -the opensource hexagon toolchain and the Hexagon SDK. +This target uses `hexagon-unknown-linux-musl-clang` as the default linker. +The linker is available from [the opensource hexagon toolchain](https://github.com/quic/toolchain_for_hexagon/releases) +at paths like `/opt/clang+llvm-21.1.8-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/bin/hexagon-unknown-linux-musl-clang`. + +Alternative linkers include: +- [eld](https://github.com/qualcomm/eld), which is provided with + the opensource hexagon toolchain and the Hexagon SDK +- `rust-lld` can be used by specifying `-C linker=rust-lld` ## Building the target Because it is Tier 3, rust does not yet ship pre-compiled artifacts for this @@ -63,9 +68,9 @@ cxx = "hexagon-unknown-linux-musl-clang++" linker = "hexagon-unknown-linux-musl-clang" ar = "hexagon-unknown-linux-musl-ar" ranlib = "hexagon-unknown-linux-musl-ranlib" -musl-root = "/opt/clang+llvm-18.1.0-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/target/hexagon-unknown-linux-musl/usr" +musl-root = "/opt/clang+llvm-VERSION-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/target/hexagon-unknown-linux-musl/usr" llvm-libunwind = 'in-tree' -qemu-rootfs = "/opt/clang+llvm-18.1.0-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/target/hexagon-unknown-linux-musl/usr" +qemu-rootfs = "/opt/clang+llvm-VERSION-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/target/hexagon-unknown-linux-musl/usr" ``` @@ -88,7 +93,7 @@ target = "hexagon-unknown-linux-musl" [target.hexagon-unknown-linux-musl] linker = "hexagon-unknown-linux-musl-clang" ar = "hexagon-unknown-linux-musl-ar" -runner = "qemu-hexagon -L /opt/clang+llvm-18.1.0-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/target/hexagon-unknown-linux-musl/usr" +runner = "qemu-hexagon -L /opt/clang+llvm-VERSION-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/target/hexagon-unknown-linux-musl/usr" ``` Edit the "runner" in `.cargo/config` to point to the path to your toolchain's @@ -96,13 +101,13 @@ C library. ```text ... -runner = "qemu-hexagon -L /path/to/my/inst/clang+llvm-18.1.0-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/target/hexagon-unknown-linux-musl/usr" +runner = "qemu-hexagon -L /path/to/my/inst/clang+llvm-VERSION-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/target/hexagon-unknown-linux-musl/usr" ... ``` Build/run your rust program with `qemu-hexagon` in your `PATH`: ```text -export PATH=/path/to/my/inst/clang+llvm-18.1.0-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/bin/:$PATH +export PATH=/path/to/my/inst/clang+llvm-VERSION-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/bin/:$PATH cargo run -Zbuild-std -Zbuild-std-features=llvm-libunwind ``` diff --git a/src/doc/rustc/src/platform-support/hexagon-unknown-qurt.md b/src/doc/rustc/src/platform-support/hexagon-unknown-qurt.md index 7928804d0954..d33a90bf188c 100644 --- a/src/doc/rustc/src/platform-support/hexagon-unknown-qurt.md +++ b/src/doc/rustc/src/platform-support/hexagon-unknown-qurt.md @@ -33,10 +33,15 @@ required for building programs for this target. ## Linking -This target selects `rust-lld` by default. Another option to use is -[eld](https://github.com/qualcomm/eld), which is also provided with -[the opensource hexagon toolchain](https://github.com/quic/toolchain_for_hexagon) -and the Hexagon SDK. +This target uses `hexagon-clang` from the Hexagon SDK as the default linker. +The linker is available at paths like +`/opt/Hexagon_SDK/6.4.0.2/tools/HEXAGON_Tools/19.0.04/Tools/bin/hexagon-clang`. + +Alternative linkers include: +- [eld](https://github.com/qualcomm/eld), which is provided with both + [the opensource hexagon toolchain](https://github.com/quic/toolchain_for_hexagon) + and the Hexagon SDK +- `rust-lld` can be used by specifying `-C linker=rust-lld` ## Building the target diff --git a/src/doc/rustc/src/platform-support/m68k-unknown-linux-gnu.md b/src/doc/rustc/src/platform-support/m68k-unknown-linux-gnu.md index 1efea86df92b..c15a57c1ceac 100644 --- a/src/doc/rustc/src/platform-support/m68k-unknown-linux-gnu.md +++ b/src/doc/rustc/src/platform-support/m68k-unknown-linux-gnu.md @@ -52,7 +52,7 @@ Atari systems or emulated environments such as QEMU version 4.2 or newer or ARAn ISO images for installation are provided by the Debian Ports team and can be obtained from the Debian CD image server available at: -[https://cdimage.debian.org/cdimage/ports/current](https://cdimage.debian.org/cdimage/ports/current/) +[https://cdimage.debian.org/cdimage/ports/12.0/m68k/](https://cdimage.debian.org/cdimage/ports/12.0/m68k/) Documentation for Debian/m68k is available on the Debian Wiki at: diff --git a/src/tools/clippy/clippy_lints/src/time_subtraction.rs b/src/tools/clippy/clippy_lints/src/time_subtraction.rs index 3ba59aefea06..ca8378ba7c6a 100644 --- a/src/tools/clippy/clippy_lints/src/time_subtraction.rs +++ b/src/tools/clippy/clippy_lints/src/time_subtraction.rs @@ -85,7 +85,7 @@ impl LateLintPass<'_> for UncheckedTimeSubtraction { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { let (lhs, rhs) = match expr.kind { ExprKind::Binary(op, lhs, rhs) if matches!(op.node, BinOpKind::Sub,) => (lhs, rhs), - ExprKind::MethodCall(fn_name, lhs, [rhs], _) if cx.ty_based_def(expr).is_diag_item(cx, sym::sub) => { + ExprKind::MethodCall(_, lhs, [rhs], _) if cx.ty_based_def(expr).is_diag_item(cx, sym::sub) => { (lhs, rhs) }, _ => return, diff --git a/src/tools/compiletest/src/directives/auxiliary.rs b/src/tools/compiletest/src/directives/auxiliary.rs index 14cbab640eb6..0e7e370adbd4 100644 --- a/src/tools/compiletest/src/directives/auxiliary.rs +++ b/src/tools/compiletest/src/directives/auxiliary.rs @@ -26,8 +26,12 @@ pub struct AuxCrate { } /// The value of a `proc-macro` directive. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub(crate) struct ProcMacro { + /// Contains `--extern` modifiers, if any. See the tracking issue for more + /// info: + /// With `proc-macro: noprelude:bar.rs` this will be `noprelude`. + pub extern_modifiers: Option, /// With `proc-macro: bar.rs` this will be `bar.rs`. pub path: String, } @@ -108,5 +112,17 @@ fn parse_aux_crate(r: String) -> AuxCrate { } fn parse_proc_macro(r: String) -> ProcMacro { - ProcMacro { path: r.trim().to_string() } + let r = r.trim(); + + // Matches: + // path + // modifiers:path + let caps = static_regex!(r"^(?:(?[^=]*?):)?(?.*)$") + .captures(r) + .expect("can never fail"); + + let modifiers = caps.name("modifiers").map(|m| m.as_str().to_string()); + let path = caps["path"].to_string(); + + ProcMacro { extern_modifiers: modifiers, path } } diff --git a/src/tools/compiletest/src/directives/auxiliary/tests.rs b/src/tools/compiletest/src/directives/auxiliary/tests.rs index ad205eaabfda..74fff630692e 100644 --- a/src/tools/compiletest/src/directives/auxiliary/tests.rs +++ b/src/tools/compiletest/src/directives/auxiliary/tests.rs @@ -25,3 +25,19 @@ fn test_aux_crate_value_with_modifiers() { fn test_aux_crate_value_invalid() { parse_aux_crate("foo.rs".to_string()); } + +#[test] +fn test_proc_macro_value_no_modifiers() { + assert_eq!( + ProcMacro { extern_modifiers: None, path: "foo.rs".to_string() }, + parse_proc_macro("foo.rs".to_string()) + ); +} + +#[test] +fn test_proc_macro_value_with_modifiers() { + assert_eq!( + ProcMacro { extern_modifiers: Some("noprelude".to_string()), path: "foo.rs".to_string() }, + parse_proc_macro("noprelude:foo.rs".to_string()) + ); +} diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index e6eb1f3bd957..1804490a5f12 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1312,7 +1312,7 @@ impl<'test> TestCx<'test> { let crate_name = path_to_crate_name(&proc_macro.path); add_extern( rustc, - None, // `extern_modifiers` + proc_macro.extern_modifiers.as_deref(), &crate_name, &proc_macro.path, AuxType::ProcMacro, @@ -1438,7 +1438,7 @@ impl<'test> TestCx<'test> { } else if aux_type.is_some() { panic!("aux_type {aux_type:?} not expected"); } else if aux_props.no_prefer_dynamic { - (AuxType::Dylib, None) + (AuxType::Lib, None) } else if self.config.target.contains("emscripten") || (self.config.target.contains("musl") && !aux_props.force_host diff --git a/src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.rs b/src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.rs index c84895799b69..2d3d36c0e64a 100644 --- a/src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.rs +++ b/src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.rs @@ -1,6 +1,5 @@ //@compile-flags: -Zmiri-deterministic-concurrency // A case that is not covered by `mixed_size_read_write`. -#![feature(ptr_as_ref_unchecked)] use std::sync::atomic::*; use std::thread; diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index a74a66d5455a..052c83d1aa31 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -353,7 +353,7 @@ macro_rules! test_ftoi_itof { assert_itof(i, f, msg); } - let fbits = <$fty>::BITS; + let fbits = <$fty as Float>::BITS; let fsig_bits = <$fty>::SIGNIFICAND_BITS; let ibits = <$ity>::BITS; let imax: $ity = <$ity>::MAX; @@ -528,9 +528,9 @@ macro_rules! test_ftof { assert!((<$f1>::NAN as $f2).is_nan(), "{} -> {} nan", stringify!($f1), stringify!($f2)); let min_sub_casted = <$f1>::from_bits(0x1) as $f2; - let min_neg_sub_casted = <$f1>::from_bits(0x1 | 1 << (<$f1>::BITS - 1)) as $f2; + let min_neg_sub_casted = <$f1>::from_bits(0x1 | 1 << (<$f1 as Float>::BITS - 1)) as $f2; - if <$f1>::BITS > <$f2>::BITS { + if <$f1 as Float>::BITS > <$f2 as Float>::BITS { assert_feq(<$f1>::MAX as $f2, <$f2>::INFINITY, "max -> inf"); assert_feq(<$f1>::MIN as $f2, <$f2>::NEG_INFINITY, "max -> inf"); assert_biteq(min_sub_casted, f2zero, "min subnormal -> 0.0"); diff --git a/src/tools/rust-analyzer/.github/workflows/rustdoc.yaml b/src/tools/rust-analyzer/.github/workflows/rustdoc.yaml index 9cc18fc69ede..0cc7ce77ddb6 100644 --- a/src/tools/rust-analyzer/.github/workflows/rustdoc.yaml +++ b/src/tools/rust-analyzer/.github/workflows/rustdoc.yaml @@ -24,7 +24,7 @@ jobs: run: rustup update --no-self-update stable - name: Build Documentation - run: cargo doc --all --no-deps + run: cargo doc --all --no-deps --document-private-items - name: Deploy Docs uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 42eaeb01f1f2..2cf3e37a43bc 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -178,9 +178,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "camino" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ "serde_core", ] @@ -1864,6 +1864,7 @@ dependencies = [ "intern", "libc", "libloading", + "line-index 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "memmap2", "object", "paths", @@ -1878,9 +1879,14 @@ name = "proc-macro-srv-cli" version = "0.0.0" dependencies = [ "clap", - "postcard", + "expect-test", + "intern", + "paths", "proc-macro-api", "proc-macro-srv", + "proc-macro-test", + "span", + "tt", ] [[package]] @@ -2628,7 +2634,7 @@ dependencies = [ [[package]] name = "smol_str" -version = "0.3.4" +version = "0.3.5" dependencies = [ "arbitrary", "borsh", diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 8003cb2fba8e..2288933a96cc 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -42,7 +42,7 @@ debug = 2 # lsp-server = { path = "lib/lsp-server" } -# ungrammar = { path = "lin/ungrammar" } +# ungrammar = { path = "lib/ungrammar" } # salsa = { path = "../salsa" } # salsa-macros = { path = "../salsa/components/salsa-macros" } @@ -107,7 +107,7 @@ anyhow = "1.0.98" arrayvec = "0.7.6" bitflags = "2.9.1" cargo_metadata = "0.23.0" -camino = "1.1.10" +camino = "1.2.2" crossbeam-channel = "0.5.15" dissimilar = "1.0.10" dot = "0.1.4" diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs index 240f1264917a..94793a3618e1 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/input.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs @@ -221,6 +221,7 @@ pub enum LangCrateOrigin { ProcMacro, Std, Test, + Dependency, Other, } @@ -245,7 +246,7 @@ impl fmt::Display for LangCrateOrigin { LangCrateOrigin::ProcMacro => "proc_macro", LangCrateOrigin::Std => "std", LangCrateOrigin::Test => "test", - LangCrateOrigin::Other => "other", + LangCrateOrigin::Other | LangCrateOrigin::Dependency => "other", }; f.write_str(text) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index 83df11f2d2a4..0b8f65687218 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -135,6 +135,7 @@ fn match_attr_flags(attr_flags: &mut AttrFlags, attr: Meta) -> ControlFlow match name.text() { "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED), + "ignore" => attr_flags.insert(AttrFlags::IS_IGNORE), "lang" => attr_flags.insert(AttrFlags::LANG_ITEM), "path" => attr_flags.insert(AttrFlags::HAS_PATH), "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs b/src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs index 32385516ab58..946f08ec3682 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs @@ -8,7 +8,8 @@ use intern::{Symbol, sym}; use tt::TextRange; use crate::{ - AdtId, BuiltinDeriveImplId, BuiltinDeriveImplLoc, FunctionId, HasModule, db::DefDatabase, + AdtId, BuiltinDeriveImplId, BuiltinDeriveImplLoc, FunctionId, HasModule, MacroId, + db::DefDatabase, lang_item::LangItems, }; macro_rules! declare_enum { @@ -86,6 +87,25 @@ declare_enum!( DispatchFromDyn => [], ); +impl BuiltinDeriveImplTrait { + pub fn derive_macro(self, lang_items: &LangItems) -> Option { + match self { + BuiltinDeriveImplTrait::Copy => lang_items.CopyDerive, + BuiltinDeriveImplTrait::Clone => lang_items.CloneDerive, + BuiltinDeriveImplTrait::Default => lang_items.DefaultDerive, + BuiltinDeriveImplTrait::Debug => lang_items.DebugDerive, + BuiltinDeriveImplTrait::Hash => lang_items.HashDerive, + BuiltinDeriveImplTrait::Ord => lang_items.OrdDerive, + BuiltinDeriveImplTrait::PartialOrd => lang_items.PartialOrdDerive, + BuiltinDeriveImplTrait::Eq => lang_items.EqDerive, + BuiltinDeriveImplTrait::PartialEq => lang_items.PartialEqDerive, + BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => { + lang_items.CoercePointeeDerive + } + } + } +} + impl BuiltinDeriveImplMethod { pub fn trait_method( self, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs index 7d3a94b03833..4308d0ef1c29 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs @@ -27,14 +27,15 @@ pub mod keys { use std::marker::PhantomData; + use either::Either; use hir_expand::{MacroCallId, attrs::AttrId}; use rustc_hash::FxHashMap; use syntax::{AstNode, AstPtr, ast}; use crate::{ - BlockId, ConstId, EnumId, EnumVariantId, ExternBlockId, ExternCrateId, FieldId, FunctionId, - ImplId, LifetimeParamId, Macro2Id, MacroRulesId, ProcMacroId, StaticId, StructId, TraitId, - TypeAliasId, TypeOrConstParamId, UnionId, UseId, + BlockId, BuiltinDeriveImplId, ConstId, EnumId, EnumVariantId, ExternBlockId, ExternCrateId, + FieldId, FunctionId, ImplId, LifetimeParamId, Macro2Id, MacroRulesId, ProcMacroId, + StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, UnionId, UseId, dyn_map::{DynMap, Policy}, }; @@ -71,7 +72,8 @@ pub mod keys { ( AttrId, /* derive() */ MacroCallId, - /* actual derive macros */ Box<[Option]>, + /* actual derive macros */ + Box<[Option>]>, ), > = Key::new(); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs index 10cd460d1d36..1ce4c881e7ea 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs @@ -32,7 +32,7 @@ use crate::{ expr_store::path::Path, hir::{ Array, AsmOperand, Binding, BindingId, Expr, ExprId, ExprOrPatId, Label, LabelId, Pat, - PatId, RecordFieldPat, Statement, + PatId, RecordFieldPat, RecordSpread, Statement, }, nameres::{DefMap, block_def_map}, type_ref::{LifetimeRef, LifetimeRefId, PathId, TypeRef, TypeRefId}, @@ -474,8 +474,8 @@ impl ExpressionStore { match expr_only.binding_owners.get(&binding) { Some(it) => { // We assign expression ids in a way that outer closures will receive - // a lower id - it.into_raw() < relative_to.into_raw() + // a higher id (allocated after their body is collected) + it.into_raw() > relative_to.into_raw() } None => true, } @@ -575,8 +575,8 @@ impl ExpressionStore { for field in fields.iter() { f(field.expr); } - if let &Some(expr) = spread { - f(expr); + if let RecordSpread::Expr(expr) = spread { + f(*expr); } } Expr::Closure { body, .. } => { @@ -706,8 +706,8 @@ impl ExpressionStore { for field in fields.iter() { f(field.expr); } - if let &Some(expr) = spread { - f(expr); + if let RecordSpread::Expr(expr) = spread { + f(*expr); } } Expr::Closure { body, .. } => { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index 4ae4271b92f5..4fbf6d951779 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -47,7 +47,7 @@ use crate::{ hir::{ Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind, Expr, ExprId, Item, Label, LabelId, Literal, MatchArm, Movability, OffsetOf, Pat, PatId, - RecordFieldPat, RecordLitField, Statement, generics::GenericParams, + RecordFieldPat, RecordLitField, RecordSpread, Statement, generics::GenericParams, }, item_scope::BuiltinShadowMode, item_tree::FieldsShape, @@ -150,6 +150,7 @@ pub(super) fn lower_body( }; let body_expr = collector.collect( + &mut params, body, if is_async_fn { Awaitable::Yes @@ -903,24 +904,57 @@ impl<'db> ExprCollector<'db> { }) } - fn collect(&mut self, expr: Option, awaitable: Awaitable) -> ExprId { + /// An `async fn` needs to capture all parameters in the generated `async` block, even if they have + /// non-captured patterns such as wildcards (to ensure consistent drop order). + fn lower_async_fn(&mut self, params: &mut Vec, body: ExprId) -> ExprId { + let mut statements = Vec::new(); + for param in params { + let name = match self.store.pats[*param] { + Pat::Bind { id, .. } + if matches!( + self.store.bindings[id].mode, + BindingAnnotation::Unannotated | BindingAnnotation::Mutable + ) => + { + // If this is a direct binding, we can leave it as-is, as it'll always be captured anyway. + continue; + } + Pat::Bind { id, .. } => { + // If this is a `ref` binding, we can't leave it as is but we can at least reuse the name, for better display. + self.store.bindings[id].name.clone() + } + _ => self.generate_new_name(), + }; + let binding_id = + self.alloc_binding(name.clone(), BindingAnnotation::Mutable, HygieneId::ROOT); + let pat_id = self.alloc_pat_desugared(Pat::Bind { id: binding_id, subpat: None }); + let expr = self.alloc_expr_desugared(Expr::Path(name.into())); + statements.push(Statement::Let { + pat: *param, + type_ref: None, + initializer: Some(expr), + else_branch: None, + }); + *param = pat_id; + } + + self.alloc_expr_desugared(Expr::Async { + id: None, + statements: statements.into_boxed_slice(), + tail: Some(body), + }) + } + + fn collect( + &mut self, + params: &mut Vec, + expr: Option, + awaitable: Awaitable, + ) -> ExprId { self.awaitable_context.replace(awaitable); self.with_label_rib(RibKind::Closure, |this| { - if awaitable == Awaitable::Yes { - match expr { - Some(e) => { - let syntax_ptr = AstPtr::new(&e); - let expr = this.collect_expr(e); - this.alloc_expr_desugared_with_ptr( - Expr::Async { id: None, statements: Box::new([]), tail: Some(expr) }, - syntax_ptr, - ) - } - None => this.missing_expr(), - } - } else { - this.collect_expr_opt(expr) - } + let body = this.collect_expr_opt(expr); + if awaitable == Awaitable::Yes { this.lower_async_fn(params, body) } else { body } }) } @@ -1232,10 +1266,16 @@ impl<'db> ExprCollector<'db> { Some(RecordLitField { name, expr }) }) .collect(); - let spread = nfl.spread().map(|s| self.collect_expr(s)); + let spread_expr = nfl.spread().map(|s| self.collect_expr(s)); + let has_spread_syntax = nfl.dotdot_token().is_some(); + let spread = match (spread_expr, has_spread_syntax) { + (None, false) => RecordSpread::None, + (None, true) => RecordSpread::FieldDefaults, + (Some(expr), _) => RecordSpread::Expr(expr), + }; Expr::RecordLit { path, fields, spread } } else { - Expr::RecordLit { path, fields: Box::default(), spread: None } + Expr::RecordLit { path, fields: Box::default(), spread: RecordSpread::None } }; self.alloc_expr(record_lit, syntax_ptr) @@ -1961,7 +2001,7 @@ impl<'db> ExprCollector<'db> { } } - fn collect_expr_opt(&mut self, expr: Option) -> ExprId { + pub fn collect_expr_opt(&mut self, expr: Option) -> ExprId { match expr { Some(expr) => self.collect_expr(expr), None => self.missing_expr(), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs index 7ef84f27f664..51616afb3892 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs @@ -10,7 +10,8 @@ use crate::{ builtin_type::BuiltinUint, expr_store::{HygieneId, lower::ExprCollector, path::Path}, hir::{ - Array, BindingAnnotation, Expr, ExprId, Literal, Pat, RecordLitField, Statement, + Array, BindingAnnotation, Expr, ExprId, Literal, Pat, RecordLitField, RecordSpread, + Statement, format_args::{ self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions, @@ -869,7 +870,7 @@ impl<'db> ExprCollector<'db> { self.alloc_expr_desugared(Expr::RecordLit { path: self.lang_path(lang_items.FormatPlaceholder).map(Box::new), fields: Box::new([position, flags, precision, width]), - spread: None, + spread: RecordSpread::None, }) } else { let format_placeholder_new = diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs index f5ef8e1a3595..35f3cd114e36 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs @@ -16,7 +16,8 @@ use crate::{ attrs::AttrFlags, expr_store::path::{GenericArg, GenericArgs}, hir::{ - Array, BindingAnnotation, CaptureBy, ClosureKind, Literal, Movability, Statement, + Array, BindingAnnotation, CaptureBy, ClosureKind, Literal, Movability, RecordSpread, + Statement, generics::{GenericParams, WherePredicate}, }, lang_item::LangItemTarget, @@ -139,7 +140,7 @@ pub fn print_variant_body_hir(db: &dyn DefDatabase, owner: VariantId, edition: E } for (_, data) in fields.fields().iter() { - let FieldData { name, type_ref, visibility, is_unsafe } = data; + let FieldData { name, type_ref, visibility, is_unsafe, default_value: _ } = data; match visibility { crate::item_tree::RawVisibility::Module(interned, _visibility_explicitness) => { w!(p, "pub(in {})", interned.display(db, p.edition)) @@ -679,10 +680,17 @@ impl Printer<'_> { p.print_expr(field.expr); wln!(p, ","); } - if let Some(spread) = spread { - w!(p, ".."); - p.print_expr(*spread); - wln!(p); + match spread { + RecordSpread::None => {} + RecordSpread::FieldDefaults => { + w!(p, ".."); + wln!(p); + } + RecordSpread::Expr(spread_expr) => { + w!(p, ".."); + p.print_expr(*spread_expr); + wln!(p); + } } }); w!(self, "}}"); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs index 504c310684d6..8f857aeeff95 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs @@ -659,3 +659,21 @@ fn main() { }"#]] .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT)) } + +#[test] +fn async_fn_weird_param_patterns() { + let (db, body, def) = lower( + r#" +async fn main(&self, param1: i32, ref mut param2: i32, _: i32, param4 @ _: i32, 123: i32) {} +"#, + ); + + expect![[r#" + fn main(self, param1, mut param2, mut 0, param4 @ _, mut 1) async { + let ref mut param2 = param2; + let _ = 0; + let 123 = 1; + {} + }"#]] + .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT)) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs index 53be0de7d9c3..7781a8fe54ee 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs @@ -187,6 +187,13 @@ impl From for Literal { } } +#[derive(Debug, Clone, Eq, PartialEq, Copy)] +pub enum RecordSpread { + None, + FieldDefaults, + Expr(ExprId), +} + #[derive(Debug, Clone, Eq, PartialEq)] pub enum Expr { /// This is produced if the syntax tree does not have a required expression piece. @@ -259,7 +266,7 @@ pub enum Expr { RecordLit { path: Option>, fields: Box<[RecordLitField]>, - spread: Option, + spread: RecordSpread, }, Field { expr: ExprId, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs index a3278dd76c86..9e1efb977786 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs @@ -4,6 +4,7 @@ use std::{fmt, sync::LazyLock}; use base_db::Crate; +use either::Either; use hir_expand::{AstId, MacroCallId, attrs::AttrId, name::Name}; use indexmap::map::Entry; use itertools::Itertools; @@ -199,7 +200,7 @@ struct DeriveMacroInvocation { attr_id: AttrId, /// The `#[derive]` call attr_call_id: MacroCallId, - derive_call_ids: SmallVec<[Option; 4]>, + derive_call_ids: SmallVec<[Option>; 4]>, } pub(crate) static BUILTIN_SCOPE: LazyLock> = LazyLock::new(|| { @@ -345,7 +346,9 @@ impl ItemScope { pub fn all_macro_calls(&self) -> impl Iterator + '_ { self.macro_invocations.values().copied().chain(self.attr_macros.values().copied()).chain( self.derive_macros.values().flat_map(|it| { - it.iter().flat_map(|it| it.derive_call_ids.iter().copied().flatten()) + it.iter().flat_map(|it| { + it.derive_call_ids.iter().copied().flatten().flat_map(|it| it.left()) + }) }), ) } @@ -379,6 +382,10 @@ impl ItemScope { self.types.get(name).map(|item| (item.def, item.vis)) } + pub(crate) fn makro(&self, name: &Name) -> Option { + self.macros.get(name).map(|item| item.def) + } + /// XXX: this is O(N) rather than O(1), try to not introduce new usages. pub(crate) fn name_of(&self, item: ItemInNs) -> Option<(&Name, Visibility, /*declared*/ bool)> { match item { @@ -519,7 +526,7 @@ impl ItemScope { pub(crate) fn set_derive_macro_invoc( &mut self, adt: AstId, - call: MacroCallId, + call: Either, id: AttrId, idx: usize, ) { @@ -539,7 +546,7 @@ impl ItemScope { adt: AstId, attr_id: AttrId, attr_call_id: MacroCallId, - mut derive_call_ids: SmallVec<[Option; 4]>, + mut derive_call_ids: SmallVec<[Option>; 4]>, ) { derive_call_ids.shrink_to_fit(); self.derive_macros.entry(adt).or_default().push(DeriveMacroInvocation { @@ -554,7 +561,9 @@ impl ItemScope { ) -> impl Iterator< Item = ( AstId, - impl Iterator])>, + impl Iterator< + Item = (AttrId, MacroCallId, &[Option>]), + >, ), > + '_ { self.derive_macros.iter().map(|(k, v)| { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs index eba4d87ec9f8..51dd55301f44 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs @@ -7,8 +7,8 @@ use intern::{Symbol, sym}; use stdx::impl_from; use crate::{ - AdtId, AssocItemId, AttrDefId, Crate, EnumId, EnumVariantId, FunctionId, ImplId, ModuleDefId, - StaticId, StructId, TraitId, TypeAliasId, UnionId, + AdtId, AssocItemId, AttrDefId, Crate, EnumId, EnumVariantId, FunctionId, ImplId, MacroId, + ModuleDefId, StaticId, StructId, TraitId, TypeAliasId, UnionId, attrs::AttrFlags, db::DefDatabase, nameres::{DefMap, assoc::TraitItems, crate_def_map, crate_local_def_map}, @@ -99,7 +99,7 @@ pub fn crate_lang_items(db: &dyn DefDatabase, krate: Crate) -> Option Option { + let mut current = &core_def_map[core_def_map.root]; + for module in modules { + let Some((ModuleDefId::ModuleId(cur), _)) = + current.scope.type_(&Name::new_symbol_root(module.clone())) + else { + return None; + }; + if cur.krate(db) != core_def_map.krate() || cur.block(db) != core_def_map.block_id() { + return None; + } + current = &core_def_map[cur]; + } + current.scope.makro(&Name::new_symbol_root(name)) +} + #[salsa::tracked(returns(as_deref))] pub(crate) fn crate_notable_traits(db: &dyn DefDatabase, krate: Crate) -> Option> { let mut traits = Vec::new(); @@ -195,7 +216,11 @@ macro_rules! language_item_table { @non_lang_core_traits: - $( core::$($non_lang_module:ident)::*, $non_lang_trait:ident; )* + $( core::$($non_lang_trait_module:ident)::*, $non_lang_trait:ident; )* + + @non_lang_core_macros: + + $( core::$($non_lang_macro_module:ident)::*, $non_lang_macro:ident, $non_lang_macro_field:ident; )* ) => { #[allow(non_snake_case)] // FIXME: Should we remove this? #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] @@ -207,6 +232,9 @@ macro_rules! language_item_table { $( pub $non_lang_trait: Option, )* + $( + pub $non_lang_macro_field: Option, + )* } impl LangItems { @@ -218,6 +246,7 @@ macro_rules! language_item_table { fn merge_prefer_self(&mut self, other: &Self) { $( self.$lang_item = self.$lang_item.or(other.$lang_item); )* $( self.$non_lang_trait = self.$non_lang_trait.or(other.$non_lang_trait); )* + $( self.$non_lang_macro_field = self.$non_lang_macro_field.or(other.$non_lang_macro_field); )* } fn assign_lang_item(&mut self, name: Symbol, target: LangItemTarget) { @@ -233,8 +262,9 @@ macro_rules! language_item_table { } } - fn fill_non_lang_core_traits(&mut self, db: &dyn DefDatabase, core_def_map: &DefMap) { - $( self.$non_lang_trait = resolve_core_trait(db, core_def_map, &[ $(sym::$non_lang_module),* ], sym::$non_lang_trait); )* + fn fill_non_lang_core_items(&mut self, db: &dyn DefDatabase, core_def_map: &DefMap) { + $( self.$non_lang_trait = resolve_core_trait(db, core_def_map, &[ $(sym::$non_lang_trait_module),* ], sym::$non_lang_trait); )* + $( self.$non_lang_macro_field = resolve_core_macro(db, core_def_map, &[ $(sym::$non_lang_macro_module),* ], sym::$non_lang_macro); )* } } @@ -469,6 +499,11 @@ language_item_table! { LangItems => RangeToInclusive, sym::RangeToInclusive, StructId; RangeTo, sym::RangeTo, StructId; + RangeFromCopy, sym::RangeFromCopy, StructId; + RangeInclusiveCopy, sym::RangeInclusiveCopy, StructId; + RangeCopy, sym::RangeCopy, StructId; + RangeToInclusiveCopy, sym::RangeToInclusiveCopy, StructId; + String, sym::String, StructId; CStr, sym::CStr, StructId; Ordering, sym::Ordering, EnumId; @@ -479,4 +514,16 @@ language_item_table! { LangItems => core::hash, Hash; core::cmp, Ord; core::cmp, Eq; + + @non_lang_core_macros: + core::default, Default, DefaultDerive; + core::fmt, Debug, DebugDerive; + core::hash, Hash, HashDerive; + core::cmp, PartialOrd, PartialOrdDerive; + core::cmp, Ord, OrdDerive; + core::cmp, PartialEq, PartialEqDerive; + core::cmp, Eq, EqDerive; + core::marker, CoercePointee, CoercePointeeDerive; + core::marker, Copy, CopyDerive; + core::clone, Clone, CloneDerive; } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs index e33a366769b0..bbadcf8794bf 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs @@ -237,3 +237,23 @@ fn test() { "#]], ); } + +#[test] +fn meta_fat_arrow() { + check( + r#" +macro_rules! m { + ( $m:meta => ) => {}; +} + +m! { foo => } + "#, + expect![[r#" +macro_rules! m { + ( $m:meta => ) => {}; +} + + + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs index 5f05cdb1e2ba..1e3ea50c5a0f 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -61,6 +61,7 @@ mod tests; use std::ops::{Deref, DerefMut, Index, IndexMut}; use base_db::Crate; +use either::Either; use hir_expand::{ EditionedFileId, ErasedAstId, HirFileId, InFile, MacroCallId, mod_path::ModPath, name::Name, proc_macro::ProcMacroKind, @@ -75,8 +76,8 @@ use triomphe::Arc; use tt::TextRange; use crate::{ - AstId, BlockId, BlockLoc, ExternCrateId, FunctionId, FxIndexMap, Lookup, MacroCallStyles, - MacroExpander, MacroId, ModuleId, ModuleIdLt, ProcMacroId, UseId, + AstId, BlockId, BlockLoc, BuiltinDeriveImplId, ExternCrateId, FunctionId, FxIndexMap, Lookup, + MacroCallStyles, MacroExpander, MacroId, ModuleId, ModuleIdLt, ProcMacroId, UseId, db::DefDatabase, item_scope::{BuiltinShadowMode, ItemScope}, item_tree::TreeId, @@ -192,7 +193,8 @@ pub struct DefMap { /// Tracks which custom derives are in scope for an item, to allow resolution of derive helper /// attributes. // FIXME: Figure out a better way for the IDE layer to resolve these? - derive_helpers_in_scope: FxHashMap, Vec<(Name, MacroId, MacroCallId)>>, + derive_helpers_in_scope: + FxHashMap, Vec<(Name, MacroId, Either)>>, /// A mapping from [`hir_expand::MacroDefId`] to [`crate::MacroId`]. pub macro_def_to_macro_id: FxHashMap, @@ -214,7 +216,7 @@ struct DefMapCrateData { registered_tools: Vec, /// Unstable features of Rust enabled with `#![feature(A, B)]`. unstable_features: FxHashSet, - /// #[rustc_coherence_is_core] + /// `#[rustc_coherence_is_core]` rustc_coherence_is_core: bool, no_core: bool, no_std: bool, @@ -540,7 +542,7 @@ impl DefMap { pub fn derive_helpers_in_scope( &self, id: AstId, - ) -> Option<&[(Name, MacroId, MacroCallId)]> { + ) -> Option<&[(Name, MacroId, Either)]> { self.derive_helpers_in_scope.get(&id.map(|it| it.upcast())).map(Deref::deref) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index 87ade0651762..323060f61d15 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -3,7 +3,7 @@ //! `DefCollector::collect` contains the fixed-point iteration loop which //! resolves imports and expands macros. -use std::{iter, mem}; +use std::{iter, mem, ops::Range}; use base_db::{BuiltDependency, Crate, CrateOrigin, LangCrateOrigin}; use cfg::{CfgAtom, CfgExpr, CfgOptions}; @@ -226,6 +226,7 @@ struct DeferredBuiltinDerive { container: ItemContainerId, derive_attr_id: AttrId, derive_index: u32, + helpers_range: Range, } /// Walks the tree of module recursively @@ -1354,7 +1355,7 @@ impl<'db> DefCollector<'db> { if let Ok((macro_id, def_id, call_id)) = id { self.def_map.modules[directive.module_id].scope.set_derive_macro_invoc( ast_id.ast_id, - call_id, + Either::Left(call_id), *derive_attr, *derive_pos, ); @@ -1369,7 +1370,7 @@ impl<'db> DefCollector<'db> { .extend(izip!( helpers.iter().cloned(), iter::repeat(macro_id), - iter::repeat(call_id), + iter::repeat(Either::Left(call_id)), )); } } @@ -1492,6 +1493,8 @@ impl<'db> DefCollector<'db> { Interned::new(path), ); + derive_call_ids.push(None); + // Try to resolve the derive immediately. If we succeed, we can also use the fast path // for builtin derives. If not, we cannot use it, as it can cause the ADT to become // interned while the derive is still unresolved, which will cause it to get forgotten. @@ -1506,23 +1509,42 @@ impl<'db> DefCollector<'db> { call_id, ); + let ast_id_without_path = ast_id.ast_id; + let directive = MacroDirective { + module_id: directive.module_id, + depth: directive.depth + 1, + kind: MacroDirectiveKind::Derive { + ast_id, + derive_attr: *attr_id, + derive_pos: idx, + ctxt: call_site.ctx, + derive_macro_id: call_id, + }, + container: directive.container, + }; + if let Ok((macro_id, def_id, call_id)) = id { - derive_call_ids.push(Some(call_id)); + let (mut helpers_start, mut helpers_end) = (0, 0); // Record its helper attributes. if def_id.krate != self.def_map.krate { let def_map = crate_def_map(self.db, def_id.krate); if let Some(helpers) = def_map.data.exported_derives.get(¯o_id) { - self.def_map + let derive_helpers = self + .def_map .derive_helpers_in_scope - .entry(ast_id.ast_id.map(|it| it.upcast())) - .or_default() - .extend(izip!( - helpers.iter().cloned(), - iter::repeat(macro_id), - iter::repeat(call_id), - )); + .entry( + ast_id_without_path.map(|it| it.upcast()), + ) + .or_default(); + helpers_start = derive_helpers.len(); + derive_helpers.extend(izip!( + helpers.iter().cloned(), + iter::repeat(macro_id), + iter::repeat(Either::Left(call_id)), + )); + helpers_end = derive_helpers.len(); } } @@ -1531,7 +1553,7 @@ impl<'db> DefCollector<'db> { def_id.kind { self.deferred_builtin_derives - .entry(ast_id.ast_id.upcast()) + .entry(ast_id_without_path.upcast()) .or_default() .push(DeferredBuiltinDerive { call_id, @@ -1541,24 +1563,15 @@ impl<'db> DefCollector<'db> { depth: directive.depth, derive_attr_id: *attr_id, derive_index: idx as u32, + helpers_range: helpers_start..helpers_end, }); } else { - push_resolved(&mut resolved, directive, call_id); + push_resolved(&mut resolved, &directive, call_id); + *derive_call_ids.last_mut().unwrap() = + Some(Either::Left(call_id)); } } else { - derive_call_ids.push(None); - self.unresolved_macros.push(MacroDirective { - module_id: directive.module_id, - depth: directive.depth + 1, - kind: MacroDirectiveKind::Derive { - ast_id, - derive_attr: *attr_id, - derive_pos: idx, - ctxt: call_site.ctx, - derive_macro_id: call_id, - }, - container: directive.container, - }); + self.unresolved_macros.push(directive); } } @@ -1858,9 +1871,8 @@ impl ModCollector<'_, '_> { ast_id: FileAstId, id: AdtId, def_map: &mut DefMap| { - let Some(deferred_derives) = - deferred_derives.remove(&InFile::new(file_id, ast_id.upcast())) - else { + let ast_id = InFile::new(file_id, ast_id.upcast()); + let Some(deferred_derives) = deferred_derives.remove(&ast_id.upcast()) else { return; }; let module = &mut def_map.modules[module_id]; @@ -1876,6 +1888,22 @@ impl ModCollector<'_, '_> { }, ); module.scope.define_builtin_derive_impl(impl_id); + module.scope.set_derive_macro_invoc( + ast_id, + Either::Right(impl_id), + deferred_derive.derive_attr_id, + deferred_derive.derive_index as usize, + ); + // Change its helper attributes to the new id. + if let Some(derive_helpers) = + def_map.derive_helpers_in_scope.get_mut(&ast_id.map(|it| it.upcast())) + { + for (_, _, call_id) in + &mut derive_helpers[deferred_derive.helpers_range.clone()] + { + *call_id = Either::Right(impl_id); + } + } }); } }; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs index 23d60d58f085..fe55252e2540 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs @@ -1,8 +1,8 @@ mod globs; +mod imports; mod incremental; mod macros; mod mod_resolution; -mod primitives; use base_db::RootQueryDb; use expect_test::{Expect, expect}; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/imports.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/imports.rs new file mode 100644 index 000000000000..b1960b785a83 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/imports.rs @@ -0,0 +1,63 @@ +use super::*; + +#[test] +fn kw_path_renames() { + check( + r#" +macro_rules! m { + () => { + pub use $crate as dollar_crate; + pub use $crate::{self as self_dollar_crate}; + }; +} + +pub use self as this; +pub use crate as krate; + +pub use crate::{self as self_krate}; +m!(); + +mod foo { + pub use super as zuper; + pub use super::{self as self_zuper}; +} +"#, + expect![[r#" + crate + - dollar_crate : type (import) + - foo : type + - krate : type (import) + - self_dollar_crate : type (import) + - self_krate : type (import) + - this : type (import) + - (legacy) m : macro! + + crate::foo + - self_zuper : type (import) + - zuper : type (import) + - (legacy) m : macro! + "#]], + ); +} + +#[test] +fn primitive_reexport() { + check( + r#" +//- /lib.rs +mod foo; +use foo::int; + +//- /foo.rs +pub use i32 as int; +"#, + expect![[r#" + crate + - foo : type + - int : type (import) + + crate::foo + - int : type (import) + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/primitives.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/primitives.rs deleted file mode 100644 index 861690238d47..000000000000 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/primitives.rs +++ /dev/null @@ -1,23 +0,0 @@ -use super::*; - -#[test] -fn primitive_reexport() { - check( - r#" -//- /lib.rs -mod foo; -use foo::int; - -//- /foo.rs -pub use i32 as int; -"#, - expect![[r#" - crate - - foo : type - - int : type (import) - - crate::foo - - int : type (import) - "#]], - ); -} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs index 0dd88edbfb08..37c8f762fe5d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs @@ -12,7 +12,7 @@ use intern::{Symbol, sym}; use la_arena::{Arena, Idx}; use rustc_abi::{IntegerType, ReprOptions}; use syntax::{ - NodeOrToken, SyntaxNodePtr, T, + AstNode, NodeOrToken, SyntaxNodePtr, T, ast::{self, HasGenericParams, HasName, HasVisibility, IsString}, }; use thin_vec::ThinVec; @@ -754,6 +754,7 @@ pub struct FieldData { pub type_ref: TypeRefId, pub visibility: RawVisibility, pub is_unsafe: bool, + pub default_value: Option, } pub type LocalFieldId = Idx; @@ -903,7 +904,14 @@ fn lower_fields( .filter_map(NodeOrToken::into_token) .any(|token| token.kind() == T![unsafe]); let name = field_name(idx, &field); - arena.alloc(FieldData { name, type_ref, visibility, is_unsafe }); + + // Check if field has default value (only for record fields) + let default_value = ast::RecordField::cast(field.syntax().clone()) + .and_then(|rf| rf.eq_token().is_some().then_some(rf.expr())) + .flatten() + .map(|expr| col.collect_expr_opt(Some(expr))); + + arena.alloc(FieldData { name, type_ref, visibility, is_unsafe, default_value }); idx += 1; } Err(cfg) => { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs index 06b9b5418e37..c94663ca0cbc 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs @@ -115,7 +115,7 @@ fn dummy_gate_test_expand( /// wasting a lot of memory, and it would also require some way to use a path in a way that makes it /// always resolve as a derive without nameres recollecting them. /// So this hacky approach is a lot more friendly for us, though it does require a bit of support in -/// [`hir::Semantics`] to make this work. +/// hir::Semantics to make this work. fn derive_expand( db: &dyn ExpandDatabase, id: MacroCallId, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs b/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs index a0de36548e9f..ccef9168ac3a 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs @@ -1,4 +1,4 @@ -//! Processes out #[cfg] and #[cfg_attr] attributes from the input for the derive macro +//! Processes out `#[cfg]` and `#[cfg_attr]` attributes from the input for the derive macro use std::{cell::OnceCell, ops::ControlFlow}; use ::tt::TextRange; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/declarative.rs b/src/tools/rust-analyzer/crates/hir-expand/src/declarative.rs index d10e122a5deb..172641227599 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/declarative.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/declarative.rs @@ -101,7 +101,9 @@ impl DeclarativeMacroExpander { match &*value { "transparent" => ControlFlow::Break(Transparency::Transparent), // "semitransparent" is for old rustc versions. - "semiopaque" | "semitransparent" => ControlFlow::Break(Transparency::SemiOpaque), + "semiopaque" | "semitransparent" => { + ControlFlow::Break(Transparency::SemiOpaque) + } "opaque" => ControlFlow::Break(Transparency::Opaque), _ => ControlFlow::Continue(()), } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs index 1712c28aa8ab..78228cf82e67 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs @@ -423,6 +423,10 @@ macro_rules! __known_path { (core::ops::RangeTo) => {}; (core::ops::RangeToInclusive) => {}; (core::ops::RangeInclusive) => {}; + (core::range::Range) => {}; + (core::range::RangeFrom) => {}; + (core::range::RangeInclusive) => {}; + (core::range::RangeToInclusive) => {}; (core::future::Future) => {}; (core::future::IntoFuture) => {}; (core::fmt::Debug) => {}; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs index dd1fc3b36ef8..4e1bb6f4c533 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs @@ -41,7 +41,7 @@ use crate::{ pub(crate) use hir_def::{ LocalFieldId, VariantId, expr_store::Body, - hir::{Expr, ExprId, MatchArm, Pat, PatId, Statement}, + hir::{Expr, ExprId, MatchArm, Pat, PatId, RecordSpread, Statement}, }; pub enum BodyValidationDiagnostic { @@ -123,7 +123,7 @@ impl<'db> ExprValidator<'db> { } for (id, expr) in body.exprs() { - if let Some((variant, missed_fields, true)) = + if let Some((variant, missed_fields)) = record_literal_missing_fields(db, self.infer, id, expr) { self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields { @@ -154,7 +154,7 @@ impl<'db> ExprValidator<'db> { } for (id, pat) in body.pats() { - if let Some((variant, missed_fields, true)) = + if let Some((variant, missed_fields)) = record_pattern_missing_fields(db, self.infer, id, pat) { self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields { @@ -557,9 +557,9 @@ pub fn record_literal_missing_fields( infer: &InferenceResult, id: ExprId, expr: &Expr, -) -> Option<(VariantId, Vec, /*exhaustive*/ bool)> { - let (fields, exhaustive) = match expr { - Expr::RecordLit { fields, spread, .. } => (fields, spread.is_none()), +) -> Option<(VariantId, Vec)> { + let (fields, spread) = match expr { + Expr::RecordLit { fields, spread, .. } => (fields, spread), _ => return None, }; @@ -571,15 +571,28 @@ pub fn record_literal_missing_fields( let variant_data = variant_def.fields(db); let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); + // don't show missing fields if: + // - has ..expr + // - or has default value + .. + // - or already in code let missed_fields: Vec = variant_data .fields() .iter() - .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) }) + .filter_map(|(f, d)| { + if specified_fields.contains(&d.name) + || matches!(spread, RecordSpread::Expr(_)) + || (d.default_value.is_some() && matches!(spread, RecordSpread::FieldDefaults)) + { + None + } else { + Some(f) + } + }) .collect(); if missed_fields.is_empty() { return None; } - Some((variant_def, missed_fields, exhaustive)) + Some((variant_def, missed_fields)) } pub fn record_pattern_missing_fields( @@ -587,9 +600,9 @@ pub fn record_pattern_missing_fields( infer: &InferenceResult, id: PatId, pat: &Pat, -) -> Option<(VariantId, Vec, /*exhaustive*/ bool)> { - let (fields, exhaustive) = match pat { - Pat::Record { path: _, args, ellipsis } => (args, !ellipsis), +) -> Option<(VariantId, Vec)> { + let (fields, ellipsis) = match pat { + Pat::Record { path: _, args, ellipsis } => (args, *ellipsis), _ => return None, }; @@ -601,15 +614,22 @@ pub fn record_pattern_missing_fields( let variant_data = variant_def.fields(db); let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); + // don't show missing fields if: + // - in code + // - or has .. let missed_fields: Vec = variant_data .fields() .iter() - .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) }) + .filter_map( + |(f, d)| { + if specified_fields.contains(&d.name) || ellipsis { None } else { Some(f) } + }, + ) .collect(); if missed_fields.is_empty() { return None; } - Some((variant_def, missed_fields, exhaustive)) + Some((variant_def, missed_fields)) } fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResult) -> bool { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index d527a4ae29c2..35d744e7d16b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -1815,18 +1815,34 @@ impl<'body, 'db> InferenceContext<'body, 'db> { Some(struct_.into()) } + fn has_new_range_feature(&self) -> bool { + self.resolver.top_level_def_map().is_unstable_feature_enabled(&sym::new_range) + } + fn resolve_range(&self) -> Option { - let struct_ = self.lang_items.Range?; + let struct_ = if self.has_new_range_feature() { + self.lang_items.RangeCopy? + } else { + self.lang_items.Range? + }; Some(struct_.into()) } fn resolve_range_inclusive(&self) -> Option { - let struct_ = self.lang_items.RangeInclusiveStruct?; + let struct_ = if self.has_new_range_feature() { + self.lang_items.RangeInclusiveCopy? + } else { + self.lang_items.RangeInclusiveStruct? + }; Some(struct_.into()) } fn resolve_range_from(&self) -> Option { - let struct_ = self.lang_items.RangeFrom?; + let struct_ = if self.has_new_range_feature() { + self.lang_items.RangeFromCopy? + } else { + self.lang_items.RangeFrom? + }; Some(struct_.into()) } @@ -1836,7 +1852,11 @@ impl<'body, 'db> InferenceContext<'body, 'db> { } fn resolve_range_to_inclusive(&self) -> Option { - let struct_ = self.lang_items.RangeToInclusive?; + let struct_ = if self.has_new_range_feature() { + self.lang_items.RangeToInclusiveCopy? + } else { + self.lang_items.RangeToInclusive? + }; Some(struct_.into()) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs index d073b06ccc8a..d69b00adb7f7 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs @@ -2,8 +2,10 @@ use hir_def::{AdtId, hir::ExprId, signatures::TraitFlags}; use rustc_ast_ir::Mutability; +use rustc_hash::FxHashSet; use rustc_type_ir::{ - Flags, InferTy, TypeFlags, UintTy, + InferTy, TypeVisitableExt, UintTy, elaborate, + error::TypeError, inherent::{AdtDef, BoundExistentialPredicates as _, IntoKind, Ty as _}, }; use stdx::never; @@ -12,7 +14,10 @@ use crate::{ InferenceDiagnostic, db::HirDatabase, infer::{AllowTwoPhase, InferenceContext, expr::ExprIsRead}, - next_solver::{BoundExistentialPredicates, DbInterner, ParamTy, Ty, TyKind}, + next_solver::{ + BoundExistentialPredicates, ExistentialPredicate, ParamTy, Region, Ty, TyKind, + infer::traits::ObligationCause, + }, }; #[derive(Debug)] @@ -66,12 +71,13 @@ pub enum CastError { DifferingKinds, SizedUnsizedCast, IllegalCast, - IntToFatCast, + IntToWideCast, NeedDeref, NeedViaPtr, NeedViaThinPtr, NeedViaInt, NonScalar, + PtrPtrAddingAutoTraits, // We don't want to report errors with unknown types currently. // UnknownCastPtrKind, // UnknownExprPtrKind, @@ -137,22 +143,13 @@ impl<'db> CastCheck<'db> { return Ok(()); } - if !self.cast_ty.flags().contains(TypeFlags::HAS_TY_INFER) - && !ctx.table.is_sized(self.cast_ty) - { + if !self.cast_ty.has_infer_types() && !ctx.table.is_sized(self.cast_ty) { return Err(InferenceDiagnostic::CastToUnsized { expr: self.expr, cast_ty: self.cast_ty.store(), }); } - // Chalk doesn't support trait upcasting and fails to solve some obvious goals - // when the trait environment contains some recursive traits (See issue #18047) - // We skip cast checks for such cases for now, until the next-gen solver. - if contains_dyn_trait(self.cast_ty) { - return Ok(()); - } - self.do_check(ctx).map_err(|e| e.into_diagnostic(self.expr, self.expr_ty, self.cast_ty)) } @@ -162,22 +159,23 @@ impl<'db> CastCheck<'db> { (Some(t_from), Some(t_cast)) => (t_from, t_cast), (None, Some(t_cast)) => match self.expr_ty.kind() { TyKind::FnDef(..) => { - let sig = - self.expr_ty.callable_sig(ctx.interner()).expect("FnDef had no sig"); - let sig = ctx.table.normalize_associated_types_in(sig); + // rustc calls `FnCtxt::normalize` on this but it's a no-op in next-solver + let sig = self.expr_ty.fn_sig(ctx.interner()); let fn_ptr = Ty::new_fn_ptr(ctx.interner(), sig); - if ctx - .coerce( - self.source_expr.into(), - self.expr_ty, - fn_ptr, - AllowTwoPhase::No, - ExprIsRead::Yes, - ) - .is_ok() - { - } else { - return Err(CastError::IllegalCast); + match ctx.coerce( + self.source_expr.into(), + self.expr_ty, + fn_ptr, + AllowTwoPhase::No, + ExprIsRead::Yes, + ) { + Ok(_) => {} + Err(TypeError::IntrinsicCast) => { + return Err(CastError::IllegalCast); + } + Err(_) => { + return Err(CastError::NonScalar); + } } (CastTy::FnPtr, t_cast) @@ -213,23 +211,41 @@ impl<'db> CastCheck<'db> { // rustc checks whether the `expr_ty` is foreign adt with `non_exhaustive` sym match (t_from, t_cast) { + // These types have invariants! can't cast into them. (_, CastTy::Int(Int::CEnum) | CastTy::FnPtr) => Err(CastError::NonScalar), + + // * -> Bool (_, CastTy::Int(Int::Bool)) => Err(CastError::CastToBool), - (CastTy::Int(Int::U(UintTy::U8)), CastTy::Int(Int::Char)) => Ok(()), + + // * -> Char + (CastTy::Int(Int::U(UintTy::U8)), CastTy::Int(Int::Char)) => Ok(()), // u8-char-cast (_, CastTy::Int(Int::Char)) => Err(CastError::CastToChar), + + // prim -> float,ptr (CastTy::Int(Int::Bool | Int::CEnum | Int::Char), CastTy::Float) => { Err(CastError::NeedViaInt) } + (CastTy::Int(Int::Bool | Int::CEnum | Int::Char) | CastTy::Float, CastTy::Ptr(..)) | (CastTy::Ptr(..) | CastTy::FnPtr, CastTy::Float) => Err(CastError::IllegalCast), - (CastTy::Ptr(src, _), CastTy::Ptr(dst, _)) => self.check_ptr_ptr_cast(ctx, src, dst), + + // ptr -> ptr + (CastTy::Ptr(src, _), CastTy::Ptr(dst, _)) => self.check_ptr_ptr_cast(ctx, src, dst), // ptr-ptr-cast + + // // ptr-addr-cast (CastTy::Ptr(src, _), CastTy::Int(_)) => self.check_ptr_addr_cast(ctx, src), + (CastTy::FnPtr, CastTy::Int(_)) => Ok(()), + + // addr-ptr-cast (CastTy::Int(_), CastTy::Ptr(dst, _)) => self.check_addr_ptr_cast(ctx, dst), + + // fn-ptr-cast (CastTy::FnPtr, CastTy::Ptr(dst, _)) => self.check_fptr_ptr_cast(ctx, dst), + + // prim -> prim (CastTy::Int(Int::CEnum), CastTy::Int(_)) => Ok(()), (CastTy::Int(Int::Char | Int::Bool), CastTy::Int(_)) => Ok(()), (CastTy::Int(_) | CastTy::Float, CastTy::Int(_) | CastTy::Float) => Ok(()), - (CastTy::FnPtr, CastTy::Int(_)) => Ok(()), } } @@ -241,10 +257,16 @@ impl<'db> CastCheck<'db> { t_cast: Ty<'db>, m_cast: Mutability, ) -> Result<(), CastError> { - // Mutability order is opposite to rustc. `Mut < Not` - if m_expr <= m_cast + let t_expr = ctx.table.try_structurally_resolve_type(t_expr); + let t_cast = ctx.table.try_structurally_resolve_type(t_cast); + + if m_expr >= m_cast && let TyKind::Array(ety, _) = t_expr.kind() + && ctx.infcx().can_eq(ctx.table.param_env, ety, t_cast) { + // Due to historical reasons we allow directly casting references of + // arrays into raw pointers of their element type. + // Coerce to a raw pointer so that we generate RawPtr in MIR. let array_ptr_type = Ty::new_ptr(ctx.interner(), t_expr, m_expr); if ctx @@ -265,14 +287,9 @@ impl<'db> CastCheck<'db> { ); } - // This is a less strict condition than rustc's `demand_eqtype`, - // but false negative is better than false positive - if ctx - .coerce(self.source_expr.into(), ety, t_cast, AllowTwoPhase::No, ExprIsRead::Yes) - .is_ok() - { - return Ok(()); - } + // this will report a type mismatch if needed + let _ = ctx.demand_eqtype(self.expr.into(), ety, t_cast); + return Ok(()); } Err(CastError::IllegalCast) @@ -289,30 +306,147 @@ impl<'db> CastCheck<'db> { match (src_kind, dst_kind) { (Some(PointerKind::Error), _) | (_, Some(PointerKind::Error)) => Ok(()), + // (_, None) => Err(CastError::UnknownCastPtrKind), // (None, _) => Err(CastError::UnknownExprPtrKind), (_, None) | (None, _) => Ok(()), + + // Cast to thin pointer is OK (_, Some(PointerKind::Thin)) => Ok(()), + + // thin -> fat? report invalid cast (don't complain about vtable kinds) (Some(PointerKind::Thin), _) => Err(CastError::SizedUnsizedCast), + + // trait object -> trait object? need to do additional checks (Some(PointerKind::VTable(src_tty)), Some(PointerKind::VTable(dst_tty))) => { match (src_tty.principal_def_id(), dst_tty.principal_def_id()) { + // A + SrcAuto> -> B + DstAuto>. need to make sure + // - `Src` and `Dst` traits are the same + // - traits have the same generic arguments + // - projections are the same + // - `SrcAuto` (+auto traits implied by `Src`) is a superset of `DstAuto` + // + // Note that trait upcasting goes through a different mechanism (`coerce_unsized`) + // and is unaffected by this check. (Some(src_principal), Some(dst_principal)) => { if src_principal == dst_principal { return Ok(()); } - let src_principal = ctx.db.trait_signature(src_principal.0); - let dst_principal = ctx.db.trait_signature(dst_principal.0); - if src_principal.flags.contains(TraitFlags::AUTO) - && dst_principal.flags.contains(TraitFlags::AUTO) + + // We need to reconstruct trait object types. + // `m_src` and `m_dst` won't work for us here because they will potentially + // contain wrappers, which we do not care about. + // + // e.g. we want to allow `dyn T -> (dyn T,)`, etc. + // + // We also need to skip auto traits to emit an FCW and not an error. + let src_obj = Ty::new_dynamic( + ctx.interner(), + BoundExistentialPredicates::new_from_iter( + ctx.interner(), + src_tty.iter().filter(|pred| { + !matches!( + pred.skip_binder(), + ExistentialPredicate::AutoTrait(_) + ) + }), + ), + Region::new_erased(ctx.interner()), + ); + let dst_obj = Ty::new_dynamic( + ctx.interner(), + BoundExistentialPredicates::new_from_iter( + ctx.interner(), + dst_tty.iter().filter(|pred| { + !matches!( + pred.skip_binder(), + ExistentialPredicate::AutoTrait(_) + ) + }), + ), + Region::new_erased(ctx.interner()), + ); + + // `dyn Src = dyn Dst`, this checks for matching traits/generics/projections + // This is `fcx.demand_eqtype`, but inlined to give a better error. + if ctx + .table + .at(&ObligationCause::dummy()) + .eq(src_obj, dst_obj) + .map(|infer_ok| ctx.table.register_infer_ok(infer_ok)) + .is_err() { - Ok(()) - } else { - Err(CastError::DifferingKinds) + return Err(CastError::DifferingKinds); } + + // Check that `SrcAuto` (+auto traits implied by `Src`) is a superset of `DstAuto`. + // Emit an FCW otherwise. + let src_auto: FxHashSet<_> = src_tty + .auto_traits() + .into_iter() + .chain( + elaborate::supertrait_def_ids(ctx.interner(), src_principal) + .filter(|trait_| { + ctx.db + .trait_signature(trait_.0) + .flags + .contains(TraitFlags::AUTO) + }), + ) + .collect(); + + let added = dst_tty + .auto_traits() + .into_iter() + .any(|trait_| !src_auto.contains(&trait_)); + + if added { + return Err(CastError::PtrPtrAddingAutoTraits); + } + + Ok(()) } - _ => Err(CastError::Unknown), + + // dyn Auto -> dyn Auto'? ok. + (None, None) => Ok(()), + + // dyn Trait -> dyn Auto? not ok (for now). + // + // Although dropping the principal is already allowed for unsizing coercions + // (e.g. `*const (dyn Trait + Auto)` to `*const dyn Auto`), dropping it is + // currently **NOT** allowed for (non-coercion) ptr-to-ptr casts (e.g + // `*const Foo` to `*const Bar` where `Foo` has a `dyn Trait + Auto` tail + // and `Bar` has a `dyn Auto` tail), because the underlying MIR operations + // currently work very differently: + // + // * A MIR unsizing coercion on raw pointers to trait objects (`*const dyn Src` + // to `*const dyn Dst`) is currently equivalent to downcasting the source to + // the concrete sized type that it was originally unsized from first (via a + // ptr-to-ptr cast from `*const Src` to `*const T` with `T: Sized`) and then + // unsizing this thin pointer to the target type (unsizing `*const T` to + // `*const Dst`). In particular, this means that the pointer's metadata + // (vtable) will semantically change, e.g. for const eval and miri, even + // though the vtables will always be merged for codegen. + // + // * A MIR ptr-to-ptr cast is currently equivalent to a transmute and does not + // change the pointer metadata (vtable) at all. + // + // In addition to this potentially surprising difference between coercion and + // non-coercion casts, casting away the principal with a MIR ptr-to-ptr cast + // is currently considered undefined behavior: + // + // As a validity invariant of pointers to trait objects, we currently require + // that the principal of the vtable in the pointer metadata exactly matches + // the principal of the pointee type, where "no principal" is also considered + // a kind of principal. + (Some(_), None) => Err(CastError::DifferingKinds), + + // dyn Auto -> dyn Trait? not ok. + (None, Some(_)) => Err(CastError::DifferingKinds), } } + + // fat -> fat? metadata kinds must match (Some(src_kind), Some(dst_kind)) if src_kind == dst_kind => Ok(()), (_, _) => Err(CastError::DifferingKinds), } @@ -342,9 +476,9 @@ impl<'db> CastCheck<'db> { None => Ok(()), Some(PointerKind::Error) => Ok(()), Some(PointerKind::Thin) => Ok(()), - Some(PointerKind::VTable(_)) => Err(CastError::IntToFatCast), - Some(PointerKind::Length) => Err(CastError::IntToFatCast), - Some(PointerKind::OfAlias | PointerKind::OfParam(_)) => Err(CastError::IntToFatCast), + Some(PointerKind::VTable(_)) => Err(CastError::IntToWideCast), + Some(PointerKind::Length) => Err(CastError::IntToWideCast), + Some(PointerKind::OfAlias | PointerKind::OfParam(_)) => Err(CastError::IntToWideCast), } } @@ -363,15 +497,20 @@ impl<'db> CastCheck<'db> { } } +/// The kind of pointer and associated metadata (thin, length or vtable) - we +/// only allow casts between wide pointers if their metadata have the same +/// kind. #[derive(Debug, PartialEq, Eq)] enum PointerKind<'db> { - // thin pointer + /// No metadata attached, ie pointer to sized type or foreign type Thin, - // trait object + /// A trait object VTable(BoundExistentialPredicates<'db>), - // slice + /// Slice Length, + /// The unsize info of this projection or opaque type OfAlias, + /// The unsize info of this parameter OfParam(ParamTy), Error, } @@ -439,24 +578,3 @@ fn pointer_kind<'db>( } } } - -fn contains_dyn_trait<'db>(ty: Ty<'db>) -> bool { - use std::ops::ControlFlow; - - use rustc_type_ir::{TypeSuperVisitable, TypeVisitable, TypeVisitor}; - - struct DynTraitVisitor; - - impl<'db> TypeVisitor> for DynTraitVisitor { - type Result = ControlFlow<()>; - - fn visit_ty(&mut self, ty: Ty<'db>) -> ControlFlow<()> { - match ty.kind() { - TyKind::Dynamic(..) => ControlFlow::Break(()), - _ => ty.super_visit_with(self), - } - } - } - - ty.visit_with(&mut DynTraitVisitor).is_break() -} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs index d1391ad24e4d..ce99016470c1 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs @@ -466,7 +466,7 @@ impl<'db> InferenceContext<'_, 'db> { } /// Given an `FnOnce::Output` or `AsyncFn::Output` projection, extract the args - /// and return type to infer a [`ty::PolyFnSig`] for the closure. + /// and return type to infer a `PolyFnSig` for the closure. fn extract_sig_from_projection( &self, projection: PolyProjectionPredicate<'db>, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs index b25901cc3b99..5a3eba1a71ae 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs @@ -8,7 +8,7 @@ use hir_def::{ expr_store::path::Path, hir::{ Array, AsmOperand, BinaryOp, BindingId, CaptureBy, Expr, ExprId, ExprOrPatId, Pat, PatId, - Statement, UnaryOp, + RecordSpread, Statement, UnaryOp, }, item_tree::FieldsShape, resolver::ValueNs, @@ -627,7 +627,7 @@ impl<'db> InferenceContext<'_, 'db> { self.consume_expr(expr); } Expr::RecordLit { fields, spread, .. } => { - if let &Some(expr) = spread { + if let RecordSpread::Expr(expr) = *spread { self.consume_expr(expr); } self.consume_exprs(fields.iter().map(|it| it.expr)); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index 226e9f5cd667..9f2d9d25b957 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs @@ -8,7 +8,7 @@ use hir_def::{ expr_store::path::{GenericArgs as HirGenericArgs, Path}, hir::{ Array, AsmOperand, AsmOptions, BinaryOp, BindingAnnotation, Expr, ExprId, ExprOrPatId, - LabelId, Literal, Pat, PatId, Statement, UnaryOp, + InlineAsmKind, LabelId, Literal, Pat, PatId, RecordSpread, Statement, UnaryOp, }, resolver::ValueNs, }; @@ -657,8 +657,8 @@ impl<'db> InferenceContext<'_, 'db> { } } } - if let Some(expr) = spread { - self.infer_expr(*expr, &Expectation::has_type(ty), ExprIsRead::Yes); + if let RecordSpread::Expr(expr) = *spread { + self.infer_expr(expr, &Expectation::has_type(ty), ExprIsRead::Yes); } ty } @@ -1037,7 +1037,11 @@ impl<'db> InferenceContext<'_, 'db> { // FIXME: `sym` should report for things that are not functions or statics. AsmOperand::Sym(_) => (), }); - if diverge { self.types.types.never } else { self.types.types.unit } + if diverge || asm.kind == InlineAsmKind::NakedAsm { + self.types.types.never + } else { + self.types.types.unit + } } }; // use a new type variable if we got unknown here @@ -1704,7 +1708,7 @@ impl<'db> InferenceContext<'_, 'db> { }); match resolved { Ok((func, _is_visible)) => { - self.check_method_call(tgt_expr, &[], func.sig, receiver_ty, expected) + self.check_method_call(tgt_expr, &[], func.sig, expected) } Err(_) => self.err_ty(), } @@ -1844,7 +1848,7 @@ impl<'db> InferenceContext<'_, 'db> { item: func.def_id.into(), }) } - self.check_method_call(tgt_expr, args, func.sig, receiver_ty, expected) + self.check_method_call(tgt_expr, args, func.sig, expected) } // Failed to resolve, report diagnostic and try to resolve as call to field access or // assoc function @@ -1934,16 +1938,14 @@ impl<'db> InferenceContext<'_, 'db> { tgt_expr: ExprId, args: &[ExprId], sig: FnSig<'db>, - receiver_ty: Ty<'db>, expected: &Expectation<'db>, ) -> Ty<'db> { - let (formal_receiver_ty, param_tys) = if !sig.inputs_and_output.inputs().is_empty() { - (sig.inputs_and_output.as_slice()[0], &sig.inputs_and_output.inputs()[1..]) + let param_tys = if !sig.inputs_and_output.inputs().is_empty() { + &sig.inputs_and_output.inputs()[1..] } else { - (self.types.types.error, &[] as _) + &[] }; let ret_ty = sig.output(); - self.table.unify(formal_receiver_ty, receiver_ty); self.check_call_arguments(tgt_expr, param_tys, ret_ty, expected, args, &[], sig.c_variadic); ret_ty diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs index 729ed214daea..45fa141b6d3d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs @@ -2,7 +2,8 @@ //! between `Deref` and `DerefMut` or `Index` and `IndexMut` or similar. use hir_def::hir::{ - Array, AsmOperand, BinaryOp, BindingAnnotation, Expr, ExprId, Pat, PatId, Statement, UnaryOp, + Array, AsmOperand, BinaryOp, BindingAnnotation, Expr, ExprId, Pat, PatId, RecordSpread, + Statement, UnaryOp, }; use rustc_ast_ir::Mutability; @@ -132,8 +133,11 @@ impl<'db> InferenceContext<'_, 'db> { Expr::Become { expr } => { self.infer_mut_expr(*expr, Mutability::Not); } - Expr::RecordLit { path: _, fields, spread } => { - self.infer_mut_not_expr_iter(fields.iter().map(|it| it.expr).chain(*spread)) + Expr::RecordLit { path: _, fields, spread, .. } => { + self.infer_mut_not_expr_iter(fields.iter().map(|it| it.expr)); + if let RecordSpread::Expr(expr) = *spread { + self.infer_mut_expr(expr, Mutability::Not); + } } &Expr::Index { base, index } => { if mutability == Mutability::Mut { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs index b11650bbcd9a..ef1a610a323d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs @@ -93,6 +93,7 @@ impl<'db> InferenceContext<'_, 'db> { if let GenericDefId::StaticId(_) = generic_def { // `Static` is the kind of item that can never be generic currently. We can just skip the binders to get its type. let ty = self.db.value_ty(value_def)?.skip_binder(); + let ty = self.process_remote_user_written_ty(ty); return Some(ValuePathResolution::NonGeneric(ty)); }; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs index d55fc0ab0da6..2057159c46d2 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs @@ -261,16 +261,6 @@ impl<'db> InferenceTable<'db> { self.infer_ctxt.canonicalize_response(t) } - // FIXME: We should get rid of this method. We cannot deeply normalize during inference, only when finishing. - // Inference should use shallow normalization (`try_structurally_resolve_type()`) only, when needed. - pub(crate) fn normalize_associated_types_in(&mut self, ty: T) -> T - where - T: TypeFoldable> + Clone, - { - let ty = self.resolve_vars_with_obligations(ty); - self.at(&ObligationCause::new()).deeply_normalize(ty.clone()).unwrap_or(ty) - } - pub(crate) fn normalize_alias_ty(&mut self, alias: Ty<'db>) -> Ty<'db> { self.infer_ctxt .at(&ObligationCause::new(), self.param_env) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index 8e1ea9c47850..5789bf02a42e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -27,8 +27,8 @@ use hir_def::{ resolver::{HasResolver, LifetimeNs, Resolver, TypeNs, ValueNs}, signatures::{FunctionSignature, TraitFlags, TypeAliasFlags}, type_ref::{ - ConstRef, LifetimeRefId, PathId, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, - TypeRef, TypeRefId, + ConstRef, FnType, LifetimeRefId, PathId, TraitBoundModifier, TraitRef as HirTraitRef, + TypeBound, TypeRef, TypeRefId, }, }; use hir_expand::name::Name; @@ -53,7 +53,7 @@ use tracing::debug; use triomphe::{Arc, ThinArc}; use crate::{ - FnAbi, ImplTraitId, TyLoweringDiagnostic, TyLoweringDiagnosticKind, + FnAbi, ImplTraitId, TyLoweringDiagnostic, TyLoweringDiagnosticKind, all_super_traits, consteval::intern_const_ref, db::{HirDatabase, InternedOpaqueTyId}, generics::{Generics, generics, trait_self_param_idx}, @@ -77,6 +77,7 @@ pub struct ImplTraits { #[derive(PartialEq, Eq, Debug, Hash)] pub struct ImplTrait { pub(crate) predicates: StoredClauses, + pub(crate) assoc_ty_bounds_start: u32, } pub type ImplTraitIdx = Idx; @@ -97,7 +98,7 @@ impl ImplTraitLoweringState { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub enum LifetimeElisionKind<'db> { /// Create a new anonymous lifetime parameter and reference it. /// @@ -166,6 +167,12 @@ impl<'db> LifetimeElisionKind<'db> { } } +#[derive(Clone, Copy, PartialEq, Debug)] +pub(crate) enum GenericPredicateSource { + SelfOnly, + AssocTyBound, +} + #[derive(Debug)] pub struct TyLoweringContext<'db, 'a> { pub db: &'db dyn HirDatabase, @@ -430,26 +437,7 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { Ty::new_ref(interner, lifetime, inner_ty, lower_mutability(ref_.mutability)) } TypeRef::Placeholder => Ty::new_error(interner, ErrorGuaranteed), - TypeRef::Fn(fn_) => { - let substs = self.with_shifted_in( - DebruijnIndex::from_u32(1), - |ctx: &mut TyLoweringContext<'_, '_>| { - Tys::new_from_iter( - interner, - fn_.params.iter().map(|&(_, tr)| ctx.lower_ty(tr)), - ) - }, - ); - Ty::new_fn_ptr( - interner, - Binder::dummy(FnSig { - abi: fn_.abi.as_ref().map_or(FnAbi::Rust, FnAbi::from_symbol), - safety: if fn_.is_unsafe { Safety::Unsafe } else { Safety::Safe }, - c_variadic: fn_.is_varargs, - inputs_and_output: substs, - }), - ) - } + TypeRef::Fn(fn_) => self.lower_fn_ptr(fn_), TypeRef::DynTrait(bounds) => self.lower_dyn_trait(bounds), TypeRef::ImplTrait(bounds) => { match self.impl_trait_mode.mode { @@ -465,10 +453,10 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { // this dance is to make sure the data is in the right // place even if we encounter more opaque types while // lowering the bounds - let idx = self - .impl_trait_mode - .opaque_type_data - .alloc(ImplTrait { predicates: Clauses::empty(interner).store() }); + let idx = self.impl_trait_mode.opaque_type_data.alloc(ImplTrait { + predicates: Clauses::empty(interner).store(), + assoc_ty_bounds_start: 0, + }); let impl_trait_id = origin.either( |f| ImplTraitId::ReturnTypeImplTrait(f, idx), @@ -510,6 +498,30 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { (ty, res) } + fn lower_fn_ptr(&mut self, fn_: &FnType) -> Ty<'db> { + let interner = self.interner; + let (params, ret_ty) = fn_.split_params_and_ret(); + let old_lifetime_elision = self.lifetime_elision; + let mut args = Vec::with_capacity(fn_.params.len()); + self.with_shifted_in(DebruijnIndex::from_u32(1), |ctx: &mut TyLoweringContext<'_, '_>| { + ctx.lifetime_elision = + LifetimeElisionKind::AnonymousCreateParameter { report_in_path: false }; + args.extend(params.iter().map(|&(_, tr)| ctx.lower_ty(tr))); + ctx.lifetime_elision = LifetimeElisionKind::for_fn_ret(interner); + args.push(ctx.lower_ty(ret_ty)); + }); + self.lifetime_elision = old_lifetime_elision; + Ty::new_fn_ptr( + interner, + Binder::dummy(FnSig { + abi: fn_.abi.as_ref().map_or(FnAbi::Rust, FnAbi::from_symbol), + safety: if fn_.is_unsafe { Safety::Unsafe } else { Safety::Safe }, + c_variadic: fn_.is_varargs, + inputs_and_output: Tys::new_from_slice(&args), + }), + ) + } + /// This is only for `generic_predicates_for_param`, where we can't just /// lower the self types of the predicates since that could lead to cycles. /// So we just check here if the `type_ref` resolves to a generic param, and which. @@ -608,7 +620,7 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { ignore_bindings: bool, generics: &Generics, predicate_filter: PredicateFilter, - ) -> impl Iterator> + use<'a, 'b, 'db> { + ) -> impl Iterator, GenericPredicateSource)> + use<'a, 'b, 'db> { match where_predicate { WherePredicate::ForLifetime { target, bound, .. } | WherePredicate::TypeBound { target, bound } => { @@ -634,8 +646,8 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { let self_ty = self.lower_ty(*target); Either::Left(Either::Right(self.lower_type_bound(bound, self_ty, ignore_bindings))) } - &WherePredicate::Lifetime { bound, target } => { - Either::Right(iter::once(Clause(Predicate::new( + &WherePredicate::Lifetime { bound, target } => Either::Right(iter::once(( + Clause(Predicate::new( self.interner, Binder::dummy(rustc_type_ir::PredicateKind::Clause( rustc_type_ir::ClauseKind::RegionOutlives(OutlivesPredicate( @@ -643,8 +655,9 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { self.lower_lifetime(target), )), )), - )))) - } + )), + GenericPredicateSource::SelfOnly, + ))), } .into_iter() } @@ -654,7 +667,7 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { bound: &'b TypeBound, self_ty: Ty<'db>, ignore_bindings: bool, - ) -> impl Iterator> + use<'b, 'a, 'db> { + ) -> impl Iterator, GenericPredicateSource)> + use<'b, 'a, 'db> { let interner = self.interner; let meta_sized = self.lang_items.MetaSized; let pointee_sized = self.lang_items.PointeeSized; @@ -712,7 +725,10 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { } TypeBound::Use(_) | TypeBound::Error => {} } - clause.into_iter().chain(assoc_bounds.into_iter().flatten()) + clause + .into_iter() + .map(|pred| (pred, GenericPredicateSource::SelfOnly)) + .chain(assoc_bounds.into_iter().flatten()) } fn lower_dyn_trait(&mut self, bounds: &[TypeBound]) -> Ty<'db> { @@ -732,7 +748,7 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { for b in bounds { let db = ctx.db; - ctx.lower_type_bound(b, dummy_self_ty, false).for_each(|b| { + ctx.lower_type_bound(b, dummy_self_ty, false).for_each(|(b, _)| { match b.kind().skip_binder() { rustc_type_ir::ClauseKind::Trait(t) => { let id = t.def_id(); @@ -990,35 +1006,49 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { rustc_type_ir::AliasTyKind::Opaque, AliasTy::new_from_args(interner, def_id, args), ); - let predicates = self.with_shifted_in(DebruijnIndex::from_u32(1), |ctx| { - let mut predicates = Vec::new(); - for b in bounds { - predicates.extend(ctx.lower_type_bound(b, self_ty, false)); - } + let (predicates, assoc_ty_bounds_start) = + self.with_shifted_in(DebruijnIndex::from_u32(1), |ctx| { + let mut predicates = Vec::new(); + let mut assoc_ty_bounds = Vec::new(); + for b in bounds { + for (pred, source) in ctx.lower_type_bound(b, self_ty, false) { + match source { + GenericPredicateSource::SelfOnly => predicates.push(pred), + GenericPredicateSource::AssocTyBound => assoc_ty_bounds.push(pred), + } + } + } - if !ctx.unsized_types.contains(&self_ty) { - let sized_trait = self.lang_items.Sized; - let sized_clause = sized_trait.map(|trait_id| { - let trait_ref = TraitRef::new_from_args( - interner, - trait_id.into(), - GenericArgs::new_from_slice(&[self_ty.into()]), - ); - Clause(Predicate::new( - interner, - Binder::dummy(rustc_type_ir::PredicateKind::Clause( - rustc_type_ir::ClauseKind::Trait(TraitPredicate { - trait_ref, - polarity: rustc_type_ir::PredicatePolarity::Positive, - }), - )), - )) - }); - predicates.extend(sized_clause); - } - predicates - }); - ImplTrait { predicates: Clauses::new_from_slice(&predicates).store() } + if !ctx.unsized_types.contains(&self_ty) { + let sized_trait = self.lang_items.Sized; + let sized_clause = sized_trait.map(|trait_id| { + let trait_ref = TraitRef::new_from_args( + interner, + trait_id.into(), + GenericArgs::new_from_slice(&[self_ty.into()]), + ); + Clause(Predicate::new( + interner, + Binder::dummy(rustc_type_ir::PredicateKind::Clause( + rustc_type_ir::ClauseKind::Trait(TraitPredicate { + trait_ref, + polarity: rustc_type_ir::PredicatePolarity::Positive, + }), + )), + )) + }); + predicates.extend(sized_clause); + } + + let assoc_ty_bounds_start = predicates.len() as u32; + predicates.extend(assoc_ty_bounds); + (predicates, assoc_ty_bounds_start) + }); + + ImplTrait { + predicates: Clauses::new_from_slice(&predicates).store(), + assoc_ty_bounds_start, + } } pub(crate) fn lower_lifetime(&mut self, lifetime: LifetimeRefId) -> Region<'db> { @@ -1139,6 +1169,31 @@ impl ImplTraitId { .expect("owner should have opaque type") .get_with(|it| it.impl_traits[idx].predicates.as_ref().as_slice()) } + + #[inline] + pub fn self_predicates<'db>( + self, + db: &'db dyn HirDatabase, + ) -> EarlyBinder<'db, &'db [Clause<'db>]> { + let (impl_traits, idx) = match self { + ImplTraitId::ReturnTypeImplTrait(owner, idx) => { + (ImplTraits::return_type_impl_traits(db, owner), idx) + } + ImplTraitId::TypeAliasImplTrait(owner, idx) => { + (ImplTraits::type_alias_impl_traits(db, owner), idx) + } + }; + let predicates = + impl_traits.as_deref().expect("owner should have opaque type").get_with(|it| { + let impl_trait = &it.impl_traits[idx]; + ( + impl_trait.predicates.as_ref().as_slice(), + impl_trait.assoc_ty_bounds_start as usize, + ) + }); + + predicates.map_bound(|(preds, len)| &preds[..len]) + } } impl InternedOpaqueTyId { @@ -1146,6 +1201,14 @@ impl InternedOpaqueTyId { pub fn predicates<'db>(self, db: &'db dyn HirDatabase) -> EarlyBinder<'db, &'db [Clause<'db>]> { self.loc(db).predicates(db) } + + #[inline] + pub fn self_predicates<'db>( + self, + db: &'db dyn HirDatabase, + ) -> EarlyBinder<'db, &'db [Clause<'db>]> { + self.loc(db).self_predicates(db) + } } #[salsa::tracked] @@ -1561,11 +1624,16 @@ pub(crate) fn field_types_with_diagnostics_query<'db>( (res, create_diagnostics(ctx.diagnostics)) } +/// Predicates for `param_id` of the form `P: SomeTrait`. If +/// `assoc_name` is provided, only return predicates referencing traits +/// that have an associated type of that name. +/// /// This query exists only to be used when resolving short-hand associated types /// like `T::Item`. /// /// See the analogous query in rustc and its comment: /// +/// /// This is a query mostly to handle cycles somewhat gracefully; e.g. the /// following bounds are disallowed: `T: Foo, U: Foo`, but /// these are fine: `T: Foo, U: Foo<()>`. @@ -1589,7 +1657,7 @@ pub(crate) fn generic_predicates_for_param<'db>( ); // we have to filter out all other predicates *first*, before attempting to lower them - let predicate = |pred: &_, ctx: &mut TyLoweringContext<'_, '_>| match pred { + let has_relevant_bound = |pred: &_, ctx: &mut TyLoweringContext<'_, '_>| match pred { WherePredicate::ForLifetime { target, bound, .. } | WherePredicate::TypeBound { target, bound, .. } => { let invalid_target = { ctx.lower_ty_only_param(*target) != Some(param_id) }; @@ -1637,11 +1705,7 @@ pub(crate) fn generic_predicates_for_param<'db>( return false; }; - rustc_type_ir::elaborate::supertrait_def_ids(interner, tr.into()).any(|tr| { - tr.0.trait_items(db).items.iter().any(|(name, item)| { - matches!(item, AssocItemId::TypeAliasId(_)) && name == assoc_name - }) - }) + trait_or_supertrait_has_assoc_type(db, tr, assoc_name) } TypeBound::Use(_) | TypeBound::Lifetime(_) | TypeBound::Error => false, } @@ -1654,13 +1718,16 @@ pub(crate) fn generic_predicates_for_param<'db>( { ctx.store = maybe_parent_generics.store(); for pred in maybe_parent_generics.where_predicates() { - if predicate(pred, &mut ctx) { - predicates.extend(ctx.lower_where_predicate( - pred, - true, - maybe_parent_generics, - PredicateFilter::All, - )); + if has_relevant_bound(pred, &mut ctx) { + predicates.extend( + ctx.lower_where_predicate( + pred, + true, + maybe_parent_generics, + PredicateFilter::All, + ) + .map(|(pred, _)| pred), + ); } } } @@ -1691,26 +1758,70 @@ pub(crate) fn generic_predicates_for_param_cycle_result( StoredEarlyBinder::bind(Clauses::empty(DbInterner::new_no_crate(db)).store()) } +/// Check if this trait or any of its supertraits define an associated +/// type with the given name. +fn trait_or_supertrait_has_assoc_type( + db: &dyn HirDatabase, + tr: TraitId, + assoc_name: &Name, +) -> bool { + for trait_id in all_super_traits(db, tr) { + if trait_id + .trait_items(db) + .items + .iter() + .any(|(name, item)| matches!(item, AssocItemId::TypeAliasId(_)) && name == assoc_name) + { + return true; + } + } + + false +} + #[inline] pub(crate) fn type_alias_bounds<'db>( db: &'db dyn HirDatabase, type_alias: TypeAliasId, ) -> EarlyBinder<'db, &'db [Clause<'db>]> { - type_alias_bounds_with_diagnostics(db, type_alias).0.map_bound(|it| it.as_slice()) + type_alias_bounds_with_diagnostics(db, type_alias).0.predicates.map_bound(|it| it.as_slice()) } -pub(crate) fn type_alias_bounds_with_diagnostics<'db>( +#[inline] +pub(crate) fn type_alias_self_bounds<'db>( db: &'db dyn HirDatabase, type_alias: TypeAliasId, -) -> (EarlyBinder<'db, Clauses<'db>>, Diagnostics) { - let (bounds, diags) = type_alias_bounds_with_diagnostics_query(db, type_alias); - return (bounds.get(), diags.clone()); +) -> EarlyBinder<'db, &'db [Clause<'db>]> { + let (TypeAliasBounds { predicates, assoc_ty_bounds_start }, _) = + type_alias_bounds_with_diagnostics(db, type_alias); + predicates.map_bound(|it| &it.as_slice()[..assoc_ty_bounds_start as usize]) +} + +#[derive(PartialEq, Eq, Debug, Hash)] +struct TypeAliasBounds { + predicates: T, + assoc_ty_bounds_start: u32, +} + +fn type_alias_bounds_with_diagnostics<'db>( + db: &'db dyn HirDatabase, + type_alias: TypeAliasId, +) -> (TypeAliasBounds>>, Diagnostics) { + let (TypeAliasBounds { predicates, assoc_ty_bounds_start }, diags) = + type_alias_bounds_with_diagnostics_query(db, type_alias); + return ( + TypeAliasBounds { + predicates: predicates.get(), + assoc_ty_bounds_start: *assoc_ty_bounds_start, + }, + diags.clone(), + ); #[salsa::tracked(returns(ref))] pub fn type_alias_bounds_with_diagnostics_query<'db>( db: &'db dyn HirDatabase, type_alias: TypeAliasId, - ) -> (StoredEarlyBinder, Diagnostics) { + ) -> (TypeAliasBounds>, Diagnostics) { let type_alias_data = db.type_alias_signature(type_alias); let resolver = hir_def::resolver::HasResolver::resolver(type_alias, db); let mut ctx = TyLoweringContext::new( @@ -1727,10 +1838,18 @@ pub(crate) fn type_alias_bounds_with_diagnostics<'db>( let interner_ty = Ty::new_projection_from_args(interner, def_id, item_args); let mut bounds = Vec::new(); + let mut assoc_ty_bounds = Vec::new(); for bound in &type_alias_data.bounds { - ctx.lower_type_bound(bound, interner_ty, false).for_each(|pred| { - bounds.push(pred); - }); + ctx.lower_type_bound(bound, interner_ty, false).for_each( + |(pred, source)| match source { + GenericPredicateSource::SelfOnly => { + bounds.push(pred); + } + GenericPredicateSource::AssocTyBound => { + assoc_ty_bounds.push(pred); + } + }, + ); } if !ctx.unsized_types.contains(&interner_ty) { @@ -1745,8 +1864,14 @@ pub(crate) fn type_alias_bounds_with_diagnostics<'db>( }; } + let assoc_ty_bounds_start = bounds.len() as u32; + bounds.extend(assoc_ty_bounds); + ( - StoredEarlyBinder::bind(Clauses::new_from_slice(&bounds).store()), + TypeAliasBounds { + predicates: StoredEarlyBinder::bind(Clauses::new_from_slice(&bounds).store()), + assoc_ty_bounds_start, + }, create_diagnostics(ctx.diagnostics), ) } @@ -1754,11 +1879,15 @@ pub(crate) fn type_alias_bounds_with_diagnostics<'db>( #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GenericPredicates { - // The order is the following: first, if `parent_is_trait == true`, comes the implicit trait predicate for the - // parent. Then come the explicit predicates for the parent, then the explicit trait predicate for the child, + // The order is the following: first, if `parent_is_trait == true`, comes the implicit trait + // predicate for the parent. Then come the bounds of the associated types of the parents, + // then the explicit, self-only predicates for the parent, then the explicit, self-only trait + // predicate for the child, then the bounds of the associated types of the child, // then the implicit trait predicate for the child, if `is_trait` is `true`. predicates: StoredEarlyBinder, + parent_explicit_self_predicates_start: u32, own_predicates_start: u32, + own_assoc_ty_bounds_start: u32, is_trait: bool, parent_is_trait: bool, } @@ -1782,7 +1911,15 @@ impl GenericPredicates { pub(crate) fn from_explicit_own_predicates( predicates: StoredEarlyBinder, ) -> Self { - Self { predicates, own_predicates_start: 0, is_trait: false, parent_is_trait: false } + let len = predicates.get().skip_binder().len() as u32; + Self { + predicates, + parent_explicit_self_predicates_start: 0, + own_predicates_start: 0, + own_assoc_ty_bounds_start: len, + is_trait: false, + parent_is_trait: false, + } } #[inline] @@ -1814,6 +1951,14 @@ impl GenericPredicates { Self::query(db, def).explicit_predicates() } + #[inline] + pub fn query_explicit_implied<'db>( + db: &'db dyn HirDatabase, + def: GenericDefId, + ) -> EarlyBinder<'db, &'db [Clause<'db>]> { + Self::query(db, def).explicit_implied_predicates() + } + #[inline] pub fn all_predicates(&self) -> EarlyBinder<'_, &[Clause<'_>]> { self.predicates.get().map_bound(|it| it.as_slice()) @@ -1824,9 +1969,18 @@ impl GenericPredicates { self.predicates.get().map_bound(|it| &it.as_slice()[self.own_predicates_start as usize..]) } - /// Returns the predicates, minus the implicit `Self: Trait` predicate for a trait. + /// Returns the predicates, minus the implicit `Self: Trait` predicate and bounds of the + /// associated types for a trait. #[inline] pub fn explicit_predicates(&self) -> EarlyBinder<'_, &[Clause<'_>]> { + self.predicates.get().map_bound(|it| { + &it.as_slice()[self.parent_explicit_self_predicates_start as usize + ..self.own_assoc_ty_bounds_start as usize] + }) + } + + #[inline] + pub fn explicit_implied_predicates(&self) -> EarlyBinder<'_, &[Clause<'_>]> { self.predicates.get().map_bound(|it| { &it.as_slice()[usize::from(self.parent_is_trait)..it.len() - usize::from(self.is_trait)] }) @@ -1902,26 +2056,22 @@ where ); let sized_trait = ctx.lang_items.Sized; - let mut predicates = Vec::new(); + // We need to lower parents and self separately - see the comment below lowering of implicit + // `Sized` predicates for why. + let mut own_predicates = Vec::new(); + let mut parent_predicates = Vec::new(); + let mut own_assoc_ty_bounds = Vec::new(); + let mut parent_assoc_ty_bounds = Vec::new(); let all_generics = std::iter::successors(Some(&generics), |generics| generics.parent_generics()) .collect::>(); - let mut is_trait = false; - let mut parent_is_trait = false; - if all_generics.len() > 1 { - add_implicit_trait_predicate( - interner, - all_generics.last().unwrap().def(), - predicate_filter, - &mut predicates, - &mut parent_is_trait, - ); - } - // We need to lower parent predicates first - see the comment below lowering of implicit `Sized` predicates - // for why. - let mut own_predicates_start = 0; + let own_implicit_trait_predicate = implicit_trait_predicate(interner, def, predicate_filter); + let parent_implicit_trait_predicate = if all_generics.len() > 1 { + implicit_trait_predicate(interner, all_generics.last().unwrap().def(), predicate_filter) + } else { + None + }; for &maybe_parent_generics in all_generics.iter().rev() { - let current_def_predicates_start = predicates.len(); // Collect only diagnostics from the child, not including parents. ctx.diagnostics.clear(); @@ -1929,15 +2079,37 @@ where ctx.store = maybe_parent_generics.store(); for pred in maybe_parent_generics.where_predicates() { tracing::debug!(?pred); - predicates.extend(ctx.lower_where_predicate( - pred, - false, - maybe_parent_generics, - predicate_filter, - )); + for (pred, source) in + ctx.lower_where_predicate(pred, false, maybe_parent_generics, predicate_filter) + { + match source { + GenericPredicateSource::SelfOnly => { + if maybe_parent_generics.def() == def { + own_predicates.push(pred); + } else { + parent_predicates.push(pred); + } + } + GenericPredicateSource::AssocTyBound => { + if maybe_parent_generics.def() == def { + own_assoc_ty_bounds.push(pred); + } else { + parent_assoc_ty_bounds.push(pred); + } + } + } + } } - push_const_arg_has_type_predicates(db, &mut predicates, maybe_parent_generics); + if maybe_parent_generics.def() == def { + push_const_arg_has_type_predicates(db, &mut own_predicates, maybe_parent_generics); + } else { + push_const_arg_has_type_predicates( + db, + &mut parent_predicates, + maybe_parent_generics, + ); + } if let Some(sized_trait) = sized_trait { let mut add_sized_clause = |param_idx, param_id, param_data| { @@ -1971,7 +2143,11 @@ where }), )), )); - predicates.push(clause); + if maybe_parent_generics.def() == def { + own_predicates.push(clause); + } else { + parent_predicates.push(clause); + } }; let parent_params_len = maybe_parent_generics.len_parent(); maybe_parent_generics.iter_self().enumerate().for_each( @@ -1990,30 +2166,55 @@ where // predicates before lowering the child, as a child cannot define a `?Sized` predicate for its parent. // But we do have to lower the parent first. } - - if maybe_parent_generics.def() == def { - own_predicates_start = current_def_predicates_start as u32; - } } - add_implicit_trait_predicate(interner, def, predicate_filter, &mut predicates, &mut is_trait); - let diagnostics = create_diagnostics(ctx.diagnostics); + + // The order is: + // + // 1. parent implicit trait pred + // 2. parent assoc bounds + // 3. parent self only preds + // 4. own self only preds + // 5. own assoc ty bounds + // 6. own implicit trait pred + // + // The purpose of this is to index the slice of the followings, without making extra `Vec`s or + // iterators: + // - explicit self only predicates, of own or own + self + // - explicit predicates, of own or own + self + let predicates = parent_implicit_trait_predicate + .iter() + .chain(parent_assoc_ty_bounds.iter()) + .chain(parent_predicates.iter()) + .chain(own_predicates.iter()) + .chain(own_assoc_ty_bounds.iter()) + .chain(own_implicit_trait_predicate.iter()) + .copied() + .collect::>(); + let parent_is_trait = parent_implicit_trait_predicate.is_some(); + let is_trait = own_implicit_trait_predicate.is_some(); + let parent_explicit_self_predicates_start = + parent_is_trait as u32 + parent_assoc_ty_bounds.len() as u32; + let own_predicates_start = + parent_explicit_self_predicates_start + parent_predicates.len() as u32; + let own_assoc_ty_bounds_start = own_predicates_start + own_predicates.len() as u32; + let predicates = GenericPredicates { + parent_explicit_self_predicates_start, own_predicates_start, + own_assoc_ty_bounds_start, is_trait, parent_is_trait, predicates: StoredEarlyBinder::bind(Clauses::new_from_slice(&predicates).store()), }; return (predicates, diagnostics); - fn add_implicit_trait_predicate<'db>( + fn implicit_trait_predicate<'db>( interner: DbInterner<'db>, def: GenericDefId, predicate_filter: PredicateFilter, - predicates: &mut Vec>, - set_is_trait: &mut bool, - ) { + ) -> Option> { // For traits, add `Self: Trait` predicate. This is // not part of the predicates that a user writes, but it // is something that one must prove in order to invoke a @@ -2029,8 +2230,9 @@ where if let GenericDefId::TraitId(def_id) = def && predicate_filter == PredicateFilter::All { - *set_is_trait = true; - predicates.push(TraitRef::identity(interner, def_id.into()).upcast(interner)); + Some(TraitRef::identity(interner, def_id.into()).upcast(interner)) + } else { + None } } } @@ -2327,7 +2529,7 @@ pub(crate) fn associated_ty_item_bounds<'db>( let mut bounds = Vec::new(); for bound in &type_alias_data.bounds { - ctx.lower_type_bound(bound, self_ty, false).for_each(|pred| { + ctx.lower_type_bound(bound, self_ty, false).for_each(|(pred, _)| { if let Some(bound) = pred .kind() .map_bound(|c| match c { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs index a79f547c2a44..f3d0de12275e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs @@ -32,7 +32,8 @@ use crate::{ db::HirDatabase, generics::{Generics, generics}, lower::{ - LifetimeElisionKind, PathDiagnosticCallbackData, named_associated_type_shorthand_candidates, + GenericPredicateSource, LifetimeElisionKind, PathDiagnosticCallbackData, + named_associated_type_shorthand_candidates, }, next_solver::{ Binder, Clause, Const, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, Predicate, @@ -598,7 +599,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { explicit_self_ty: Option>, lowering_assoc_type_generics: bool, ) -> GenericArgs<'db> { - let old_lifetime_elision = self.ctx.lifetime_elision.clone(); + let old_lifetime_elision = self.ctx.lifetime_elision; if let Some(args) = self.current_or_prev_segment.args_and_bindings && args.parenthesized != GenericArgsParentheses::No @@ -639,7 +640,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { explicit_self_ty, PathGenericsSource::Segment(self.current_segment_u32()), lowering_assoc_type_generics, - self.ctx.lifetime_elision.clone(), + self.ctx.lifetime_elision, ); self.ctx.lifetime_elision = old_lifetime_elision; result @@ -853,7 +854,8 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { pub(super) fn assoc_type_bindings_from_type_bound<'c>( mut self, trait_ref: TraitRef<'db>, - ) -> Option> + use<'a, 'b, 'c, 'db>> { + ) -> Option, GenericPredicateSource)> + use<'a, 'b, 'c, 'db>> + { let interner = self.ctx.interner; self.current_or_prev_segment.args_and_bindings.map(|args_and_bindings| { args_and_bindings.bindings.iter().enumerate().flat_map(move |(binding_idx, binding)| { @@ -882,7 +884,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { assoc_type: binding_idx as u32, }, false, - this.ctx.lifetime_elision.clone(), + this.ctx.lifetime_elision, ) }); let args = GenericArgs::new_from_iter( @@ -900,7 +902,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { // `Fn()`-style generics are elided like functions. This is `Output` (we lower to it in hir-def). LifetimeElisionKind::for_fn_ret(self.ctx.interner) } else { - self.ctx.lifetime_elision.clone() + self.ctx.lifetime_elision }; self.with_lifetime_elision(lifetime_elision, |this| { match (&this.ctx.store[type_ref], this.ctx.impl_trait_mode.mode) { @@ -921,21 +923,29 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { ), )), )); - predicates.push(pred); + predicates.push((pred, GenericPredicateSource::SelfOnly)); } } }) } for bound in binding.bounds.iter() { - predicates.extend(self.ctx.lower_type_bound( - bound, - Ty::new_alias( - self.ctx.interner, - AliasTyKind::Projection, - AliasTy::new_from_args(self.ctx.interner, associated_ty.into(), args), - ), - false, - )); + predicates.extend( + self.ctx + .lower_type_bound( + bound, + Ty::new_alias( + self.ctx.interner, + AliasTyKind::Projection, + AliasTy::new_from_args( + self.ctx.interner, + associated_ty.into(), + args, + ), + ), + false, + ) + .map(|(pred, _)| (pred, GenericPredicateSource::AssocTyBound)), + ); } predicates }) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs index e4681b464fec..ad4d79e68a9f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs @@ -206,11 +206,11 @@ impl<'a, 'db> InferenceContext<'a, 'db> { } } -/// Used by [FnCtxt::lookup_method_for_operator] with `-Znext-solver`. +/// Used by `FnCtxt::lookup_method_for_operator` with `-Znext-solver`. /// /// With `AsRigid` we error on `impl Opaque: NotInItemBounds` while /// `AsInfer` just treats it as ambiguous and succeeds. This is necessary -/// as we want [FnCtxt::check_expr_call] to treat not-yet-defined opaque +/// as we want `FnCtxt::check_expr_call` to treat not-yet-defined opaque /// types as rigid to support `impl Deref` and /// `Box`. /// diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs index 4a7c7d93539e..fc2bd87ee429 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs @@ -285,11 +285,15 @@ impl<'a, 'db> MethodResolutionContext<'a, 'db> { let infcx = self.infcx; let (self_ty, var_values) = infcx.instantiate_canonical(&query_input); debug!(?self_ty, ?query_input, "probe_op: Mode::Path"); + let prev_opaque_entries = + self.infcx.inner.borrow_mut().opaque_types().num_entries(); MethodAutoderefStepsResult { steps: smallvec![CandidateStep { - self_ty: self - .infcx - .make_query_response_ignoring_pending_obligations(var_values, self_ty), + self_ty: self.infcx.make_query_response_ignoring_pending_obligations( + var_values, + self_ty, + prev_opaque_entries + ), self_ty_is_opaque: false, autoderefs: 0, from_unsafe_deref: false, @@ -376,6 +380,8 @@ impl<'a, 'db> MethodResolutionContext<'a, 'db> { // infer var is not an opaque. let infcx = self.infcx; let (self_ty, inference_vars) = infcx.instantiate_canonical(self_ty); + let prev_opaque_entries = infcx.inner.borrow_mut().opaque_types().num_entries(); + let self_ty_is_opaque = |ty: Ty<'_>| { if let TyKind::Infer(InferTy::TyVar(vid)) = ty.kind() { infcx.has_opaques_with_sub_unified_hidden_type(vid) @@ -414,6 +420,7 @@ impl<'a, 'db> MethodResolutionContext<'a, 'db> { self_ty: infcx.make_query_response_ignoring_pending_obligations( inference_vars, ty, + prev_opaque_entries, ), self_ty_is_opaque: self_ty_is_opaque(ty), autoderefs: d, @@ -437,6 +444,7 @@ impl<'a, 'db> MethodResolutionContext<'a, 'db> { self_ty: infcx.make_query_response_ignoring_pending_obligations( inference_vars, ty, + prev_opaque_entries, ), self_ty_is_opaque: self_ty_is_opaque(ty), autoderefs: d, @@ -461,13 +469,17 @@ impl<'a, 'db> MethodResolutionContext<'a, 'db> { ty: infcx.make_query_response_ignoring_pending_obligations( inference_vars, final_ty, + prev_opaque_entries, ), }) } TyKind::Error(_) => Some(MethodAutoderefBadTy { reached_raw_pointer, - ty: infcx - .make_query_response_ignoring_pending_obligations(inference_vars, final_ty), + ty: infcx.make_query_response_ignoring_pending_obligations( + inference_vars, + final_ty, + prev_opaque_entries, + ), }), TyKind::Array(elem_ty, _) => { let autoderefs = steps.iter().filter(|s| s.reachable_via_deref).count() - 1; @@ -475,6 +487,7 @@ impl<'a, 'db> MethodResolutionContext<'a, 'db> { self_ty: infcx.make_query_response_ignoring_pending_obligations( inference_vars, Ty::new_slice(infcx.interner, elem_ty), + prev_opaque_entries, ), self_ty_is_opaque: false, autoderefs, @@ -1246,9 +1259,9 @@ impl<'a, 'db, Choice: ProbeChoice<'db>> ProbeContext<'a, 'db, Choice> { .filter(|step| step.reachable_via_deref) .filter(|step| { debug!("pick_all_method: step={:?}", step); - // skip types that are from a type error or that would require dereferencing - // a raw pointer - !step.self_ty.value.value.references_non_lt_error() && !step.from_unsafe_deref + // Skip types with type errors (but not const/lifetime errors, which are + // often spurious due to incomplete const evaluation) and raw pointer derefs. + !step.self_ty.value.value.references_only_ty_error() && !step.from_unsafe_deref }) .try_for_each(|step| { let InferOk { value: self_ty, obligations: instantiate_self_ty_obligations } = self @@ -1740,7 +1753,7 @@ impl<'a, 'db, Choice: ProbeChoice<'db>> ProbeContext<'a, 'db, Choice> { /// We want to only accept trait methods if they were hold even if the /// opaque types were rigid. To handle this, we both check that for trait /// candidates the goal were to hold even when treating opaques as rigid, - /// see [OpaqueTypesJank](rustc_trait_selection::solve::OpaqueTypesJank). + /// see `rustc_trait_selection::solve::OpaqueTypesJank`. /// /// We also check that all opaque types encountered as self types in the /// autoderef chain don't get constrained when applying the candidate. diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index 1579f00e9266..199db7a3e718 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -9,7 +9,7 @@ use hir_def::{ expr_store::{Body, ExpressionStore, HygieneId, path::Path}, hir::{ ArithOp, Array, BinaryOp, BindingAnnotation, BindingId, ExprId, LabelId, Literal, MatchArm, - Pat, PatId, RecordFieldPat, RecordLitField, + Pat, PatId, RecordFieldPat, RecordLitField, RecordSpread, }, item_tree::FieldsShape, lang_item::LangItems, @@ -867,16 +867,17 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { } Expr::Become { .. } => not_supported!("tail-calls"), Expr::Yield { .. } => not_supported!("yield"), - Expr::RecordLit { fields, path, spread } => { - let spread_place = match spread { - &Some(it) => { + Expr::RecordLit { fields, path, spread, .. } => { + let spread_place = match *spread { + RecordSpread::Expr(it) => { let Some((p, c)) = self.lower_expr_as_place(current, it, true)? else { return Ok(None); }; current = c; Some(p) } - None => None, + RecordSpread::None => None, + RecordSpread::FieldDefaults => not_supported!("empty record spread"), }; let variant_id = self.infer.variant_resolution_for_expr(expr_id).ok_or_else(|| match path { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/fulfill.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/fulfill.rs index 0fe073297279..a8bff44a0258 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/fulfill.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/fulfill.rs @@ -48,6 +48,7 @@ pub struct FulfillmentCtxt<'db> { /// use the context in exactly this snapshot. #[expect(unused)] usable_in_snapshot: usize, + try_evaluate_obligations_scratch: PendingObligations<'db>, } #[derive(Default, Debug, Clone)] @@ -115,6 +116,7 @@ impl<'db> FulfillmentCtxt<'db> { FulfillmentCtxt { obligations: Default::default(), usable_in_snapshot: infcx.num_open_snapshots(), + try_evaluate_obligations_scratch: Default::default(), } } } @@ -162,12 +164,12 @@ impl<'db> FulfillmentCtxt<'db> { // and select. They should use a different `ObligationCtxt` instead. Then we'll be also able // to not put the obligations queue in `InferenceTable`'s snapshots. // assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); + self.try_evaluate_obligations_scratch.clear(); let mut errors = Vec::new(); - let mut obligations = Vec::new(); loop { let mut any_changed = false; - obligations.extend(self.obligations.drain_pending(|_| true)); - for (mut obligation, stalled_on) in obligations.drain(..) { + self.try_evaluate_obligations_scratch.extend(self.obligations.drain_pending(|_| true)); + for (mut obligation, stalled_on) in self.try_evaluate_obligations_scratch.drain(..) { if obligation.recursion_depth >= infcx.interner.recursion_limit() { self.obligations.on_fulfillment_overflow(infcx); // Only return true errors that we have accumulated while processing. diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/canonical/instantiate.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/canonical/instantiate.rs index b758042e85b0..61d1e9774622 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/canonical/instantiate.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/canonical/instantiate.rs @@ -15,6 +15,7 @@ use crate::next_solver::{ infer::{ InferCtxt, InferOk, InferResult, canonical::{QueryRegionConstraints, QueryResponse, canonicalizer::OriginalQueryValues}, + opaque_types::table::OpaqueTypeStorageEntries, traits::{ObligationCause, PredicateObligations}, }, }; @@ -194,6 +195,7 @@ impl<'db> InferCtxt<'db> { &self, inference_vars: CanonicalVarValues<'db>, answer: T, + prev_entries: OpaqueTypeStorageEntries, ) -> Canonical<'db, QueryResponse<'db, T>> where T: TypeFoldable>, @@ -209,7 +211,7 @@ impl<'db> InferCtxt<'db> { .inner .borrow_mut() .opaque_type_storage - .iter_opaque_types() + .opaque_types_added_since(prev_entries) .map(|(k, v)| (k, v.ty)) .collect(); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/mod.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/mod.rs index 7d291f7ddbed..21baacb11693 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/mod.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/mod.rs @@ -140,7 +140,7 @@ pub struct InferCtxtInner<'db> { /// /// Before running `resolve_regions_and_report_errors`, the creator /// of the inference context is expected to invoke - /// [`InferCtxt::process_registered_region_obligations`] + /// `InferCtxt::process_registered_region_obligations` /// for each body-id in this map, which will process the /// obligations within. This is expected to be done 'late enough' /// that all type inference variables have been bound and so forth. diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/traits.rs index 14df42dc2aeb..dde623483642 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/traits.rs @@ -55,6 +55,13 @@ impl ObligationCause { } } +impl Default for ObligationCause { + #[inline] + fn default() -> Self { + Self::new() + } +} + /// An `Obligation` represents some trait reference (e.g., `i32: Eq`) for /// which the "impl_source" must be found. The process of finding an "impl_source" is /// called "resolving" the `Obligation`. This process consists of diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs index 2a3df1d32a30..e17bdac68cdd 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs @@ -41,7 +41,8 @@ use crate::{ AdtIdWrapper, AnyImplId, BoundConst, CallableIdWrapper, CanonicalVarKind, ClosureIdWrapper, CoroutineIdWrapper, Ctor, FnSig, FxIndexMap, GeneralConstIdWrapper, OpaqueTypeKey, RegionAssumptions, SimplifiedType, SolverContext, SolverDefIds, TraitIdWrapper, - TypeAliasIdWrapper, UnevaluatedConst, util::explicit_item_bounds, + TypeAliasIdWrapper, UnevaluatedConst, + util::{explicit_item_bounds, explicit_item_self_bounds}, }, }; @@ -1421,7 +1422,7 @@ impl<'db> Interner for DbInterner<'db> { self, def_id: Self::DefId, ) -> EarlyBinder> { - explicit_item_bounds(self, def_id) + explicit_item_self_bounds(self, def_id) .map_bound(|bounds| elaborate(self, bounds).filter_only_self()) } @@ -1500,7 +1501,7 @@ impl<'db> Interner for DbInterner<'db> { } } - predicates_of(self.db, def_id).explicit_predicates().map_bound(|predicates| { + predicates_of(self.db, def_id).explicit_implied_predicates().map_bound(|predicates| { predicates .iter() .copied() diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/predicate.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/predicate.rs index 5758e2dc7e93..6f4fae707317 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/predicate.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/predicate.rs @@ -273,9 +273,8 @@ impl<'db> std::fmt::Debug for Clauses<'db> { impl<'db> Clauses<'db> { #[inline] - pub fn empty(_interner: DbInterner<'db>) -> Self { - // FIXME: Get from a static. - Self::new_from_slice(&[]) + pub fn empty(interner: DbInterner<'db>) -> Self { + interner.default_types().empty.clauses } #[inline] diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs index 66a24d394990..1173028a1092 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs @@ -508,6 +508,11 @@ impl<'db> Ty<'db> { references_non_lt_error(&self) } + /// Whether the type contains a type error (ignoring const and lifetime errors). + pub fn references_only_ty_error(self) -> bool { + references_only_ty_error(&self) + } + pub fn callable_sig(self, interner: DbInterner<'db>) -> Option>> { match self.kind() { TyKind::FnDef(callable, args) => { @@ -777,6 +782,20 @@ impl<'db> TypeVisitor> for ReferencesNonLifetimeError { } } +pub fn references_only_ty_error<'db, T: TypeVisitableExt>>(t: &T) -> bool { + t.references_error() && t.visit_with(&mut ReferencesOnlyTyError).is_break() +} + +struct ReferencesOnlyTyError; + +impl<'db> TypeVisitor> for ReferencesOnlyTyError { + type Result = ControlFlow<()>; + + fn visit_ty(&mut self, ty: Ty<'db>) -> Self::Result { + if ty.is_ty_error() { ControlFlow::Break(()) } else { ty.super_visit_with(self) } + } +} + impl<'db> std::fmt::Debug for Ty<'db> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.inner().internee.fmt(f) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs index 34ecfed08f29..9a1b476976e3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs @@ -455,6 +455,21 @@ pub fn explicit_item_bounds<'db>( clauses.map_bound(|clauses| clauses.iter().copied()) } +pub fn explicit_item_self_bounds<'db>( + interner: DbInterner<'db>, + def_id: SolverDefId, +) -> EarlyBinder<'db, impl DoubleEndedIterator> + ExactSizeIterator> { + let db = interner.db(); + let clauses = match def_id { + SolverDefId::TypeAliasId(type_alias) => { + crate::lower::type_alias_self_bounds(db, type_alias) + } + SolverDefId::InternedOpaqueTyId(id) => id.self_predicates(db), + _ => panic!("Unexpected GenericDefId"), + }; + clauses.map_bound(|clauses| clauses.iter().copied()) +} + pub struct ContainsTypeErrors; impl<'db> TypeVisitor> for ContainsTypeErrors { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs index 8408c0a7bfcd..f089120cd7b8 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs @@ -135,7 +135,7 @@ fn check_closure_captures(#[rust_analyzer::rust_fixture] ra_fixture: &str, expec fn deref_in_let() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn fn main() { let a = &mut true; let closure = || { let b = *a; }; @@ -149,7 +149,7 @@ fn main() { fn deref_then_ref_pattern() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn fn main() { let a = &mut true; let closure = || { let &mut ref b = a; }; @@ -159,7 +159,7 @@ fn main() { ); check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn fn main() { let a = &mut true; let closure = || { let &mut ref mut b = a; }; @@ -173,7 +173,7 @@ fn main() { fn unique_borrow() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn fn main() { let a = &mut true; let closure = || { *a = false; }; @@ -187,7 +187,7 @@ fn main() { fn deref_ref_mut() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn fn main() { let a = &mut true; let closure = || { let ref mut b = *a; }; @@ -201,7 +201,7 @@ fn main() { fn let_else_not_consuming() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn fn main() { let a = &mut true; let closure = || { let _ = *a else { return; }; }; @@ -215,7 +215,7 @@ fn main() { fn consume() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn struct NonCopy; fn main() { let a = NonCopy; @@ -230,7 +230,7 @@ fn main() { fn ref_to_upvar() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn struct NonCopy; fn main() { let mut a = NonCopy; @@ -248,7 +248,7 @@ fn main() { fn field() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn struct Foo { a: i32, b: i32 } fn main() { let a = Foo { a: 0, b: 0 }; @@ -263,7 +263,7 @@ fn main() { fn fields_different_mode() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn struct NonCopy; struct Foo { a: i32, b: i32, c: NonCopy, d: bool } fn main() { @@ -286,7 +286,7 @@ fn main() { fn autoref() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn struct Foo; impl Foo { fn imm(&self) {} @@ -308,7 +308,7 @@ fn main() { fn captures_priority() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn struct NonCopy; fn main() { let mut a = &mut true; @@ -336,7 +336,7 @@ fn main() { fn let_underscore() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn fn main() { let mut a = true; let closure = || { let _ = a; }; @@ -350,7 +350,7 @@ fn main() { fn match_wildcard() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn struct NonCopy; fn main() { let mut a = NonCopy; @@ -375,7 +375,7 @@ fn main() { fn multiple_bindings() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn fn main() { let mut a = false; let mut closure = || { let (b | b) = a; }; @@ -389,7 +389,7 @@ fn main() { fn multiple_usages() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn fn main() { let mut a = false; let mut closure = || { @@ -410,7 +410,7 @@ fn main() { fn ref_then_deref() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn fn main() { let mut a = false; let mut closure = || { let b = *&mut a; }; @@ -424,7 +424,7 @@ fn main() { fn ref_of_ref() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn fn main() { let mut a = &false; let closure = || { let b = &a; }; @@ -446,7 +446,7 @@ fn main() { fn multiple_capture_usages() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn struct A { a: i32, b: bool } fn main() { let mut a = A { a: 123, b: false }; @@ -465,7 +465,7 @@ fn main() { fn let_binding_is_a_ref_capture_in_ref_binding() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn struct S; fn main() { let mut s = S; @@ -489,7 +489,7 @@ fn main() { fn let_binding_is_a_value_capture_in_binding() { check_closure_captures( r#" -//- minicore:copy, option +//- minicore:copy, fn, option struct Box(i32); fn main() { let b = Some(Box(0)); @@ -508,7 +508,7 @@ fn main() { fn alias_needs_to_be_normalized() { check_closure_captures( r#" -//- minicore:copy +//- minicore:copy, fn trait Trait { type Associated; } @@ -528,3 +528,41 @@ fn main() { expect!["220..257;174..175;245..250 ByRef(Shared) c.b.x &'? i32"], ); } + +#[test] +fn nested_ref_captures_from_outer() { + check_closure_captures( + r#" +//- minicore:copy, fn +fn f() { + let a = 1; + let a_closure = || { + let b_closure = || { + { a }; + }; + }; +} +"#, + expect![[r#" + 44..113;17..18;92..93 ByRef(Shared) a &'? i32 + 73..106;17..18;92..93 ByRef(Shared) a &'? i32"#]], + ); +} + +#[test] +fn nested_ref_captures() { + check_closure_captures( + r#" +//- minicore:copy, fn +fn f() { + let a_closure = || { + let b = 2; + let b_closure = || { + { b }; + }; + }; +} +"#, + expect!["77..110;46..47;96..97 ByRef(Shared) b &'? i32"], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/opaque_types.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/opaque_types.rs index ca986336ff30..21d830ed51e3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/opaque_types.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/opaque_types.rs @@ -1,5 +1,7 @@ use expect_test::expect; +use crate::tests::check_infer; + use super::{check_infer_with_mismatches, check_no_mismatches, check_types}; #[test] @@ -176,3 +178,37 @@ fn main() { "#, ); } + +#[test] +fn regression_21455() { + check_infer( + r#" +//- minicore: copy + +struct Vec(T); +impl Vec { + pub fn new() -> Self { loop {} } +} + +pub struct Miku {} + +impl Miku { + pub fn all_paths_to(&self) -> impl Copy { + Miku { + full_paths: Vec::new(), + } + } +} + "#, + expect![[r#" + 61..72 '{ loop {} }': Vec + 63..70 'loop {}': ! + 68..70 '{}': () + 133..137 'self': &'? Miku + 152..220 '{ ... }': Miku + 162..214 'Miku {... }': Miku + 193..201 'Vec::new': fn new<{unknown}>() -> Vec<{unknown}> + 193..203 'Vec::new()': Vec<{unknown}> + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs index 0b776938c5b3..8c7d29f99371 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs @@ -13,11 +13,11 @@ fn infer_pattern() { let a = z; let (c, d) = (1, "hello"); - for (e, f) in some_iter { + for (e, f) in [(0, 1)] { let g = e; } - if let [val] = opt { + if let [val] = [y] { let h = val; } @@ -33,7 +33,7 @@ fn infer_pattern() { "#, expect![[r#" 8..9 'x': &'? i32 - 17..400 '{ ...o_x; }': () + 17..399 '{ ...o_x; }': () 27..28 'y': &'? i32 31..32 'x': &'? i32 42..44 '&z': &'? i32 @@ -47,58 +47,62 @@ fn infer_pattern() { 82..94 '(1, "hello")': (i32, &'? str) 83..84 '1': i32 86..93 '"hello"': &'static str - 101..151 'for (e... }': fn into_iter<{unknown}>({unknown}) -> <{unknown} as IntoIterator>::IntoIter - 101..151 'for (e... }': <{unknown} as IntoIterator>::IntoIter - 101..151 'for (e... }': ! - 101..151 'for (e... }': {unknown} - 101..151 'for (e... }': &'? mut {unknown} - 101..151 'for (e... }': fn next<{unknown}>(&'? mut {unknown}) -> Option<<{unknown} as Iterator>::Item> - 101..151 'for (e... }': Option<<{unknown} as Iterator>::Item> - 101..151 'for (e... }': () - 101..151 'for (e... }': () - 101..151 'for (e... }': () - 101..151 'for (e... }': () - 105..111 '(e, f)': ({unknown}, {unknown}) - 106..107 'e': {unknown} - 109..110 'f': {unknown} - 115..124 'some_iter': {unknown} - 125..151 '{ ... }': () - 139..140 'g': {unknown} - 143..144 'e': {unknown} - 157..204 'if let... }': () - 160..175 'let [val] = opt': bool - 164..169 '[val]': [{unknown}] - 165..168 'val': {unknown} - 172..175 'opt': [{unknown}] - 176..204 '{ ... }': () - 190..191 'h': {unknown} - 194..197 'val': {unknown} - 210..236 'if let...rue {}': () - 213..233 'let x ... &true': bool - 217..225 'x @ true': &'? bool - 221..225 'true': bool - 221..225 'true': bool - 228..233 '&true': &'? bool - 229..233 'true': bool - 234..236 '{}': () - 246..252 'lambda': impl Fn(u64, u64, i32) -> i32 - 255..287 '|a: u6...b; c }': impl Fn(u64, u64, i32) -> i32 - 256..257 'a': u64 - 264..265 'b': u64 - 267..268 'c': i32 - 275..287 '{ a + b; c }': i32 - 277..278 'a': u64 - 277..282 'a + b': u64 - 281..282 'b': u64 - 284..285 'c': i32 - 298..310 'ref ref_to_x': &'? &'? i32 - 313..314 'x': &'? i32 - 324..333 'mut mut_x': &'? i32 - 336..337 'x': &'? i32 - 347..367 'ref mu...f_to_x': &'? mut &'? i32 - 370..371 'x': &'? i32 - 381..382 'k': &'? mut &'? i32 - 385..397 'mut_ref_to_x': &'? mut &'? i32 + 101..150 'for (e... }': fn into_iter<[(i32, i32); 1]>([(i32, i32); 1]) -> <[(i32, i32); 1] as IntoIterator>::IntoIter + 101..150 'for (e... }': IntoIter<(i32, i32), 1> + 101..150 'for (e... }': ! + 101..150 'for (e... }': IntoIter<(i32, i32), 1> + 101..150 'for (e... }': &'? mut IntoIter<(i32, i32), 1> + 101..150 'for (e... }': fn next>(&'? mut IntoIter<(i32, i32), 1>) -> Option< as Iterator>::Item> + 101..150 'for (e... }': Option<(i32, i32)> + 101..150 'for (e... }': () + 101..150 'for (e... }': () + 101..150 'for (e... }': () + 101..150 'for (e... }': () + 105..111 '(e, f)': (i32, i32) + 106..107 'e': i32 + 109..110 'f': i32 + 115..123 '[(0, 1)]': [(i32, i32); 1] + 116..122 '(0, 1)': (i32, i32) + 117..118 '0': i32 + 120..121 '1': i32 + 124..150 '{ ... }': () + 138..139 'g': i32 + 142..143 'e': i32 + 156..203 'if let... }': () + 159..174 'let [val] = [y]': bool + 163..168 '[val]': [&'? i32; 1] + 164..167 'val': &'? i32 + 171..174 '[y]': [&'? i32; 1] + 172..173 'y': &'? i32 + 175..203 '{ ... }': () + 189..190 'h': &'? i32 + 193..196 'val': &'? i32 + 209..235 'if let...rue {}': () + 212..232 'let x ... &true': bool + 216..224 'x @ true': &'? bool + 220..224 'true': bool + 220..224 'true': bool + 227..232 '&true': &'? bool + 228..232 'true': bool + 233..235 '{}': () + 245..251 'lambda': impl Fn(u64, u64, i32) -> i32 + 254..286 '|a: u6...b; c }': impl Fn(u64, u64, i32) -> i32 + 255..256 'a': u64 + 263..264 'b': u64 + 266..267 'c': i32 + 274..286 '{ a + b; c }': i32 + 276..277 'a': u64 + 276..281 'a + b': u64 + 280..281 'b': u64 + 283..284 'c': i32 + 297..309 'ref ref_to_x': &'? &'? i32 + 312..313 'x': &'? i32 + 323..332 'mut mut_x': &'? i32 + 335..336 'x': &'? i32 + 346..366 'ref mu...f_to_x': &'? mut &'? i32 + 369..370 'x': &'? i32 + 380..381 'k': &'? mut &'? i32 + 384..396 'mut_ref_to_x': &'? mut &'? i32 "#]], ); } @@ -380,7 +384,7 @@ fn infer_pattern_match_string_literal() { fn infer_pattern_match_byte_string_literal() { check_infer_with_mismatches( r#" - //- minicore: index + //- minicore: index, range struct S; impl core::ops::Index for [T; N] { type Output = [u8]; @@ -395,7 +399,7 @@ fn infer_pattern_match_byte_string_literal() { "#, expect![[r#" 105..109 'self': &'? [T; N] - 111..116 'index': {unknown} + 111..116 'index': RangeFull 157..180 '{ ... }': &'? [u8] 167..174 'loop {}': ! 172..174 '{}': () diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs index c805f030446c..4f1480c39366 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs @@ -891,13 +891,14 @@ use core::ops::Deref; struct BufWriter {} -struct Mutex {} -struct MutexGuard<'a, T> {} +struct Mutex(T); +struct MutexGuard<'a, T>(&'a T); impl Mutex { fn lock(&self) -> MutexGuard<'_, T> {} } impl<'a, T: 'a> Deref for MutexGuard<'a, T> { type Target = T; + fn deref(&self) -> &Self::Target { loop {} } } fn flush(&self) { let w: &Mutex; @@ -905,14 +906,18 @@ fn flush(&self) { } "#, expect![[r#" - 123..127 'self': &'? Mutex - 150..152 '{}': MutexGuard<'?, T> - 234..238 'self': &'? {unknown} - 240..290 '{ ...()); }': () - 250..251 'w': &'? Mutex - 276..287 '*(w.lock())': BufWriter - 278..279 'w': &'? Mutex - 278..286 'w.lock()': MutexGuard<'?, BufWriter> + 129..133 'self': &'? Mutex + 156..158 '{}': MutexGuard<'?, T> + 242..246 'self': &'? MutexGuard<'a, T> + 265..276 '{ loop {} }': &'? T + 267..274 'loop {}': ! + 272..274 '{}': () + 289..293 'self': &'? {unknown} + 295..345 '{ ...()); }': () + 305..306 'w': &'? Mutex + 331..342 '*(w.lock())': BufWriter + 333..334 'w': &'? Mutex + 333..341 'w.lock()': MutexGuard<'?, BufWriter> "#]], ); } @@ -2230,7 +2235,6 @@ async fn f() -> Bar {} "#, expect![[r#" 64..66 '{}': () - 64..66 '{}': impl Future "#]], ); } @@ -2563,3 +2567,81 @@ fn main() { "#, ); } + +#[test] +fn regression_21429() { + check_no_mismatches( + r#" +trait DatabaseLike { + type ForeignKey: ForeignKeyLike; +} + +trait ForeignKeyLike { + type DB: DatabaseLike; + + fn host_columns(&self, database: &Self::DB); +} + +trait ColumnLike { + type DB: DatabaseLike; + + fn foo() -> &&<::DB as DatabaseLike>::ForeignKey { + loop {} + } + + fn foreign_keys(&self, database: &Self::DB) { + let fk = Self::foo(); + fk.host_columns(database); + } +} + "#, + ); +} + +#[test] +fn issue_21006_generic_predicates_for_param_supertrait_cycle() { + check_no_mismatches( + r#" +trait VCipherSuite {} + +trait CipherSuite +where + OprfHash: Hash, +{ +} + +type Bar = ::Hash; + +type OprfHash = ::Hash; + +impl Foo { + fn seal() {} +} + "#, + ); +} + +#[test] +fn issue_21006_self_assoc_trait() { + check_types( + r#" +trait Baz { + fn baz(&self); +} + +trait Foo { + type Assoc; +} + +trait Bar: Foo +where + Self::Assoc: Baz, +{ + fn bar(v: Self::Assoc) { + let _ = v.baz(); + // ^ () + } +} + "#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs index a4554673cdd5..f47a26d429fd 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs @@ -471,7 +471,76 @@ fn foo() { 244..246 '_x': {unknown} 249..257 'to_bytes': fn to_bytes() -> [u8; _] 249..259 'to_bytes()': [u8; _] - 249..268 'to_byt..._vec()': {unknown} + 249..268 'to_byt..._vec()': Vec<<[u8; _] as Foo>::Item> + "#]], + ); +} + +#[test] +fn regression_21315() { + check_infer( + r#" +struct Consts; +impl Consts { const MAX: usize = 0; } + +struct Between(T); + +impl Between { + fn sep_once(self, _sep: &str, _other: Self) -> Self { + self + } +} + +trait Parser: Sized { + fn at_least(self) -> Between { + Between(self) + } + fn at_most(self) -> Between<0, N, Self> { + Between(self) + } +} + +impl Parser for char {} + +fn test_at_least() { + let num = '9'.at_least::<1>(); + let _ver = num.sep_once(".", num); +} + +fn test_at_most() { + let num = '9'.at_most::<1>(); +} + "#, + expect![[r#" + 48..49 '0': usize + 182..186 'self': Between + 188..192 '_sep': &'? str + 200..206 '_other': Between + 222..242 '{ ... }': Between + 232..236 'self': Between + 300..304 'self': Self + 343..372 '{ ... }': Between + 353..360 'Between': fn Between(Self) -> Between + 353..366 'Between(self)': Between + 361..365 'self': Self + 404..408 'self': Self + 433..462 '{ ... }': Between<0, N, Self> + 443..450 'Between': fn Between<0, N, Self>(Self) -> Between<0, N, Self> + 443..456 'Between(self)': Between<0, N, Self> + 451..455 'self': Self + 510..587 '{ ...um); }': () + 520..523 'num': Between<1, _, char> + 526..529 ''9'': char + 526..545 ''9'.at...:<1>()': Between<1, _, char> + 555..559 '_ver': Between<1, _, char> + 562..565 'num': Between<1, _, char> + 562..584 'num.se..., num)': Between<1, _, char> + 575..578 '"."': &'static str + 580..583 'num': Between<1, _, char> + 607..644 '{ ...>(); }': () + 617..620 'num': Between<0, 1, char> + 623..626 ''9'': char + 623..641 ''9'.at...:<1>()': Between<0, 1, char> "#]], ); } @@ -750,3 +819,63 @@ fn main() { "#]], ); } + +#[test] +fn regression_19339() { + check_infer( + r#" +trait Bar { + type Baz; + + fn baz(&self) -> Self::Baz; +} + +trait Foo { + type Bar; + + fn bar(&self) -> Self::Bar; +} + +trait FooFactory { + type Output: Foo>; + + fn foo(&self) -> Self::Output; + + fn foo_rpit(&self) -> impl Foo>; +} + +fn test1(foo: impl Foo>) { + let baz = foo.bar().baz(); +} + +fn test2(factory: T) { + let baz = factory.foo().bar().baz(); + let baz = factory.foo_rpit().bar().baz(); +} +"#, + expect![[r#" + 39..43 'self': &'? Self + 101..105 'self': &'? Self + 198..202 'self': &'? Self + 239..243 'self': &'? Self + 290..293 'foo': impl Foo + ?Sized + 325..359 '{ ...z(); }': () + 335..338 'baz': u8 + 341..344 'foo': impl Foo + ?Sized + 341..350 'foo.bar()': impl Bar + 341..356 'foo.bar().baz()': u8 + 385..392 'factory': T + 397..487 '{ ...z(); }': () + 407..410 'baz': u8 + 413..420 'factory': T + 413..426 'factory.foo()': ::Output + 413..432 'factor....bar()': <::Output as Foo>::Bar + 413..438 'factor....baz()': u8 + 448..451 'baz': u8 + 454..461 'factory': T + 454..472 'factor...rpit()': impl Foo + Bar + ?Sized + 454..478 'factor....bar()': + ?Sized as Foo>::Bar + 454..484 'factor....baz()': u8 + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs index a9a5e96f75cc..98503452d348 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs @@ -64,20 +64,37 @@ fn type_alias_in_struct_lit() { #[test] fn infer_ranges() { - check_types( + check_no_mismatches( r#" -//- minicore: range -fn test() { - let a = ..; - let b = 1..; - let c = ..2u32; - let d = 1..2usize; - let e = ..=10; - let f = 'a'..='z'; +//- minicore: range, new_range - let t = (a, b, c, d, e, f); - t; -} //^ (RangeFull, RangeFrom, RangeTo, Range, RangeToInclusive, RangeInclusive) +fn test() { + let _: core::ops::RangeFull = ..; + let _: core::ops::RangeFrom = 1..; + let _: core::ops::RangeTo = ..2u32; + let _: core::ops::Range = 1..2usize; + let _: core::ops::RangeToInclusive = ..=10; + let _: core::ops::RangeInclusive = 'a'..='z'; +} +"#, + ); +} + +#[test] +fn infer_ranges_new_range() { + check_no_mismatches( + r#" +//- minicore: range, new_range +#![feature(new_range)] + +fn test() { + let _: core::ops::RangeFull = ..; + let _: core::range::RangeFrom = 1..; + let _: core::ops::RangeTo = ..2u32; + let _: core::range::Range = 1..2usize; + let _: core::range::RangeToInclusive = ..=10; + let _: core::range::RangeInclusive = 'a'..='z'; +} "#, ); } @@ -2139,7 +2156,6 @@ async fn main() { "#, expect![[r#" 16..193 '{ ...2 }; }': () - 16..193 '{ ...2 }; }': impl Future 26..27 'x': i32 30..43 'unsafe { 92 }': i32 39..41 '92': i32 @@ -3983,3 +3999,60 @@ fn foo() { "#]], ); } + +#[test] +fn naked_asm_returns_never() { + check_no_mismatches( + r#" +//- minicore: asm + +#[unsafe(naked)] +extern "C" fn foo() -> ! { + core::arch::naked_asm!(""); +} + "#, + ); +} + +#[test] +fn regression_21478() { + check_infer( + r#" +//- minicore: unsize, coerce_unsized +struct LazyLock(T); + +impl LazyLock { + const fn new() -> Self { + loop {} + } + + fn force(this: &Self) -> &T { + loop {} + } +} + +static VALUES_LAZY_LOCK: LazyLock<[u32; { 0 }]> = LazyLock::new(); + +fn foo() { + let _ = LazyLock::force(&VALUES_LAZY_LOCK); +} + "#, + expect![[r#" + 73..96 '{ ... }': LazyLock + 83..90 'loop {}': ! + 88..90 '{}': () + 111..115 'this': &'? LazyLock + 130..153 '{ ... }': &'? T + 140..147 'loop {}': ! + 145..147 '{}': () + 207..220 'LazyLock::new': fn new<[u32; _]>() -> LazyLock<[u32; _]> + 207..222 'LazyLock::new()': LazyLock<[u32; _]> + 234..285 '{ ...CK); }': () + 244..245 '_': &'? [u32; _] + 248..263 'LazyLock::force': fn force<[u32; _]>(&'? LazyLock<[u32; _]>) -> &'? [u32; _] + 248..282 'LazyLo..._LOCK)': &'? [u32; _] + 264..281 '&VALUE...Y_LOCK': &'? LazyLock<[u32; _]> + 265..281 'VALUES...Y_LOCK': LazyLock<[u32; _]> + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs index 38591f486e97..390553c0d7a9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs @@ -429,7 +429,7 @@ fn associated_type_shorthand_from_method_bound() { trait Iterable { type Item; } -struct S; +struct S(T); impl S { fn foo(self) -> T::Item where T: Iterable { loop {} } } @@ -1103,40 +1103,50 @@ fn test() { fn argument_impl_trait_type_args_2() { check_infer_with_mismatches( r#" -//- minicore: sized +//- minicore: sized, phantom_data +use core::marker::PhantomData; + trait Trait {} struct S; impl Trait for S {} -struct F; +struct F(PhantomData); impl F { fn foo(self, x: impl Trait) -> (T, U) { loop {} } } fn test() { - F.foo(S); - F::.foo(S); - F::.foo::(S); - F::.foo::(S); // extraneous argument should be ignored + F(PhantomData).foo(S); + F::(PhantomData).foo(S); + F::(PhantomData).foo::(S); + F::(PhantomData).foo::(S); // extraneous argument should be ignored }"#, expect![[r#" - 87..91 'self': F - 93..94 'x': impl Trait - 118..129 '{ loop {} }': (T, U) - 120..127 'loop {}': ! - 125..127 '{}': () - 143..283 '{ ...ored }': () - 149..150 'F': F<{unknown}> - 149..157 'F.foo(S)': ({unknown}, {unknown}) - 155..156 'S': S - 163..171 'F::': F - 163..178 'F::.foo(S)': (u32, {unknown}) - 176..177 'S': S - 184..192 'F::': F - 184..206 'F::(S)': (u32, i32) - 204..205 'S': S - 212..220 'F::': F - 212..239 'F::(S)': (u32, i32) - 237..238 'S': S + 135..139 'self': F + 141..142 'x': impl Trait + 166..177 '{ loop {} }': (T, U) + 168..175 'loop {}': ! + 173..175 '{}': () + 191..383 '{ ...ored }': () + 197..198 'F': fn F<{unknown}>(PhantomData<{unknown}>) -> F<{unknown}> + 197..211 'F(PhantomData)': F<{unknown}> + 197..218 'F(Phan...foo(S)': ({unknown}, {unknown}) + 199..210 'PhantomData': PhantomData<{unknown}> + 216..217 'S': S + 224..232 'F::': fn F(PhantomData) -> F + 224..245 'F:: + 224..252 'F:: + 250..251 'S': S + 258..266 'F::': fn F(PhantomData) -> F + 258..279 'F:: + 258..293 'F::(S)': (u32, i32) + 267..278 'PhantomData': PhantomData + 291..292 'S': S + 299..307 'F::': fn F(PhantomData) -> F + 299..320 'F:: + 299..339 'F::(S)': (u32, i32) + 308..319 'PhantomData': PhantomData + 337..338 'S': S "#]], ); } @@ -4012,7 +4022,7 @@ fn f() { fn dyn_map() { check_types( r#" -pub struct Key {} +pub struct Key(K, V, P); pub trait Policy { type K; @@ -4024,7 +4034,7 @@ impl Policy for (K, V) { type V = V; } -pub struct KeyMap {} +pub struct KeyMap(KEY); impl KeyMap> { pub fn get(&self, key: &P::K) -> P::V { @@ -4859,7 +4869,6 @@ async fn baz i32>(c: T) { expect![[r#" 37..38 'a': T 43..83 '{ ...ait; }': () - 43..83 '{ ...ait; }': impl Future 53..57 'fut1': >::CallRefFuture<'?> 60..61 'a': T 60..64 'a(0)': >::CallRefFuture<'?> @@ -4868,7 +4877,6 @@ async fn baz i32>(c: T) { 70..80 'fut1.await': i32 124..129 'mut b': T 134..174 '{ ...ait; }': () - 134..174 '{ ...ait; }': impl Future 144..148 'fut2': >::CallRefFuture<'?> 151..152 'b': T 151..155 'b(0)': >::CallRefFuture<'?> @@ -4877,7 +4885,6 @@ async fn baz i32>(c: T) { 161..171 'fut2.await': i32 216..217 'c': T 222..262 '{ ...ait; }': () - 222..262 '{ ...ait; }': impl Future 232..236 'fut3': >::CallOnceFuture 239..240 'c': T 239..243 'c(0)': >::CallOnceFuture @@ -5023,7 +5030,7 @@ fn main() { 278..280 '{}': () 290..291 '_': Box + '?> 294..298 'iter': Box + 'static> - 294..310 'iter.i...iter()': Box + 'static> + 294..310 'iter.i...iter()': Box + '?> 152..156 'self': &'? mut Box 177..208 '{ ... }': Option<::Item> 191..198 'loop {}': ! diff --git a/src/tools/rust-analyzer/crates/hir/src/attrs.rs b/src/tools/rust-analyzer/crates/hir/src/attrs.rs index cba1b39e5254..cfb95e07c362 100644 --- a/src/tools/rust-analyzer/crates/hir/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir/src/attrs.rs @@ -3,7 +3,8 @@ use cfg::CfgExpr; use either::Either; use hir_def::{ - AssocItemId, AttrDefId, FieldId, LifetimeParamId, ModuleDefId, TypeOrConstParamId, + AssocItemId, AttrDefId, FieldId, GenericDefId, ItemContainerId, LifetimeParamId, ModuleDefId, + TraitId, TypeOrConstParamId, attrs::{AttrFlags, Docs, IsInnerDoc}, expr_store::path::Path, item_scope::ItemInNs, @@ -22,6 +23,7 @@ use hir_ty::{ next_solver::{DbInterner, TypingMode, infer::DbInternerInferExt}, }; use intern::Symbol; +use stdx::never; use crate::{ Adt, AsAssocItem, AssocItem, BuiltinType, Const, ConstParam, DocLinkDef, Enum, ExternCrateDecl, @@ -357,13 +359,46 @@ fn resolve_assoc_or_field( ns: Option, ) -> Option { let path = Path::from_known_path_with_no_generic(path); - // FIXME: This does not handle `Self` on trait definitions, which we should resolve to the - // trait itself. let base_def = resolver.resolve_path_in_type_ns_fully(db, &path)?; + let handle_trait = |id: TraitId| { + // Doc paths in this context may only resolve to an item of this trait + // (i.e. no items of its supertraits), so we need to handle them here + // independently of others. + id.trait_items(db).items.iter().find(|it| it.0 == name).map(|(_, assoc_id)| { + let def = match *assoc_id { + AssocItemId::FunctionId(it) => ModuleDef::Function(it.into()), + AssocItemId::ConstId(it) => ModuleDef::Const(it.into()), + AssocItemId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()), + }; + DocLinkDef::ModuleDef(def) + }) + }; let ty = match base_def { TypeNs::SelfType(id) => Impl::from(id).self_ty(db), - TypeNs::GenericParam(_) => { + TypeNs::GenericParam(param) => { + let generic_params = db.generic_params(param.parent()); + if generic_params[param.local_id()].is_trait_self() { + // `Self::assoc` in traits should refer to the trait itself. + let parent_trait = |container| match container { + ItemContainerId::TraitId(trait_) => handle_trait(trait_), + _ => { + never!("container {container:?} should be a trait"); + None + } + }; + return match param.parent() { + GenericDefId::TraitId(trait_) => handle_trait(trait_), + GenericDefId::ConstId(it) => parent_trait(it.loc(db).container), + GenericDefId::FunctionId(it) => parent_trait(it.loc(db).container), + GenericDefId::TypeAliasId(it) => parent_trait(it.loc(db).container), + _ => { + never!("type param {param:?} should belong to a trait"); + None + } + }; + } + // Even if this generic parameter has some trait bounds, rustdoc doesn't // resolve `name` to trait items. return None; @@ -384,19 +419,7 @@ fn resolve_assoc_or_field( alias.ty(db) } TypeNs::BuiltinType(id) => BuiltinType::from(id).ty(db), - TypeNs::TraitId(id) => { - // Doc paths in this context may only resolve to an item of this trait - // (i.e. no items of its supertraits), so we need to handle them here - // independently of others. - return id.trait_items(db).items.iter().find(|it| it.0 == name).map(|(_, assoc_id)| { - let def = match *assoc_id { - AssocItemId::FunctionId(it) => ModuleDef::Function(it.into()), - AssocItemId::ConstId(it) => ModuleDef::Const(it.into()), - AssocItemId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()), - }; - DocLinkDef::ModuleDef(def) - }); - } + TypeNs::TraitId(id) => return handle_trait(id), TypeNs::ModuleId(_) => { return None; } @@ -414,7 +437,14 @@ fn resolve_assoc_or_field( let variant_def = match ty.as_adt()? { Adt::Struct(it) => it.into(), Adt::Union(it) => it.into(), - Adt::Enum(_) => return None, + Adt::Enum(enum_) => { + // Can happen on `Self::Variant` (otherwise would be fully resolved by the resolver). + return enum_ + .id + .enum_variants(db) + .variant(&name) + .map(|variant| DocLinkDef::ModuleDef(ModuleDef::Variant(variant.into()))); + } }; resolve_field(db, variant_def, name, ns) } diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 78be5a7e8fa9..252d71fb80a4 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -610,6 +610,23 @@ impl Module { res } + pub fn modules_in_scope(&self, db: &dyn HirDatabase, pub_only: bool) -> Vec<(Name, Module)> { + let def_map = self.id.def_map(db); + let scope = &def_map[self.id].scope; + + let mut res = Vec::new(); + + for (name, item) in scope.types() { + if let ModuleDefId::ModuleId(m) = item.def + && (!pub_only || item.vis == Visibility::Public) + { + res.push((name.clone(), Module { id: m })); + } + } + + res + } + /// Returns a `ModuleScope`: a set of items, visible in this module. pub fn scope( self, diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index f4c42537de93..4bc757da4417 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -13,7 +13,7 @@ use std::{ use base_db::FxIndexSet; use either::Either; use hir_def::{ - DefWithBodyId, MacroId, StructId, TraitId, VariantId, + BuiltinDeriveImplId, DefWithBodyId, HasModule, MacroId, StructId, TraitId, VariantId, attrs::parse_extra_crate_attrs, expr_store::{Body, ExprOrPatSource, HygieneId, path::Path}, hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat}, @@ -622,18 +622,34 @@ impl<'db> SemanticsImpl<'db> { Some( calls .into_iter() - .map(|call| macro_call_to_macro_id(ctx, call?).map(|id| Macro { id })) + .map(|call| { + let call = call?; + match call { + Either::Left(call) => { + macro_call_to_macro_id(ctx, call).map(|id| Macro { id }) + } + Either::Right(call) => { + let call = call.loc(self.db); + let krate = call.krate(self.db); + let lang_items = hir_def::lang_item::lang_items(self.db, krate); + call.trait_.derive_macro(lang_items).map(|id| Macro { id }) + } + } + }) .collect(), ) }) } - pub fn expand_derive_macro(&self, attr: &ast::Attr) -> Option>> { + pub fn expand_derive_macro( + &self, + attr: &ast::Attr, + ) -> Option>>> { let res: Vec<_> = self .derive_macro_calls(attr)? .into_iter() - .flat_map(|call| { - let file_id = call?; + .map(|call| { + let file_id = call?.left()?; let ExpandResult { value, err } = self.db.parse_macro_expansion(file_id); let root_node = value.0.syntax_node(); self.cache(root_node.clone(), file_id.into()); @@ -643,7 +659,10 @@ impl<'db> SemanticsImpl<'db> { Some(res) } - fn derive_macro_calls(&self, attr: &ast::Attr) -> Option>> { + fn derive_macro_calls( + &self, + attr: &ast::Attr, + ) -> Option>>> { let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; let file_id = self.find_file(adt.syntax()).file_id; let adt = InFile::new(file_id, &adt); @@ -690,8 +709,9 @@ impl<'db> SemanticsImpl<'db> { .derive_helpers_in_scope(InFile::new(sa.file_id, id))? .iter() .filter(|&(name, _, _)| *name == attr_name) - .map(|&(_, macro_, call)| (macro_.into(), call)) + .filter_map(|&(_, macro_, call)| Some((macro_.into(), call.left()?))) .collect(); + // FIXME: We filter our builtin derive "fake" expansions, is this correct? Should we still expose them somehow? res.is_empty().not().then_some(res) } @@ -1338,6 +1358,7 @@ impl<'db> SemanticsImpl<'db> { // FIXME: We need to call `f` for all of them as well though! process_expansion_for_token(ctx, &mut stack, derive_attr); for derive in derives.into_iter().flatten() { + let Either::Left(derive) = derive else { continue }; process_expansion_for_token(ctx, &mut stack, derive); } } @@ -1467,11 +1488,12 @@ impl<'db> SemanticsImpl<'db> { for (.., derive) in helpers.iter().filter(|(helper, ..)| *helper == attr_name) { + let Either::Left(derive) = *derive else { continue }; // as there may be multiple derives registering the same helper // name, we gotta make sure to call this for all of them! // FIXME: We need to call `f` for all of them as well though! res = res - .or(process_expansion_for_token(ctx, &mut stack, *derive)); + .or(process_expansion_for_token(ctx, &mut stack, derive)); } res }) @@ -1981,6 +2003,15 @@ impl<'db> SemanticsImpl<'db> { .unwrap_or_default() } + pub fn record_literal_matched_fields( + &self, + literal: &ast::RecordExpr, + ) -> Vec<(Field, Type<'db>)> { + self.analyze(literal.syntax()) + .and_then(|it| it.record_literal_matched_fields(self.db, literal)) + .unwrap_or_default() + } + pub fn record_pattern_missing_fields( &self, pattern: &ast::RecordPat, @@ -1990,6 +2021,15 @@ impl<'db> SemanticsImpl<'db> { .unwrap_or_default() } + pub fn record_pattern_matched_fields( + &self, + pattern: &ast::RecordPat, + ) -> Vec<(Field, Type<'db>)> { + self.analyze(pattern.syntax()) + .and_then(|it| it.record_pattern_matched_fields(self.db, pattern)) + .unwrap_or_default() + } + fn with_ctx) -> T, T>(&self, f: F) -> T { let mut ctx = SourceToDefCtx { db: self.db, cache: &mut self.s2d_cache.borrow_mut() }; f(&mut ctx) diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs index 257405992731..d222c3dc7ed1 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs @@ -87,10 +87,10 @@ use either::Either; use hir_def::{ - AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId, - ExternCrateId, FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, - Lookup, MacroId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId, - UseId, VariantId, + AdtId, BlockId, BuiltinDeriveImplId, ConstId, ConstParamId, DefWithBodyId, EnumId, + EnumVariantId, ExternBlockId, ExternCrateId, FieldId, FunctionId, GenericDefId, GenericParamId, + ImplId, LifetimeParamId, Lookup, MacroId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, + TypeParamId, UnionId, UseId, VariantId, dyn_map::{ DynMap, keys::{self, Key}, @@ -394,7 +394,7 @@ impl SourceToDefCtx<'_, '_> { &mut self, item: InFile<&ast::Adt>, src: InFile, - ) -> Option<(AttrId, MacroCallId, &[Option])> { + ) -> Option<(AttrId, MacroCallId, &[Option>])> { let map = self.dyn_map(item)?; map[keys::DERIVE_MACRO_CALL] .get(&AstPtr::new(&src.value)) @@ -409,8 +409,11 @@ impl SourceToDefCtx<'_, '_> { pub(super) fn derive_macro_calls<'slf>( &'slf mut self, adt: InFile<&ast::Adt>, - ) -> Option])> + use<'slf>> - { + ) -> Option< + impl Iterator< + Item = (AttrId, MacroCallId, &'slf [Option>]), + > + use<'slf>, + > { self.dyn_map(adt).as_ref().map(|&map| { let dyn_map = &map[keys::DERIVE_MACRO_CALL]; adt.value diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs index 6ba7a42c1946..c6f2d151f582 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -17,7 +17,7 @@ use hir_def::{ path::Path, scope::{ExprScopes, ScopeId}, }, - hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat}, + hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat, PatId}, lang_item::LangItems, nameres::MacroSubNs, resolver::{HasResolver, Resolver, TypeNs, ValueNs, resolver_for_scope}, @@ -44,6 +44,7 @@ use hir_ty::{ }; use intern::sym; use itertools::Itertools; +use rustc_hash::FxHashSet; use rustc_type_ir::{ AliasTyKind, inherent::{AdtDef, IntoKind, Ty as _}, @@ -531,18 +532,12 @@ impl<'db> SourceAnalyzer<'db> { db: &'db dyn HirDatabase, range_pat: &ast::RangePat, ) -> Option { - let path: ModPath = match (range_pat.op_kind()?, range_pat.start(), range_pat.end()) { - (RangeOp::Exclusive, None, Some(_)) => path![core::ops::RangeTo], - (RangeOp::Exclusive, Some(_), None) => path![core::ops::RangeFrom], - (RangeOp::Exclusive, Some(_), Some(_)) => path![core::ops::Range], - (RangeOp::Inclusive, None, Some(_)) => path![core::ops::RangeToInclusive], - (RangeOp::Inclusive, Some(_), Some(_)) => path![core::ops::RangeInclusive], - - (RangeOp::Exclusive, None, None) => return None, - (RangeOp::Inclusive, None, None) => return None, - (RangeOp::Inclusive, Some(_), None) => return None, - }; - self.resolver.resolve_known_struct(db, &path) + self.resolve_range_struct( + db, + range_pat.op_kind()?, + range_pat.start().is_some(), + range_pat.end().is_some(), + ) } pub(crate) fn resolve_range_expr( @@ -550,19 +545,59 @@ impl<'db> SourceAnalyzer<'db> { db: &'db dyn HirDatabase, range_expr: &ast::RangeExpr, ) -> Option { - let path: ModPath = match (range_expr.op_kind()?, range_expr.start(), range_expr.end()) { - (RangeOp::Exclusive, None, None) => path![core::ops::RangeFull], - (RangeOp::Exclusive, None, Some(_)) => path![core::ops::RangeTo], - (RangeOp::Exclusive, Some(_), None) => path![core::ops::RangeFrom], - (RangeOp::Exclusive, Some(_), Some(_)) => path![core::ops::Range], - (RangeOp::Inclusive, None, Some(_)) => path![core::ops::RangeToInclusive], - (RangeOp::Inclusive, Some(_), Some(_)) => path![core::ops::RangeInclusive], + self.resolve_range_struct( + db, + range_expr.op_kind()?, + range_expr.start().is_some(), + range_expr.end().is_some(), + ) + } + fn resolve_range_struct( + &self, + db: &'db dyn HirDatabase, + op_kind: RangeOp, + has_start: bool, + has_end: bool, + ) -> Option { + let has_new_range = + self.resolver.top_level_def_map().is_unstable_feature_enabled(&sym::new_range); + let lang_items = self.lang_items(db); + match (op_kind, has_start, has_end) { + (RangeOp::Exclusive, false, false) => lang_items.RangeFull, + (RangeOp::Exclusive, false, true) => lang_items.RangeTo, + (RangeOp::Exclusive, true, false) => { + if has_new_range { + lang_items.RangeFromCopy + } else { + lang_items.RangeFrom + } + } + (RangeOp::Exclusive, true, true) => { + if has_new_range { + lang_items.RangeCopy + } else { + lang_items.Range + } + } + (RangeOp::Inclusive, false, true) => { + if has_new_range { + lang_items.RangeToInclusiveCopy + } else { + lang_items.RangeToInclusive + } + } + (RangeOp::Inclusive, true, true) => { + if has_new_range { + lang_items.RangeInclusiveCopy + } else { + lang_items.RangeInclusiveStruct + } + } // [E0586] inclusive ranges must be bounded at the end - (RangeOp::Inclusive, None, None) => return None, - (RangeOp::Inclusive, Some(_), None) => return None, - }; - self.resolver.resolve_known_struct(db, &path) + (RangeOp::Inclusive, false, false) => None, + (RangeOp::Inclusive, true, false) => None, + } } pub(crate) fn resolve_await_to_poll( @@ -1241,21 +1276,31 @@ impl<'db> SourceAnalyzer<'db> { let body = self.store()?; let infer = self.infer()?; - let expr_id = self.expr_id(literal.clone().into())?; - let substs = infer.expr_or_pat_ty(expr_id).as_adt()?.1; - - let (variant, missing_fields, _exhaustive) = match expr_id { - ExprOrPatId::ExprId(expr_id) => { - record_literal_missing_fields(db, infer, expr_id, &body[expr_id])? - } - ExprOrPatId::PatId(pat_id) => { - record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])? - } - }; + let expr_id = self.expr_id(literal.clone().into())?.as_expr()?; + let substs = infer.expr_ty(expr_id).as_adt()?.1; + let (variant, missing_fields) = + record_literal_missing_fields(db, infer, expr_id, &body[expr_id])?; let res = self.missing_fields(db, substs, variant, missing_fields); Some(res) } + pub(crate) fn record_literal_matched_fields( + &self, + db: &'db dyn HirDatabase, + literal: &ast::RecordExpr, + ) -> Option)>> { + let body = self.store()?; + let infer = self.infer()?; + + let expr_id = self.expr_id(literal.clone().into())?.as_expr()?; + let substs = infer.expr_ty(expr_id).as_adt()?.1; + let (variant, matched_fields) = + record_literal_matched_fields(db, infer, expr_id, &body[expr_id])?; + + let res = self.missing_fields(db, substs, variant, matched_fields); + Some(res) + } + pub(crate) fn record_pattern_missing_fields( &self, db: &'db dyn HirDatabase, @@ -1267,12 +1312,29 @@ impl<'db> SourceAnalyzer<'db> { let pat_id = self.pat_id(&pattern.clone().into())?.as_pat()?; let substs = infer.pat_ty(pat_id).as_adt()?.1; - let (variant, missing_fields, _exhaustive) = + let (variant, missing_fields) = record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?; let res = self.missing_fields(db, substs, variant, missing_fields); Some(res) } + pub(crate) fn record_pattern_matched_fields( + &self, + db: &'db dyn HirDatabase, + pattern: &ast::RecordPat, + ) -> Option)>> { + let body = self.store()?; + let infer = self.infer()?; + + let pat_id = self.pat_id(&pattern.clone().into())?.as_pat()?; + let substs = infer.pat_ty(pat_id).as_adt()?.1; + + let (variant, matched_fields) = + record_pattern_matched_fields(db, infer, pat_id, &body[pat_id])?; + let res = self.missing_fields(db, substs, variant, matched_fields); + Some(res) + } + fn missing_fields( &self, db: &'db dyn HirDatabase, @@ -1810,3 +1872,67 @@ pub(crate) fn name_hygiene(db: &dyn HirDatabase, name: InFile<&SyntaxNode>) -> H let ctx = span_map.span_at(name.value.text_range().start()).ctx; HygieneId::new(ctx.opaque_and_semiopaque(db)) } + +fn record_literal_matched_fields( + db: &dyn HirDatabase, + infer: &InferenceResult, + id: ExprId, + expr: &Expr, +) -> Option<(VariantId, Vec)> { + let (fields, _spread) = match expr { + Expr::RecordLit { fields, spread, .. } => (fields, spread), + _ => return None, + }; + + let variant_def = infer.variant_resolution_for_expr(id)?; + if let VariantId::UnionId(_) = variant_def { + return None; + } + + let variant_data = variant_def.fields(db); + + let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); + // suggest fields if: + // - not in code + let matched_fields: Vec = variant_data + .fields() + .iter() + .filter_map(|(f, d)| (!specified_fields.contains(&d.name)).then_some(f)) + .collect(); + if matched_fields.is_empty() { + return None; + } + Some((variant_def, matched_fields)) +} + +fn record_pattern_matched_fields( + db: &dyn HirDatabase, + infer: &InferenceResult, + id: PatId, + pat: &Pat, +) -> Option<(VariantId, Vec)> { + let (fields, _ellipsis) = match pat { + Pat::Record { path: _, args, ellipsis } => (args, *ellipsis), + _ => return None, + }; + + let variant_def = infer.variant_resolution_for_pat(id)?; + if let VariantId::UnionId(_) = variant_def { + return None; + } + + let variant_data = variant_def.fields(db); + + let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); + // suggest fields if: + // - not in code + let matched_fields: Vec = variant_data + .fields() + .iter() + .filter_map(|(f, d)| if !specified_fields.contains(&d.name) { Some(f) } else { None }) + .collect(); + if matched_fields.is_empty() { + return None; + } + Some((variant_def, matched_fields)) +} diff --git a/src/tools/rust-analyzer/crates/hir/src/symbols.rs b/src/tools/rust-analyzer/crates/hir/src/symbols.rs index 073142670d2a..c088f3aa0cc0 100644 --- a/src/tools/rust-analyzer/crates/hir/src/symbols.rs +++ b/src/tools/rust-analyzer/crates/hir/src/symbols.rs @@ -5,10 +5,11 @@ use std::marker::PhantomData; use base_db::FxIndexSet; use either::Either; use hir_def::{ - AdtId, AssocItemId, Complete, DefWithBodyId, ExternCrateId, HasModule, ImplId, Lookup, MacroId, - ModuleDefId, ModuleId, TraitId, + AdtId, AssocItemId, AstIdLoc, Complete, DefWithBodyId, ExternCrateId, HasModule, ImplId, + Lookup, MacroId, ModuleDefId, ModuleId, TraitId, db::DefDatabase, item_scope::{ImportId, ImportOrExternCrate, ImportOrGlob}, + nameres::crate_def_map, per_ns::Item, src::{HasChildSource, HasSource}, visibility::{Visibility, VisibilityExplicitness}, @@ -22,7 +23,7 @@ use intern::Symbol; use rustc_hash::FxHashMap; use syntax::{AstNode, AstPtr, SyntaxNode, SyntaxNodePtr, ToSmolStr, ast::HasName}; -use crate::{HasCrate, Module, ModuleDef, Semantics}; +use crate::{Crate, HasCrate, Module, ModuleDef, Semantics}; /// The actual data that is stored in the index. It should be as compact as /// possible. @@ -40,14 +41,14 @@ pub struct FileSymbol<'db> { _marker: PhantomData<&'db ()>, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct DeclarationLocation { /// The file id for both the `ptr` and `name_ptr`. pub hir_file_id: HirFileId, /// This points to the whole syntax node of the declaration. pub ptr: SyntaxNodePtr, /// This points to the [`syntax::ast::Name`] identifier of the declaration. - pub name_ptr: AstPtr>, + pub name_ptr: Option>>, } impl DeclarationLocation { @@ -108,6 +109,51 @@ impl<'a> SymbolCollector<'a> { } } + /// Push a symbol for a crate's root module. + /// This allows crate roots to appear in the symbol index for queries like `::` or `::foo`. + pub fn push_crate_root(&mut self, krate: Crate) { + let Some(display_name) = krate.display_name(self.db) else { return }; + let crate_name = display_name.crate_name(); + let canonical_name = display_name.canonical_name(); + + let def_map = crate_def_map(self.db, krate.into()); + let module_data = &def_map[def_map.crate_root(self.db)]; + + let definition = module_data.origin.definition_source(self.db); + let hir_file_id = definition.file_id; + let syntax_node = definition.value.node(); + let ptr = SyntaxNodePtr::new(&syntax_node); + + let loc = DeclarationLocation { hir_file_id, ptr, name_ptr: None }; + let root_module = krate.root_module(self.db); + + self.symbols.insert(FileSymbol { + name: crate_name.symbol().clone(), + def: ModuleDef::Module(root_module), + loc, + container_name: None, + is_alias: false, + is_assoc: false, + is_import: false, + do_not_complete: Complete::Yes, + _marker: PhantomData, + }); + + if canonical_name != crate_name.symbol() { + self.symbols.insert(FileSymbol { + name: canonical_name.clone(), + def: ModuleDef::Module(root_module), + loc, + container_name: None, + is_alias: false, + is_assoc: false, + is_import: false, + do_not_complete: Complete::Yes, + _marker: PhantomData, + }); + } + } + pub fn finish(self) -> Box<[FileSymbol<'a>]> { self.symbols.into_iter().collect() } @@ -123,6 +169,7 @@ impl<'a> SymbolCollector<'a> { fn collect_from_module(&mut self, module_id: ModuleId) { let collect_pub_only = self.collect_pub_only; + let is_block_module = module_id.is_block_module(self.db); let push_decl = |this: &mut Self, def: ModuleDefId, name, vis| { if collect_pub_only && vis != Visibility::Public { return; @@ -194,6 +241,10 @@ impl<'a> SymbolCollector<'a> { let source = import_child_source_cache .entry(i.use_) .or_insert_with(|| i.use_.child_source(this.db)); + if is_block_module && source.file_id.is_macro() { + // Macros tend to generate a lot of imports, the user really won't care about them + return; + } let Some(use_tree_src) = source.value.get(i.idx) else { return }; let rename = use_tree_src.rename().and_then(|rename| rename.name()); let name_syntax = match rename { @@ -209,7 +260,7 @@ impl<'a> SymbolCollector<'a> { let dec_loc = DeclarationLocation { hir_file_id: source.file_id, ptr: SyntaxNodePtr::new(use_tree_src.syntax()), - name_ptr: AstPtr::new(&name_syntax), + name_ptr: Some(AstPtr::new(&name_syntax)), }; this.symbols.insert(FileSymbol { name: name.symbol().clone(), @@ -230,6 +281,12 @@ impl<'a> SymbolCollector<'a> { return; } let loc = i.lookup(this.db); + if is_block_module && loc.ast_id().file_id.is_macro() { + // Macros (especially derivves) tend to generate renamed extern crate items, + // the user really won't care about them + return; + } + let source = loc.source(this.db); let rename = source.value.rename().and_then(|rename| rename.name()); @@ -244,7 +301,7 @@ impl<'a> SymbolCollector<'a> { let dec_loc = DeclarationLocation { hir_file_id: source.file_id, ptr: SyntaxNodePtr::new(source.value.syntax()), - name_ptr: AstPtr::new(&name_syntax), + name_ptr: Some(AstPtr::new(&name_syntax)), }; this.symbols.insert(FileSymbol { name: name.symbol().clone(), @@ -409,10 +466,10 @@ impl<'a> SymbolCollector<'a> { let source = loc.source(self.db); let Some(name_node) = source.value.name() else { return Complete::Yes }; let def = ModuleDef::from(id.into()); - let dec_loc = DeclarationLocation { + let loc = DeclarationLocation { hir_file_id: source.file_id, ptr: SyntaxNodePtr::new(source.value.syntax()), - name_ptr: AstPtr::new(&name_node).wrap_left(), + name_ptr: Some(AstPtr::new(&name_node).wrap_left()), }; let mut do_not_complete = Complete::Yes; @@ -427,7 +484,7 @@ impl<'a> SymbolCollector<'a> { self.symbols.insert(FileSymbol { name: alias.clone(), def, - loc: dec_loc.clone(), + loc, container_name: self.current_container_name.clone(), is_alias: true, is_assoc, @@ -442,7 +499,7 @@ impl<'a> SymbolCollector<'a> { name: name.symbol().clone(), def, container_name: self.current_container_name.clone(), - loc: dec_loc, + loc, is_alias: false, is_assoc, is_import: false, @@ -459,10 +516,10 @@ impl<'a> SymbolCollector<'a> { let Some(declaration) = module_data.origin.declaration() else { return }; let module = declaration.to_node(self.db); let Some(name_node) = module.name() else { return }; - let dec_loc = DeclarationLocation { + let loc = DeclarationLocation { hir_file_id: declaration.file_id, ptr: SyntaxNodePtr::new(module.syntax()), - name_ptr: AstPtr::new(&name_node).wrap_left(), + name_ptr: Some(AstPtr::new(&name_node).wrap_left()), }; let def = ModuleDef::Module(module_id.into()); @@ -475,7 +532,7 @@ impl<'a> SymbolCollector<'a> { self.symbols.insert(FileSymbol { name: alias.clone(), def, - loc: dec_loc.clone(), + loc, container_name: self.current_container_name.clone(), is_alias: true, is_assoc: false, @@ -490,7 +547,7 @@ impl<'a> SymbolCollector<'a> { name: name.symbol().clone(), def: ModuleDef::Module(module_id.into()), container_name: self.current_container_name.clone(), - loc: dec_loc, + loc, is_alias: false, is_assoc: false, is_import: false, diff --git a/src/tools/rust-analyzer/crates/hir/src/term_search.rs b/src/tools/rust-analyzer/crates/hir/src/term_search.rs index e4089218305c..f2dc1ce798ad 100644 --- a/src/tools/rust-analyzer/crates/hir/src/term_search.rs +++ b/src/tools/rust-analyzer/crates/hir/src/term_search.rs @@ -172,7 +172,7 @@ impl<'db> LookupTable<'db> { /// Insert new type trees for type /// /// Note that the types have to be the same, unification is not enough as unification is not - /// transitive. For example Vec and FxHashSet both unify with Iterator, + /// transitive. For example `Vec` and `FxHashSet` both unify with `Iterator`, /// but they clearly do not unify themselves. fn insert(&mut self, ty: Type<'db>, exprs: impl Iterator>) { match self.data.get_mut(&ty) { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs index d193e8a9d8dc..80d0a6da1243 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs @@ -3,7 +3,7 @@ use std::collections::VecDeque; use ide_db::{ assists::GroupLabel, famous_defs::FamousDefs, - syntax_helpers::node_ext::{for_each_tail_expr, walk_expr}, + syntax_helpers::node_ext::{for_each_tail_expr, is_pattern_cond, walk_expr}, }; use syntax::{ NodeOrToken, SyntaxKind, T, @@ -69,6 +69,10 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti } } + if is_pattern_cond(bin_expr.clone().into()) { + return None; + } + let op = bin_expr.op_kind()?; let (inv_token, prec) = match op { ast::BinaryOp::LogicOp(ast::LogicOp::And) => (SyntaxKind::PIPE2, ExprPrecedence::LOr), @@ -375,6 +379,16 @@ fn f() { !(S <= S || S < S) } ) } + #[test] + fn demorgan_doesnt_handles_pattern() { + check_assist_not_applicable( + apply_demorgan, + r#" +fn f() { if let 1 = 1 &&$0 true { } } +"#, + ); + } + #[test] fn demorgan_on_not() { check_assist( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs index ba577b217df7..2e649f14be26 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs @@ -1,13 +1,15 @@ use ide_db::assists::AssistId; use itertools::Itertools; use syntax::{ - AstNode, T, + AstNode, SyntaxElement, + SyntaxKind::WHITESPACE, + T, algo::previous_non_trivia_token, ast::{ self, HasArgList, HasLoopBody, HasName, RangeItem, edit::AstNodeEdit, make, syntax_factory::SyntaxFactory, }, - syntax_editor::{Element, Position}, + syntax_editor::{Element, Position, SyntaxEditor}, }; use crate::assist_context::{AssistContext, Assists}; @@ -40,8 +42,8 @@ pub(crate) fn convert_range_for_to_while(acc: &mut Assists, ctx: &AssistContext< let iterable = for_.iterable()?; let (start, end, step, inclusive) = extract_range(&iterable)?; let name = pat.name()?; - let body = for_.loop_body()?; - let last = previous_non_trivia_token(body.stmt_list()?.r_curly_token()?)?; + let body = for_.loop_body()?.stmt_list()?; + let label = for_.label(); let description = if end.is_some() { "Replace with while expression" @@ -90,8 +92,10 @@ pub(crate) fn convert_range_for_to_while(acc: &mut Assists, ctx: &AssistContext< ); let op = ast::BinaryOp::Assignment { op: Some(ast::ArithOp::Add) }; - edit.insert_all( - Position::after(last), + process_loop_body( + body, + label, + &mut edit, vec![ make.whitespace(&format!("\n{}", indent + 1)).syntax_element(), make.expr_bin(var_expr, op, step).syntax().syntax_element(), @@ -121,6 +125,86 @@ fn extract_range(iterable: &ast::Expr) -> Option<(ast::Expr, Option, }) } +fn process_loop_body( + body: ast::StmtList, + label: Option, + edit: &mut SyntaxEditor, + incrementer: Vec, +) -> Option<()> { + let last = previous_non_trivia_token(body.r_curly_token()?)?.syntax_element(); + + let new_body = body.indent(1.into()).clone_subtree(); + let mut continues = vec![]; + collect_continue_to( + &mut continues, + &label.and_then(|it| it.lifetime()), + new_body.syntax(), + false, + ); + + if continues.is_empty() { + edit.insert_all(Position::after(last), incrementer); + return Some(()); + } + + let mut children = body + .syntax() + .children_with_tokens() + .filter(|it| !matches!(it.kind(), WHITESPACE | T!['{'] | T!['}'])); + let first = children.next()?; + let block_content = first.clone()..=children.last().unwrap_or(first); + + let continue_label = make::lifetime("'cont"); + let break_expr = make::expr_break(Some(continue_label.clone()), None).clone_for_update(); + let mut new_edit = SyntaxEditor::new(new_body.syntax().clone()); + for continue_expr in &continues { + new_edit.replace(continue_expr.syntax(), break_expr.syntax()); + } + let new_body = new_edit.finish().new_root().clone(); + let elements = itertools::chain( + [ + continue_label.syntax().clone_for_update().syntax_element(), + make::token(T![:]).syntax_element(), + make::tokens::single_space().syntax_element(), + new_body.syntax_element(), + ], + incrementer, + ); + edit.replace_all(block_content, elements.collect()); + + Some(()) +} + +fn collect_continue_to( + acc: &mut Vec, + label: &Option, + node: &syntax::SyntaxNode, + only_label: bool, +) { + let match_label = |it: &Option, label: &Option| match (it, label) + { + (None, _) => !only_label, + (Some(a), Some(b)) if a.text() == b.text() => true, + _ => false, + }; + if let Some(expr) = ast::ContinueExpr::cast(node.clone()) + && match_label(&expr.lifetime(), label) + { + acc.push(expr); + } else if let Some(any_loop) = ast::AnyHasLoopBody::cast(node.clone()) { + if match_label(label, &any_loop.label().and_then(|it| it.lifetime())) { + return; + } + for children in node.children() { + collect_continue_to(acc, label, &children, true); + } + } else { + for children in node.children() { + collect_continue_to(acc, label, &children, only_label); + } + } +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -219,6 +303,67 @@ fn foo() { ); } + #[test] + fn test_convert_range_for_to_while_with_continue() { + check_assist( + convert_range_for_to_while, + " +fn foo() { + $0for mut i in 3..7 { + foo(i); + continue; + loop { break; continue } + bar(i); + } +} + ", + " +fn foo() { + let mut i = 3; + while i < 7 { + 'cont: { + foo(i); + break 'cont; + loop { break; continue } + bar(i); + } + i += 1; + } +} + ", + ); + + check_assist( + convert_range_for_to_while, + " +fn foo() { + 'x: $0for mut i in 3..7 { + foo(i); + continue 'x; + loop { break; continue 'x } + 'x: loop { continue 'x } + bar(i); + } +} + ", + " +fn foo() { + let mut i = 3; + 'x: while i < 7 { + 'cont: { + foo(i); + break 'cont; + loop { break; break 'cont } + 'x: loop { continue 'x } + bar(i); + } + i += 1; + } +} + ", + ); + } + #[test] fn test_convert_range_for_to_while_step_by() { check_assist( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index 08b114072fd9..ea5c1637b760 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -95,7 +95,9 @@ fn if_expr_to_guarded_return( let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?; - if parent_block.tail_expr()? != if_expr.clone().into() { + if parent_block.tail_expr() != Some(if_expr.clone().into()) + && !(else_block.is_some() && ast::ExprStmt::can_cast(if_expr.syntax().parent()?.kind())) + { return None; } @@ -502,6 +504,36 @@ fn main() { ); } + #[test] + fn convert_if_let_has_else_block_in_statement() { + check_assist( + convert_to_guarded_return, + r#" +fn main() { + some_statements(); + if$0 let Ok(x) = Err(92) { + foo(x); + } else { + // needless comment + return; + } + some_statements(); +} +"#, + r#" +fn main() { + some_statements(); + let Ok(x) = Err(92) else { + // needless comment + return; + }; + foo(x); + some_statements(); +} +"#, + ); + } + #[test] fn convert_if_let_result_inside_let() { check_assist( @@ -1136,6 +1168,44 @@ fn main() { ); } + #[test] + fn ignore_else_if() { + check_assist_not_applicable( + convert_to_guarded_return, + r#" +fn main() { + some_statements(); + if cond { + () + } else if$0 let Ok(x) = Err(92) { + foo(x); + } else { + return; + } + some_statements(); +} +"#, + ); + } + + #[test] + fn ignore_if_inside_let() { + check_assist_not_applicable( + convert_to_guarded_return, + r#" +fn main() { + some_statements(); + let _ = if$0 let Ok(x) = Err(92) { + foo(x); + } else { + return; + } + some_statements(); +} +"#, + ); + } + #[test] fn ignore_let_else_branch() { check_assist_not_applicable( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs index b746099e7279..867ac4851864 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs @@ -33,8 +33,8 @@ fn expand_record_rest_pattern( record_pat: ast::RecordPat, rest_pat: ast::RestPat, ) -> Option<()> { - let missing_fields = ctx.sema.record_pattern_missing_fields(&record_pat); - if missing_fields.is_empty() { + let matched_fields = ctx.sema.record_pattern_matched_fields(&record_pat); + if matched_fields.is_empty() { cov_mark::hit!(no_missing_fields); return None; } @@ -53,7 +53,7 @@ fn expand_record_rest_pattern( |builder| { let make = SyntaxFactory::with_mappings(); let mut editor = builder.make_editor(rest_pat.syntax()); - let new_fields = old_field_list.fields().chain(missing_fields.iter().map(|(f, _)| { + let new_fields = old_field_list.fields().chain(matched_fields.iter().map(|(f, _)| { make.record_pat_field_shorthand( make.ident_pat( false, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index 231df9b5b3e1..f2363c6f7ba2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -25,7 +25,7 @@ use syntax::{ SyntaxKind::{self, COMMENT}, SyntaxNode, SyntaxToken, T, TextRange, TextSize, TokenAtOffset, WalkEvent, ast::{ - self, AstNode, AstToken, HasGenericParams, HasName, edit::IndentLevel, + self, AstNode, AstToken, HasAttrs, HasGenericParams, HasName, edit::IndentLevel, edit_in_place::Indent, }, match_ast, ted, @@ -120,7 +120,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let params = body.extracted_function_params(ctx, &container_info, locals_used); - let name = make_function_name(&semantics_scope); + let name = make_function_name(&semantics_scope, &body); let fun = Function { name, @@ -241,7 +241,10 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op ) } -fn make_function_name(semantics_scope: &hir::SemanticsScope<'_>) -> ast::NameRef { +fn make_function_name( + semantics_scope: &hir::SemanticsScope<'_>, + body: &FunctionBody, +) -> ast::NameRef { let mut names_in_scope = vec![]; semantics_scope.process_all_names(&mut |name, _| { names_in_scope.push( @@ -252,7 +255,10 @@ fn make_function_name(semantics_scope: &hir::SemanticsScope<'_>) -> ast::NameRef let default_name = "fun_name"; - let mut name = default_name.to_owned(); + let mut name = body + .suggest_name() + .filter(|name| name.len() > 2) + .unwrap_or_else(|| default_name.to_owned()); let mut counter = 0; while names_in_scope.contains(&name) { counter += 1; @@ -375,6 +381,7 @@ struct ContainerInfo<'db> { ret_type: Option>, generic_param_lists: Vec, where_clauses: Vec, + attrs: Vec, edition: Edition, } @@ -778,6 +785,16 @@ impl FunctionBody { fn contains_node(&self, node: &SyntaxNode) -> bool { self.contains_range(node.text_range()) } + + fn suggest_name(&self) -> Option { + if let Some(ast::Pat::IdentPat(pat)) = self.parent().and_then(ast::LetStmt::cast)?.pat() + && let Some(name) = pat.name().and_then(|it| it.ident_token()) + { + Some(name.text().to_owned()) + } else { + None + } + } } impl FunctionBody { @@ -911,6 +928,7 @@ impl FunctionBody { let parents = generic_parents(&parent); let generic_param_lists = parents.iter().filter_map(|it| it.generic_param_list()).collect(); let where_clauses = parents.iter().filter_map(|it| it.where_clause()).collect(); + let attrs = parents.iter().flat_map(|it| it.attrs()).filter(is_inherit_attr).collect(); Some(( ContainerInfo { @@ -919,6 +937,7 @@ impl FunctionBody { ret_type: ty, generic_param_lists, where_clauses, + attrs, edition, }, contains_tail_expr, @@ -1103,6 +1122,14 @@ impl GenericParent { GenericParent::Trait(trait_) => trait_.where_clause(), } } + + fn attrs(&self) -> impl Iterator { + match self { + GenericParent::Fn(fn_) => fn_.attrs(), + GenericParent::Impl(impl_) => impl_.attrs(), + GenericParent::Trait(trait_) => trait_.attrs(), + } + } } /// Search `parent`'s ancestors for items with potentially applicable generic parameters @@ -1578,7 +1605,7 @@ fn format_function( let (generic_params, where_clause) = make_generic_params_and_where_clause(ctx, fun); make::fn_( - None, + fun.mods.attrs.clone(), None, fun_name, generic_params, @@ -1958,6 +1985,11 @@ fn format_type(ty: &hir::Type<'_>, ctx: &AssistContext<'_>, module: hir::Module) ty.display_source_code(ctx.db(), module.into(), true).ok().unwrap_or_else(|| "_".to_owned()) } +fn is_inherit_attr(attr: &ast::Attr) -> bool { + let Some(name) = attr.simple_name() else { return false }; + matches!(name.as_str(), "track_caller" | "cfg") +} + fn make_ty(ty: &hir::Type<'_>, ctx: &AssistContext<'_>, module: hir::Module) -> ast::Type { let ty_str = format_type(ty, ctx, module); make::ty(&ty_str) @@ -5414,12 +5446,12 @@ impl Struct { impl Trait for Struct { fn bar(&self) -> i32 { - let three_squared = fun_name(); + let three_squared = three_squared(); self.0 + three_squared } } -fn $0fun_name() -> i32 { +fn $0three_squared() -> i32 { 3 * 3 } "#, @@ -6372,6 +6404,55 @@ fn foo() { fn $0fun_name(mut a: i32, mut b: i32) { (a, b) = (b, a); } +"#, + ); + } + + #[test] + fn with_cfg_attr() { + check_assist( + extract_function, + r#" +//- /main.rs crate:main cfg:test +#[cfg(test)] +fn foo() { + foo($01 + 1$0); +} +"#, + r#" +#[cfg(test)] +fn foo() { + foo(fun_name()); +} + +#[cfg(test)] +fn $0fun_name() -> i32 { + 1 + 1 +} +"#, + ); + } + + #[test] + fn with_track_caller() { + check_assist( + extract_function, + r#" +#[track_caller] +fn foo() { + foo($01 + 1$0); +} +"#, + r#" +#[track_caller] +fn foo() { + foo(fun_name()); +} + +#[track_caller] +fn $0fun_name() -> i32 { + 1 + 1 +} "#, ); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs index ae1ae24d1ec1..53f6f4883f4d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs @@ -1,8 +1,8 @@ use ide_db::{famous_defs::FamousDefs, traits::resolve_target_trait}; use syntax::{ - AstNode, T, - ast::{self, edit_in_place::Indent, make}, - ted, + AstNode, SyntaxElement, SyntaxNode, T, + ast::{self, edit::AstNodeEdit, edit_in_place::Indent, syntax_factory::SyntaxFactory}, + syntax_editor::{Element, Position, SyntaxEditor}, }; use crate::{AssistContext, AssistId, Assists}; @@ -45,12 +45,13 @@ use crate::{AssistContext, AssistId, Assists}; // } // ``` pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let impl_def = ctx.find_node_at_offset::()?.clone_for_update(); - let indent = impl_def.indent_level(); + let impl_def = ctx.find_node_at_offset::()?; + let indent = Indent::indent_level(&impl_def); let ast::Type::PathType(path) = impl_def.trait_()? else { return None; }; + let trait_name = path.path()?.segment()?.name_ref()?; let scope = ctx.sema.scope(impl_def.trait_()?.syntax())?; @@ -59,75 +60,133 @@ pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_> let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; let trait_new = get_trait_mut(&trait_, famous)?; - // Index -> IndexMut - ted::replace(trait_name.syntax(), make::name_ref(trait_new).clone_for_update().syntax()); - - // index -> index_mut - let (trait_method_name, new_trait_method_name) = impl_def - .syntax() - .descendants() - .filter_map(ast::Name::cast) - .find_map(process_method_name)?; - ted::replace( - trait_method_name.syntax(), - make::name(new_trait_method_name).clone_for_update().syntax(), - ); - - if let Some(type_alias) = impl_def.syntax().descendants().find_map(ast::TypeAlias::cast) { - ted::remove(type_alias.syntax()); - } - - // &self -> &mut self - let mut_self_param = make::mut_self_param(); - let self_param: ast::SelfParam = - impl_def.syntax().descendants().find_map(ast::SelfParam::cast)?; - ted::replace(self_param.syntax(), mut_self_param.clone_for_update().syntax()); - - // &Self::Output -> &mut Self::Output - let ret_type = impl_def.syntax().descendants().find_map(ast::RetType::cast)?; - let new_ret_type = process_ret_type(&ret_type)?; - ted::replace(ret_type.syntax(), make::ret_type(new_ret_type).clone_for_update().syntax()); - - let fn_ = impl_def.assoc_item_list()?.assoc_items().find_map(|it| match it { - ast::AssocItem::Fn(f) => Some(f), - _ => None, - })?; - let _ = process_ref_mut(&fn_); - - let assoc_list = make::assoc_item_list(None).clone_for_update(); - ted::replace(impl_def.assoc_item_list()?.syntax(), assoc_list.syntax()); - impl_def.get_or_create_assoc_item_list().add_item(syntax::ast::AssocItem::Fn(fn_)); - let target = impl_def.syntax().text_range(); + acc.add( AssistId::generate("generate_mut_trait_impl"), format!("Generate `{trait_new}` impl from this `{trait_name}` trait"), target, |edit| { - edit.insert( - target.start(), - if ctx.config.snippet_cap.is_some() { - format!("$0{impl_def}\n\n{indent}") - } else { - format!("{impl_def}\n\n{indent}") - }, + let impl_clone = impl_def.reset_indent().clone_subtree(); + let mut editor = SyntaxEditor::new(impl_clone.syntax().clone()); + let factory = SyntaxFactory::without_mappings(); + + apply_generate_mut_impl(&mut editor, &factory, &impl_clone, trait_new); + + let new_root = editor.finish(); + let new_root = new_root.new_root(); + + let new_impl = ast::Impl::cast(new_root.clone()).unwrap(); + + Indent::indent(&new_impl, indent); + + let mut editor = edit.make_editor(impl_def.syntax()); + editor.insert_all( + Position::before(impl_def.syntax()), + vec![ + new_impl.syntax().syntax_element(), + factory.whitespace(&format!("\n\n{indent}")).syntax_element(), + ], ); + + if let Some(cap) = ctx.config.snippet_cap { + let tabstop_before = edit.make_tabstop_before(cap); + editor.add_annotation(new_impl.syntax(), tabstop_before); + } + + edit.add_file_edits(ctx.vfs_file_id(), editor); }, ) } -fn process_ref_mut(fn_: &ast::Fn) -> Option<()> { - let expr = fn_.body()?.tail_expr()?; - match &expr { - ast::Expr::RefExpr(ref_expr) if ref_expr.mut_token().is_none() => { - ted::insert_all_raw( - ted::Position::after(ref_expr.amp_token()?), - vec![make::token(T![mut]).into(), make::tokens::whitespace(" ").into()], - ); - } - _ => {} +fn delete_with_trivia(editor: &mut SyntaxEditor, node: &SyntaxNode) { + let mut end: SyntaxElement = node.clone().into(); + + if let Some(next) = node.next_sibling_or_token() + && let SyntaxElement::Token(tok) = &next + && tok.kind().is_trivia() + { + end = next.clone(); } - None + + editor.delete_all(node.clone().into()..=end); +} + +fn apply_generate_mut_impl( + editor: &mut SyntaxEditor, + factory: &SyntaxFactory, + impl_def: &ast::Impl, + trait_new: &str, +) -> Option<()> { + let path = + impl_def.trait_().and_then(|t| t.syntax().descendants().find_map(ast::Path::cast))?; + let seg = path.segment()?; + let name_ref = seg.name_ref()?; + + let new_name_ref = factory.name_ref(trait_new); + editor.replace(name_ref.syntax(), new_name_ref.syntax()); + + if let Some((name, new_name)) = + impl_def.syntax().descendants().filter_map(ast::Name::cast).find_map(process_method_name) + { + let new_name_node = factory.name(new_name); + editor.replace(name.syntax(), new_name_node.syntax()); + } + + if let Some(type_alias) = impl_def.syntax().descendants().find_map(ast::TypeAlias::cast) { + delete_with_trivia(editor, type_alias.syntax()); + } + + if let Some(self_param) = impl_def.syntax().descendants().find_map(ast::SelfParam::cast) { + let mut_self = factory.mut_self_param(); + editor.replace(self_param.syntax(), mut_self.syntax()); + } + + if let Some(ret_type) = impl_def.syntax().descendants().find_map(ast::RetType::cast) + && let Some(new_ty) = process_ret_type(factory, &ret_type) + { + let new_ret = factory.ret_type(new_ty); + editor.replace(ret_type.syntax(), new_ret.syntax()) + } + + if let Some(fn_) = impl_def.assoc_item_list().and_then(|l| { + l.assoc_items().find_map(|it| match it { + ast::AssocItem::Fn(f) => Some(f), + _ => None, + }) + }) { + process_ref_mut(editor, factory, &fn_); + } + + Some(()) +} + +fn process_ref_mut(editor: &mut SyntaxEditor, factory: &SyntaxFactory, fn_: &ast::Fn) { + let Some(expr) = fn_.body().and_then(|b| b.tail_expr()) else { return }; + + let ast::Expr::RefExpr(ref_expr) = expr else { return }; + + if ref_expr.mut_token().is_some() { + return; + } + + let Some(amp) = ref_expr.amp_token() else { return }; + + let mut_kw = factory.token(T![mut]); + let space = factory.whitespace(" "); + + editor.insert(Position::after(amp.clone()), space.syntax_element()); + editor.insert(Position::after(amp), mut_kw.syntax_element()); +} + +fn process_ret_type(factory: &SyntaxFactory, ref_ty: &ast::RetType) -> Option { + let ty = ref_ty.ty()?; + let ast::Type::RefType(ref_type) = ty else { + return None; + }; + + let inner = ref_type.ty()?; + Some(factory.ty_ref(inner, true)) } fn get_trait_mut(apply_trait: &hir::Trait, famous: FamousDefs<'_, '_>) -> Option<&'static str> { @@ -158,14 +217,6 @@ fn process_method_name(name: ast::Name) -> Option<(ast::Name, &'static str)> { Some((name, new_name)) } -fn process_ret_type(ref_ty: &ast::RetType) -> Option { - let ty = ref_ty.ty()?; - let ast::Type::RefType(ref_type) = ty else { - return None; - }; - Some(make::ty_ref(ref_type.ty()?, true)) -} - #[cfg(test)] mod tests { use crate::{ diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs index ae8d130df23c..c7a48f3261a9 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs @@ -290,19 +290,23 @@ impl ConstAndTypeMap { /// ^ alias generic params /// let a: A<100>; /// ^ instance generic args -/// ``` /// /// generic['a] = '_ due to omission /// generic[N] = 100 due to the instance arg /// generic[T] = u64 due to the default param +/// ``` /// /// 2. Copy the concrete type and substitute in each found mapping: /// +/// ```ignore /// &'_ [u64; 100] +/// ``` /// /// 3. Remove wildcard lifetimes entirely: /// +/// ```ignore /// &[u64; 100] +/// ``` fn create_replacement( lifetime_map: &LifetimeMap, const_and_type_map: &ConstAndTypeMap, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs index 1c0c6e43d53b..84f02bdfdba6 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs @@ -3,7 +3,7 @@ use syntax::{ SyntaxKind::WHITESPACE, ast::{ AstNode, BlockExpr, ElseBranch, Expr, IfExpr, MatchArm, Pat, edit::AstNodeEdit, make, - syntax_factory::SyntaxFactory, + prec::ExprPrecedence, syntax_factory::SyntaxFactory, }, syntax_editor::Element, }; @@ -49,7 +49,7 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext<'_>) let guard_condition = guard.condition()?.reset_indent(); let arm_expr = match_arm.expr()?; - let then_branch = make::block_expr(None, Some(arm_expr.reset_indent().indent(1.into()))); + let then_branch = crate::utils::wrap_block(&arm_expr); let if_expr = make::expr_if(guard_condition, then_branch, None).indent(arm_expr.indent_level()); let target = guard.syntax().text_range(); @@ -109,6 +109,7 @@ pub(crate) fn move_arm_cond_to_match_guard( let match_arm: MatchArm = ctx.find_node_at_offset::()?; let match_pat = match_arm.pat()?; let arm_body = match_arm.expr()?; + let arm_guard = match_arm.guard().and_then(|it| it.condition()); let mut replace_node = None; let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone()).or_else(|| { @@ -149,6 +150,25 @@ pub(crate) fn move_arm_cond_to_match_guard( 0 }; let indent_level = match_arm.indent_level(); + let make_guard = |cond: Option| { + let condition = match (arm_guard.clone(), cond) { + (None, None) => return None, + (None, Some(it)) | (Some(it), None) => it, + (Some(lhs), Some(rhs)) => { + let op_expr = |expr: Expr| { + if expr.precedence().needs_parentheses_in(ExprPrecedence::LAnd) { + make.expr_paren(expr).into() + } else { + expr + } + }; + let op = syntax::ast::BinaryOp::LogicOp(syntax::ast::LogicOp::And); + let expr_bin = make.expr_bin(op_expr(lhs), op, op_expr(rhs)); + expr_bin.into() + } + }; + Some(make.match_guard(condition)) + }; for (cond, block) in conds_blocks { let only_expr = block.statements().next().is_none(); @@ -156,8 +176,7 @@ pub(crate) fn move_arm_cond_to_match_guard( Some(then_expr) if only_expr => then_expr, _ => block.dedent(dedent.into()).into(), }; - let guard = make.match_guard(cond); - let new_arm = make.match_arm(match_pat.clone(), Some(guard), expr); + let new_arm = make.match_arm(match_pat.clone(), make_guard(Some(cond)), expr); replace_arms.push(new_arm); } if let Some(block) = tail { @@ -170,7 +189,7 @@ pub(crate) fn move_arm_cond_to_match_guard( } _ => block.dedent(dedent.into()).into(), }; - let new_arm = make.match_arm(match_pat, None, expr); + let new_arm = make.match_arm(match_pat, make_guard(None), expr); replace_arms.push(new_arm); } else { // There's no else branch. Add a pattern without guard, unless the following match @@ -185,7 +204,7 @@ pub(crate) fn move_arm_cond_to_match_guard( } _ => { let block_expr = make.expr_empty_block().into(); - replace_arms.push(make.match_arm(match_pat, None, block_expr)); + replace_arms.push(make.match_arm(match_pat, make_guard(None), block_expr)); } } } @@ -325,6 +344,35 @@ fn main() { ); } + #[test] + fn move_guard_to_block_arm_body_works() { + check_assist( + move_guard_to_arm_body, + r#" +fn main() { + match 92 { + x $0if x > 10 => { + let _ = true; + false + }, + _ => true + } +} +"#, + r#" +fn main() { + match 92 { + x => if x > 10 { + let _ = true; + false + }, + _ => true + } +} +"#, + ); + } + #[test] fn move_let_guard_to_arm_body_works() { check_assist( @@ -376,9 +424,7 @@ fn main() { && true && true { { - { - false - } + false } }, _ => true @@ -1081,6 +1127,42 @@ fn main() { x => {} } } +"#, + ) + } + + #[test] + fn move_arm_cond_to_match_guard_elseif_exist_guard() { + check_assist( + move_arm_cond_to_match_guard, + r#" +fn main() { + let cond = true; + match 92 { + 3 => true, + x if cond => if x $0> 10 { + false + } else if x > 5 { + true + } else if x > 4 || x < -2 { + false + } else { + true + }, + } +} +"#, + r#" +fn main() { + let cond = true; + match 92 { + 3 => true, + x if cond && x > 10 => false, + x if cond && x > 5 => true, + x if cond && (x > 4 || x < -2) => false, + x if cond => true, + } +} "#, ) } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_parentheses.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_parentheses.rs index aa4d2bcadb01..f07da489e23a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_parentheses.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_parentheses.rs @@ -321,6 +321,12 @@ mod tests { ); } + #[test] + fn remove_parens_conflict_cast_before_l_angle() { + check_assist_not_applicable(remove_parentheses, r#"fn f() { _ = $0(1 as u32) << 10; }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() { _ = $0(1 as u32) < 10; }"#); + } + #[test] fn remove_parens_double_paren_stmt() { check_assist( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs index bf1546986ed2..60b0797f028a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs @@ -86,7 +86,14 @@ pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>) } MacroDelims::LCur | MacroDelims::RCur => { editor.replace(ltoken, make.token(T!['['])); - editor.replace(rtoken, make.token(T![']'])); + if semicolon.is_some() || !needs_semicolon(token_tree) { + editor.replace(rtoken, make.token(T![']'])); + } else { + editor.replace_with_many( + rtoken, + vec![make.token(T![']']).into(), make.token(T![;]).into()], + ); + } } } editor.add_mappings(make.finish_with_mappings()); @@ -103,6 +110,30 @@ fn macro_semicolon(makro: &ast::MacroCall) -> Option { }) } +fn needs_semicolon(tt: ast::TokenTree) -> bool { + (|| { + let call = ast::MacroCall::cast(tt.syntax().parent()?)?; + let container = call.syntax().parent()?; + let kind = container.kind(); + + if call.semicolon_token().is_some() { + return Some(false); + } + + Some( + ast::ItemList::can_cast(kind) + || ast::SourceFile::can_cast(kind) + || ast::AssocItemList::can_cast(kind) + || ast::ExternItemList::can_cast(kind) + || ast::MacroItems::can_cast(kind) + || ast::MacroExpr::can_cast(kind) + && ast::ExprStmt::cast(container.parent()?) + .is_some_and(|it| it.semicolon_token().is_none()), + ) + })() + .unwrap_or(false) +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -161,7 +192,7 @@ macro_rules! sth { () => {}; } -sth!$0{ }; +sth!$0{ } "#, r#" macro_rules! sth { @@ -170,7 +201,117 @@ macro_rules! sth { sth![ ]; "#, - ) + ); + + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! sth { + () => {}; +} + +fn foo() -> i32 { + sth!$0{ } + 2 +} + "#, + r#" +macro_rules! sth { + () => {}; +} + +fn foo() -> i32 { + sth![ ]; + 2 +} + "#, + ); + + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! sth { + () => {2}; +} + +fn foo() { + sth!$0{ }; +} + "#, + r#" +macro_rules! sth { + () => {2}; +} + +fn foo() { + sth![ ]; +} + "#, + ); + + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! sth { + () => {2}; +} + +fn foo() -> i32 { + sth!$0{ } +} + "#, + r#" +macro_rules! sth { + () => {2}; +} + +fn foo() -> i32 { + sth![ ] +} + "#, + ); + + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! sth { + () => {}; +} +impl () { + sth!$0{} +} + "#, + r#" +macro_rules! sth { + () => {}; +} +impl () { + sth![]; +} + "#, + ); + + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! sth { + () => {2}; +} + +fn foo() -> i32 { + bar(sth!$0{ }) +} + "#, + r#" +macro_rules! sth { + () => {2}; +} + +fn foo() -> i32 { + bar(sth![ ]) +} + "#, + ); } #[test] @@ -204,7 +345,7 @@ mod abc { () => {}; } - sth!$0{ }; + sth!$0{ } } "#, r#" 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 a83f6835ca61..e4f5e3523bd2 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 @@ -1,10 +1,12 @@ use syntax::{ - AstNode, SyntaxKind, T, TextRange, + AstNode, SyntaxElement, SyntaxKind, SyntaxNode, T, ast::{ self, edit::{AstNodeEdit, IndentLevel}, make, }, + match_ast, + syntax_editor::{Element, Position, SyntaxEditor}, }; use crate::{AssistContext, AssistId, Assists}; @@ -27,123 +29,108 @@ use crate::{AssistContext, AssistId, Assists}; // } // ``` pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let assist_id = AssistId::refactor_rewrite("unwrap_block"); - let assist_label = "Unwrap block"; let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?; - let mut block = ast::BlockExpr::cast(l_curly_token.parent_ancestors().nth(1)?)?; + let block = l_curly_token.parent_ancestors().nth(1).and_then(ast::BlockExpr::cast)?; let target = block.syntax().text_range(); - let mut parent = block.syntax().parent()?; - if ast::MatchArm::can_cast(parent.kind()) { - parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))? - } + let mut container = block.syntax().clone(); + let mut replacement = block.clone(); + let mut prefer_container = None; - let kind = parent.kind(); - if matches!(kind, SyntaxKind::STMT_LIST | SyntaxKind::EXPR_STMT) { - acc.add(assist_id, assist_label, target, |builder| { - builder.replace(block.syntax().text_range(), update_expr_string(block.to_string())); - }) - } else if matches!(kind, SyntaxKind::LET_STMT) { - let parent = ast::LetStmt::cast(parent)?; - let pattern = ast::Pat::cast(parent.syntax().first_child()?)?; - let ty = parent.ty(); - let list = block.stmt_list()?; - let replaced = match list.syntax().last_child() { - Some(last) => { - let stmts: Vec = list.statements().collect(); - let initializer = ast::Expr::cast(last)?; - let let_stmt = make::let_stmt(pattern, ty, Some(initializer)); - if !stmts.is_empty() { - let block = make::block_expr(stmts, None); - format!("{}\n {}", update_expr_string(block.to_string()), let_stmt) - } else { - let_stmt.to_string() - } - } - None => { - let empty_tuple = make::ext::expr_unit(); - make::let_stmt(pattern, ty, Some(empty_tuple)).to_string() - } - }; - acc.add(assist_id, assist_label, target, |builder| { - builder.replace(parent.syntax().text_range(), replaced); - }) - } else { - let parent = ast::Expr::cast(parent)?; - match parent.clone() { - ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (), - ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)), - ast::Expr::IfExpr(if_expr) => { - let then_branch = if_expr.then_branch()?; - if then_branch == block { - if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) { - // For `else if` blocks - let ancestor_then_branch = ancestor.then_branch()?; - - return acc.add(assist_id, assist_label, target, |edit| { - let range_to_del_else_if = TextRange::new( - ancestor_then_branch.syntax().text_range().end(), - l_curly_token.text_range().start(), - ); - let range_to_del_rest = TextRange::new( - then_branch.syntax().text_range().end(), - if_expr.syntax().text_range().end(), - ); - - edit.delete(range_to_del_rest); - edit.delete(range_to_del_else_if); - edit.replace( - target, - update_expr_string_without_newline(then_branch.to_string()), - ); - }); - } - } else { - return acc.add(assist_id, assist_label, target, |edit| { - let range_to_del = TextRange::new( - then_branch.syntax().text_range().end(), - l_curly_token.text_range().start(), - ); - - edit.delete(range_to_del); - edit.replace(target, update_expr_string_without_newline(block.to_string())); + let from_indent = block.indent_level(); + let into_indent = loop { + let parent = container.parent()?; + container = match_ast! { + match parent { + ast::ForExpr(it) => it.syntax().clone(), + ast::LoopExpr(it) => it.syntax().clone(), + ast::WhileExpr(it) => it.syntax().clone(), + ast::MatchArm(it) => it.parent_match().syntax().clone(), + ast::LetStmt(it) => { + replacement = wrap_let(&it, replacement); + prefer_container = Some(it.syntax().clone()); + it.syntax().clone() + }, + ast::IfExpr(it) => { + prefer_container.get_or_insert_with(|| { + if let Some(else_branch) = it.else_branch() + && *else_branch.syntax() == container + { + else_branch.syntax().clone() + } else { + it.syntax().clone() + } }); - } + it.syntax().clone() + }, + ast::ExprStmt(it) => it.syntax().clone(), + ast::StmtList(it) => break it.indent_level(), + _ => return None, } - _ => return None, }; + }; + let replacement = replacement.stmt_list()?; - acc.add(assist_id, assist_label, target, |builder| { - builder.replace(parent.syntax().text_range(), update_expr_string(block.to_string())); - }) - } + acc.add(AssistId::refactor_rewrite("unwrap_block"), "Unwrap block", target, |builder| { + let mut edit = builder.make_editor(block.syntax()); + let replacement = replacement.dedent(from_indent).indent(into_indent); + let container = prefer_container.unwrap_or(container); + + edit.replace_with_many(&container, extract_statements(replacement)); + delete_else_before(container, &mut edit); + + builder.add_file_edits(ctx.vfs_file_id(), edit); + }) } -fn update_expr_string(expr_string: String) -> String { - update_expr_string_with_pat(expr_string, &[' ', '\n']) +fn delete_else_before(container: SyntaxNode, edit: &mut SyntaxEditor) { + let Some(else_token) = container + .siblings_with_tokens(syntax::Direction::Prev) + .skip(1) + .map_while(|it| it.into_token()) + .find(|it| it.kind() == T![else]) + else { + return; + }; + itertools::chain(else_token.prev_token(), else_token.next_token()) + .filter(|it| it.kind() == SyntaxKind::WHITESPACE) + .for_each(|it| edit.delete(it)); + let indent = IndentLevel::from_node(&container); + let newline = make::tokens::whitespace(&format!("\n{indent}")); + edit.replace(else_token, newline); } -fn update_expr_string_without_newline(expr_string: String) -> String { - update_expr_string_with_pat(expr_string, &[' ']) +fn wrap_let(assign: &ast::LetStmt, replacement: ast::BlockExpr) -> ast::BlockExpr { + let try_wrap_assign = || { + let initializer = assign.initializer()?.syntax().syntax_element(); + let replacement = replacement.clone_subtree(); + let assign = assign.clone_for_update(); + let tail_expr = replacement.tail_expr()?; + let before = + assign.syntax().children_with_tokens().take_while(|it| *it != initializer).collect(); + let after = assign + .syntax() + .children_with_tokens() + .skip_while(|it| *it != initializer) + .skip(1) + .collect(); + + let mut edit = SyntaxEditor::new(replacement.syntax().clone()); + edit.insert_all(Position::before(tail_expr.syntax()), before); + edit.insert_all(Position::after(tail_expr.syntax()), after); + ast::BlockExpr::cast(edit.finish().new_root().clone()) + }; + try_wrap_assign().unwrap_or(replacement) } -fn update_expr_string_with_pat(expr_str: String, whitespace_pat: &[char]) -> String { - // Remove leading whitespace, index to remove the leading '{', - // then continue to remove leading whitespace. - // We cannot assume the `{` is the first character because there are block modifiers - // (`unsafe`, `async` etc.). - let after_open_brace_index = expr_str.find('{').map_or(0, |it| it + 1); - let expr_str = expr_str[after_open_brace_index..].trim_start_matches(whitespace_pat); - - // Remove trailing whitespace, index [..expr_str.len() - 1] to remove the trailing '}', - // then continue to remove trailing whitespace. - let expr_str = expr_str.trim_end_matches(whitespace_pat); - let expr_str = expr_str[..expr_str.len() - 1].trim_end_matches(whitespace_pat); - - expr_str - .lines() - .map(|line| line.replacen(" ", "", 1)) // Delete indentation - .collect::>() - .join("\n") +fn extract_statements(stmt_list: ast::StmtList) -> Vec { + let mut elements = stmt_list + .syntax() + .children_with_tokens() + .filter(|it| !matches!(it.kind(), T!['{'] | T!['}'])) + .skip_while(|it| it.kind() == SyntaxKind::WHITESPACE) + .collect::>(); + while elements.pop_if(|it| it.kind() == SyntaxKind::WHITESPACE).is_some() {} + elements } #[cfg(test)] @@ -593,6 +580,30 @@ fn main() { ); } + #[test] + fn unwrap_match_arm_in_let() { + check_assist( + unwrap_block, + r#" +fn main() { + let value = match rel_path { + Ok(rel_path) => {$0 + let rel_path = RelativePathBuf::from_path(rel_path).ok()?; + Some((*id, rel_path)) + } + Err(_) => None, + }; +} +"#, + r#" +fn main() { + let rel_path = RelativePathBuf::from_path(rel_path).ok()?; + let value = Some((*id, rel_path)); +} +"#, + ); + } + #[test] fn simple_if_in_while_bad_cursor_position() { check_assist_not_applicable( @@ -750,19 +761,6 @@ fn main() -> i32 { check_assist( unwrap_block, r#" -fn main() { - let x = {$0}; -} -"#, - r#" -fn main() { - let x = (); -} -"#, - ); - check_assist( - unwrap_block, - r#" fn main() { let x = {$0 bar @@ -784,8 +782,7 @@ fn main() -> i32 { "#, r#" fn main() -> i32 { - 1; - let _ = 2; + 1; let _ = 2; } "#, ); @@ -795,11 +792,29 @@ fn main() -> i32 { fn main() -> i32 { let mut a = {$01; 2}; } +"#, + r#" +fn main() -> i32 { + 1; let mut a = 2; +} +"#, + ); + check_assist( + unwrap_block, + r#" +fn main() -> i32 { + let mut a = {$0 + 1; + 2; + 3 + }; +} "#, r#" fn main() -> i32 { 1; - let mut a = 2; + 2; + let mut a = 3; } "#, ); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index 9a96374c00af..4b8c19305793 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -86,6 +86,17 @@ pub fn extract_trivial_expression(block_expr: &ast::BlockExpr) -> Option ast::BlockExpr { + if let ast::Expr::BlockExpr(block) = expr + && let Some(first) = block.syntax().first_token() + && first.kind() == T!['{'] + { + block.reset_indent() + } else { + make::block_expr(None, Some(expr.reset_indent().indent(1.into()))) + } +} + /// This is a method with a heuristics to support test methods annotated with custom test annotations, such as /// `#[test_case(...)]`, `#[tokio::test]` and similar. /// Also a regular `#[test]` annotation is supported. diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs index 77734c5d6f98..8c532e0f4d04 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs @@ -340,7 +340,7 @@ pub(crate) fn complete_expr_path( let missing_fields = ctx.sema.record_literal_missing_fields(record_expr); if !missing_fields.is_empty() { - add_default_update(acc, ctx, ty); + add_default_update(acc, ctx, ty.as_ref()); } } }; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_crate.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_crate.rs index 71a3e4eb4ed6..91202e8b32fc 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_crate.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_crate.rs @@ -17,7 +17,7 @@ pub(crate) fn complete_extern_crate(acc: &mut Completions, ctx: &CompletionConte } let mut item = CompletionItem::new( - CompletionItemKind::SymbolKind(SymbolKind::Module), + CompletionItemKind::SymbolKind(SymbolKind::CrateRoot), ctx.source_range(), name.display_no_db(ctx.edition).to_smolstr(), ctx.edition, @@ -48,7 +48,7 @@ mod other_mod {} let completion_list = completion_list_no_kw(case); - assert_eq!("md other_crate_a\n".to_owned(), completion_list); + assert_eq!("cr other_crate_a\n".to_owned(), completion_list); } #[test] @@ -68,6 +68,6 @@ mod other_mod {} let completion_list = completion_list_no_kw(case); - assert_eq!("md other_crate_a\n".to_owned(), completion_list); + assert_eq!("cr other_crate_a\n".to_owned(), completion_list); } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs index c5bfdcb8b734..12c564af5cba 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs @@ -36,7 +36,7 @@ pub(crate) fn complete_record_pattern_fields( true => return, } } - _ => ctx.sema.record_pattern_missing_fields(record_pat), + _ => ctx.sema.record_pattern_matched_fields(record_pat), }; complete_fields(acc, ctx, missing_fields); } @@ -69,14 +69,14 @@ pub(crate) fn complete_record_expr_fields( } } _ => { - let missing_fields = ctx.sema.record_literal_missing_fields(record_expr); + let suggest_fields = ctx.sema.record_literal_matched_fields(record_expr); let update_exists = record_expr .record_expr_field_list() .is_some_and(|list| list.dotdot_token().is_some()); - if !missing_fields.is_empty() && !update_exists { + if !suggest_fields.is_empty() && !update_exists { cov_mark::hit!(functional_update_field); - add_default_update(acc, ctx, ty); + add_default_update(acc, ctx, ty.as_ref()); } if dot_prefix { cov_mark::hit!(functional_update_one_dot); @@ -90,7 +90,7 @@ pub(crate) fn complete_record_expr_fields( item.add_to(acc, ctx.db); return; } - missing_fields + suggest_fields } }; complete_fields(acc, ctx, missing_fields); @@ -99,11 +99,11 @@ pub(crate) fn complete_record_expr_fields( pub(crate) fn add_default_update( acc: &mut Completions, ctx: &CompletionContext<'_>, - ty: Option>, + ty: Option<&hir::TypeInfo<'_>>, ) { let default_trait = ctx.famous_defs().core_default_Default(); let impls_default_trait = default_trait - .zip(ty.as_ref()) + .zip(ty) .is_some_and(|(default_trait, ty)| ty.original.impls_trait(ctx.db, default_trait, &[])); if impls_default_trait { // FIXME: This should make use of scope_def like completions so we get all the other goodies diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs index d116f665adbd..cab8bced88df 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -628,7 +628,7 @@ impl CompletionContext<'_> { } /// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items and - /// passes all doc-aliases along, to funnel it into [`Completions::add_path_resolution`]. + /// passes all doc-aliases along, to funnel it into `Completions::add_path_resolution`. pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef, Vec)) { let _p = tracing::info_span!("CompletionContext::process_all_names").entered(); self.scope.process_all_names(&mut |name, def| { 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 65bae5b66e17..1c8bc656ca25 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 @@ -1250,6 +1250,11 @@ fn classify_name_ref<'db>( let original = ast::Const::cast(name.syntax().parent()?)?; TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body())) }, + ast::Static(it) => { + let name = find_opt_node_in_file(original_file, it.name())?; + let original = ast::Static::cast(name.syntax().parent()?)?; + TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body())) + }, ast::RetType(it) => { it.thin_arrow_token()?; let parent = match ast::Fn::cast(parent.parent()?) { @@ -1305,14 +1310,14 @@ fn classify_name_ref<'db>( let make_path_kind_expr = |expr: ast::Expr| { let it = expr.syntax(); + let prev_token = iter::successors(it.first_token(), |it| it.prev_token()) + .skip(1) + .find(|it| !it.kind().is_trivia()); let in_block_expr = is_in_block(it); let (in_loop_body, innermost_breakable) = is_in_breakable(it).unzip(); let after_if_expr = is_after_if_expr(it.clone()); - let ref_expr_parent = - path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast); - let after_amp = non_trivia_sibling(it.clone().into(), Direction::Prev) - .map(|it| it.kind() == SyntaxKind::AMP) - .unwrap_or(false); + let after_amp = prev_token.as_ref().is_some_and(|it| it.kind() == SyntaxKind::AMP); + let ref_expr_parent = prev_token.and_then(|it| it.parent()).and_then(ast::RefExpr::cast); let (innermost_ret_ty, self_param) = { let find_ret_ty = |it: SyntaxNode| { if let Some(item) = ast::Item::cast(it.clone()) { @@ -2030,9 +2035,10 @@ fn is_after_if_expr(node: SyntaxNode) -> bool { Some(stmt) => stmt.syntax().clone(), None => node, }; - let prev_sibling = - non_trivia_sibling(node.into(), Direction::Prev).and_then(NodeOrToken::into_node); - iter::successors(prev_sibling, |it| it.last_child_or_token()?.into_node()) + let Some(prev_token) = previous_non_trivia_token(node) else { return false }; + prev_token + .parent_ancestors() + .take_while(|it| it.text_range().end() == prev_token.text_range().end()) .find_map(ast::IfExpr::cast) .is_some() } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs index 71d32da74710..1a9139d8553b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs @@ -381,6 +381,7 @@ impl CompletionItemKind { SymbolKind::BuiltinAttr => "ba", SymbolKind::Const => "ct", SymbolKind::ConstParam => "cp", + SymbolKind::CrateRoot => "cr", SymbolKind::Derive => "de", SymbolKind::DeriveHelper => "dh", SymbolKind::Enum => "en", diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs index 78f003dd210b..df39591a3346 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs @@ -706,7 +706,30 @@ fn completes_after_ref_expr() { kw while kw while let "#]], - ) + ); + check( + r#"fn main() { let _ = &$0x.foo() }"#, + expect![[r#" + fn main() fn() + bt u32 u32 + kw const + kw crate:: + kw false + kw for + kw if + kw if let + kw loop + kw match + kw mut + kw raw + kw return + kw self:: + kw true + kw unsafe + kw while + kw while let + "#]], + ); } #[test] @@ -2159,6 +2182,32 @@ fn foo() { match () { () => if foo {} $0, _ => (), } } kw ref "#]], ); + check( + r#" +fn foo() -> (i32, i32) { if foo {} el$0 (2, 3) } +"#, + expect![[r#" + fn foo fn() -> (i32, i32) + bt u32 u32 + kw const + kw crate:: + kw else + kw else if + kw false + kw for + kw if + kw if let + kw loop + kw match + kw return + kw self:: + kw true + kw unsafe + kw while + kw while let + ex foo() + "#]], + ); // FIXME: support else completion after ast::RecordExprField } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs index 797df3f163da..d7db896679df 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs @@ -1976,3 +1976,51 @@ fn main() { "#]], ); } + +#[test] +fn trait_method_import_across_multiple_crates() { + let fixture = r#" + //- /lib.rs crate:test-trait + pub trait TestTrait { + fn test_function(&self) -> u32; + } + + //- /lib.rs crate:test-implementation deps:test-trait + pub struct TestStruct(pub usize); + + impl test_trait::TestTrait for TestStruct { + fn test_function(&self) -> u32 { + 1 + } + } + + //- /main.rs crate:main deps:test-implementation,test-trait + use test_implementation::TestStruct; + + fn main() { + let test = TestStruct(42); + test.test_f$0 + } + "#; + + check( + fixture, + expect![[r#" + me test_function() (use test_trait::TestTrait) fn(&self) -> u32 + "#]], + ); + + check_edit( + "test_function", + fixture, + r#" +use test_implementation::TestStruct; +use test_trait::TestTrait; + +fn main() { + let test = TestStruct(42); + test.test_function()$0 +} +"#, + ); +} diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs index d9be6556fa5b..045b2d03b051 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs @@ -286,6 +286,24 @@ fn main() { ); } +#[test] +fn functional_update_fields_completion() { + // Complete fields before functional update `..` + check( + r#" +struct Point { x: i32 = 0, y: i32 = 0 } + +fn main() { + let p = Point { $0, .. }; +} +"#, + expect![[r#" + fd x i32 + fd y i32 + "#]], + ); +} + #[test] fn empty_union_literal() { check( @@ -302,7 +320,27 @@ fn foo() { fd bar f32 fd foo u32 "#]], - ) + ); +} + +#[test] +fn record_pattern_field_with_rest_pat() { + // When .. is present, complete all unspecified fields (even those with default values) + check( + r#" +struct UserInfo { id: i32, age: f32, email: u64 } + +fn foo(u1: UserInfo) { + let UserInfo { id, $0, .. } = u1; +} +"#, + expect![[r#" + fd age f32 + fd email u64 + kw mut + kw ref + "#]], + ); } #[test] diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs index 3bbba18c2b9f..7c6b7370aafd 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs @@ -183,6 +183,35 @@ const FOO: $0 = Foo(2); ); } +#[test] +fn inferred_type_static() { + check_with_base_items( + r#" +struct Foo(T); +static FOO: $0 = Foo(2); +"#, + expect![[r#" + en Enum Enum + ma makro!(…) macro_rules! makro + md module + st Foo<…> Foo<{unknown}> + st Record Record + st Tuple Tuple + st Unit Unit + tt Trait + un Union Union + bt u32 u32 + it Foo + kw crate:: + kw dyn + kw fn + kw for + kw impl + kw self:: + "#]], + ); +} + #[test] fn inferred_type_closure_param() { check_with_base_items( diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs index 90e3bb61f44d..35579eb2590d 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs @@ -600,7 +600,19 @@ fn trait_applicable_items<'db>( } deref_chain .into_iter() - .filter_map(|ty| Some((ty.krate(db).into(), ty.fingerprint_for_trait_impl()?))) + .flat_map(|ty| { + let fingerprint = ty.fingerprint_for_trait_impl()?; + let mut crates = vec![]; + + if let Some(adt) = ty.as_adt() { + // Push crate where ADT was defined + crates.push((adt.krate(db).into(), fingerprint)); + } + // Always include environment crate + crates.push((ty.krate(db).into(), fingerprint)); + Some(crates) + }) + .flatten() .unique() .collect::>() }; diff --git a/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs b/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs index 0d305530d925..af0c69c6856d 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs @@ -110,7 +110,7 @@ pub fn items_with_name_in_module( local_query } }; - local_query.search(&[SymbolIndex::module_symbols(db, module)], |local_candidate| { + local_query.search(db, &[SymbolIndex::module_symbols(db, module)], |local_candidate| { cb(match local_candidate.def { hir::ModuleDef::Macro(macro_def) => ItemInNs::Macros(macro_def), def => ItemInNs::from(def), @@ -140,7 +140,7 @@ fn find_items( // Query the local crate using the symbol index. let mut local_results = Vec::new(); - local_query.search(&symbol_index::crate_symbols(db, krate), |local_candidate| { + local_query.search(db, &symbol_index::crate_symbols(db, krate), |local_candidate| { let def = match local_candidate.def { hir::ModuleDef::Macro(macro_def) => ItemInNs::Macros(macro_def), def => ItemInNs::from(def), diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs index 413b58bf7980..023b32b36195 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs @@ -65,7 +65,7 @@ use base_db::{ }; use hir::{ FilePositionWrapper, FileRangeWrapper, - db::{DefDatabase, ExpandDatabase}, + db::{DefDatabase, ExpandDatabase, HirDatabase}, }; use triomphe::Arc; @@ -269,6 +269,7 @@ pub enum SymbolKind { BuiltinAttr, Const, ConstParam, + CrateRoot, Derive, DeriveHelper, Enum, @@ -307,14 +308,15 @@ impl From for SymbolKind { } } -impl From for SymbolKind { - fn from(it: hir::ModuleDef) -> Self { +impl SymbolKind { + pub fn from_module_def(db: &dyn HirDatabase, it: hir::ModuleDef) -> Self { match it { hir::ModuleDef::Const(..) => SymbolKind::Const, hir::ModuleDef::Variant(..) => SymbolKind::Variant, hir::ModuleDef::Function(..) => SymbolKind::Function, hir::ModuleDef::Macro(mac) if mac.is_proc_macro() => SymbolKind::ProcMacro, hir::ModuleDef::Macro(..) => SymbolKind::Macro, + hir::ModuleDef::Module(m) if m.is_crate_root(db) => SymbolKind::CrateRoot, hir::ModuleDef::Module(..) => SymbolKind::Module, hir::ModuleDef::Static(..) => SymbolKind::Static, hir::ModuleDef::Adt(hir::Adt::Struct(..)) => SymbolKind::Struct, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs index eb0529d6b5e7..183f6b649537 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs @@ -27,7 +27,7 @@ use std::{ ops::ControlFlow, }; -use base_db::{LibraryRoots, LocalRoots, RootQueryDb, SourceRootId}; +use base_db::{CrateOrigin, LangCrateOrigin, LibraryRoots, LocalRoots, RootQueryDb, SourceRootId}; use fst::{Automaton, Streamer, raw::IndexedValue}; use hir::{ Crate, Module, @@ -35,29 +35,84 @@ use hir::{ import_map::{AssocSearchMode, SearchMode}, symbols::{FileSymbol, SymbolCollector}, }; +use itertools::Itertools; use rayon::prelude::*; use salsa::Update; use crate::RootDatabase; +/// A query for searching symbols in the workspace or dependencies. +/// +/// This struct configures how symbol search is performed, including the search text, +/// matching strategy, and filtering options. It is used by [`world_symbols`] to find +/// symbols across the codebase. +/// +/// # Example +/// ```ignore +/// let mut query = Query::new("MyStruct".to_string()); +/// query.only_types(); // Only search for type definitions +/// query.libs(); // Include library dependencies +/// query.exact(); // Use exact matching instead of fuzzy +/// ``` #[derive(Debug, Clone)] pub struct Query { + /// The item name to search for (last segment of the path, or full query if no path). + /// When empty with a non-empty `path_filter`, returns all items in that module. query: String, + /// Lowercase version of [`Self::query`], pre-computed for efficiency. + /// Used to build FST automata for case-insensitive index lookups. lowercased: String, + /// Path segments to filter by (all segments except the last). + /// Empty if no `::` in the original query. + path_filter: Vec, + /// If true, the first path segment must be a crate name (query started with `::`). + anchor_to_crate: bool, + /// The search strategy to use when matching symbols. + /// - [`SearchMode::Exact`]: Symbol name must exactly match the query. + /// - [`SearchMode::Fuzzy`]: Symbol name must contain all query characters in order (subsequence match). + /// - [`SearchMode::Prefix`]: Symbol name must start with the query string. + /// + /// Defaults to [`SearchMode::Fuzzy`]. mode: SearchMode, + /// Controls filtering of trait-associated items (methods, constants, types). + /// - [`AssocSearchMode::Include`]: Include both associated and non-associated items. + /// - [`AssocSearchMode::Exclude`]: Exclude trait-associated items from results. + /// - [`AssocSearchMode::AssocItemsOnly`]: Only return trait-associated items. + /// + /// Defaults to [`AssocSearchMode::Include`]. assoc_mode: AssocSearchMode, + /// Whether the final symbol name comparison should be case-sensitive. + /// When `false`, matching is case-insensitive (e.g., "foo" matches "Foo"). + /// + /// Defaults to `false`. case_sensitive: bool, + /// When `true`, only return type definitions: structs, enums, unions, + /// type aliases, built-in types, and traits. Functions, constants, statics, + /// and modules are excluded. + /// + /// Defaults to `false`. only_types: bool, + /// When `true`, search library dependency roots instead of local workspace crates. + /// This enables finding symbols in external dependencies including the standard library. + /// + /// Defaults to `false` (search local workspace only). libs: bool, + /// When `true`, exclude re-exported/imported symbols from results, + /// showing only the original definitions. + /// + /// Defaults to `false`. exclude_imports: bool, } impl Query { pub fn new(query: String) -> Query { - let lowercased = query.to_lowercase(); + let (path_filter, item_query, anchor_to_crate) = Self::parse_path_query(&query); + let lowercased = item_query.to_lowercase(); Query { - query, + query: item_query, lowercased, + path_filter, + anchor_to_crate, only_types: false, libs: false, mode: SearchMode::Fuzzy, @@ -67,6 +122,35 @@ impl Query { } } + /// Parse a query string that may contain path segments. + /// + /// Returns (path_filter, item_query, anchor_to_crate) where: + /// - `path_filter`: Path segments to match (all but the last segment) + /// - `item_query`: The item name to search for (last segment) + /// - `anchor_to_crate`: Whether the first segment must be a crate name + fn parse_path_query(query: &str) -> (Vec, String, bool) { + // Check for leading :: (absolute path / crate search) + let (query, anchor_to_crate) = match query.strip_prefix("::") { + Some(q) => (q, true), + None => (query, false), + }; + + let Some((prefix, query)) = query.rsplit_once("::") else { + return (vec![], query.to_owned(), anchor_to_crate); + }; + + let prefix: Vec<_> = + prefix.split("::").filter(|s| !s.is_empty()).map(ToOwned::to_owned).collect(); + + (prefix, query.to_owned(), anchor_to_crate) + } + + /// Returns true if this query is searching for crates + /// (i.e., the query was "::" alone or "::foo" for fuzzy crate search) + fn is_crate_search(&self) -> bool { + self.anchor_to_crate && self.path_filter.is_empty() + } + pub fn only_types(&mut self) { self.only_types = true; } @@ -123,19 +207,41 @@ pub fn crate_symbols(db: &dyn HirDatabase, krate: Crate) -> Box<[&SymbolIndex<'_ // That is, `#` switches from "types" to all symbols, `*` switches from the current // workspace to dependencies. // -// Note that filtering does not currently work in VSCode due to the editor never -// sending the special symbols to the language server. Instead, you can configure -// the filtering via the `rust-analyzer.workspace.symbol.search.scope` and -// `rust-analyzer.workspace.symbol.search.kind` settings. Symbols prefixed -// with `__` are hidden from the search results unless configured otherwise. +// This also supports general Rust path syntax with the usual rules. +// +// Note that paths do not currently work in VSCode due to the editor never +// sending the special symbols to the language server. Some other editors might not support the # or +// * search either, instead, you can configure the filtering via the +// `rust-analyzer.workspace.symbol.search.scope` and `rust-analyzer.workspace.symbol.search.kind` +// settings. Symbols prefixed with `__` are hidden from the search results unless configured +// otherwise. // // | Editor | Shortcut | // |---------|-----------| // | VS Code | Ctrl+T -pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec> { +pub fn world_symbols(db: &RootDatabase, mut query: Query) -> Vec> { let _p = tracing::info_span!("world_symbols", query = ?query.query).entered(); - let indices: Vec<_> = if query.libs { + // Search for crates by name (handles "::" and "::foo" queries) + let indices: Vec<_> = if query.is_crate_search() { + query.only_types = false; + vec![SymbolIndex::extern_prelude_symbols(db)] + // If we have a path filter, resolve it to target modules + } else if !query.path_filter.is_empty() { + query.only_types = false; + let target_modules = resolve_path_to_modules( + db, + &query.path_filter, + query.anchor_to_crate, + query.case_sensitive, + ); + + if target_modules.is_empty() { + return vec![]; + } + + target_modules.iter().map(|&module| SymbolIndex::module_symbols(db, module)).collect() + } else if query.libs { LibraryRoots::get(db) .roots(db) .par_iter() @@ -154,17 +260,103 @@ pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec> { crates .par_iter() .for_each_with(db.clone(), |snap, &krate| _ = crate_symbols(snap, krate.into())); - crates.into_iter().flat_map(|krate| Vec::from(crate_symbols(db, krate.into()))).collect() + crates + .into_iter() + .flat_map(|krate| Vec::from(crate_symbols(db, krate.into()))) + .chain(std::iter::once(SymbolIndex::extern_prelude_symbols(db))) + .collect() }; let mut res = vec![]; - query.search::<()>(&indices, |f| { + + // Normal search: use FST to match item name + query.search::<()>(db, &indices, |f| { res.push(f.clone()); ControlFlow::Continue(()) }); + res } +/// Resolve a path filter to the target module(s) it points to. +/// Returns the modules whose symbol indices should be searched. +/// +/// The path_filter contains segments like ["std", "vec"] for a query like "std::vec::Vec". +/// We resolve this by: +/// 1. Finding crates matching the first segment +/// 2. Walking down the module tree following subsequent segments +fn resolve_path_to_modules( + db: &dyn HirDatabase, + path_filter: &[String], + anchor_to_crate: bool, + case_sensitive: bool, +) -> Vec { + let [first_segment, rest_segments @ ..] = path_filter else { + return vec![]; + }; + + // Helper for name comparison + let names_match = |actual: &str, expected: &str| -> bool { + if case_sensitive { actual == expected } else { actual.eq_ignore_ascii_case(expected) } + }; + + // Find crates matching the first segment + let matching_crates: Vec = Crate::all(db) + .into_iter() + .filter(|krate| { + krate + .display_name(db) + .is_some_and(|name| names_match(name.crate_name().as_str(), first_segment)) + }) + .collect(); + + // If anchor_to_crate is true, first segment MUST be a crate name + // If anchor_to_crate is false, first segment could be a crate OR a module in local crates + let mut candidate_modules: Vec<(Module, bool)> = vec![]; + + // Add crate root modules for matching crates + for krate in matching_crates { + candidate_modules.push((krate.root_module(db), krate.origin(db).is_local())); + } + + // If not anchored to crate, also search for modules matching first segment in local crates + if !anchor_to_crate { + for &root in LocalRoots::get(db).roots(db).iter() { + for &krate in db.source_root_crates(root).iter() { + let root_module = Crate::from(krate).root_module(db); + for child in root_module.children(db) { + if let Some(name) = child.name(db) + && names_match(name.as_str(), first_segment) + { + candidate_modules.push((child, true)); + } + } + } + } + } + + // Walk down the module tree for remaining path segments + for segment in rest_segments { + candidate_modules = candidate_modules + .into_iter() + .flat_map(|(module, local)| { + module + .modules_in_scope(db, !local) + .into_iter() + .filter(|(name, _)| names_match(name.as_str(), segment)) + .map(move |(_, module)| (module, local)) + }) + .unique() + .collect(); + + if candidate_modules.is_empty() { + break; + } + } + + candidate_modules.into_iter().map(|(module, _)| module).collect() +} + #[derive(Default)] pub struct SymbolIndex<'db> { symbols: Box<[FileSymbol<'db>]>, @@ -236,6 +428,39 @@ impl<'db> SymbolIndex<'db> { module_symbols(db, InternedModuleId::new(db, hir::ModuleId::from(module))) } + + /// The symbol index for all extern prelude crates. + pub fn extern_prelude_symbols(db: &dyn HirDatabase) -> &SymbolIndex<'_> { + #[salsa::tracked(returns(ref))] + fn extern_prelude_symbols<'db>(db: &'db dyn HirDatabase) -> SymbolIndex<'db> { + let _p = tracing::info_span!("extern_prelude_symbols").entered(); + + // We call this without attaching because this runs in parallel, so we need to attach here. + hir::attach_db(db, || { + let mut collector = SymbolCollector::new(db, false); + + for krate in Crate::all(db) { + if krate + .display_name(db) + .is_none_or(|name| name.canonical_name().as_str() == "build-script-build") + { + continue; + } + if let CrateOrigin::Lang(LangCrateOrigin::Dependency | LangCrateOrigin::Other) = + krate.origin(db) + { + // don't show dependencies of the sysroot + continue; + } + collector.push_crate_root(krate); + } + + SymbolIndex::new(collector.finish()) + }) + } + + extern_prelude_symbols(db) + } } impl fmt::Debug for SymbolIndex<'_> { @@ -336,12 +561,15 @@ impl<'db> SymbolIndex<'db> { } impl Query { + /// Search symbols in the given indices. pub(crate) fn search<'db, T>( - self, + &self, + db: &'db RootDatabase, indices: &[&'db SymbolIndex<'db>], cb: impl FnMut(&'db FileSymbol<'db>) -> ControlFlow, ) -> Option { let _p = tracing::info_span!("symbol_index::Query::search").entered(); + let mut op = fst::map::OpBuilder::new(); match self.mode { SearchMode::Exact => { @@ -350,7 +578,7 @@ impl Query { for index in indices.iter() { op = op.add(index.map.search(&automaton)); } - self.search_maps(indices, op.union(), cb) + self.search_maps(db, indices, op.union(), cb) } SearchMode::Fuzzy => { let automaton = fst::automaton::Subsequence::new(&self.lowercased); @@ -358,7 +586,7 @@ impl Query { for index in indices.iter() { op = op.add(index.map.search(&automaton)); } - self.search_maps(indices, op.union(), cb) + self.search_maps(db, indices, op.union(), cb) } SearchMode::Prefix => { let automaton = fst::automaton::Str::new(&self.lowercased).starts_with(); @@ -366,13 +594,14 @@ impl Query { for index in indices.iter() { op = op.add(index.map.search(&automaton)); } - self.search_maps(indices, op.union(), cb) + self.search_maps(db, indices, op.union(), cb) } } } fn search_maps<'db, T>( &self, + db: &'db RootDatabase, indices: &[&'db SymbolIndex<'db>], mut stream: fst::map::Union<'_>, mut cb: impl FnMut(&'db FileSymbol<'db>) -> ControlFlow, @@ -380,18 +609,21 @@ impl Query { let ignore_underscore_prefixed = !self.query.starts_with("__"); while let Some((_, indexed_values)) = stream.next() { for &IndexedValue { index, value } in indexed_values { - let symbol_index = &indices[index]; + let symbol_index = indices[index]; let (start, end) = SymbolIndex::map_value_to_range(value); for symbol in &symbol_index.symbols[start..end] { let non_type_for_type_only_query = self.only_types - && !matches!( + && !(matches!( symbol.def, hir::ModuleDef::Adt(..) | hir::ModuleDef::TypeAlias(..) | hir::ModuleDef::BuiltinType(..) | hir::ModuleDef::Trait(..) - ); + ) || matches!( + symbol.def, + hir::ModuleDef::Module(module) if module.is_crate_root(db) + )); if non_type_for_type_only_query || !self.matches_assoc_mode(symbol.is_assoc) { continue; } @@ -576,4 +808,416 @@ pub struct Foo; let symbols = world_symbols(&db, query); expect_file!["./test_data/test_symbols_exclude_imports.txt"].assert_debug_eq(&symbols); } + + #[test] + fn test_parse_path_query() { + // Plain query - no path + let (path, item, anchor) = Query::parse_path_query("Item"); + assert_eq!(path, Vec::::new()); + assert_eq!(item, "Item"); + assert!(!anchor); + + // Path with item + let (path, item, anchor) = Query::parse_path_query("foo::Item"); + assert_eq!(path, vec!["foo"]); + assert_eq!(item, "Item"); + assert!(!anchor); + + // Multi-segment path + let (path, item, anchor) = Query::parse_path_query("foo::bar::Item"); + assert_eq!(path, vec!["foo", "bar"]); + assert_eq!(item, "Item"); + assert!(!anchor); + + // Leading :: (anchor to crate) + let (path, item, anchor) = Query::parse_path_query("::std::vec::Vec"); + assert_eq!(path, vec!["std", "vec"]); + assert_eq!(item, "Vec"); + assert!(anchor); + + // Just "::" - return all crates + let (path, item, anchor) = Query::parse_path_query("::"); + assert_eq!(path, Vec::::new()); + assert_eq!(item, ""); + assert!(anchor); + + // "::foo" - fuzzy search crate names + let (path, item, anchor) = Query::parse_path_query("::foo"); + assert_eq!(path, Vec::::new()); + assert_eq!(item, "foo"); + assert!(anchor); + + // Trailing :: + let (path, item, anchor) = Query::parse_path_query("foo::"); + assert_eq!(path, vec!["foo"]); + assert_eq!(item, ""); + assert!(!anchor); + + // Full path with trailing :: + let (path, item, anchor) = Query::parse_path_query("foo::bar::"); + assert_eq!(path, vec!["foo", "bar"]); + assert_eq!(item, ""); + assert!(!anchor); + + // Absolute path with trailing :: + let (path, item, anchor) = Query::parse_path_query("::std::vec::"); + assert_eq!(path, vec!["std", "vec"]); + assert_eq!(item, ""); + assert!(anchor); + + // Empty segments should be filtered + let (path, item, anchor) = Query::parse_path_query("foo::::bar"); + assert_eq!(path, vec!["foo"]); + assert_eq!(item, "bar"); + assert!(!anchor); + } + + #[test] + fn test_path_search() { + let (mut db, _) = RootDatabase::with_many_files( + r#" +//- /lib.rs crate:main +mod inner; +pub struct RootStruct; + +//- /inner.rs +pub struct InnerStruct; +pub mod nested { + pub struct NestedStruct; +} +"#, + ); + + let mut local_roots = FxHashSet::default(); + local_roots.insert(WORKSPACE); + LocalRoots::get(&db).set_roots(&mut db).to(local_roots); + + // Search for item in specific module + let query = Query::new("inner::InnerStruct".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + assert!(names.contains(&"InnerStruct"), "Expected InnerStruct in {:?}", names); + + // Search for item in nested module + let query = Query::new("inner::nested::NestedStruct".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + assert!(names.contains(&"NestedStruct"), "Expected NestedStruct in {:?}", names); + + // Search with crate prefix + let query = Query::new("main::inner::InnerStruct".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + assert!(names.contains(&"InnerStruct"), "Expected InnerStruct in {:?}", names); + + // Wrong path should return empty + let query = Query::new("wrong::InnerStruct".to_owned()); + let symbols = world_symbols(&db, query); + assert!(symbols.is_empty(), "Expected empty results for wrong path"); + } + + #[test] + fn test_path_search_module() { + let (mut db, _) = RootDatabase::with_many_files( + r#" +//- /lib.rs crate:main +mod mymod; + +//- /mymod.rs +pub struct MyStruct; +pub fn my_func() {} +pub const MY_CONST: u32 = 1; +"#, + ); + + let mut local_roots = FxHashSet::default(); + local_roots.insert(WORKSPACE); + LocalRoots::get(&db).set_roots(&mut db).to(local_roots); + + // Browse all items in module + let query = Query::new("main::mymod::".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + + assert!(names.contains(&"MyStruct"), "Expected MyStruct in {:?}", names); + assert!(names.contains(&"my_func"), "Expected my_func in {:?}", names); + assert!(names.contains(&"MY_CONST"), "Expected MY_CONST in {:?}", names); + } + + #[test] + fn test_fuzzy_item_with_path() { + let (mut db, _) = RootDatabase::with_many_files( + r#" +//- /lib.rs crate:main +mod mymod; + +//- /mymod.rs +pub struct MyLongStructName; +"#, + ); + + let mut local_roots = FxHashSet::default(); + local_roots.insert(WORKSPACE); + LocalRoots::get(&db).set_roots(&mut db).to(local_roots); + + // Fuzzy match on item name with exact path + let query = Query::new("main::mymod::MyLong".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + assert!( + names.contains(&"MyLongStructName"), + "Expected fuzzy match for MyLongStructName in {:?}", + names + ); + } + + #[test] + fn test_case_insensitive_path() { + let (mut db, _) = RootDatabase::with_many_files( + r#" +//- /lib.rs crate:main +mod MyMod; + +//- /MyMod.rs +pub struct MyStruct; +"#, + ); + + let mut local_roots = FxHashSet::default(); + local_roots.insert(WORKSPACE); + LocalRoots::get(&db).set_roots(&mut db).to(local_roots); + + // Case insensitive path matching (default) + let query = Query::new("main::mymod::MyStruct".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + assert!(names.contains(&"MyStruct"), "Expected case-insensitive match in {:?}", names); + } + + #[test] + fn test_absolute_path_search() { + let (mut db, _) = RootDatabase::with_many_files( + r#" +//- /lib.rs crate:mycrate +mod inner; +pub struct CrateRoot; + +//- /inner.rs +pub struct InnerItem; +"#, + ); + + let mut local_roots = FxHashSet::default(); + local_roots.insert(WORKSPACE); + LocalRoots::get(&db).set_roots(&mut db).to(local_roots); + + // Absolute path with leading :: + let query = Query::new("::mycrate::inner::InnerItem".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + assert!( + names.contains(&"InnerItem"), + "Expected InnerItem with absolute path in {:?}", + names + ); + + // Absolute path should NOT match if crate name is wrong + let query = Query::new("::wrongcrate::inner::InnerItem".to_owned()); + let symbols = world_symbols(&db, query); + assert!(symbols.is_empty(), "Expected empty results for wrong crate name"); + } + + #[test] + fn test_wrong_path_returns_empty() { + let (mut db, _) = RootDatabase::with_many_files( + r#" +//- /lib.rs crate:main +mod existing; + +//- /existing.rs +pub struct MyStruct; +"#, + ); + + let mut local_roots = FxHashSet::default(); + local_roots.insert(WORKSPACE); + LocalRoots::get(&db).set_roots(&mut db).to(local_roots); + + // Non-existent module path + let query = Query::new("nonexistent::MyStruct".to_owned()); + let symbols = world_symbols(&db, query); + assert!(symbols.is_empty(), "Expected empty results for non-existent path"); + + // Correct item, wrong module + let query = Query::new("wrongmod::MyStruct".to_owned()); + let symbols = world_symbols(&db, query); + assert!(symbols.is_empty(), "Expected empty results for wrong module"); + } + + #[test] + fn test_root_module_items() { + let (mut db, _) = RootDatabase::with_many_files( + r#" +//- /lib.rs crate:mylib +pub struct RootItem; +pub fn root_fn() {} +"#, + ); + + let mut local_roots = FxHashSet::default(); + local_roots.insert(WORKSPACE); + LocalRoots::get(&db).set_roots(&mut db).to(local_roots); + + // Items at crate root - path is just the crate name + let query = Query::new("mylib::RootItem".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + assert!(names.contains(&"RootItem"), "Expected RootItem at crate root in {:?}", names); + + let query = Query::new("mylib::".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + assert!(names.contains(&"RootItem"), "Expected RootItem {:?}", names); + assert!(names.contains(&"root_fn"), "Expected root_fn {:?}", names); + } + + #[test] + fn test_crate_search_all() { + // Test that sole "::" returns all crates + let (mut db, _) = RootDatabase::with_many_files( + r#" +//- /lib.rs crate:alpha +pub struct AlphaStruct; + +//- /beta.rs crate:beta +pub struct BetaStruct; + +//- /gamma.rs crate:gamma +pub struct GammaStruct; +"#, + ); + + let mut local_roots = FxHashSet::default(); + local_roots.insert(WORKSPACE); + LocalRoots::get(&db).set_roots(&mut db).to(local_roots); + + // Sole "::" should return all crates (as module symbols) + let query = Query::new("::".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + + assert!(names.contains(&"alpha"), "Expected alpha crate in {:?}", names); + assert!(names.contains(&"beta"), "Expected beta crate in {:?}", names); + assert!(names.contains(&"gamma"), "Expected gamma crate in {:?}", names); + assert_eq!(symbols.len(), 3, "Expected exactly 3 crates, got {:?}", names); + } + + #[test] + fn test_crate_search_fuzzy() { + // Test that "::foo" fuzzy-matches crate names + let (mut db, _) = RootDatabase::with_many_files( + r#" +//- /lib.rs crate:my_awesome_lib +pub struct AwesomeStruct; + +//- /other.rs crate:another_lib +pub struct OtherStruct; + +//- /foo.rs crate:foobar +pub struct FooStruct; +"#, + ); + + let mut local_roots = FxHashSet::default(); + local_roots.insert(WORKSPACE); + LocalRoots::get(&db).set_roots(&mut db).to(local_roots); + + // "::foo" should fuzzy-match crate names containing "foo" + let query = Query::new("::foo".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + + assert!(names.contains(&"foobar"), "Expected foobar crate in {:?}", names); + assert_eq!(symbols.len(), 1, "Expected only foobar crate, got {:?}", names); + + // "::awesome" should match my_awesome_lib + let query = Query::new("::awesome".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + + assert!(names.contains(&"my_awesome_lib"), "Expected my_awesome_lib crate in {:?}", names); + assert_eq!(symbols.len(), 1, "Expected only my_awesome_lib crate, got {:?}", names); + + // "::lib" should match multiple crates + let query = Query::new("::lib".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + + assert!(names.contains(&"my_awesome_lib"), "Expected my_awesome_lib in {:?}", names); + assert!(names.contains(&"another_lib"), "Expected another_lib in {:?}", names); + assert_eq!(symbols.len(), 2, "Expected 2 crates matching 'lib', got {:?}", names); + + // "::nonexistent" should return empty + let query = Query::new("::nonexistent".to_owned()); + let symbols = world_symbols(&db, query); + assert!(symbols.is_empty(), "Expected empty results for non-matching crate pattern"); + } + + #[test] + fn test_path_search_with_use_reexport() { + // Test that module resolution works for `use` items (re-exports), not just `mod` items + let (mut db, _) = RootDatabase::with_many_files( + r#" +//- /lib.rs crate:main +mod inner; +pub use inner::nested; + +//- /inner.rs +pub mod nested { + pub struct NestedStruct; + pub fn nested_fn() {} +} +"#, + ); + + let mut local_roots = FxHashSet::default(); + local_roots.insert(WORKSPACE); + LocalRoots::get(&db).set_roots(&mut db).to(local_roots); + + // Search via the re-exported path (main::nested::NestedStruct) + // This should work because `nested` is in scope via `pub use inner::nested` + let query = Query::new("main::nested::NestedStruct".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + assert!( + names.contains(&"NestedStruct"), + "Expected NestedStruct via re-exported path in {:?}", + names + ); + + // Also verify the original path still works + let query = Query::new("main::inner::nested::NestedStruct".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + assert!( + names.contains(&"NestedStruct"), + "Expected NestedStruct via original path in {:?}", + names + ); + + // Browse the re-exported module + let query = Query::new("main::nested::".to_owned()); + let symbols = world_symbols(&db, query); + let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect(); + assert!( + names.contains(&"NestedStruct"), + "Expected NestedStruct when browsing re-exported module in {:?}", + names + ); + assert!( + names.contains(&"nested_fn"), + "Expected nested_fn when browsing re-exported module in {:?}", + names + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt index 5783d97564d0..0c28c312f83b 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt @@ -27,11 +27,13 @@ kind: STRUCT, range: 83..119, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 109..118, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 109..118, + }, + ), ), }, container_name: None, @@ -62,11 +64,13 @@ kind: STRUCT, range: 0..81, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 74..80, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 74..80, + }, + ), ), }, container_name: None, @@ -97,11 +101,13 @@ kind: STRUCT, range: 0..81, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 74..80, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 74..80, + }, + ), ), }, container_name: None, @@ -132,11 +138,13 @@ kind: STRUCT, range: 0..81, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 74..80, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 74..80, + }, + ), ), }, container_name: None, @@ -167,11 +175,13 @@ kind: STRUCT, range: 0..81, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 74..80, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 74..80, + }, + ), ), }, container_name: None, @@ -202,11 +212,13 @@ kind: STRUCT, range: 83..119, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 109..118, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 109..118, + }, + ), ), }, container_name: None, @@ -237,11 +249,13 @@ kind: STRUCT, range: 0..81, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 74..80, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 74..80, + }, + ), ), }, container_name: None, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt index 7692a7d61abf..4b588572d328 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt @@ -25,11 +25,13 @@ kind: VARIANT, range: 201..202, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 201..202, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 201..202, + }, + ), ), }, container_name: Some( @@ -60,11 +62,13 @@ kind: TYPE_ALIAS, range: 470..490, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 475..480, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 475..480, + }, + ), ), }, container_name: None, @@ -93,11 +97,13 @@ kind: VARIANT, range: 204..205, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 204..205, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 204..205, + }, + ), ), }, container_name: Some( @@ -128,11 +134,13 @@ kind: CONST, range: 413..434, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 419..424, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 419..424, + }, + ), ), }, container_name: None, @@ -161,11 +169,13 @@ kind: CONST, range: 593..665, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 599..615, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 599..615, + }, + ), ), }, container_name: None, @@ -196,11 +206,13 @@ kind: ENUM, range: 185..207, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 190..194, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 190..194, + }, + ), ), }, container_name: None, @@ -231,11 +243,13 @@ kind: USE_TREE, range: 727..749, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 736..749, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 736..749, + }, + ), ), }, container_name: None, @@ -266,11 +280,13 @@ kind: MACRO_DEF, range: 153..168, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 159..164, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 159..164, + }, + ), ), }, container_name: None, @@ -299,11 +315,13 @@ kind: STATIC, range: 435..469, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 442..448, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 442..448, + }, + ), ), }, container_name: None, @@ -334,11 +352,13 @@ kind: STRUCT, range: 170..184, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 177..183, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 177..183, + }, + ), ), }, container_name: None, @@ -369,11 +389,13 @@ kind: STRUCT, range: 0..22, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 6..21, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 6..21, + }, + ), ), }, container_name: None, @@ -404,11 +426,13 @@ kind: STRUCT, range: 391..409, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 398..408, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 398..408, + }, + ), ), }, container_name: Some( @@ -441,11 +465,13 @@ kind: STRUCT, range: 628..654, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 635..653, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 635..653, + }, + ), ), }, container_name: Some( @@ -478,11 +504,13 @@ kind: STRUCT, range: 552..580, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 559..579, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 559..579, + }, + ), ), }, container_name: None, @@ -513,11 +541,13 @@ kind: STRUCT, range: 261..279, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 268..275, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 268..275, + }, + ), ), }, container_name: None, @@ -546,11 +576,13 @@ kind: TRAIT, range: 334..373, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 340..345, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 340..345, + }, + ), ), }, container_name: None, @@ -581,11 +613,13 @@ kind: USE_TREE, range: 755..769, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 764..769, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 764..769, + }, + ), ), }, container_name: None, @@ -616,11 +650,13 @@ kind: UNION, range: 208..222, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 214..219, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 214..219, + }, + ), ), }, container_name: None, @@ -649,11 +685,13 @@ kind: MODULE, range: 492..530, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 496..501, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 496..501, + }, + ), ), }, container_name: None, @@ -682,11 +720,13 @@ kind: MODULE, range: 667..677, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 671..676, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 671..676, + }, + ), ), }, container_name: None, @@ -717,11 +757,13 @@ kind: MACRO_RULES, range: 51..131, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 64..77, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 64..77, + }, + ), ), }, container_name: None, @@ -750,11 +792,13 @@ kind: FN, range: 307..330, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 310..325, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 310..325, + }, + ), ), }, container_name: Some( @@ -785,11 +829,13 @@ kind: FN, range: 242..257, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 245..252, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 245..252, + }, + ), ), }, container_name: Some( @@ -822,11 +868,13 @@ kind: MACRO_RULES, range: 1..48, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 14..31, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 14..31, + }, + ), ), }, container_name: None, @@ -855,11 +903,13 @@ kind: FN, range: 375..411, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 378..382, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 378..382, + }, + ), ), }, container_name: None, @@ -890,11 +940,13 @@ kind: USE_TREE, range: 684..721, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 701..721, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 701..721, + }, + ), ), }, container_name: None, @@ -923,11 +975,13 @@ kind: FN, range: 352..371, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 355..363, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 355..363, + }, + ), ), }, container_name: Some( @@ -969,11 +1023,13 @@ kind: STRUCT, range: 508..528, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 515..527, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 515..527, + }, + ), ), }, container_name: None, @@ -1011,11 +1067,13 @@ kind: USE_TREE, range: 141..173, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 157..173, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 157..173, + }, + ), ), }, container_name: None, @@ -1046,11 +1104,13 @@ kind: USE_TREE, range: 141..173, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 157..173, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 157..173, + }, + ), ), }, container_name: None, @@ -1081,11 +1141,13 @@ kind: STRUCT, range: 0..20, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 7..19, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 7..19, + }, + ), ), }, container_name: None, @@ -1116,11 +1178,13 @@ kind: USE_TREE, range: 35..69, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 51..69, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 51..69, + }, + ), ), }, container_name: None, @@ -1151,11 +1215,13 @@ kind: USE_TREE, range: 85..125, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 115..125, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 115..125, + }, + ), ), }, container_name: None, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt index 6f5f8f889c7d..87f0c7d9a817 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt @@ -20,11 +20,13 @@ kind: STRUCT, range: 0..15, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 11..14, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 11..14, + }, + ), ), }, container_name: None, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt index 5d3fe4d2658d..e96aa889ba06 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt @@ -20,11 +20,13 @@ kind: STRUCT, range: 0..15, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME, - range: 11..14, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 11..14, + }, + ), ), }, container_name: None, @@ -55,11 +57,13 @@ kind: USE_TREE, range: 17..25, }, - name_ptr: AstPtr( - SyntaxNodePtr { - kind: NAME_REF, - range: 22..25, - }, + name_ptr: Some( + AstPtr( + SyntaxNodePtr { + kind: NAME_REF, + range: 22..25, + }, + ), ), }, container_name: None, diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs index a59077b757b1..7479f8147d2e 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs @@ -51,7 +51,7 @@ pub(crate) fn invalid_cast(ctx: &DiagnosticsContext<'_>, d: &hir::InvalidCast<'_ DiagnosticCode::RustcHardError("E0606"), format_ty!(ctx, "casting `{}` as `{}` is invalid", d.expr_ty, d.cast_ty), ), - CastError::IntToFatCast => ( + CastError::IntToWideCast => ( DiagnosticCode::RustcHardError("E0606"), format_ty!(ctx, "cannot cast `{}` to a fat pointer `{}`", d.expr_ty, d.cast_ty), ), @@ -95,6 +95,10 @@ pub(crate) fn invalid_cast(ctx: &DiagnosticsContext<'_>, d: &hir::InvalidCast<'_ DiagnosticCode::RustcHardError("E0605"), format_ty!(ctx, "non-primitive cast: `{}` as `{}`", d.expr_ty, d.cast_ty), ), + CastError::PtrPtrAddingAutoTraits => ( + DiagnosticCode::RustcHardError("E0804"), + "cannot add auto trait to dyn bound via pointer cast".to_owned(), + ), // CastError::UnknownCastPtrKind | CastError::UnknownExprPtrKind => ( // DiagnosticCode::RustcHardError("E0641"), // "cannot cast to a pointer of an unknown kind".to_owned(), @@ -444,8 +448,8 @@ fn main() { q as *const [i32]; //^^^^^^^^^^^^^^^^^ error: cannot cast thin pointer `*const i32` to fat pointer `*const [i32]` - // FIXME: This should emit diagnostics but disabled to prevent many false positives let t: *mut (dyn Trait + 'static) = 0 as *mut _; + //^^^^^^^^^^^ error: cannot cast `usize` to a fat pointer `*mut (dyn Trait + 'static)` let mut fail: *const str = 0 as *const str; //^^^^^^^^^^^^^^^ error: cannot cast `usize` to a fat pointer `*const str` @@ -543,7 +547,7 @@ fn main() { fn ptr_to_trait_obj_ok() { check_diagnostics( r#" -//- minicore: pointee +//- minicore: pointee, send, sync trait Trait<'a> {} fn remove_auto<'a>(x: *mut (dyn Trait<'a> + Send)) -> *mut dyn Trait<'a> { diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs index 2a251382d465..d5f25dfaf208 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs @@ -857,4 +857,81 @@ pub struct Claims { "#, ); } + + #[test] + fn test_default_field_values_basic() { + // This should work without errors - only field 'b' is required + check_diagnostics( + r#" +#![feature(default_field_values)] +struct Struct { + a: usize = 0, + b: usize, +} + +fn main() { + Struct { b: 1, .. }; +} +"#, + ); + } + + #[test] + fn test_default_field_values_missing_field_error() { + // This should report a missing field error because email is required + check_diagnostics( + r#" +#![feature(default_field_values)] +struct UserInfo { + id: i32, + age: f32 = 1.0, + email: String, +} + +fn main() { + UserInfo { id: 20, .. }; +// ^^^^^^^^💡 error: missing structure fields: +// |- email +} +"#, + ); + } + + #[test] + fn test_default_field_values_requires_spread_syntax() { + // without `..` should report missing fields + check_diagnostics( + r#" +#![feature(default_field_values)] +struct Point { + x: i32 = 0, + y: i32 = 0, +} + +fn main() { + Point { x: 0 }; +// ^^^^^💡 error: missing structure fields: +// |- y +} +"#, + ); + } + + #[test] + fn test_default_field_values_pattern_matching() { + check_diagnostics( + r#" +#![feature(default_field_values)] +struct Point { + x: i32 = 0, + y: i32 = 0, + z: i32, +} + +fn main() { + let Point { x, .. } = Point { z: 5, .. }; +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_lifetime.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_lifetime.rs index b07f9e68f634..5cb710b66b5f 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_lifetime.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_lifetime.rs @@ -100,4 +100,19 @@ fn foo WithLifetime>() {} "#, ); } + + #[test] + fn regression_21430() { + check_diagnostics( + r#" +struct S { + f: fn(A<()>), +} + +struct A<'a, T> { + a: &'a T, +} + "#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs index 2887a32825db..18280a4addec 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs @@ -995,10 +995,6 @@ fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 { } "#, ); - // FIXME: There should be no "unused variable" here, and there should be a mutability error, - // but our MIR infra is horribly broken and due to the order in which expressions are lowered - // there is no `StorageLive` for `x` in the closure (in fact, `x` should not even be a variable - // of the closure, the environment should be, but as I said, our MIR infra is horribly broken). check_diagnostics( r#" //- minicore: copy, fn @@ -1007,8 +1003,8 @@ fn f() { || { || { let x = 2; - // ^ 💡 warn: unused variable || { || { x = 5; } } + //^^^^^ 💡 error: cannot mutate immutable variable `x` } } }; diff --git a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs index 7d02b8091890..44285d9315af 100644 --- a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs +++ b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs @@ -63,7 +63,7 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< .take_while(|it| it != &token) .filter(|it| it.kind() == T![,]) .count(); - let ExpandResult { err, value: expansion } = expansions.get(idx)?.clone(); + let ExpandResult { err, value: expansion } = expansions.get(idx)?.clone()?; let expansion_file_id = sema.hir_file_for(&expansion).macro_file()?; let expansion_span_map = db.expansion_span_map(expansion_file_id); let mut expansion = format( @@ -583,26 +583,16 @@ fn main() { fn macro_expand_derive() { check( r#" -//- proc_macros: identity -//- minicore: clone, derive +//- proc_macros: identity, derive_identity +//- minicore: derive #[proc_macros::identity] -#[derive(C$0lone)] +#[derive(proc_macros::DeriveIde$0ntity)] struct Foo {} "#, expect![[r#" - Clone - impl <>core::clone::Clone for Foo< >where { - fn clone(&self) -> Self { - match self { - Foo{} - => Foo{} - , - - } - } - - }"#]], + proc_macros::DeriveIdentity + struct Foo{}"#]], ); } @@ -610,15 +600,17 @@ struct Foo {} fn macro_expand_derive2() { check( r#" -//- minicore: copy, clone, derive +//- proc_macros: derive_identity +//- minicore: derive -#[derive(Cop$0y)] -#[derive(Clone)] +#[derive(proc_macros::$0DeriveIdentity)] +#[derive(proc_macros::DeriveIdentity)] struct Foo {} "#, expect![[r#" - Copy - impl <>core::marker::Copy for Foo< >where{}"#]], + proc_macros::DeriveIdentity + #[derive(proc_macros::DeriveIdentity)] + struct Foo{}"#]], ); } @@ -626,35 +618,27 @@ struct Foo {} fn macro_expand_derive_multi() { check( r#" -//- minicore: copy, clone, derive +//- proc_macros: derive_identity +//- minicore: derive -#[derive(Cop$0y, Clone)] +#[derive(proc_macros::DeriveIdent$0ity, proc_macros::DeriveIdentity)] struct Foo {} "#, expect![[r#" - Copy - impl <>core::marker::Copy for Foo< >where{}"#]], + proc_macros::DeriveIdentity + struct Foo{}"#]], ); check( r#" -//- minicore: copy, clone, derive +//- proc_macros: derive_identity +//- minicore: derive -#[derive(Copy, Cl$0one)] +#[derive(proc_macros::DeriveIdentity, proc_macros::De$0riveIdentity)] struct Foo {} "#, expect![[r#" - Clone - impl <>core::clone::Clone for Foo< >where { - fn clone(&self) -> Self { - match self { - Foo{} - => Foo{} - , - - } - } - - }"#]], + proc_macros::DeriveIdentity + struct Foo{}"#]], ); } @@ -864,4 +848,19 @@ struct S { u32"#]], ); } + + #[test] + fn regression_21489() { + check( + r#" +//- proc_macros: derive_identity +//- minicore: derive, fmt +#[derive(Debug, proc_macros::DeriveIdentity$0)] +struct Foo; + "#, + expect![[r#" + proc_macros::DeriveIdentity + struct Foo;"#]], + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs index feac5fff84a7..15ea92d1c6ec 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -272,9 +272,9 @@ pub(super) fn struct_rest_pat( edition: Edition, display_target: DisplayTarget, ) -> HoverResult { - let missing_fields = sema.record_pattern_missing_fields(pattern); + let matched_fields = sema.record_pattern_matched_fields(pattern); - // if there are no missing fields, the end result is a hover that shows ".." + // if there are no matched fields, the end result is a hover that shows ".." // should be left in to indicate that there are no more fields in the pattern // example, S {a: 1, b: 2, ..} when struct S {a: u32, b: u32} @@ -285,13 +285,13 @@ pub(super) fn struct_rest_pat( targets.push(item); } }; - for (_, t) in &missing_fields { + for (_, t) in &matched_fields { walk_and_push_ty(sema.db, t, &mut push_new_def); } res.markup = { let mut s = String::from(".., "); - for (f, _) in &missing_fields { + for (f, _) in &matched_fields { s += f.display(sema.db, display_target).to_string().as_ref(); s += ", "; } diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index 0b518021e39e..7900a0dc9991 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -11239,3 +11239,75 @@ impl Foo for T { "#]], ); } + +#[test] +fn doc_link_enum_self_variant() { + check( + r#" +/// - [`VariantOne$0`](Self::One) +pub enum MyEnum { + One, + Two, +} + "#, + expect![[r#" + *[`VariantOne`](Self::One)* + + ```rust + ra_test_fixture::MyEnum + ``` + + ```rust + One = 0 + ``` + "#]], + ); +} + +#[test] +fn doc_link_trait_self() { + check( + r#" +/// - [`do_something$0`](Self::do_something) +pub trait MyTrait { + fn do_something(&self); +} + "#, + expect![[r#" + *[`do_something`](Self::do_something)* + + ```rust + ra_test_fixture::MyTrait + ``` + + ```rust + pub trait MyTrait + pub fn do_something(&self) + ``` + "#]], + ); + check( + r#" +pub trait MyTrait { + /// - [`do_something$0`](Self::do_something) + fn do_something(&self); +} + "#, + expect![[r#" + *[`do_something`](Self::do_something)* + + ```rust + ra_test_fixture::MyTrait + ``` + + ```rust + pub trait MyTrait + pub fn do_something(&self) + ``` + + --- + + * [`do_something`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/trait.MyTrait.html#tymethod.do_something) + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs index a271cac6fcd0..185df92e2d39 100644 --- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs +++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs @@ -19,7 +19,7 @@ use ide_db::{ }; use stdx::never; use syntax::{ - AstNode, SyntaxNode, TextRange, + AstNode, AstPtr, SyntaxNode, TextRange, ast::{self, HasName}, }; @@ -253,7 +253,7 @@ impl<'db> TryToNav for FileSymbol<'db> { db, self.loc.hir_file_id, self.loc.ptr.text_range(), - Some(self.loc.name_ptr.text_range()), + self.loc.name_ptr.map(AstPtr::text_range), ) .map(|(FileRange { file_id, range: full_range }, focus_range)| { NavigationTarget { @@ -264,7 +264,7 @@ impl<'db> TryToNav for FileSymbol<'db> { .flatten() .map_or_else(|| self.name.clone(), |it| it.symbol().clone()), alias: self.is_alias.then(|| self.name.clone()), - kind: Some(self.def.into()), + kind: Some(SymbolKind::from_module_def(db, self.def)), full_range, focus_range, container_name: self.container_name.clone(), @@ -480,16 +480,11 @@ impl ToNav for hir::Module { ModuleSource::Module(node) => (node.syntax(), node.name()), ModuleSource::BlockExpr(node) => (node.syntax(), None), }; + let kind = if self.is_crate_root(db) { SymbolKind::CrateRoot } else { SymbolKind::Module }; orig_range_with_focus(db, file_id, syntax, focus).map( |(FileRange { file_id, range: full_range }, focus_range)| { - NavigationTarget::from_syntax( - file_id, - name.clone(), - focus_range, - full_range, - SymbolKind::Module, - ) + NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind) }, ) } @@ -549,7 +544,7 @@ impl TryToNav for hir::ExternCrateDecl { self.alias_or_name(db).unwrap_or_else(|| self.name(db)).symbol().clone(), focus_range, full_range, - SymbolKind::Module, + SymbolKind::CrateRoot, ); res.docs = self.docs(db).map(Documentation::into_owned); diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 4918fe4ff9a4..5443021988d4 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -1079,7 +1079,7 @@ use self$0; use self$0; "#, expect![[r#" - _ Module FileId(0) 0..10 + _ CrateRoot FileId(0) 0..10 FileId(0) 4..8 import "#]], diff --git a/src/tools/rust-analyzer/crates/ide/src/runnables.rs b/src/tools/rust-analyzer/crates/ide/src/runnables.rs index 6cec91250351..42efa7142b50 100644 --- a/src/tools/rust-analyzer/crates/ide/src/runnables.rs +++ b/src/tools/rust-analyzer/crates/ide/src/runnables.rs @@ -815,7 +815,7 @@ mod not_a_root { "#, expect![[r#" [ - "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..331, name: \"_\", kind: Module })", + "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..331, name: \"_\", kind: CrateRoot })", "(Bin, NavigationTarget { file_id: FileId(0), full_range: 1..13, focus_range: 4..8, name: \"main\", kind: Function })", "(Bin, NavigationTarget { file_id: FileId(0), full_range: 15..76, focus_range: 42..71, name: \"__cortex_m_rt_main_trampoline\", kind: Function })", "(Bin, NavigationTarget { file_id: FileId(0), full_range: 78..154, focus_range: 113..149, name: \"__cortex_m_rt_main_trampoline_unsafe\", kind: Function })", @@ -1136,7 +1136,7 @@ fn test_foo1() {} "#, expect![[r#" [ - "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..51, name: \"_\", kind: Module })", + "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..51, name: \"_\", kind: CrateRoot })", "(Test, NavigationTarget { file_id: FileId(0), full_range: 1..50, focus_range: 36..45, name: \"test_foo1\", kind: Function }, Atom(KeyValue { key: \"feature\", value: \"foo\" }))", ] "#]], @@ -1155,7 +1155,7 @@ fn test_foo1() {} "#, expect![[r#" [ - "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..73, name: \"_\", kind: Module })", + "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..73, name: \"_\", kind: CrateRoot })", "(Test, NavigationTarget { file_id: FileId(0), full_range: 1..72, focus_range: 58..67, name: \"test_foo1\", kind: Function }, All([Atom(KeyValue { key: \"feature\", value: \"foo\" }), Atom(KeyValue { key: \"feature\", value: \"bar\" })]))", ] "#]], @@ -1234,7 +1234,7 @@ generate_main!(); "#, expect![[r#" [ - "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..345, name: \"_\", kind: Module })", + "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..345, name: \"_\", kind: CrateRoot })", "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 282..312, focus_range: 286..291, name: \"tests\", kind: Module, description: \"mod tests\" })", "(Test, NavigationTarget { file_id: FileId(0), full_range: 298..307, name: \"foo_test\", kind: Function })", "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 313..323, name: \"tests2\", kind: Module, description: \"mod tests2\" }, true)", diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index e7c5f95a250e..e64fd6488f2a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -513,21 +513,21 @@ fn string_injections( ); if !string.is_raw() { - highlight_escape_string(hl, &string); + highlight_escape_string(hl, config, &string); } } } else if let Some(byte_string) = ast::ByteString::cast(token.clone()) { if !byte_string.is_raw() { - highlight_escape_string(hl, &byte_string); + highlight_escape_string(hl, config, &byte_string); } } else if let Some(c_string) = ast::CString::cast(token.clone()) { if !c_string.is_raw() { - highlight_escape_string(hl, &c_string); + highlight_escape_string(hl, config, &c_string); } } else if let Some(char) = ast::Char::cast(token.clone()) { - highlight_escape_char(hl, &char) + highlight_escape_char(hl, config, &char) } else if let Some(byte) = ast::Byte::cast(token) { - highlight_escape_byte(hl, &byte) + highlight_escape_byte(hl, config, &byte) } ControlFlow::Continue(()) } @@ -586,7 +586,11 @@ fn descend_token( fn filter_by_config(highlight: &mut Highlight, config: &HighlightConfig<'_>) -> bool { match &mut highlight.tag { - HlTag::StringLiteral if !config.strings => return false, + HlTag::StringLiteral | HlTag::EscapeSequence | HlTag::InvalidEscapeSequence + if !config.strings => + { + return false; + } HlTag::Comment if !config.comments => return false, // If punctuation is disabled, make the macro bang part of the macro call again. tag @ HlTag::Punctuation(HlPunct::MacroBang) => { diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs index 094f88f3a864..4da69cc43d9e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs @@ -1,10 +1,14 @@ //! Syntax highlighting for escape sequences use crate::syntax_highlighting::highlights::Highlights; -use crate::{HlRange, HlTag}; +use crate::{HighlightConfig, HlRange, HlTag}; use syntax::ast::{Byte, Char, IsString}; use syntax::{AstToken, TextRange, TextSize}; -pub(super) fn highlight_escape_string(stack: &mut Highlights, string: &T) { +pub(super) fn highlight_escape_string( + stack: &mut Highlights, + config: &HighlightConfig<'_>, + string: &T, +) { let text = string.text(); let start = string.syntax().text_range().start(); string.escaped_char_ranges(&mut |piece_range, char| { @@ -13,16 +17,23 @@ pub(super) fn highlight_escape_string(stack: &mut Highlights, strin Ok(_) => HlTag::EscapeSequence, Err(_) => HlTag::InvalidEscapeSequence, }; - stack.add(HlRange { - range: piece_range + start, - highlight: highlight.into(), - binding_hash: None, - }); + stack.add_with( + config, + HlRange { + range: piece_range + start, + highlight: highlight.into(), + binding_hash: None, + }, + ); } }); } -pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char) { +pub(super) fn highlight_escape_char( + stack: &mut Highlights, + config: &HighlightConfig<'_>, + char: &Char, +) { if char.value().is_err() { // We do not emit invalid escapes highlighting here. The lexer would likely be in a bad // state and this token contains junk, since `'` is not a reliable delimiter (consider @@ -43,10 +54,17 @@ pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char) { char.syntax().text_range().start() + TextSize::from(1), TextSize::from(text.len() as u32), ); - stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None }) + stack.add_with( + config, + HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None }, + ) } -pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte) { +pub(super) fn highlight_escape_byte( + stack: &mut Highlights, + config: &HighlightConfig<'_>, + byte: &Byte, +) { if byte.value().is_err() { // See `highlight_escape_char` for why no error highlighting here. return; @@ -65,5 +83,8 @@ pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte) { byte.syntax().text_range().start() + TextSize::from(2), TextSize::from(text.len() as u32), ); - stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None }) + stack.add_with( + config, + HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None }, + ) } diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs index 044fd3f5ac9a..dcc9a8c0d5f7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs @@ -399,7 +399,7 @@ fn highlight_name_ref( highlight_def(sema, krate, field_ref.into(), edition, true) } NameRefClass::ExternCrateShorthand { decl, krate: resolved_krate } => { - let mut h = HlTag::Symbol(SymbolKind::Module).into(); + let mut h = HlTag::Symbol(SymbolKind::CrateRoot).into(); if krate.as_ref().is_some_and(|krate| resolved_krate != *krate) { h |= HlMod::Library; @@ -417,7 +417,6 @@ fn highlight_name_ref( if is_deprecated { h |= HlMod::Deprecated; } - h |= HlMod::CrateRoot; h } }; @@ -495,16 +494,15 @@ pub(super) fn highlight_def( (Highlight::new(HlTag::Symbol(SymbolKind::Field)), Some(field.attrs(sema.db))) } Definition::TupleField(_) => (Highlight::new(HlTag::Symbol(SymbolKind::Field)), None), - Definition::Crate(krate) => ( - Highlight::new(HlTag::Symbol(SymbolKind::Module)) | HlMod::CrateRoot, - Some(krate.attrs(sema.db)), - ), + Definition::Crate(krate) => { + (Highlight::new(HlTag::Symbol(SymbolKind::CrateRoot)), Some(krate.attrs(sema.db))) + } Definition::Module(module) => { - let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Module)); - if module.is_crate_root(db) { - h |= HlMod::CrateRoot; - } - + let h = Highlight::new(HlTag::Symbol(if module.is_crate_root(db) { + SymbolKind::CrateRoot + } else { + SymbolKind::Module + })); (h, Some(module.attrs(sema.db))) } Definition::Function(func) => { @@ -662,8 +660,7 @@ pub(super) fn highlight_def( (h, None) } Definition::ExternCrateDecl(extern_crate) => { - let mut highlight = - Highlight::new(HlTag::Symbol(SymbolKind::Module)) | HlMod::CrateRoot; + let mut highlight = Highlight::new(HlTag::Symbol(SymbolKind::CrateRoot)); if extern_crate.alias(db).is_none() { highlight |= HlMod::Library; } @@ -805,6 +802,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { TYPE_PARAM => SymbolKind::TypeParam, RECORD_FIELD => SymbolKind::Field, MODULE => SymbolKind::Module, + EXTERN_CRATE => SymbolKind::CrateRoot, FN => SymbolKind::Function, CONST => SymbolKind::Const, STATIC => SymbolKind::Static, @@ -835,7 +833,7 @@ fn highlight_name_ref_by_syntax( }; match parent.kind() { - EXTERN_CRATE => HlTag::Symbol(SymbolKind::Module) | HlMod::CrateRoot, + EXTERN_CRATE => HlTag::Symbol(SymbolKind::CrateRoot).into(), METHOD_CALL_EXPR => ast::MethodCallExpr::cast(parent) .and_then(|it| highlight_method_call(sema, krate, &it, is_unsafe_node)) .unwrap_or_else(|| SymbolKind::Method.into()), diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlights.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlights.rs index 340290eafedb..6fe4d0844338 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlights.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlights.rs @@ -4,7 +4,7 @@ use std::iter; use stdx::equal_range_by; use syntax::TextRange; -use crate::{HlRange, HlTag}; +use crate::{HighlightConfig, HlRange, HlTag}; pub(super) struct Highlights { root: Node, @@ -26,6 +26,12 @@ impl Highlights { self.root.add(hl_range); } + pub(super) fn add_with(&mut self, config: &HighlightConfig<'_>, mut hl_range: HlRange) { + if super::filter_by_config(&mut hl_range.highlight, config) { + self.root.add(hl_range); + } + } + pub(super) fn to_vec(&self) -> Vec { let mut res = Vec::new(); self.root.flatten(&mut res); diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs index 26d2bb5e0288..291333f09cf8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs @@ -1,6 +1,6 @@ //! "Recursive" Syntax highlighting for code in doctests and fixtures. -use hir::{EditionedFileId, HirFileId, InFile, Semantics}; +use hir::{EditionedFileId, HirFileId, InFile, Semantics, db::HirDatabase}; use ide_db::{ SymbolKind, defs::Definition, documentation::Documentation, range_mapper::RangeMapper, rust_doc::is_rust_fence, @@ -109,7 +109,7 @@ pub(super) fn doc_comment( .for_each(|(range, def)| { hl.add(HlRange { range, - highlight: module_def_to_hl_tag(def) + highlight: module_def_to_hl_tag(sema.db, def) | HlMod::Documentation | HlMod::Injected | HlMod::IntraDocLink, @@ -200,11 +200,11 @@ pub(super) fn doc_comment( } } -fn module_def_to_hl_tag(def: Definition) -> HlTag { +fn module_def_to_hl_tag(db: &dyn HirDatabase, def: Definition) -> HlTag { let symbol = match def { - Definition::Module(_) | Definition::Crate(_) | Definition::ExternCrateDecl(_) => { - SymbolKind::Module - } + Definition::Crate(_) | Definition::ExternCrateDecl(_) => SymbolKind::CrateRoot, + Definition::Module(m) if m.is_crate_root(db) => SymbolKind::CrateRoot, + Definition::Module(_) => SymbolKind::Module, Definition::Function(_) => SymbolKind::Function, Definition::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct, Definition::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum, diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs index ca3c3e3aaace..0c64d3de1012 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs @@ -144,6 +144,7 @@ impl HlTag { SymbolKind::BuiltinAttr => "builtin_attr", SymbolKind::Const => "constant", SymbolKind::ConstParam => "const_param", + SymbolKind::CrateRoot => "crate_root", SymbolKind::Derive => "derive", SymbolKind::DeriveHelper => "derive_helper", SymbolKind::Enum => "enum", diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_asm.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_asm.html index 100fdd2155a4..1228849c5bfd 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_asm.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_asm.html @@ -45,7 +45,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd unsafe { let foo = 1; let mut o = 0; - core::arch::asm!( + core::arch::asm!( "%input = OpLoad _ {0}", concat!("%result = ", "bar", " _ %input"), "OpStore {1} %result", @@ -54,7 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd ); let thread_id: usize; - core::arch::asm!(" + core::arch::asm!(" mov {0}, gs:[0x30] mov {0}, [{0}+0x48] ", out(reg) thread_id, options(pure, readonly, nostack)); @@ -64,7 +64,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd static VirtualFree: usize; const OffPtr: usize; const OffFn: usize; - core::arch::asm!(" + core::arch::asm!(" push {free_type} push {free_size} push {base} @@ -97,7 +97,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd // Ensure thumb mode is set. let rv = (rv as u32) | 1; let msp = msp as u32; - core::arch::asm!( + core::arch::asm!( "mrs {tmp}, CONTROL", "bics {tmp}, {spsel}", "msr CONTROL, {tmp}", diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html index b151ff42fc39..fa7f7b1cbafb 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html @@ -43,7 +43,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd

#[allow(dead_code)]
 #[rustfmt::skip]
-#[proc_macros::identity]
+#[proc_macros::identity]
 #[derive(Default)]
 /// This is a doc comment
 // This is a normal comment
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
index a6e6b16bead5..0b32cedca5d8 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
@@ -41,25 +41,25 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
 .unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
-
extern crate foo;
-use core::iter;
+
extern crate foo;
+use core::iter;
 
 pub const NINETY_TWO: u8 = 92;
 
-use foo as foooo;
+use foo as foooo;
 
-pub(crate) fn main() {
+pub(crate) fn main() {
     let baz = iter::repeat(92);
 }
 
 mod bar {
-    pub(in super) const FORTY_TWO: u8 = 42;
+    pub(in super) const FORTY_TWO: u8 = 42;
 
     mod baz {
-        use super::super::NINETY_TWO;
-        use crate::foooo::Point;
+        use super::super::NINETY_TWO;
+        use crate::foooo::Point;
 
-        pub(in super::super) const TWENTY_NINE: u8 = 29;
+        pub(in super::super) const TWENTY_NINE: u8 = 29;
     }
 }
 
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html index 2f4a2004f1de..29f78959a54f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html @@ -41,7 +41,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; } .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } -
use core::iter;
+
use core::iter;
 
 fn main() {
     let foo = Some(92);
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_deprecated.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_deprecated.html
index 41d3dff8ed9e..5287affbfc5c 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_deprecated.html
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_deprecated.html
@@ -42,8 +42,8 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
#![deprecated]
-use crate as _;
-extern crate bar;
+use crate as _;
+extern crate bar;
 #[deprecated]
 macro_rules! macro_ {
     () => {};
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index b5c3df6ee447..ce9ec7431a97 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -48,9 +48,9 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 
 //! Syntactic name ref highlighting testing
 //! ```rust
-//! extern crate self;
-//! extern crate other as otter;
-//! extern crate core;
+//! extern crate self;
+//! extern crate other as otter;
+//! extern crate core;
 //! trait T { type Assoc; }
 //! fn f<Arg>() -> use<Arg> where (): T<Assoc = ()> {}
 //! ```
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
index 3a4518236883..8f7cbddd7ffb 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
@@ -41,12 +41,12 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
 .unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
-
extern crate self as this;
-extern crate std;
-extern crate alloc as abc;
-extern crate unresolved as definitely_unresolved;
+
extern crate self as this;
+extern crate std;
+extern crate alloc as abc;
+extern crate unresolved as definitely_unresolved;
 extern crate unresolved as _;
-extern crate test as opt_in_crate;
-extern crate test as _;
-extern crate proc_macro;
+extern crate test as opt_in_crate;
+extern crate test as _;
+extern crate proc_macro;
 
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_general.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_general.html index fd652f444ffd..c6dbc435c0e8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_general.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_general.html @@ -72,7 +72,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } } -use self::FooCopy::{self as BarCopy}; +use self::FooCopy::{self as BarCopy}; #[derive(Copy)] struct FooCopy { @@ -110,7 +110,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd FOO } -use core::ops::Fn; +use core::ops::Fn; fn baz<F: Fn() -> ()>(f: F) { f() } @@ -184,15 +184,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } fn use_foo_items() { - let bob = foo::Person { + let bob = foo::Person { name: "Bob", - age: foo::consts::NUMBER, + age: foo::consts::NUMBER, }; - let control_flow = foo::identity(foo::ControlFlow::Continue); + let control_flow = foo::identity(foo::ControlFlow::Continue); if control_flow.should_die() { - foo::die!(); + foo::die!(); } } diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection_2.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection_2.html index 5a5d9bd1f909..391a46f706c8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection_2.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection_2.html @@ -47,7 +47,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd fixture(r#" @@- /main.rs crate:main deps:other_crate fn test() { - let x = other_crate::foo::S::thing(); + let x = other_crate::foo::S::thing(); x; } //^ i128 diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_issue_18089.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_issue_18089.html index b28818e679ff..fccf34083d7f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_issue_18089.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_issue_18089.html @@ -45,5 +45,5 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd template!(template); } -#[proc_macros::issue_18089] +#[proc_macros::issue_18089] fn template() {}
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html index d2a53b2ff9e1..6366cba1bd03 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html @@ -41,12 +41,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; } .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } -
extern crate self;
+
extern crate self;
 
-use crate;
-use self;
+use crate;
+use self;
 mod __ {
-    use super::*;
+    use super::*;
 }
 
 macro_rules! void {
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html
index d309b4723238..a89e8190832e 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html
@@ -41,12 +41,12 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
 .unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
-
extern crate self;
+
extern crate self;
 
-use crate;
-use self;
+use crate;
+use self;
 mod __ {
-    use super::*;
+    use super::*;
 }
 
 macro_rules! void {
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html
index d309b4723238..a89e8190832e 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html
@@ -41,12 +41,12 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
 .unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
-
extern crate self;
+
extern crate self;
 
-use crate;
-use self;
+use crate;
+use self;
 mod __ {
-    use super::*;
+    use super::*;
 }
 
 macro_rules! void {
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html
index 575c9a6b0aca..aa1500b8f85b 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html
@@ -41,12 +41,12 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
 .unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
-
extern crate self;
+
extern crate self;
 
-use crate;
-use self;
+use crate;
+use self;
 mod __ {
-    use super::*;
+    use super::*;
 }
 
 macro_rules! void {
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_macros.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_macros.html
index caf66ace7a68..484afd81ead2 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_macros.html
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_macros.html
@@ -41,6 +41,6 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
 .unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
-
lib2015::void_2015!(try async await gen);
-lib2024::void_2024!(try async await gen);
+
lib2015::void_2015!(try async await gen);
+lib2024::void_2024!(try async await gen);
 
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html index b63d5cedc825..59612634fda3 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html @@ -41,7 +41,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; } .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } -
use proc_macros::{mirror, identity, DeriveIdentity};
+
use proc_macros::{mirror, identity, DeriveIdentity};
 
 mirror! {
     {
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index e178782c79c4..4e3822c3d31f 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -165,7 +165,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     toho!("{}fmt", 0);
     let i: u64 = 3;
     let o: u64;
-    core::arch::asm!(
+    core::arch::asm!(
         "mov {0}, {1}",
         "add {0}, 5",
         out(reg) o,
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings_disabled.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings_disabled.html
new file mode 100644
index 000000000000..344d0c2ff03b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings_disabled.html
@@ -0,0 +1,47 @@
+
+
+
fn main() {
+    format_args!("foo\nbar");
+    format_args!("foo\invalid");
+}
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html index 93513f5b575d..008987d409ad 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html @@ -91,7 +91,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd // unsafe fn and method calls unsafe_fn(); - self::unsafe_fn(); + self::unsafe_fn(); (unsafe_fn as unsafe fn())(); Struct { field: 0 }.unsafe_method(); @@ -120,7 +120,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd &EXTERN_STATIC; &raw const EXTERN_STATIC; - core::arch::asm!( + core::arch::asm!( "push {base}", base = const 0 ); diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs index 89a5e434f90c..8b529cf10f7f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs @@ -1498,6 +1498,23 @@ fn main() { ); } +#[test] +fn test_strings_highlighting_disabled() { + // Test that comments are not highlighted when disabled + check_highlighting_with_config( + r#" +//- minicore: fmt +fn main() { + format_args!("foo\nbar"); + format_args!("foo\invalid"); +} +"#, + HighlightConfig { strings: false, ..HL_CONFIG }, + expect_file!["./test_data/highlight_strings_disabled.html"], + false, + ); +} + #[test] fn regression_20952() { check_highlighting( diff --git a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs index cbaac64be4bd..2be4e41f4f1a 100644 --- a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs +++ b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs @@ -526,10 +526,17 @@ define_symbols! { arbitrary_self_types, arbitrary_self_types_pointers, supertrait_item_shadowing, + new_range, + range, + RangeCopy, + RangeFromCopy, + RangeInclusiveCopy, + RangeToInclusiveCopy, hash, partial_cmp, cmp, CoerceUnsized, DispatchFromDyn, define_opaque, + marker, } diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs index e8d98b1ce661..c2935d94a8a7 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -45,6 +45,7 @@ pub struct LoadCargoConfig { pub load_out_dirs_from_check: bool, pub with_proc_macro_server: ProcMacroServerChoice, pub prefill_caches: bool, + pub proc_macro_processes: usize, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -113,15 +114,25 @@ pub fn load_workspace_into_db( let proc_macro_server = match &load_config.with_proc_macro_server { ProcMacroServerChoice::Sysroot => ws.find_sysroot_proc_macro_srv().map(|it| { it.and_then(|it| { - ProcMacroClient::spawn(&it, extra_env, ws.toolchain.as_ref()).map_err(Into::into) + ProcMacroClient::spawn( + &it, + extra_env, + ws.toolchain.as_ref(), + load_config.proc_macro_processes, + ) + .map_err(Into::into) }) .map_err(|e| ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str())) }), - ProcMacroServerChoice::Explicit(path) => { - Some(ProcMacroClient::spawn(path, extra_env, ws.toolchain.as_ref()).map_err(|e| { - ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str()) - })) - } + ProcMacroServerChoice::Explicit(path) => Some( + ProcMacroClient::spawn( + path, + extra_env, + ws.toolchain.as_ref(), + load_config.proc_macro_processes, + ) + .map_err(|e| ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str())), + ), ProcMacroServerChoice::None => Some(Err(ProcMacroLoadingError::Disabled)), }; match &proc_macro_server { @@ -435,7 +446,7 @@ pub fn load_proc_macro( ) -> ProcMacroLoadResult { let res: Result, _> = (|| { let dylib = MacroDylib::new(path.to_path_buf()); - let vec = server.load_dylib(dylib, Some(&mut reject_subrequests)).map_err(|e| { + let vec = server.load_dylib(dylib, Some(&reject_subrequests)).map_err(|e| { ProcMacroLoadingError::ProcMacroSrvError(format!("{e}").into_boxed_str()) })?; if vec.is_empty() { @@ -541,7 +552,7 @@ impl ProcMacroExpander for Expander { mixed_site: Span, current_dir: String, ) -> Result { - let mut cb = |req| match req { + let cb = |req| match req { SubRequest::LocalFilePath { file_id } => { let file_id = FileId::from_raw(file_id); let source_root_id = db.file_source_root(file_id).source_root_id(db); @@ -553,15 +564,14 @@ impl ProcMacroExpander for Expander { Ok(SubResponse::LocalFilePathResult { name }) } + // Not incremental: requires full file text. SubRequest::SourceText { file_id, ast_id, start, end } => { - let ast_id = span::ErasedFileAstId::from_raw(ast_id); - let editioned_file_id = span::EditionedFileId::from_raw(file_id); - let span = Span { - range: TextRange::new(TextSize::from(start), TextSize::from(end)), - anchor: SpanAnchor { file_id: editioned_file_id, ast_id }, - ctx: SyntaxContext::root(editioned_file_id.edition()), - }; - let range = db.resolve_span(span); + let range = resolve_sub_span( + db, + file_id, + ast_id, + TextRange::new(TextSize::from(start), TextSize::from(end)), + ); let source = db.file_text(range.file_id.file_id(db)).text(db); let text = source .get(usize::from(range.range.start())..usize::from(range.range.end())) @@ -569,6 +579,19 @@ impl ProcMacroExpander for Expander { Ok(SubResponse::SourceTextResult { text }) } + // Not incremental: requires building line index. + SubRequest::LineColumn { file_id, ast_id, offset } => { + let range = + resolve_sub_span(db, file_id, ast_id, TextRange::empty(TextSize::from(offset))); + let source = db.file_text(range.file_id.file_id(db)).text(db); + let line_index = ide_db::line_index::LineIndex::new(source); + let (line, column) = line_index + .try_line_col(range.range.start()) + .map(|lc| (lc.line + 1, lc.col + 1)) + .unwrap_or((1, 1)); + // proc_macro::Span line/column are 1-based + Ok(SubResponse::LineColumnResult { line, column }) + } SubRequest::FilePath { file_id } => { let file_id = FileId::from_raw(file_id); let source_root_id = db.file_source_root(file_id).source_root_id(db); @@ -581,6 +604,17 @@ impl ProcMacroExpander for Expander { Ok(SubResponse::FilePathResult { name }) } + // Not incremental: requires global span resolution. + SubRequest::ByteRange { file_id, ast_id, start, end } => { + let range = resolve_sub_span( + db, + file_id, + ast_id, + TextRange::new(TextSize::from(start), TextSize::from(end)), + ); + + Ok(SubResponse::ByteRangeResult { range: range.range.into() }) + } }; match self.0.expand( subtree.view(), @@ -590,7 +624,7 @@ impl ProcMacroExpander for Expander { call_site, mixed_site, current_dir, - Some(&mut cb), + Some(&cb), ) { Ok(Ok(subtree)) => Ok(subtree), Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err)), @@ -603,6 +637,22 @@ impl ProcMacroExpander for Expander { } } +fn resolve_sub_span( + db: &dyn ExpandDatabase, + file_id: u32, + ast_id: u32, + range: TextRange, +) -> hir_expand::FileRange { + let ast_id = span::ErasedFileAstId::from_raw(ast_id); + let editioned_file_id = span::EditionedFileId::from_raw(file_id); + let span = Span { + range, + anchor: SpanAnchor { file_id: editioned_file_id, ast_id }, + ctx: SyntaxContext::root(editioned_file_id.edition()), + }; + db.resolve_span(span) +} + #[cfg(test)] mod tests { use ide_db::base_db::RootQueryDb; @@ -618,6 +668,7 @@ mod tests { load_out_dirs_from_check: false, with_proc_macro_server: ProcMacroServerChoice::None, prefill_caches: false, + proc_macro_processes: 1, }; let (db, _vfs, _proc_macro) = load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap(); diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs index 8f6627a60fe6..fe01fb1f1063 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs @@ -414,8 +414,9 @@ fn match_loop_inner<'t>( } // Check if we need a separator. - if item.sep.is_some() && !item.sep_matched { - let sep = item.sep.as_ref().unwrap(); + if let Some(sep) = &item.sep + && !item.sep_matched + { let mut fork = src.clone(); if expect_separator(&mut fork, sep) { // HACK: here we use `meta_result` to pass `TtIter` back to caller because diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar.rs b/src/tools/rust-analyzer/crates/parser/src/grammar.rs index bf8430294110..e481bbe9bc4a 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar.rs @@ -6,7 +6,7 @@ //! each submodule starts with `use super::*` import and exports //! "public" productions via `pub(super)`. //! -//! See docs for [`Parser`](super::parser::Parser) to learn about API, +//! See docs for [`Parser`] to learn about API, //! available to the grammar, and see docs for [`Event`](super::event::Event) //! to learn how this actually manages to produce parse trees. //! diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs index ccb556b2ccac..c0cf43a87bf7 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs @@ -24,15 +24,11 @@ fn attr(p: &mut Parser<'_>, inner: bool) { p.bump(T![!]); } - if p.eat(T!['[']) { + if p.expect(T!['[']) { meta(p); - - if !p.eat(T![']']) { - p.error("expected `]`"); - } - } else { - p.error("expected `[`"); + p.expect(T![']']); } + attr.complete(p, ATTR); } @@ -74,7 +70,7 @@ pub(super) fn meta(p: &mut Parser<'_>) { paths::attr_path(p); match p.current() { - T![=] => { + T![=] if !p.at(T![=>]) && !p.at(T![==]) => { p.bump(T![=]); if expressions::expr(p).is_none() { p.error("expected expression"); diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0002_duplicate_shebang.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0002_duplicate_shebang.rast index 7ee1ecfbb159..60cc690f7c98 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0002_duplicate_shebang.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0002_duplicate_shebang.rast @@ -28,7 +28,7 @@ SOURCE_FILE NAME_REF IDENT "rusti" WHITESPACE "\n" -error 23: expected `[` +error 23: expected L_BRACK error 23: expected an item error 27: expected one of `*`, `::`, `{`, `self`, `super` or an identifier error 28: expected SEMICOLON diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0005_attribute_recover.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0005_attribute_recover.rast index 6ff072e207cd..77b4d06321d5 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0005_attribute_recover.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0005_attribute_recover.rast @@ -58,5 +58,5 @@ SOURCE_FILE R_CURLY "}" WHITESPACE "\n" error 53: expected R_PAREN -error 53: expected `]` +error 53: expected R_BRACK error 53: expected an item diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0032_match_arms_inner_attrs.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0032_match_arms_inner_attrs.rast index 327bf94a49e6..b657e9834156 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0032_match_arms_inner_attrs.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0032_match_arms_inner_attrs.rast @@ -192,14 +192,14 @@ SOURCE_FILE WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" -error 52: expected `[` +error 52: expected L_BRACK error 52: expected pattern error 53: expected FAT_ARROW error 78: expected `,` -error 161: expected `[` +error 161: expected L_BRACK error 161: expected pattern error 162: expected FAT_ARROW -error 232: expected `[` +error 232: expected L_BRACK error 232: expected pattern error 233: expected FAT_ARROW error 250: expected `,` diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs index e44723a6a389..ba59cb219b9a 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs @@ -2,6 +2,7 @@ use std::{ io::{self, BufRead, Write}, + panic::{AssertUnwindSafe, catch_unwind}, sync::Arc, }; @@ -9,7 +10,7 @@ use paths::AbsPath; use span::Span; use crate::{ - Codec, ProcMacro, ProcMacroKind, ServerError, + ProcMacro, ProcMacroKind, ServerError, bidirectional_protocol::msg::{ BidirectionalMessage, ExpandMacro, ExpandMacroData, ExpnGlobals, Request, Response, SubRequest, SubResponse, @@ -22,26 +23,25 @@ use crate::{ }, }, process::ProcMacroServerProcess, - transport::codec::postcard::PostcardProtocol, - version, + transport::postcard, }; pub mod msg; -pub type SubCallback<'a> = &'a mut dyn FnMut(SubRequest) -> Result; +pub type SubCallback<'a> = &'a dyn Fn(SubRequest) -> Result; -pub fn run_conversation( +pub fn run_conversation( writer: &mut dyn Write, reader: &mut dyn BufRead, - buf: &mut C::Buf, + buf: &mut Vec, msg: BidirectionalMessage, callback: SubCallback<'_>, ) -> Result { - let encoded = C::encode(&msg).map_err(wrap_encode)?; - C::write(writer, &encoded).map_err(wrap_io("failed to write initial request"))?; + let encoded = postcard::encode(&msg).map_err(wrap_encode)?; + postcard::write(writer, &encoded).map_err(wrap_io("failed to write initial request"))?; loop { - let maybe_buf = C::read(reader, buf).map_err(wrap_io("failed to read message"))?; + let maybe_buf = postcard::read(reader, buf).map_err(wrap_io("failed to read message"))?; let Some(b) = maybe_buf else { return Err(ServerError { message: "proc-macro server closed the stream".into(), @@ -49,17 +49,28 @@ pub fn run_conversation( }); }; - let msg: BidirectionalMessage = C::decode(b).map_err(wrap_decode)?; + let msg: BidirectionalMessage = postcard::decode(b).map_err(wrap_decode)?; match msg { BidirectionalMessage::Response(response) => { return Ok(BidirectionalMessage::Response(response)); } BidirectionalMessage::SubRequest(sr) => { - let resp = callback(sr)?; - let reply = BidirectionalMessage::SubResponse(resp); - let encoded = C::encode(&reply).map_err(wrap_encode)?; - C::write(writer, &encoded).map_err(wrap_io("failed to write sub-response"))?; + // TODO: Avoid `AssertUnwindSafe` by making the callback `UnwindSafe` once `ExpandDatabase` + // becomes unwind-safe (currently blocked by `parking_lot::RwLock` in the VFS). + let resp = match catch_unwind(AssertUnwindSafe(|| callback(sr))) { + Ok(Ok(resp)) => BidirectionalMessage::SubResponse(resp), + Ok(Err(err)) => BidirectionalMessage::SubResponse(SubResponse::Cancel { + reason: err.to_string(), + }), + Err(_) => BidirectionalMessage::SubResponse(SubResponse::Cancel { + reason: "callback panicked or was cancelled".into(), + }), + }; + + let encoded = postcard::encode(&resp).map_err(wrap_encode)?; + postcard::write(writer, &encoded) + .map_err(wrap_io("failed to write sub-response"))?; } _ => { return Err(ServerError { @@ -138,6 +149,7 @@ pub(crate) fn find_proc_macros( pub(crate) fn expand( proc_macro: &ProcMacro, + process: &ProcMacroServerProcess, subtree: tt::SubtreeView<'_>, attr: Option>, env: Vec<(String, String)>, @@ -147,7 +159,7 @@ pub(crate) fn expand( current_dir: String, callback: SubCallback<'_>, ) -> Result, crate::ServerError> { - let version = proc_macro.process.version(); + let version = process.version(); let mut span_data_table = SpanDataIndexMap::default(); let def_site = span_data_table.insert_full(def_site).0; let call_site = span_data_table.insert_full(call_site).0; @@ -158,13 +170,8 @@ pub(crate) fn expand( macro_name: proc_macro.name.to_string(), attributes: attr .map(|subtree| FlatTree::from_subtree(subtree, version, &mut span_data_table)), - has_global_spans: ExpnGlobals { - serialize: version >= version::HAS_GLOBAL_SPANS, - def_site, - call_site, - mixed_site, - }, - span_data_table: if proc_macro.process.rust_analyzer_spans() { + has_global_spans: ExpnGlobals { def_site, call_site, mixed_site }, + span_data_table: if process.rust_analyzer_spans() { serialize_span_data_index_map(&span_data_table) } else { Vec::new() @@ -175,7 +182,7 @@ pub(crate) fn expand( current_dir: Some(current_dir), }))); - let response_payload = run_request(&proc_macro.process, task, callback)?; + let response_payload = run_request(process, task, callback)?; match response_payload { BidirectionalMessage::Response(Response::ExpandMacro(it)) => Ok(it @@ -212,14 +219,7 @@ fn run_request( if let Some(err) = srv.exited() { return Err(err.clone()); } - - match srv.use_postcard() { - true => srv.run_bidirectional::(msg, callback), - false => Err(ServerError { - message: "bidirectional messaging does not support JSON".to_owned(), - io: None, - }), - } + srv.run_bidirectional(msg, callback) } pub fn reject_subrequests(req: SubRequest) -> Result { diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs index e41f8a5d7da7..3f0422dc5bc8 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs @@ -1,11 +1,17 @@ //! Bidirectional protocol messages +use std::{ + io::{self, BufRead, Write}, + ops::Range, +}; + use paths::Utf8PathBuf; use serde::{Deserialize, Serialize}; use crate::{ ProcMacroKind, legacy_protocol::msg::{FlatTree, Message, PanicMessage, ServerConfig}, + transport::postcard, }; #[derive(Debug, Serialize, Deserialize)] @@ -13,13 +19,32 @@ pub enum SubRequest { FilePath { file_id: u32 }, SourceText { file_id: u32, ast_id: u32, start: u32, end: u32 }, LocalFilePath { file_id: u32 }, + LineColumn { file_id: u32, ast_id: u32, offset: u32 }, + ByteRange { file_id: u32, ast_id: u32, start: u32, end: u32 }, } #[derive(Debug, Serialize, Deserialize)] pub enum SubResponse { - FilePathResult { name: String }, - SourceTextResult { text: Option }, - LocalFilePathResult { name: Option }, + FilePathResult { + name: String, + }, + SourceTextResult { + text: Option, + }, + LocalFilePathResult { + name: Option, + }, + /// Line and column are 1-based. + LineColumnResult { + line: u32, + column: u32, + }, + ByteRangeResult { + range: Range, + }, + Cancel { + reason: String, + }, } #[derive(Debug, Serialize, Deserialize)] @@ -52,7 +77,6 @@ pub struct ExpandMacro { pub lib: Utf8PathBuf, pub env: Vec<(String, String)>, pub current_dir: Option, - #[serde(flatten)] pub data: ExpandMacroData, } @@ -67,29 +91,30 @@ pub struct ExpandMacroData { pub macro_body: FlatTree, pub macro_name: String, pub attributes: Option, - #[serde(skip_serializing_if = "ExpnGlobals::skip_serializing_if")] #[serde(default)] pub has_global_spans: ExpnGlobals, - - #[serde(skip_serializing_if = "Vec::is_empty")] #[serde(default)] pub span_data_table: Vec, } #[derive(Clone, Copy, Default, Debug, Serialize, Deserialize)] pub struct ExpnGlobals { - #[serde(skip_serializing)] - #[serde(default)] - pub serialize: bool, pub def_site: usize, pub call_site: usize, pub mixed_site: usize, } -impl ExpnGlobals { - fn skip_serializing_if(&self) -> bool { - !self.serialize +impl Message for BidirectionalMessage { + type Buf = Vec; + + fn read(inp: &mut dyn BufRead, buf: &mut Self::Buf) -> io::Result> { + Ok(match postcard::read(inp, buf)? { + None => None, + Some(buf) => Some(postcard::decode(buf)?), + }) + } + fn write(self, out: &mut dyn Write) -> io::Result<()> { + let value = postcard::encode(&self)?; + postcard::write(out, &value) } } - -impl Message for BidirectionalMessage {} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs index 22a7d9868e21..ee1795d39c2e 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs @@ -18,8 +18,6 @@ use crate::{ flat::serialize_span_data_index_map, }, process::ProcMacroServerProcess, - transport::codec::Codec, - transport::codec::{json::JsonProtocol, postcard::PostcardProtocol}, version, }; @@ -77,6 +75,7 @@ pub(crate) fn find_proc_macros( pub(crate) fn expand( proc_macro: &ProcMacro, + process: &ProcMacroServerProcess, subtree: tt::SubtreeView<'_>, attr: Option>, env: Vec<(String, String)>, @@ -85,7 +84,7 @@ pub(crate) fn expand( mixed_site: Span, current_dir: String, ) -> Result, crate::ServerError> { - let version = proc_macro.process.version(); + let version = process.version(); let mut span_data_table = SpanDataIndexMap::default(); let def_site = span_data_table.insert_full(def_site).0; let call_site = span_data_table.insert_full(call_site).0; @@ -102,7 +101,7 @@ pub(crate) fn expand( call_site, mixed_site, }, - span_data_table: if proc_macro.process.rust_analyzer_spans() { + span_data_table: if process.rust_analyzer_spans() { serialize_span_data_index_map(&span_data_table) } else { Vec::new() @@ -113,7 +112,7 @@ pub(crate) fn expand( current_dir: Some(current_dir), }; - let response = send_task(&proc_macro.process, Request::ExpandMacro(Box::new(task)))?; + let response = send_task(process, Request::ExpandMacro(Box::new(task)))?; match response { Response::ExpandMacro(it) => Ok(it @@ -148,25 +147,21 @@ fn send_task(srv: &ProcMacroServerProcess, req: Request) -> Result(send_request::, req) - } else { - srv.send_task::<_, _, JsonProtocol>(send_request::, req) - } + srv.send_task_legacy::<_, _>(send_request, req) } /// Sends a request to the server and reads the response. -fn send_request( +fn send_request( mut writer: &mut dyn Write, mut reader: &mut dyn BufRead, req: Request, - buf: &mut P::Buf, + buf: &mut String, ) -> Result, ServerError> { - req.write::<_, P>(&mut writer).map_err(|err| ServerError { + req.write(&mut writer).map_err(|err| ServerError { message: "failed to write request".into(), io: Some(Arc::new(err)), })?; - let res = Response::read::<_, P>(&mut reader, buf).map_err(|err| ServerError { + let res = Response::read(&mut reader, buf).map_err(|err| ServerError { message: "failed to read response".into(), io: Some(Arc::new(err)), })?; diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs index 4146b619ec0c..bb0dde472860 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs @@ -8,7 +8,7 @@ use paths::Utf8PathBuf; use serde::de::DeserializeOwned; use serde_derive::{Deserialize, Serialize}; -use crate::{Codec, ProcMacroKind}; +use crate::{ProcMacroKind, transport::json}; /// Represents requests sent from the client to the proc-macro-srv. #[derive(Debug, Serialize, Deserialize)] @@ -155,20 +155,40 @@ impl ExpnGlobals { } pub trait Message: serde::Serialize + DeserializeOwned { - fn read(inp: &mut R, buf: &mut C::Buf) -> io::Result> { - Ok(match C::read(inp, buf)? { + type Buf; + fn read(inp: &mut dyn BufRead, buf: &mut Self::Buf) -> io::Result>; + fn write(self, out: &mut dyn Write) -> io::Result<()>; +} + +impl Message for Request { + type Buf = String; + + fn read(inp: &mut dyn BufRead, buf: &mut Self::Buf) -> io::Result> { + Ok(match json::read(inp, buf)? { None => None, - Some(buf) => Some(C::decode(buf)?), + Some(buf) => Some(json::decode(buf)?), }) } - fn write(self, out: &mut W) -> io::Result<()> { - let value = C::encode(&self)?; - C::write(out, &value) + fn write(self, out: &mut dyn Write) -> io::Result<()> { + let value = json::encode(&self)?; + json::write(out, &value) } } -impl Message for Request {} -impl Message for Response {} +impl Message for Response { + type Buf = String; + + fn read(inp: &mut dyn BufRead, buf: &mut Self::Buf) -> io::Result> { + Ok(match json::read(inp, buf)? { + None => None, + Some(buf) => Some(json::decode(buf)?), + }) + } + fn write(self, out: &mut dyn Write) -> io::Result<()> { + let value = json::encode(&self)?; + json::write(out, &value) + } +} #[cfg(test)] mod tests { diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs index f5fcc99f14a3..e4b121b033d3 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs @@ -18,7 +18,8 @@ extern crate rustc_driver as _; pub mod bidirectional_protocol; pub mod legacy_protocol; -mod process; +pub mod pool; +pub mod process; pub mod transport; use paths::{AbsPath, AbsPathBuf}; @@ -26,8 +27,9 @@ use semver::Version; use span::{ErasedFileAstId, FIXUP_ERASED_FILE_AST_ID_MARKER, Span}; use std::{fmt, io, sync::Arc, time::SystemTime}; -pub use crate::transport::codec::Codec; -use crate::{bidirectional_protocol::SubCallback, process::ProcMacroServerProcess}; +use crate::{ + bidirectional_protocol::SubCallback, pool::ProcMacroServerPool, process::ProcMacroServerProcess, +}; /// The versions of the server protocol pub mod version { @@ -44,6 +46,26 @@ pub mod version { pub const CURRENT_API_VERSION: u32 = HASHED_AST_ID; } +/// Protocol format for communication between client and server. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ProtocolFormat { + /// JSON-based legacy protocol (newline-delimited JSON). + JsonLegacy, + /// Bidirectional postcard protocol with sub-request support. + BidirectionalPostcardPrototype, +} + +impl fmt::Display for ProtocolFormat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ProtocolFormat::JsonLegacy => write!(f, "json-legacy"), + ProtocolFormat::BidirectionalPostcardPrototype => { + write!(f, "bidirectional-postcard-prototype") + } + } + } +} + /// Represents different kinds of procedural macros that can be expanded by the external server. #[derive(Copy, Clone, Eq, PartialEq, Debug, serde_derive::Serialize, serde_derive::Deserialize)] pub enum ProcMacroKind { @@ -65,7 +87,7 @@ pub struct ProcMacroClient { /// /// That means that concurrent salsa requests may block each other when expanding proc macros, /// which is unfortunate, but simple and good enough for the time being. - process: Arc, + pool: Arc, path: AbsPathBuf, } @@ -87,7 +109,7 @@ impl MacroDylib { /// we share a single expander process for all macros within a workspace. #[derive(Debug, Clone)] pub struct ProcMacro { - process: Arc, + pool: ProcMacroServerPool, dylib_path: Arc, name: Box, kind: ProcMacroKind, @@ -101,7 +123,6 @@ impl PartialEq for ProcMacro { && self.kind == other.kind && self.dylib_path == other.dylib_path && self.dylib_last_modified == other.dylib_last_modified - && Arc::ptr_eq(&self.process, &other.process) } } @@ -131,9 +152,44 @@ impl ProcMacroClient { Item = (impl AsRef, &'a Option>), > + Clone, version: Option<&Version>, + num_process: usize, ) -> io::Result { - let process = ProcMacroServerProcess::run(process_path, env, version)?; - Ok(ProcMacroClient { process: Arc::new(process), path: process_path.to_owned() }) + let pool_size = num_process; + let mut workers = Vec::with_capacity(pool_size); + for _ in 0..pool_size { + let worker = ProcMacroServerProcess::spawn(process_path, env.clone(), version)?; + workers.push(worker); + } + + let pool = ProcMacroServerPool::new(workers); + Ok(ProcMacroClient { pool: Arc::new(pool), path: process_path.to_owned() }) + } + + /// Invokes `spawn` and returns a client connected to the resulting read and write handles. + /// + /// The `process_path` is used for `Self::server_path`. This function is mainly used for testing. + pub fn with_io_channels( + process_path: &AbsPath, + spawn: impl Fn( + Option, + ) -> io::Result<( + Box, + Box, + Box, + )> + Clone, + version: Option<&Version>, + num_process: usize, + ) -> io::Result { + let pool_size = num_process; + let mut workers = Vec::with_capacity(pool_size); + for _ in 0..pool_size { + let worker = + ProcMacroServerProcess::run(spawn.clone(), version, || "".to_owned())?; + workers.push(worker); + } + + let pool = ProcMacroServerPool::new(workers); + Ok(ProcMacroClient { pool: Arc::new(pool), path: process_path.to_owned() }) } /// Returns the absolute path to the proc-macro server. @@ -147,31 +203,12 @@ impl ProcMacroClient { dylib: MacroDylib, callback: Option>, ) -> Result, ServerError> { - let _p = tracing::info_span!("ProcMacroServer::load_dylib").entered(); - let macros = self.process.find_proc_macros(&dylib.path, callback)?; - - let dylib_path = Arc::new(dylib.path); - let dylib_last_modified = std::fs::metadata(dylib_path.as_path()) - .ok() - .and_then(|metadata| metadata.modified().ok()); - match macros { - Ok(macros) => Ok(macros - .into_iter() - .map(|(name, kind)| ProcMacro { - process: self.process.clone(), - name: name.into(), - kind, - dylib_path: dylib_path.clone(), - dylib_last_modified, - }) - .collect()), - Err(message) => Err(ServerError { message, io: None }), - } + self.pool.load_dylib(&dylib, callback) } /// Checks if the proc-macro server has exited. pub fn exited(&self) -> Option<&ServerError> { - self.process.exited() + self.pool.exited() } } @@ -187,7 +224,7 @@ impl ProcMacro { } fn needs_fixup_change(&self) -> bool { - let version = self.process.version(); + let version = self.pool.version(); (version::RUST_ANALYZER_SPAN_SUPPORT..version::HASHED_AST_ID).contains(&version) } @@ -231,7 +268,7 @@ impl ProcMacro { } } - self.process.expand( + self.pool.pick_process()?.expand( self, subtree, attr, diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/pool.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/pool.rs new file mode 100644 index 000000000000..a637bc0e480a --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/pool.rs @@ -0,0 +1,91 @@ +//! A pool of proc-macro server processes +use std::sync::Arc; + +use crate::{ + MacroDylib, ProcMacro, ServerError, bidirectional_protocol::SubCallback, + process::ProcMacroServerProcess, +}; + +#[derive(Debug, Clone)] +pub(crate) struct ProcMacroServerPool { + workers: Arc<[ProcMacroServerProcess]>, + version: u32, +} + +impl ProcMacroServerPool { + pub(crate) fn new(workers: Vec) -> Self { + let version = workers[0].version(); + Self { workers: workers.into(), version } + } +} + +impl ProcMacroServerPool { + pub(crate) fn exited(&self) -> Option<&ServerError> { + for worker in &*self.workers { + worker.exited()?; + } + self.workers[0].exited() + } + + pub(crate) fn pick_process(&self) -> Result<&ProcMacroServerProcess, ServerError> { + let mut best: Option<&ProcMacroServerProcess> = None; + let mut best_load = u32::MAX; + + for w in self.workers.iter().filter(|w| w.exited().is_none()) { + let load = w.number_of_active_req(); + + if load == 0 { + return Ok(w); + } + + if load < best_load { + best = Some(w); + best_load = load; + } + } + + best.ok_or_else(|| ServerError { + message: "all proc-macro server workers have exited".into(), + io: None, + }) + } + + pub(crate) fn load_dylib( + &self, + dylib: &MacroDylib, + callback: Option>, + ) -> Result, ServerError> { + let _span = tracing::info_span!("ProcMacroServer::load_dylib").entered(); + + let dylib_path = Arc::new(dylib.path.clone()); + let dylib_last_modified = + std::fs::metadata(dylib_path.as_path()).ok().and_then(|m| m.modified().ok()); + + let (first, rest) = self.workers.split_first().expect("worker pool must not be empty"); + + let macros = first + .find_proc_macros(&dylib.path, callback)? + .map_err(|e| ServerError { message: e, io: None })?; + + for worker in rest { + worker + .find_proc_macros(&dylib.path, callback)? + .map_err(|e| ServerError { message: e, io: None })?; + } + + Ok(macros + .into_iter() + .map(|(name, kind)| ProcMacro { + pool: self.clone(), + name: name.into(), + kind, + dylib_path: dylib_path.clone(), + dylib_last_modified, + }) + .collect()) + } + + pub(crate) fn version(&self) -> u32 { + self.version + } +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs index f6a656e3ce3a..9f80880965b8 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs @@ -1,10 +1,14 @@ //! Handle process life-time and message passing for proc-macro client use std::{ + fmt::Debug, io::{self, BufRead, BufReader, Read, Write}, panic::AssertUnwindSafe, process::{Child, ChildStdin, ChildStdout, Command, Stdio}, - sync::{Arc, Mutex, OnceLock}, + sync::{ + Arc, Mutex, OnceLock, + atomic::{AtomicU32, Ordering}, + }, }; use paths::AbsPath; @@ -13,14 +17,13 @@ use span::Span; use stdx::JodChild; use crate::{ - Codec, ProcMacro, ProcMacroKind, ServerError, + ProcMacro, ProcMacroKind, ProtocolFormat, ServerError, bidirectional_protocol::{self, SubCallback, msg::BidirectionalMessage, reject_subrequests}, legacy_protocol::{self, SpanMode}, version, }; /// Represents a process handling proc-macro communication. -#[derive(Debug)] pub(crate) struct ProcMacroServerProcess { /// The state of the proc-macro server process, the protocol is currently strictly sequential /// hence the lock on the state. @@ -29,31 +32,102 @@ pub(crate) struct ProcMacroServerProcess { protocol: Protocol, /// Populated when the server exits. exited: OnceLock>, + active: AtomicU32, +} + +impl std::fmt::Debug for ProcMacroServerProcess { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ProcMacroServerProcess") + .field("version", &self.version) + .field("protocol", &self.protocol) + .field("exited", &self.exited) + .finish() + } } #[derive(Debug, Clone)] pub(crate) enum Protocol { LegacyJson { mode: SpanMode }, - LegacyPostcard { mode: SpanMode }, BidirectionalPostcardPrototype { mode: SpanMode }, } +pub trait ProcessExit: Send + Sync { + fn exit_err(&mut self) -> Option; +} + +impl ProcessExit for Process { + fn exit_err(&mut self) -> Option { + match self.child.try_wait() { + Ok(None) | Err(_) => None, + Ok(Some(status)) => { + let mut msg = String::new(); + if !status.success() + && let Some(stderr) = self.child.stderr.as_mut() + { + _ = stderr.read_to_string(&mut msg); + } + Some(ServerError { + message: format!( + "proc-macro server exited with {status}{}{msg}", + if msg.is_empty() { "" } else { ": " } + ), + io: None, + }) + } + } + } +} + /// Maintains the state of the proc-macro server process. -#[derive(Debug)] -struct ProcessSrvState { - process: Process, - stdin: ChildStdin, - stdout: BufReader, +pub(crate) struct ProcessSrvState { + process: Box, + stdin: Box, + stdout: Box, } impl ProcMacroServerProcess { /// Starts the proc-macro server and performs a version check - pub(crate) fn run<'a>( + pub(crate) fn spawn<'a>( process_path: &AbsPath, env: impl IntoIterator< Item = (impl AsRef, &'a Option>), > + Clone, version: Option<&Version>, + ) -> io::Result { + Self::run( + |format| { + let mut process = Process::run( + process_path, + env.clone(), + format.map(|format| format.to_string()).as_deref(), + )?; + let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); + + Ok((Box::new(process), Box::new(stdin), Box::new(stdout))) + }, + version, + || { + #[expect(clippy::disallowed_methods)] + Command::new(process_path) + .arg("--version") + .output() + .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_owned()) + .unwrap_or_else(|_| "unknown version".to_owned()) + }, + ) + } + + /// Invokes `spawn` and performs a version check. + pub(crate) fn run( + spawn: impl Fn( + Option, + ) -> io::Result<( + Box, + Box, + Box, + )>, + version: Option<&Version>, + binary_server_version: impl Fn() -> String, ) -> io::Result { const VERSION: Version = Version::new(1, 93, 0); // we do `>` for nightly as this started working in the middle of the 1.93 nightly release, so we dont want to break on half of the nightlies @@ -65,40 +139,38 @@ impl ProcMacroServerProcess { && has_working_format_flag { &[ - ( - Some("bidirectional-postcard-prototype"), - Protocol::BidirectionalPostcardPrototype { mode: SpanMode::Id }, - ), - (Some("postcard-legacy"), Protocol::LegacyPostcard { mode: SpanMode::Id }), - (Some("json-legacy"), Protocol::LegacyJson { mode: SpanMode::Id }), + Some(ProtocolFormat::BidirectionalPostcardPrototype), + Some(ProtocolFormat::JsonLegacy), ] } else { - &[(None, Protocol::LegacyJson { mode: SpanMode::Id })] + &[None] }; let mut err = None; - for &(format, ref protocol) in formats { + for &format in formats { let create_srv = || { - let mut process = Process::run(process_path, env.clone(), format)?; - let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); + let (process, stdin, stdout) = spawn(format)?; io::Result::Ok(ProcMacroServerProcess { state: Mutex::new(ProcessSrvState { process, stdin, stdout }), version: 0, - protocol: protocol.clone(), + protocol: match format { + Some(ProtocolFormat::BidirectionalPostcardPrototype) => { + Protocol::BidirectionalPostcardPrototype { mode: SpanMode::Id } + } + Some(ProtocolFormat::JsonLegacy) | None => { + Protocol::LegacyJson { mode: SpanMode::Id } + } + }, exited: OnceLock::new(), + active: AtomicU32::new(0), }) }; let mut srv = create_srv()?; tracing::info!("sending proc-macro server version check"); - match srv.version_check(Some(&mut reject_subrequests)) { + match srv.version_check(Some(&reject_subrequests)) { Ok(v) if v > version::CURRENT_API_VERSION => { - #[allow(clippy::disallowed_methods)] - let process_version = Command::new(process_path) - .arg("--version") - .output() - .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_owned()) - .unwrap_or_else(|_| "unknown version".to_owned()); + let process_version = binary_server_version(); err = Some(io::Error::other(format!( "Your installed proc-macro server is too new for your rust-analyzer. API version: {}, server version: {process_version}. \ This will prevent proc-macro expansion from working. Please consider updating your rust-analyzer to ensure compatibility with your current toolchain.", @@ -110,11 +182,10 @@ impl ProcMacroServerProcess { srv.version = v; if srv.version >= version::RUST_ANALYZER_SPAN_SUPPORT && let Ok(new_mode) = - srv.enable_rust_analyzer_spans(Some(&mut reject_subrequests)) + srv.enable_rust_analyzer_spans(Some(&reject_subrequests)) { match &mut srv.protocol { Protocol::LegacyJson { mode } - | Protocol::LegacyPostcard { mode } | Protocol::BidirectionalPostcardPrototype { mode } => *mode = new_mode, } } @@ -132,15 +203,27 @@ impl ProcMacroServerProcess { Err(err.unwrap()) } + /// Finds proc-macros in a given dynamic library. + pub(crate) fn find_proc_macros( + &self, + dylib_path: &AbsPath, + callback: Option>, + ) -> Result, String>, ServerError> { + match self.protocol { + Protocol::LegacyJson { .. } => legacy_protocol::find_proc_macros(self, dylib_path), + + Protocol::BidirectionalPostcardPrototype { .. } => { + let cb = callback.expect("callback required for bidirectional protocol"); + bidirectional_protocol::find_proc_macros(self, dylib_path, cb) + } + } + } + /// Returns the server error if the process has exited. pub(crate) fn exited(&self) -> Option<&ServerError> { self.exited.get().map(|it| &it.0) } - pub(crate) fn use_postcard(&self) -> bool { - matches!(self.protocol, Protocol::LegacyPostcard { .. }) - } - /// Retrieves the API version of the proc-macro server. pub(crate) fn version(&self) -> u32 { self.version @@ -150,7 +233,6 @@ impl ProcMacroServerProcess { pub(crate) fn rust_analyzer_spans(&self) -> bool { match self.protocol { Protocol::LegacyJson { mode } => mode == SpanMode::RustAnalyzer, - Protocol::LegacyPostcard { mode } => mode == SpanMode::RustAnalyzer, Protocol::BidirectionalPostcardPrototype { mode } => mode == SpanMode::RustAnalyzer, } } @@ -158,9 +240,7 @@ impl ProcMacroServerProcess { /// Checks the API version of the running proc-macro server. fn version_check(&self, callback: Option>) -> Result { match self.protocol { - Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { - legacy_protocol::version_check(self) - } + Protocol::LegacyJson { .. } => legacy_protocol::version_check(self), Protocol::BidirectionalPostcardPrototype { .. } => { let cb = callback.expect("callback required for bidirectional protocol"); bidirectional_protocol::version_check(self, cb) @@ -174,9 +254,7 @@ impl ProcMacroServerProcess { callback: Option>, ) -> Result { match self.protocol { - Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { - legacy_protocol::enable_rust_analyzer_spans(self) - } + Protocol::LegacyJson { .. } => legacy_protocol::enable_rust_analyzer_spans(self), Protocol::BidirectionalPostcardPrototype { .. } => { let cb = callback.expect("callback required for bidirectional protocol"); bidirectional_protocol::enable_rust_analyzer_spans(self, cb) @@ -184,23 +262,6 @@ impl ProcMacroServerProcess { } } - /// Finds proc-macros in a given dynamic library. - pub(crate) fn find_proc_macros( - &self, - dylib_path: &AbsPath, - callback: Option>, - ) -> Result, String>, ServerError> { - match self.protocol { - Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { - legacy_protocol::find_proc_macros(self, dylib_path) - } - Protocol::BidirectionalPostcardPrototype { .. } => { - let cb = callback.expect("callback required for bidirectional protocol"); - bidirectional_protocol::find_proc_macros(self, dylib_path, cb) - } - } - } - pub(crate) fn expand( &self, proc_macro: &ProcMacro, @@ -213,21 +274,22 @@ impl ProcMacroServerProcess { current_dir: String, callback: Option>, ) -> Result, ServerError> { - match self.protocol { - Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { - legacy_protocol::expand( - proc_macro, - subtree, - attr, - env, - def_site, - call_site, - mixed_site, - current_dir, - ) - } + self.active.fetch_add(1, Ordering::AcqRel); + let result = match self.protocol { + Protocol::LegacyJson { .. } => legacy_protocol::expand( + proc_macro, + self, + subtree, + attr, + env, + def_site, + call_site, + mixed_site, + current_dir, + ), Protocol::BidirectionalPostcardPrototype { .. } => bidirectional_protocol::expand( proc_macro, + self, subtree, attr, env, @@ -237,20 +299,23 @@ impl ProcMacroServerProcess { current_dir, callback.expect("callback required for bidirectional protocol"), ), - } + }; + + self.active.fetch_sub(1, Ordering::AcqRel); + result } - pub(crate) fn send_task( + pub(crate) fn send_task_legacy( &self, send: impl FnOnce( &mut dyn Write, &mut dyn BufRead, Request, - &mut C::Buf, + &mut String, ) -> Result, ServerError>, req: Request, ) -> Result { - self.with_locked_io::(|writer, reader, buf| { + self.with_locked_io(String::new(), |writer, reader, buf| { send(writer, reader, req, buf).and_then(|res| { res.ok_or_else(|| { let message = "proc-macro server did not respond with data".to_owned(); @@ -266,31 +331,17 @@ impl ProcMacroServerProcess { }) } - pub(crate) fn with_locked_io( + fn with_locked_io( &self, - f: impl FnOnce(&mut dyn Write, &mut dyn BufRead, &mut C::Buf) -> Result, + mut buf: B, + f: impl FnOnce(&mut dyn Write, &mut dyn BufRead, &mut B) -> Result, ) -> Result { let state = &mut *self.state.lock().unwrap(); - let mut buf = C::Buf::default(); - f(&mut state.stdin, &mut state.stdout, &mut buf).map_err(|e| { if e.io.as_ref().map(|it| it.kind()) == Some(io::ErrorKind::BrokenPipe) { - match state.process.child.try_wait() { - Ok(None) | Err(_) => e, - Ok(Some(status)) => { - let mut msg = String::new(); - if !status.success() - && let Some(stderr) = state.process.child.stderr.as_mut() - { - _ = stderr.read_to_string(&mut msg); - } - let server_error = ServerError { - message: format!( - "proc-macro server exited with {status}{}{msg}", - if msg.is_empty() { "" } else { ": " } - ), - io: None, - }; + match state.process.exit_err() { + None => e, + Some(server_error) => { self.exited.get_or_init(|| AssertUnwindSafe(server_error)).0.clone() } } @@ -300,15 +351,19 @@ impl ProcMacroServerProcess { }) } - pub(crate) fn run_bidirectional( + pub(crate) fn run_bidirectional( &self, initial: BidirectionalMessage, callback: SubCallback<'_>, ) -> Result { - self.with_locked_io::(|writer, reader, buf| { - bidirectional_protocol::run_conversation::(writer, reader, buf, initial, callback) + self.with_locked_io(Vec::new(), |writer, reader, buf| { + bidirectional_protocol::run_conversation(writer, reader, buf, initial, callback) }) } + + pub(crate) fn number_of_active_req(&self) -> u32 { + self.active.load(Ordering::Acquire) + } } /// Manages the execution of the proc-macro server process. diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport.rs index b7a1d8f7322c..f383edb0cbbb 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport.rs @@ -1,3 +1,3 @@ //! Contains construct for transport of messages. -pub mod codec; -pub mod framing; +pub(crate) mod json; +pub(crate) mod postcard; diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec.rs deleted file mode 100644 index c9afad260a56..000000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Protocol codec - -use std::io; - -use serde::de::DeserializeOwned; - -use crate::transport::framing::Framing; - -pub mod json; -pub mod postcard; - -pub trait Codec: Framing { - fn encode(msg: &T) -> io::Result; - fn decode(buf: &mut Self::Buf) -> io::Result; -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec/json.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec/json.rs deleted file mode 100644 index 96db802e0bfd..000000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec/json.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! Protocol functions for json. -use std::io::{self, BufRead, Write}; - -use serde::{Serialize, de::DeserializeOwned}; - -use crate::{Codec, transport::framing::Framing}; - -pub struct JsonProtocol; - -impl Framing for JsonProtocol { - type Buf = String; - - fn read<'a, R: BufRead + ?Sized>( - inp: &mut R, - buf: &'a mut String, - ) -> io::Result> { - loop { - buf.clear(); - - inp.read_line(buf)?; - buf.pop(); // Remove trailing '\n' - - if buf.is_empty() { - return Ok(None); - } - - // Some ill behaved macro try to use stdout for debugging - // We ignore it here - if !buf.starts_with('{') { - tracing::error!("proc-macro tried to print : {}", buf); - continue; - } - - return Ok(Some(buf)); - } - } - - fn write(out: &mut W, buf: &String) -> io::Result<()> { - tracing::debug!("> {}", buf); - out.write_all(buf.as_bytes())?; - out.write_all(b"\n")?; - out.flush() - } -} - -impl Codec for JsonProtocol { - fn encode(msg: &T) -> io::Result { - Ok(serde_json::to_string(msg)?) - } - - fn decode(buf: &mut String) -> io::Result { - let mut deserializer = serde_json::Deserializer::from_str(buf); - // Note that some proc-macro generate very deep syntax tree - // We have to disable the current limit of serde here - deserializer.disable_recursion_limit(); - Ok(T::deserialize(&mut deserializer)?) - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec/postcard.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec/postcard.rs deleted file mode 100644 index 6f5319e75b37..000000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec/postcard.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Postcard encode and decode implementations. - -use std::io::{self, BufRead, Write}; - -use serde::{Serialize, de::DeserializeOwned}; - -use crate::{Codec, transport::framing::Framing}; - -pub struct PostcardProtocol; - -impl Framing for PostcardProtocol { - type Buf = Vec; - - fn read<'a, R: BufRead + ?Sized>( - inp: &mut R, - buf: &'a mut Vec, - ) -> io::Result>> { - buf.clear(); - let n = inp.read_until(0, buf)?; - if n == 0 { - return Ok(None); - } - Ok(Some(buf)) - } - - fn write(out: &mut W, buf: &Vec) -> io::Result<()> { - out.write_all(buf)?; - out.flush() - } -} - -impl Codec for PostcardProtocol { - fn encode(msg: &T) -> io::Result> { - postcard::to_allocvec_cobs(msg).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) - } - - fn decode(buf: &mut Self::Buf) -> io::Result { - postcard::from_bytes_cobs(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs deleted file mode 100644 index 56c3b68e8cd2..000000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Protocol framing - -use std::io::{self, BufRead, Write}; - -pub trait Framing { - type Buf: Default + Send + Sync; - - fn read<'a, R: BufRead + ?Sized>( - inp: &mut R, - buf: &'a mut Self::Buf, - ) -> io::Result>; - - fn write(out: &mut W, buf: &Self::Buf) -> io::Result<()>; -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/json.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/json.rs new file mode 100644 index 000000000000..f433bb7de033 --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/json.rs @@ -0,0 +1,48 @@ +//! Protocol functions for json. +use std::io::{self, BufRead, Write}; + +use serde::{Serialize, de::DeserializeOwned}; + +pub(crate) fn read<'a, R: BufRead + ?Sized>( + inp: &mut R, + buf: &'a mut String, +) -> io::Result> { + loop { + buf.clear(); + + inp.read_line(buf)?; + buf.pop(); // Remove trailing '\n' + + if buf.is_empty() { + return Ok(None); + } + + // Some ill behaved macro try to use stdout for debugging + // We ignore it here + if !buf.starts_with('{') { + tracing::error!("proc-macro tried to print : {}", buf); + continue; + } + + return Ok(Some(buf)); + } +} + +pub(crate) fn write(out: &mut W, buf: &String) -> io::Result<()> { + tracing::debug!("> {}", buf); + out.write_all(buf.as_bytes())?; + out.write_all(b"\n")?; + out.flush() +} + +pub(crate) fn encode(msg: &T) -> io::Result { + Ok(serde_json::to_string(msg)?) +} + +pub(crate) fn decode(buf: &mut str) -> io::Result { + let mut deserializer = serde_json::Deserializer::from_str(buf); + // Note that some proc-macro generate very deep syntax tree + // We have to disable the current limit of serde here + deserializer.disable_recursion_limit(); + Ok(T::deserialize(&mut deserializer)?) +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/postcard.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/postcard.rs new file mode 100644 index 000000000000..75aa90e4c480 --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/postcard.rs @@ -0,0 +1,30 @@ +//! Postcard encode and decode implementations. + +use std::io::{self, BufRead, Write}; + +use serde::{Serialize, de::DeserializeOwned}; + +pub(crate) fn read<'a, R: BufRead + ?Sized>( + inp: &mut R, + buf: &'a mut Vec, +) -> io::Result>> { + buf.clear(); + let n = inp.read_until(0, buf)?; + if n == 0 { + return Ok(None); + } + Ok(Some(buf)) +} + +pub(crate) fn write(out: &mut W, buf: &[u8]) -> io::Result<()> { + out.write_all(buf)?; + out.flush() +} + +pub(crate) fn encode(msg: &T) -> io::Result> { + postcard::to_allocvec_cobs(msg).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) +} + +pub(crate) fn decode(buf: &mut [u8]) -> io::Result { + postcard::from_bytes_cobs(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml index 6b2db0b269d5..f586fe7644d7 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml @@ -10,12 +10,25 @@ license.workspace = true rust-version.workspace = true publish = false +[lib] +doctest = false + [dependencies] proc-macro-srv.workspace = true proc-macro-api.workspace = true -postcard.workspace = true clap = {version = "4.5.42", default-features = false, features = ["std"]} +[dev-dependencies] +expect-test.workspace = true +paths.workspace = true +# span = {workspace = true, default-features = false} does not work +span = { path = "../span", default-features = false} +tt.workspace = true +intern.workspace = true + +# used as proc macro test target +proc-macro-test.path = "../proc-macro-srv/proc-macro-test" + [features] default = [] # default = ["sysroot-abi"] diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/lib.rs new file mode 100644 index 000000000000..8475c05ae8a1 --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/lib.rs @@ -0,0 +1,11 @@ +//! Library interface for `proc-macro-srv-cli`. +//! +//! This module exposes the server main loop and protocol format for integration testing. + +#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] + +#[cfg(feature = "in-rust-tree")] +extern crate rustc_driver as _; + +#[cfg(feature = "sysroot-abi")] +pub mod main_loop; diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs index bdfdb50002e1..928753659f1c 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs @@ -9,11 +9,11 @@ extern crate rustc_driver as _; mod version; -#[cfg(feature = "sysroot-abi")] -mod main_loop; use clap::{Command, ValueEnum}; +use proc_macro_api::ProtocolFormat; + #[cfg(feature = "sysroot-abi")] -use main_loop::run; +use proc_macro_srv_cli::main_loop::run; fn main() -> std::io::Result<()> { let v = std::env::var("RUST_ANALYZER_INTERNALS_DO_NOT_USE"); @@ -32,7 +32,7 @@ fn main() -> std::io::Result<()> { .long("format") .action(clap::ArgAction::Set) .default_value("json-legacy") - .value_parser(clap::builder::EnumValueParser::::new()), + .value_parser(clap::builder::EnumValueParser::::new()), clap::Arg::new("version") .long("version") .action(clap::ArgAction::SetTrue) @@ -43,44 +43,48 @@ fn main() -> std::io::Result<()> { println!("rust-analyzer-proc-macro-srv {}", version::version()); return Ok(()); } - let &format = - matches.get_one::("format").expect("format value should always be present"); - run(format) + let &format = matches + .get_one::("format") + .expect("format value should always be present"); + + let mut stdin = std::io::BufReader::new(std::io::stdin()); + let mut stdout = std::io::stdout(); + + run(&mut stdin, &mut stdout, format.into()) } +/// Wrapper for CLI argument parsing that implements `ValueEnum`. #[derive(Copy, Clone)] -enum ProtocolFormat { - JsonLegacy, - PostcardLegacy, - BidirectionalPostcardPrototype, +struct ProtocolFormatArg(ProtocolFormat); + +impl From for ProtocolFormat { + fn from(arg: ProtocolFormatArg) -> Self { + arg.0 + } } -impl ValueEnum for ProtocolFormat { +impl ValueEnum for ProtocolFormatArg { fn value_variants<'a>() -> &'a [Self] { &[ - ProtocolFormat::JsonLegacy, - ProtocolFormat::PostcardLegacy, - ProtocolFormat::BidirectionalPostcardPrototype, + ProtocolFormatArg(ProtocolFormat::JsonLegacy), + ProtocolFormatArg(ProtocolFormat::BidirectionalPostcardPrototype), ] } fn to_possible_value(&self) -> Option { - match self { + match self.0 { ProtocolFormat::JsonLegacy => Some(clap::builder::PossibleValue::new("json-legacy")), - ProtocolFormat::PostcardLegacy => { - Some(clap::builder::PossibleValue::new("postcard-legacy")) - } ProtocolFormat::BidirectionalPostcardPrototype => { Some(clap::builder::PossibleValue::new("bidirectional-postcard-prototype")) } } } + fn from_str(input: &str, _ignore_case: bool) -> Result { match input { - "json-legacy" => Ok(ProtocolFormat::JsonLegacy), - "postcard-legacy" => Ok(ProtocolFormat::PostcardLegacy), + "json-legacy" => Ok(ProtocolFormatArg(ProtocolFormat::JsonLegacy)), "bidirectional-postcard-prototype" => { - Ok(ProtocolFormat::BidirectionalPostcardPrototype) + Ok(ProtocolFormatArg(ProtocolFormat::BidirectionalPostcardPrototype)) } _ => Err(format!("unknown protocol format: {input}")), } @@ -88,7 +92,11 @@ impl ValueEnum for ProtocolFormat { } #[cfg(not(feature = "sysroot-abi"))] -fn run(_: ProtocolFormat) -> std::io::Result<()> { +fn run( + _: &mut std::io::BufReader, + _: &mut std::io::Stdout, + _: ProtocolFormat, +) -> std::io::Result<()> { Err(std::io::Error::new( std::io::ErrorKind::Unsupported, "proc-macro-srv-cli needs to be compiled with the `sysroot-abi` feature to function" diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs index b2f4b96bd255..9be3199a3836 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -1,18 +1,18 @@ //! The main loop of the proc-macro server. use proc_macro_api::{ - Codec, - bidirectional_protocol::msg as bidirectional, - legacy_protocol::msg as legacy, - transport::codec::{json::JsonProtocol, postcard::PostcardProtocol}, + ProtocolFormat, bidirectional_protocol::msg as bidirectional, legacy_protocol::msg as legacy, version::CURRENT_API_VERSION, }; -use std::io; +use std::panic::{panic_any, resume_unwind}; +use std::{ + io::{self, BufRead, Write}, + ops::Range, +}; use legacy::Message; -use proc_macro_srv::{EnvSnapshot, SpanId}; +use proc_macro_srv::{EnvSnapshot, ProcMacroClientError, ProcMacroPanicMarker, SpanId}; -use crate::ProtocolFormat; struct SpanTrans; impl legacy::SpanTransformer for SpanTrans { @@ -32,15 +32,21 @@ impl legacy::SpanTransformer for SpanTrans { } } -pub(crate) fn run(format: ProtocolFormat) -> io::Result<()> { +pub fn run( + stdin: &mut (dyn BufRead + Send + Sync), + stdout: &mut (dyn Write + Send + Sync), + format: ProtocolFormat, +) -> io::Result<()> { match format { - ProtocolFormat::JsonLegacy => run_::(), - ProtocolFormat::PostcardLegacy => run_::(), - ProtocolFormat::BidirectionalPostcardPrototype => run_new::(), + ProtocolFormat::JsonLegacy => run_old(stdin, stdout), + ProtocolFormat::BidirectionalPostcardPrototype => run_new(stdin, stdout), } } -fn run_new() -> io::Result<()> { +fn run_new( + stdin: &mut (dyn BufRead + Send + Sync), + stdout: &mut (dyn Write + Send + Sync), +) -> io::Result<()> { fn macro_kind_to_api(kind: proc_macro_srv::ProcMacroKind) -> proc_macro_api::ProcMacroKind { match kind { proc_macro_srv::ProcMacroKind::CustomDerive => { @@ -51,9 +57,7 @@ fn run_new() -> io::Result<()> { } } - let mut buf = C::Buf::default(); - let mut stdin = io::stdin(); - let mut stdout = io::stdout(); + let mut buf = Vec::default(); let env_snapshot = EnvSnapshot::default(); let srv = proc_macro_srv::ProcMacroSrv::new(&env_snapshot); @@ -61,8 +65,7 @@ fn run_new() -> io::Result<()> { let mut span_mode = legacy::SpanMode::Id; 'outer: loop { - let req_opt = - bidirectional::BidirectionalMessage::read::<_, C>(&mut stdin.lock(), &mut buf)?; + let req_opt = bidirectional::BidirectionalMessage::read(stdin, &mut buf)?; let Some(req) = req_opt else { break 'outer; }; @@ -77,22 +80,22 @@ fn run_new() -> io::Result<()> { .collect() }); - send_response::(&stdout, bidirectional::Response::ListMacros(res))?; + send_response(stdout, bidirectional::Response::ListMacros(res))?; } bidirectional::Request::ApiVersionCheck {} => { - send_response::( - &stdout, + send_response( + stdout, bidirectional::Response::ApiVersionCheck(CURRENT_API_VERSION), )?; } bidirectional::Request::SetConfig(config) => { span_mode = config.span_mode; - send_response::(&stdout, bidirectional::Response::SetConfig(config))?; + send_response(stdout, bidirectional::Response::SetConfig(config))?; } bidirectional::Request::ExpandMacro(task) => { - handle_expand::(&srv, &mut stdin, &mut stdout, &mut buf, span_mode, *task)?; + handle_expand(&srv, stdin, stdout, &mut buf, span_mode, *task)?; } }, _ => continue, @@ -102,23 +105,23 @@ fn run_new() -> io::Result<()> { Ok(()) } -fn handle_expand( +fn handle_expand( srv: &proc_macro_srv::ProcMacroSrv<'_>, - stdin: &io::Stdin, - stdout: &io::Stdout, - buf: &mut C::Buf, + stdin: &mut (dyn BufRead + Send + Sync), + stdout: &mut (dyn Write + Send + Sync), + buf: &mut Vec, span_mode: legacy::SpanMode, task: bidirectional::ExpandMacro, ) -> io::Result<()> { match span_mode { - legacy::SpanMode::Id => handle_expand_id::(srv, stdout, task), - legacy::SpanMode::RustAnalyzer => handle_expand_ra::(srv, stdin, stdout, buf, task), + legacy::SpanMode::Id => handle_expand_id(srv, stdout, task), + legacy::SpanMode::RustAnalyzer => handle_expand_ra(srv, stdin, stdout, buf, task), } } -fn handle_expand_id( +fn handle_expand_id( srv: &proc_macro_srv::ProcMacroSrv<'_>, - stdout: &io::Stdout, + stdout: &mut dyn Write, task: bidirectional::ExpandMacro, ) -> io::Result<()> { let bidirectional::ExpandMacro { lib, env, current_dir, data } = task; @@ -157,40 +160,65 @@ fn handle_expand_id( }) .map_err(|e| legacy::PanicMessage(e.into_string().unwrap_or_default())); - send_response::(&stdout, bidirectional::Response::ExpandMacro(res)) + send_response(stdout, bidirectional::Response::ExpandMacro(res)) } -struct ProcMacroClientHandle<'a, C: Codec> { - stdin: &'a io::Stdin, - stdout: &'a io::Stdout, - buf: &'a mut C::Buf, +struct ProcMacroClientHandle<'a> { + stdin: &'a mut (dyn BufRead + Send + Sync), + stdout: &'a mut (dyn Write + Send + Sync), + buf: &'a mut Vec, } -impl<'a, C: Codec> ProcMacroClientHandle<'a, C> { +impl<'a> ProcMacroClientHandle<'a> { fn roundtrip( &mut self, req: bidirectional::SubRequest, - ) -> Option { + ) -> Result { let msg = bidirectional::BidirectionalMessage::SubRequest(req); - if msg.write::<_, C>(&mut self.stdout.lock()).is_err() { - return None; - } + msg.write(&mut *self.stdout).map_err(ProcMacroClientError::Io)?; - match bidirectional::BidirectionalMessage::read::<_, C>(&mut self.stdin.lock(), self.buf) { - Ok(Some(msg)) => Some(msg), - _ => None, + let msg = bidirectional::BidirectionalMessage::read(&mut *self.stdin, self.buf) + .map_err(ProcMacroClientError::Io)? + .ok_or(ProcMacroClientError::Eof)?; + + match msg { + bidirectional::BidirectionalMessage::SubResponse(resp) => match resp { + bidirectional::SubResponse::Cancel { reason } => { + Err(ProcMacroClientError::Cancelled { reason }) + } + other => Ok(other), + }, + other => { + Err(ProcMacroClientError::Protocol(format!("expected SubResponse, got {other:?}"))) + } } } } -impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle<'_, C> { +fn handle_failure(failure: Result) -> ! { + match failure { + Err(ProcMacroClientError::Cancelled { reason }) => { + resume_unwind(Box::new(ProcMacroPanicMarker::Cancelled { reason })); + } + Err(err) => { + panic_any(ProcMacroPanicMarker::Internal { + reason: format!("proc-macro IPC error: {err:?}"), + }); + } + Ok(other) => { + panic_any(ProcMacroPanicMarker::Internal { + reason: format!("unexpected SubResponse {other:?}"), + }); + } + } +} + +impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle<'_> { fn file(&mut self, file_id: proc_macro_srv::span::FileId) -> String { match self.roundtrip(bidirectional::SubRequest::FilePath { file_id: file_id.index() }) { - Some(bidirectional::BidirectionalMessage::SubResponse( - bidirectional::SubResponse::FilePathResult { name }, - )) => name, - _ => String::new(), + Ok(bidirectional::SubResponse::FilePathResult { name }) => name, + other => handle_failure(other), } } @@ -204,29 +232,54 @@ impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandl start: range.start().into(), end: range.end().into(), }) { - Some(bidirectional::BidirectionalMessage::SubResponse( - bidirectional::SubResponse::SourceTextResult { text }, - )) => text, - _ => None, + Ok(bidirectional::SubResponse::SourceTextResult { text }) => text, + other => handle_failure(other), } } fn local_file(&mut self, file_id: proc_macro_srv::span::FileId) -> Option { match self.roundtrip(bidirectional::SubRequest::LocalFilePath { file_id: file_id.index() }) { - Some(bidirectional::BidirectionalMessage::SubResponse( - bidirectional::SubResponse::LocalFilePathResult { name }, - )) => name, - _ => None, + Ok(bidirectional::SubResponse::LocalFilePathResult { name }) => name, + other => handle_failure(other), + } + } + + fn line_column(&mut self, span: proc_macro_srv::span::Span) -> Option<(u32, u32)> { + let proc_macro_srv::span::Span { range, anchor, ctx: _ } = span; + match self.roundtrip(bidirectional::SubRequest::LineColumn { + file_id: anchor.file_id.as_u32(), + ast_id: anchor.ast_id.into_raw(), + offset: range.start().into(), + }) { + Ok(bidirectional::SubResponse::LineColumnResult { line, column }) => { + Some((line, column)) + } + other => handle_failure(other), + } + } + + fn byte_range( + &mut self, + proc_macro_srv::span::Span { range, anchor, ctx: _ }: proc_macro_srv::span::Span, + ) -> Range { + match self.roundtrip(bidirectional::SubRequest::ByteRange { + file_id: anchor.file_id.as_u32(), + ast_id: anchor.ast_id.into_raw(), + start: range.start().into(), + end: range.end().into(), + }) { + Ok(bidirectional::SubResponse::ByteRangeResult { range }) => range, + other => handle_failure(other), } } } -fn handle_expand_ra( +fn handle_expand_ra( srv: &proc_macro_srv::ProcMacroSrv<'_>, - stdin: &io::Stdin, - stdout: &io::Stdout, - buf: &mut C::Buf, + stdin: &mut (dyn BufRead + Send + Sync), + stdout: &mut (dyn Write + Send + Sync), + buf: &mut Vec, task: bidirectional::ExpandMacro, ) -> io::Result<()> { let bidirectional::ExpandMacro { @@ -271,7 +324,7 @@ fn handle_expand_ra( def_site, call_site, mixed_site, - Some(&mut ProcMacroClientHandle:: { stdin, stdout, buf }), + Some(&mut ProcMacroClientHandle { stdin, stdout, buf }), ) .map(|it| { ( @@ -287,10 +340,13 @@ fn handle_expand_ra( .map(|(tree, span_data_table)| bidirectional::ExpandMacroExtended { tree, span_data_table }) .map_err(|e| legacy::PanicMessage(e.into_string().unwrap_or_default())); - send_response::(&stdout, bidirectional::Response::ExpandMacroExtended(res)) + send_response(stdout, bidirectional::Response::ExpandMacroExtended(res)) } -fn run_() -> io::Result<()> { +fn run_old( + stdin: &mut (dyn BufRead + Send + Sync), + stdout: &mut (dyn Write + Send + Sync), +) -> io::Result<()> { fn macro_kind_to_api(kind: proc_macro_srv::ProcMacroKind) -> proc_macro_api::ProcMacroKind { match kind { proc_macro_srv::ProcMacroKind::CustomDerive => { @@ -301,9 +357,9 @@ fn run_() -> io::Result<()> { } } - let mut buf = C::Buf::default(); - let mut read_request = || legacy::Request::read::<_, C>(&mut io::stdin().lock(), &mut buf); - let write_response = |msg: legacy::Response| msg.write::<_, C>(&mut io::stdout().lock()); + let mut buf = String::default(); + let mut read_request = || legacy::Request::read(stdin, &mut buf); + let mut write_response = |msg: legacy::Response| msg.write(stdout); let env = EnvSnapshot::default(); let srv = proc_macro_srv::ProcMacroSrv::new(&env); @@ -432,7 +488,7 @@ fn run_() -> io::Result<()> { Ok(()) } -fn send_response(stdout: &io::Stdout, resp: bidirectional::Response) -> io::Result<()> { +fn send_response(stdout: &mut dyn Write, resp: bidirectional::Response) -> io::Result<()> { let resp = bidirectional::BidirectionalMessage::Response(resp); - resp.write::<_, C>(&mut stdout.lock()) + resp.write(stdout) } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/tests/bidirectional_postcard.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/tests/bidirectional_postcard.rs new file mode 100644 index 000000000000..ba9657a9bb45 --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/tests/bidirectional_postcard.rs @@ -0,0 +1,229 @@ +#![cfg(feature = "sysroot-abi")] +#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] + +#[cfg(feature = "in-rust-tree")] +extern crate rustc_driver as _; + +mod common { + pub(crate) mod utils; +} + +use common::utils::{ + create_empty_token_tree, proc_macro_test_dylib_path, request_bidirectional, with_server, +}; +use expect_test::expect; +use proc_macro_api::{ + ProtocolFormat::BidirectionalPostcardPrototype, + bidirectional_protocol::{ + msg::{ExpandMacro, ExpandMacroData, ExpnGlobals, Request, Response}, + reject_subrequests, + }, + legacy_protocol::msg::{PanicMessage, ServerConfig, SpanDataIndexMap, SpanMode}, + version::CURRENT_API_VERSION, +}; + +#[test] +fn test_bidi_version_check_bidirectional() { + with_server(BidirectionalPostcardPrototype, |writer, reader| { + let response = + request_bidirectional(writer, reader, Request::ApiVersionCheck {}, reject_subrequests); + + match response { + Response::ApiVersionCheck(version) => { + assert_eq!(version, CURRENT_API_VERSION); + } + other => panic!("unexpected response: {other:?}"), + } + }); +} + +#[test] +fn test_bidi_list_macros() { + with_server(BidirectionalPostcardPrototype, |writer, reader| { + let dylib_path = proc_macro_test_dylib_path(); + let response = request_bidirectional( + writer, + reader, + Request::ListMacros { dylib_path }, + &reject_subrequests, + ); + + let Response::ListMacros(Ok(macros)) = response else { + panic!("expected successful ListMacros response"); + }; + + let mut macro_list: Vec<_> = + macros.iter().map(|(name, kind)| format!("{name} [{kind:?}]")).collect(); + macro_list.sort(); + let macro_list_str = macro_list.join("\n"); + + expect![[r#" + DeriveEmpty [CustomDerive] + DeriveError [CustomDerive] + DerivePanic [CustomDerive] + DeriveReemit [CustomDerive] + attr_error [Attr] + attr_noop [Attr] + attr_panic [Attr] + fn_like_clone_tokens [Bang] + fn_like_error [Bang] + fn_like_mk_idents [Bang] + fn_like_mk_literals [Bang] + fn_like_noop [Bang] + fn_like_panic [Bang] + fn_like_span_join [Bang] + fn_like_span_line_column [Bang] + fn_like_span_ops [Bang]"#]] + .assert_eq(¯o_list_str); + }); +} + +#[test] +fn test_bidi_list_macros_invalid_path() { + with_server(BidirectionalPostcardPrototype, |writer, reader| { + let response = request_bidirectional( + writer, + reader, + Request::ListMacros { dylib_path: "/nonexistent/path/to/dylib.so".into() }, + reject_subrequests, + ); + + match response { + Response::ListMacros(Err(e)) => assert!( + e.starts_with("Cannot create expander for /nonexistent/path/to/dylib.so"), + "{e}" + ), + other => panic!("expected error response, got: {other:?}"), + } + }); +} + +#[test] +fn test_bidi_set_config() { + with_server(BidirectionalPostcardPrototype, |writer, reader| { + let config = ServerConfig { span_mode: SpanMode::Id }; + let response = + request_bidirectional(writer, reader, Request::SetConfig(config), reject_subrequests); + + match response { + Response::SetConfig(returned_config) => { + assert_eq!(returned_config.span_mode, SpanMode::Id); + } + other => panic!("unexpected response: {other:?}"), + } + }); +} + +#[test] +fn test_bidi_set_config_rust_analyzer_mode() { + with_server(BidirectionalPostcardPrototype, |writer, reader| { + let config = ServerConfig { span_mode: SpanMode::RustAnalyzer }; + let response = + request_bidirectional(writer, reader, Request::SetConfig(config), reject_subrequests); + + match response { + Response::SetConfig(returned_config) => { + assert_eq!(returned_config.span_mode, SpanMode::RustAnalyzer); + } + other => panic!("unexpected response: {other:?}"), + } + }); +} + +#[test] +fn test_bidi_expand_macro_panic() { + with_server(BidirectionalPostcardPrototype, |writer, reader| { + let dylib_path = proc_macro_test_dylib_path(); + + let mut span_data_table = SpanDataIndexMap::default(); + let macro_body = + common::utils::create_empty_token_tree(CURRENT_API_VERSION, &mut span_data_table); + + let request1 = Request::ExpandMacro(Box::new(ExpandMacro { + lib: dylib_path, + env: vec![], + current_dir: None, + data: ExpandMacroData { + macro_body, + macro_name: "fn_like_panic".to_owned(), + attributes: None, + has_global_spans: ExpnGlobals { def_site: 0, call_site: 0, mixed_site: 0 }, + span_data_table: vec![], + }, + })); + + let response = request_bidirectional(writer, reader, request1, reject_subrequests); + + match response { + Response::ExpandMacro(Err(PanicMessage(msg))) => { + assert!(msg.contains("fn_like_panic"), "panic message should mention macro name"); + } + other => panic!("expected panic response, got: {other:?}"), + } + }); +} + +#[test] +fn test_bidi_basic_call_flow() { + with_server(BidirectionalPostcardPrototype, |writer, reader| { + let dylib_path = proc_macro_test_dylib_path(); + + let response1 = + request_bidirectional(writer, reader, Request::ApiVersionCheck {}, reject_subrequests); + assert!(matches!(response1, Response::ApiVersionCheck(_))); + + let response2 = request_bidirectional( + writer, + reader, + Request::SetConfig(ServerConfig { span_mode: SpanMode::Id }), + reject_subrequests, + ); + assert!(matches!(response2, Response::SetConfig(_))); + + let response3 = request_bidirectional( + writer, + reader, + Request::ListMacros { dylib_path: dylib_path.clone() }, + reject_subrequests, + ); + assert!(matches!(response3, Response::ListMacros(Ok(_)))); + }); +} + +#[test] +fn test_bidi_expand_nonexistent_macro() { + with_server(BidirectionalPostcardPrototype, |writer, reader| { + let dylib_path = proc_macro_test_dylib_path(); + + let version_response = + request_bidirectional(writer, reader, Request::ApiVersionCheck {}, reject_subrequests); + let Response::ApiVersionCheck(version) = version_response else { + panic!("expected version check response"); + }; + + let mut span_data_table = SpanDataIndexMap::default(); + let macro_body = create_empty_token_tree(version, &mut span_data_table); + + let expand_request = Request::ExpandMacro(Box::new(ExpandMacro { + lib: dylib_path, + env: vec![], + current_dir: None, + data: ExpandMacroData { + macro_body, + macro_name: "NonexistentMacro".to_owned(), + attributes: None, + has_global_spans: ExpnGlobals { def_site: 0, call_site: 0, mixed_site: 0 }, + span_data_table: vec![], + }, + })); + + let response = request_bidirectional(writer, reader, expand_request, reject_subrequests); + + match response { + Response::ExpandMacro(Err(PanicMessage(msg))) => { + expect!["proc-macro `NonexistentMacro` is missing"].assert_eq(&msg) + } + other => panic!("expected error for nonexistent macro, got: {other:?}"), + } + }); +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/tests/common/utils.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/tests/common/utils.rs new file mode 100644 index 000000000000..3049e9800405 --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/tests/common/utils.rs @@ -0,0 +1,288 @@ +use std::{ + collections::VecDeque, + io::{self, BufRead, Read, Write}, + sync::{Arc, Condvar, Mutex}, + thread, +}; + +use paths::Utf8PathBuf; +use proc_macro_api::{ + ServerError, + bidirectional_protocol::msg::{ + BidirectionalMessage, Request as BiRequest, Response as BiResponse, SubRequest, SubResponse, + }, + legacy_protocol::msg::{FlatTree, Message, Request, Response, SpanDataIndexMap}, +}; +use span::{Edition, EditionedFileId, FileId, Span, SpanAnchor, SyntaxContext, TextRange}; +use tt::{Delimiter, DelimiterKind, TopSubtreeBuilder}; + +/// Shared state for an in-memory byte channel. +#[derive(Default)] +struct ChannelState { + buffer: VecDeque, + closed: bool, +} + +type InMemoryChannel = Arc<(Mutex, Condvar)>; + +/// Writer end of an in-memory channel. +pub(crate) struct ChannelWriter { + state: InMemoryChannel, +} + +impl Write for ChannelWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + let (lock, cvar) = &*self.state; + let mut state = lock.lock().unwrap(); + if state.closed { + return Err(io::Error::new(io::ErrorKind::BrokenPipe, "channel closed")); + } + state.buffer.extend(buf); + cvar.notify_all(); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Drop for ChannelWriter { + fn drop(&mut self) { + let (lock, cvar) = &*self.state; + let mut state = lock.lock().unwrap(); + state.closed = true; + cvar.notify_all(); + } +} + +/// Reader end of an in-memory channel. +pub(crate) struct ChannelReader { + state: InMemoryChannel, + internal_buf: Vec, +} + +impl Read for ChannelReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let (lock, cvar) = &*self.state; + let mut state = lock.lock().unwrap(); + + while state.buffer.is_empty() && !state.closed { + state = cvar.wait(state).unwrap(); + } + + if state.buffer.is_empty() && state.closed { + return Ok(0); + } + + let to_read = buf.len().min(state.buffer.len()); + for (dst, src) in buf.iter_mut().zip(state.buffer.drain(..to_read)) { + *dst = src; + } + Ok(to_read) + } +} + +impl BufRead for ChannelReader { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + let (lock, cvar) = &*self.state; + let mut state = lock.lock().unwrap(); + + while state.buffer.is_empty() && !state.closed { + state = cvar.wait(state).unwrap(); + } + + self.internal_buf.clear(); + self.internal_buf.extend(&state.buffer); + Ok(&self.internal_buf) + } + + fn consume(&mut self, amt: usize) { + let (lock, _) = &*self.state; + let mut state = lock.lock().unwrap(); + let to_drain = amt.min(state.buffer.len()); + drop(state.buffer.drain(..to_drain)); + } +} + +/// Creates a connected pair of channels for bidirectional communication. +fn create_channel_pair() -> (ChannelWriter, ChannelReader, ChannelWriter, ChannelReader) { + // Channel for client -> server communication + let client_to_server = Arc::new(( + Mutex::new(ChannelState { buffer: VecDeque::new(), closed: false }), + Condvar::new(), + )); + let client_writer = ChannelWriter { state: client_to_server.clone() }; + let server_reader = ChannelReader { state: client_to_server, internal_buf: Vec::new() }; + + // Channel for server -> client communication + let server_to_client = Arc::new(( + Mutex::new(ChannelState { buffer: VecDeque::new(), closed: false }), + Condvar::new(), + )); + + let server_writer = ChannelWriter { state: server_to_client.clone() }; + let client_reader = ChannelReader { state: server_to_client, internal_buf: Vec::new() }; + + (client_writer, client_reader, server_writer, server_reader) +} + +pub(crate) fn proc_macro_test_dylib_path() -> Utf8PathBuf { + let path = proc_macro_test::PROC_MACRO_TEST_LOCATION; + if path.is_empty() { + panic!("proc-macro-test dylib not available (requires nightly toolchain)"); + } + path.into() +} + +/// Creates a simple empty token tree suitable for testing. +pub(crate) fn create_empty_token_tree( + version: u32, + span_data_table: &mut SpanDataIndexMap, +) -> FlatTree { + let anchor = SpanAnchor { + file_id: EditionedFileId::new(FileId::from_raw(0), Edition::CURRENT), + ast_id: span::ROOT_ERASED_FILE_AST_ID, + }; + let span = Span { + range: TextRange::empty(0.into()), + anchor, + ctx: SyntaxContext::root(Edition::CURRENT), + }; + + let builder = TopSubtreeBuilder::new(Delimiter { + open: span, + close: span, + kind: DelimiterKind::Invisible, + }); + let tt = builder.build(); + + FlatTree::from_subtree(tt.view(), version, span_data_table) +} + +pub(crate) fn with_server(format: proc_macro_api::ProtocolFormat, test_fn: F) -> R +where + F: FnOnce(&mut dyn Write, &mut dyn BufRead) -> R, +{ + let (mut client_writer, mut client_reader, mut server_writer, mut server_reader) = + create_channel_pair(); + + let server_handle = thread::spawn(move || { + proc_macro_srv_cli::main_loop::run(&mut server_reader, &mut server_writer, format) + }); + + let result = test_fn(&mut client_writer, &mut client_reader); + + drop(client_writer); + + match server_handle.join() { + Ok(Ok(())) => {} + Ok(Err(e)) => { + if !matches!( + e.kind(), + io::ErrorKind::BrokenPipe + | io::ErrorKind::UnexpectedEof + | io::ErrorKind::InvalidData + ) { + panic!("Server error: {e}"); + } + } + Err(e) => std::panic::resume_unwind(e), + } + + result +} + +trait TestProtocol { + type Request; + type Response; + + fn request(&self, writer: &mut dyn Write, req: Self::Request); + fn receive(&self, reader: &mut dyn BufRead, writer: &mut dyn Write) -> Self::Response; +} + +#[allow(dead_code)] +struct JsonLegacy; + +impl TestProtocol for JsonLegacy { + type Request = Request; + type Response = Response; + + fn request(&self, writer: &mut dyn Write, req: Request) { + req.write(writer).expect("failed to write request"); + } + + fn receive(&self, reader: &mut dyn BufRead, _writer: &mut dyn Write) -> Response { + let mut buf = String::new(); + Response::read(reader, &mut buf) + .expect("failed to read response") + .expect("no response received") + } +} + +#[allow(dead_code)] +struct PostcardBidirectional +where + F: Fn(SubRequest) -> Result, +{ + callback: F, +} + +impl TestProtocol for PostcardBidirectional +where + F: Fn(SubRequest) -> Result, +{ + type Request = BiRequest; + type Response = BiResponse; + + fn request(&self, writer: &mut dyn Write, req: BiRequest) { + let msg = BidirectionalMessage::Request(req); + msg.write(writer).expect("failed to write request"); + } + + fn receive(&self, reader: &mut dyn BufRead, writer: &mut dyn Write) -> BiResponse { + let mut buf = Vec::new(); + + loop { + let msg = BidirectionalMessage::read(reader, &mut buf) + .expect("failed to read message") + .expect("no message received"); + + match msg { + BidirectionalMessage::Response(resp) => return resp, + BidirectionalMessage::SubRequest(sr) => { + let reply = (self.callback)(sr).expect("subrequest callback failed"); + let msg = BidirectionalMessage::SubResponse(reply); + msg.write(writer).expect("failed to write subresponse"); + } + other => panic!("unexpected message: {other:?}"), + } + } + } +} + +#[allow(dead_code)] +pub(crate) fn request_legacy( + writer: &mut dyn Write, + reader: &mut dyn BufRead, + request: Request, +) -> Response { + let protocol = JsonLegacy; + protocol.request(writer, request); + protocol.receive(reader, writer) +} + +#[allow(dead_code)] +pub(crate) fn request_bidirectional( + writer: &mut dyn Write, + reader: &mut dyn BufRead, + request: BiRequest, + callback: F, +) -> BiResponse +where + F: Fn(SubRequest) -> Result, +{ + let protocol = PostcardBidirectional { callback }; + protocol.request(writer, request); + protocol.receive(reader, writer) +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/tests/legacy_json.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/tests/legacy_json.rs new file mode 100644 index 000000000000..562cf0c2516f --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/tests/legacy_json.rs @@ -0,0 +1,234 @@ +//! Integration tests for the proc-macro-srv-cli main loop. +//! +//! These tests exercise the full client-server RPC procedure using in-memory +//! channels without needing to spawn the actual server and client processes. + +#![cfg(feature = "sysroot-abi")] +#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] + +#[cfg(feature = "in-rust-tree")] +extern crate rustc_driver as _; + +mod common { + pub(crate) mod utils; +} + +use common::utils::{ + create_empty_token_tree, proc_macro_test_dylib_path, request_legacy, with_server, +}; +use expect_test::expect; +use proc_macro_api::{ + ProtocolFormat::JsonLegacy, + legacy_protocol::msg::{ + ExpandMacro, ExpandMacroData, ExpnGlobals, PanicMessage, Request, Response, ServerConfig, + SpanDataIndexMap, SpanMode, + }, + version::CURRENT_API_VERSION, +}; + +#[test] +fn test_version_check() { + with_server(JsonLegacy, |writer, reader| { + let response = request_legacy(writer, reader, Request::ApiVersionCheck {}); + + match response { + Response::ApiVersionCheck(version) => { + assert_eq!(version, CURRENT_API_VERSION); + } + other => panic!("unexpected response: {other:?}"), + } + }); +} + +#[test] +fn test_list_macros() { + with_server(JsonLegacy, |writer, reader| { + let dylib_path = proc_macro_test_dylib_path(); + let response = request_legacy(writer, reader, Request::ListMacros { dylib_path }); + + let Response::ListMacros(Ok(macros)) = response else { + panic!("expected successful ListMacros response"); + }; + + let mut macro_list: Vec<_> = + macros.iter().map(|(name, kind)| format!("{name} [{kind:?}]")).collect(); + macro_list.sort(); + let macro_list_str = macro_list.join("\n"); + + expect![[r#" + DeriveEmpty [CustomDerive] + DeriveError [CustomDerive] + DerivePanic [CustomDerive] + DeriveReemit [CustomDerive] + attr_error [Attr] + attr_noop [Attr] + attr_panic [Attr] + fn_like_clone_tokens [Bang] + fn_like_error [Bang] + fn_like_mk_idents [Bang] + fn_like_mk_literals [Bang] + fn_like_noop [Bang] + fn_like_panic [Bang] + fn_like_span_join [Bang] + fn_like_span_line_column [Bang] + fn_like_span_ops [Bang]"#]] + .assert_eq(¯o_list_str); + }); +} + +#[test] +fn test_list_macros_invalid_path() { + with_server(JsonLegacy, |writer, reader| { + let response = request_legacy( + writer, + reader, + Request::ListMacros { dylib_path: "/nonexistent/path/to/dylib.so".into() }, + ); + + match response { + Response::ListMacros(Err(e)) => assert!( + e.starts_with("Cannot create expander for /nonexistent/path/to/dylib.so"), + "{e}" + ), + other => panic!("expected error response, got: {other:?}"), + } + }); +} + +#[test] +fn test_set_config() { + with_server(JsonLegacy, |writer, reader| { + let config = ServerConfig { span_mode: SpanMode::Id }; + let response = request_legacy(writer, reader, Request::SetConfig(config)); + + match response { + Response::SetConfig(returned_config) => { + assert_eq!(returned_config.span_mode, SpanMode::Id); + } + other => panic!("unexpected response: {other:?}"), + } + }); +} + +#[test] +fn test_set_config_rust_analyzer_mode() { + with_server(JsonLegacy, |writer, reader| { + let config = ServerConfig { span_mode: SpanMode::RustAnalyzer }; + let response = request_legacy(writer, reader, Request::SetConfig(config)); + + match response { + Response::SetConfig(returned_config) => { + assert_eq!(returned_config.span_mode, SpanMode::RustAnalyzer); + } + other => panic!("unexpected response: {other:?}"), + } + }); +} + +#[test] +fn test_expand_macro_panic() { + with_server(JsonLegacy, |writer, reader| { + let dylib_path = proc_macro_test_dylib_path(); + + let version_response = request_legacy(writer, reader, Request::ApiVersionCheck {}); + let Response::ApiVersionCheck(version) = version_response else { + panic!("expected version check response"); + }; + + let mut span_data_table = SpanDataIndexMap::default(); + let macro_body = create_empty_token_tree(version, &mut span_data_table); + + let expand_request = Request::ExpandMacro(Box::new(ExpandMacro { + lib: dylib_path, + env: vec![], + current_dir: None, + data: ExpandMacroData { + macro_body, + macro_name: "fn_like_panic".to_owned(), + attributes: None, + has_global_spans: ExpnGlobals { + serialize: version >= 3, + def_site: 0, + call_site: 0, + mixed_site: 0, + }, + span_data_table: vec![], + }, + })); + + let response = request_legacy(writer, reader, expand_request); + + match response { + Response::ExpandMacro(Err(PanicMessage(msg))) => { + assert!(msg.contains("fn_like_panic"), "panic message should mention the macro"); + } + Response::ExpandMacro(Ok(_)) => { + panic!("expected panic, but macro succeeded"); + } + other => panic!("unexpected response: {other:?}"), + } + }); +} + +#[test] +fn test_basic_call_flow() { + with_server(JsonLegacy, |writer, reader| { + let dylib_path = proc_macro_test_dylib_path(); + + let response1 = request_legacy(writer, reader, Request::ApiVersionCheck {}); + assert!(matches!(response1, Response::ApiVersionCheck(_))); + + let response2 = request_legacy( + writer, + reader, + Request::SetConfig(ServerConfig { span_mode: SpanMode::Id }), + ); + assert!(matches!(response2, Response::SetConfig(_))); + + let response3 = + request_legacy(writer, reader, Request::ListMacros { dylib_path: dylib_path.clone() }); + assert!(matches!(response3, Response::ListMacros(Ok(_)))); + }); +} + +#[test] +fn test_expand_nonexistent_macro() { + with_server(JsonLegacy, |writer, reader| { + let dylib_path = proc_macro_test_dylib_path(); + + let version_response = request_legacy(writer, reader, Request::ApiVersionCheck {}); + let Response::ApiVersionCheck(version) = version_response else { + panic!("expected version check response"); + }; + + let mut span_data_table = SpanDataIndexMap::default(); + let macro_body = create_empty_token_tree(version, &mut span_data_table); + + let expand_request = Request::ExpandMacro(Box::new(ExpandMacro { + lib: dylib_path, + env: vec![], + current_dir: None, + data: ExpandMacroData { + macro_body, + macro_name: "NonexistentMacro".to_owned(), + attributes: None, + has_global_spans: ExpnGlobals { + serialize: version >= 3, + def_site: 0, + call_site: 0, + mixed_site: 0, + }, + span_data_table: vec![], + }, + })); + + let response = request_legacy(writer, reader, expand_request); + + match response { + Response::ExpandMacro(Err(PanicMessage(msg))) => { + expect!["proc-macro `NonexistentMacro` is missing"].assert_eq(&msg) + } + other => panic!("expected error for nonexistent macro, got: {other:?}"), + } + }); +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml index 361017178409..8e5617f8a20e 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml @@ -31,6 +31,7 @@ libc.workspace = true [dev-dependencies] expect-test.workspace = true +line-index.workspace = true # used as proc macro test targets proc-macro-test.path = "./proc-macro-test" diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs index b4fac26d6e72..06c76b6d0381 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs @@ -79,6 +79,16 @@ pub fn fn_like_span_ops(args: TokenStream) -> TokenStream { TokenStream::from_iter(vec![first, second, third]) } +/// Returns the line and column of the first token's span as two integer literals. +#[proc_macro] +pub fn fn_like_span_line_column(args: TokenStream) -> TokenStream { + let first = args.into_iter().next().unwrap(); + let span = first.span(); + let line = Literal::usize_unsuffixed(span.line()); + let column = Literal::usize_unsuffixed(span.column()); + TokenStream::from_iter(vec![TokenTree::Literal(line), TokenTree::Literal(column)]) +} + #[proc_macro_attribute] pub fn attr_noop(_args: TokenStream, item: TokenStream) -> TokenStream { item diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs index 920d58b4e981..c548dc620ad1 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs @@ -22,8 +22,11 @@ )] #![deny(deprecated_safe, clippy::undocumented_unsafe_blocks)] +#[cfg(not(feature = "in-rust-tree"))] +extern crate proc_macro as rustc_proc_macro; #[cfg(feature = "in-rust-tree")] extern crate rustc_driver as _; +#[cfg(feature = "in-rust-tree")] extern crate rustc_proc_macro; #[cfg(not(feature = "in-rust-tree"))] @@ -41,6 +44,7 @@ use std::{ env, ffi::OsString, fs, + ops::Range, path::{Path, PathBuf}, sync::{Arc, Mutex, PoisonError}, thread, @@ -92,16 +96,50 @@ impl<'env> ProcMacroSrv<'env> { } } +#[derive(Debug)] +pub enum ProcMacroClientError { + Cancelled { reason: String }, + Io(std::io::Error), + Protocol(String), + Eof, +} + +#[derive(Debug)] +pub enum ProcMacroPanicMarker { + Cancelled { reason: String }, + Internal { reason: String }, +} + pub type ProcMacroClientHandle<'a> = &'a mut (dyn ProcMacroClientInterface + Sync + Send); pub trait ProcMacroClientInterface { fn file(&mut self, file_id: span::FileId) -> String; fn source_text(&mut self, span: Span) -> Option; fn local_file(&mut self, file_id: span::FileId) -> Option; + /// Line and column are 1-based. + fn line_column(&mut self, span: Span) -> Option<(u32, u32)>; + + fn byte_range(&mut self, span: Span) -> Range; } const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024; +pub enum ExpandError { + Panic(PanicMessage), + Cancelled { reason: Option }, + Internal { reason: Option }, +} + +impl ExpandError { + pub fn into_string(self) -> Option { + match self { + ExpandError::Panic(panic_message) => panic_message.into_string(), + ExpandError::Cancelled { reason } => reason, + ExpandError::Internal { reason } => reason, + } + } +} + impl ProcMacroSrv<'_> { pub fn expand( &self, @@ -115,10 +153,10 @@ impl ProcMacroSrv<'_> { call_site: S, mixed_site: S, callback: Option>, - ) -> Result, PanicMessage> { + ) -> Result, ExpandError> { let snapped_env = self.env; - let expander = self.expander(lib.as_ref()).map_err(|err| PanicMessage { - message: Some(format!("failed to load macro: {err}")), + let expander = self.expander(lib.as_ref()).map_err(|err| ExpandError::Internal { + reason: Some(format!("failed to load macro: {err}")), })?; let prev_env = EnvChange::apply(snapped_env, env, current_dir.as_ref().map(<_>::as_ref)); @@ -136,8 +174,22 @@ impl ProcMacroSrv<'_> { ) }); match thread.unwrap().join() { - Ok(res) => res, - Err(e) => std::panic::resume_unwind(e), + Ok(res) => res.map_err(ExpandError::Panic), + + Err(payload) => { + if let Some(marker) = payload.downcast_ref::() { + return match marker { + ProcMacroPanicMarker::Cancelled { reason } => { + Err(ExpandError::Cancelled { reason: Some(reason.clone()) }) + } + ProcMacroPanicMarker::Internal { reason } => { + Err(ExpandError::Internal { reason: Some(reason.clone()) }) + } + }; + } + + std::panic::resume_unwind(payload) + } } }); prev_env.rollback(); diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index eacb100fbc9f..c114d52ec33c 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -177,7 +177,9 @@ impl server::Server for RaSpanServer<'_> { span } fn span_byte_range(&mut self, span: Self::Span) -> Range { - // FIXME requires db to resolve the ast id, THIS IS NOT INCREMENTAL + if let Some(cb) = self.callback.as_mut() { + return cb.byte_range(span); + } Range { start: span.range.start().into(), end: span.range.end().into() } } fn span_join(&mut self, first: Self::Span, second: Self::Span) -> Option { @@ -272,14 +274,12 @@ impl server::Server for RaSpanServer<'_> { Span { range: TextRange::empty(span.range.start()), ..span } } - fn span_line(&mut self, _span: Self::Span) -> usize { - // FIXME requires db to resolve line index, THIS IS NOT INCREMENTAL - 1 + fn span_line(&mut self, span: Self::Span) -> usize { + self.callback.as_mut().and_then(|cb| cb.line_column(span)).map_or(1, |(l, _)| l as usize) } - fn span_column(&mut self, _span: Self::Span) -> usize { - // FIXME requires db to resolve line index, THIS IS NOT INCREMENTAL - 1 + fn span_column(&mut self, span: Self::Span) -> usize { + self.callback.as_mut().and_then(|cb| cb.line_column(span)).map_or(1, |(_, c)| c as usize) } fn symbol_normalize_and_validate_ident(&mut self, string: &str) -> Result { diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs index 20507a6def54..ebef9a9a519a 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs @@ -703,6 +703,7 @@ fn list_test_macros() { fn_like_mk_idents [Bang] fn_like_span_join [Bang] fn_like_span_ops [Bang] + fn_like_span_line_column [Bang] attr_noop [Attr] attr_panic [Attr] attr_error [Attr] @@ -712,3 +713,17 @@ fn list_test_macros() { DeriveError [CustomDerive]"#]] .assert_eq(&res); } + +#[test] +fn test_fn_like_span_line_column() { + assert_expand_with_callback( + "fn_like_span_line_column", + // Input text with known position: "hello" starts at offset 1 (line 2, column 1 in 1-based) + " +hello", + expect![[r#" + LITER 42:Root[0000, 0]@0..100#ROOT2024 Integer 2 + LITER 42:Root[0000, 0]@0..100#ROOT2024 Integer 1 + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs index 61fcd810b1d9..b7c5c4fdd21f 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs @@ -4,9 +4,11 @@ use expect_test::Expect; use span::{ EditionedFileId, FileId, ROOT_ERASED_FILE_AST_ID, Span, SpanAnchor, SyntaxContext, TextRange, }; +use std::ops::Range; use crate::{ - EnvSnapshot, ProcMacroSrv, SpanId, dylib, proc_macro_test_dylib_path, token_stream::TokenStream, + EnvSnapshot, ProcMacroClientInterface, ProcMacroSrv, SpanId, dylib, proc_macro_test_dylib_path, + token_stream::TokenStream, }; fn parse_string(call_site: SpanId, src: &str) -> TokenStream { @@ -109,3 +111,70 @@ pub(crate) fn list() -> Vec { let res = srv.list_macros(&dylib_path).unwrap(); res.into_iter().map(|(name, kind)| format!("{name} [{kind:?}]")).collect() } + +/// A mock callback for testing that computes line/column from the input text. +struct MockCallback<'a> { + text: &'a str, +} + +impl ProcMacroClientInterface for MockCallback<'_> { + fn source_text(&mut self, span: Span) -> Option { + self.text + .get(usize::from(span.range.start())..usize::from(span.range.end())) + .map(ToOwned::to_owned) + } + + fn file(&mut self, _file_id: FileId) -> String { + String::new() + } + + fn local_file(&mut self, _file_id: FileId) -> Option { + None + } + + fn line_column(&mut self, span: Span) -> Option<(u32, u32)> { + let line_index = line_index::LineIndex::new(self.text); + let line_col = line_index.try_line_col(span.range.start())?; + // proc_macro uses 1-based line/column + Some((line_col.line as u32 + 1, line_col.col as u32 + 1)) + } + + fn byte_range(&mut self, span: Span) -> Range { + Range { start: span.range.start().into(), end: span.range.end().into() } + } +} + +pub fn assert_expand_with_callback( + macro_name: &str, + #[rust_analyzer::rust_fixture] ra_fixture: &str, + expect_spanned: Expect, +) { + let path = proc_macro_test_dylib_path(); + let expander = dylib::Expander::new(&temp_dir::TempDir::new().unwrap(), &path).unwrap(); + + let def_site = Span { + range: TextRange::new(0.into(), 150.into()), + anchor: SpanAnchor { + file_id: EditionedFileId::current_edition(FileId::from_raw(41)), + ast_id: ROOT_ERASED_FILE_AST_ID, + }, + ctx: SyntaxContext::root(span::Edition::CURRENT), + }; + let call_site = Span { + range: TextRange::new(0.into(), 100.into()), + anchor: SpanAnchor { + file_id: EditionedFileId::current_edition(FileId::from_raw(42)), + ast_id: ROOT_ERASED_FILE_AST_ID, + }, + ctx: SyntaxContext::root(span::Edition::CURRENT), + }; + let mixed_site = call_site; + + let fixture = parse_string_spanned(call_site.anchor, call_site.ctx, ra_fixture); + + let mut callback = MockCallback { text: ra_fixture }; + let res = expander + .expand(macro_name, fixture, None, def_site, call_site, mixed_site, Some(&mut callback)) + .unwrap(); + expect_spanned.assert_eq(&format!("{res:?}")); +} diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs index 6e1a3f37ff1c..483ab2845045 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs @@ -640,7 +640,7 @@ impl FetchMetadata { /// Builds a command to fetch metadata for the given `cargo_toml` manifest. /// /// Performs a lightweight pre-fetch using the `--no-deps` option, - /// available via [`FetchMetadata::no_deps_metadata`], to gather basic + /// available via `FetchMetadata::no_deps_metadata`, to gather basic /// information such as the `target-dir`. /// /// The provided sysroot is used to set the `RUSTUP_TOOLCHAIN` 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 b3478d2cfe03..6938010cbd70 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 @@ -78,6 +78,13 @@ pub struct ProjectJson { runnables: Vec, } +impl std::ops::Index for ProjectJson { + type Output = Crate; + fn index(&self, index: CrateArrayIdx) -> &Self::Output { + &self.crates[index.0] + } +} + impl ProjectJson { /// Create a new ProjectJson instance. /// @@ -195,12 +202,11 @@ impl ProjectJson { &self.project_root } - pub fn crate_by_root(&self, root: &AbsPath) -> Option { + pub fn crate_by_root(&self, root: &AbsPath) -> Option<&Crate> { self.crates .iter() .filter(|krate| krate.is_workspace_member) .find(|krate| krate.root_module == root) - .cloned() } /// Returns the path to the project's manifest, if it exists. @@ -214,8 +220,17 @@ impl ProjectJson { self.crates .iter() .filter(|krate| krate.is_workspace_member) - .filter_map(|krate| krate.build.clone()) + .filter_map(|krate| krate.build.as_ref()) .find(|build| build.build_file.as_std_path() == path) + .cloned() + } + + pub fn crate_by_label(&self, label: &str) -> Option<&Crate> { + // this is fast enough for now, but it's unfortunate that this is O(crates). + self.crates + .iter() + .filter(|krate| krate.is_workspace_member) + .find(|krate| krate.build.as_ref().is_some_and(|build| build.label == label)) } /// Returns the path to the project's manifest or root folder, if no manifest exists. @@ -231,6 +246,10 @@ impl ProjectJson { pub fn runnables(&self) -> &[Runnable] { &self.runnables } + + pub fn runnable_template(&self, kind: RunnableKind) -> Option<&Runnable> { + self.runnables().iter().find(|r| r.kind == kind) + } } /// A crate points to the root module of a crate and lists the dependencies of the crate. This is @@ -258,6 +277,12 @@ pub struct Crate { pub build: Option, } +impl Crate { + pub fn iter_deps(&self) -> impl ExactSizeIterator { + self.deps.iter().map(|dep| dep.krate) + } +} + /// Additional, build-specific data about a crate. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Build { @@ -328,13 +353,21 @@ pub struct Runnable { /// The kind of runnable. #[derive(Debug, Clone, PartialEq, Eq)] pub enum RunnableKind { + /// `cargo check`, basically, with human-readable output. Check, /// Can run a binary. + /// May include {label} which will get the label from the `build` section of a crate. Run, /// Run a single test. + /// May include {label} which will get the label from the `build` section of a crate. + /// May include {test_id} which will get the test clicked on by the user. TestOne, + + /// 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, } #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] @@ -441,6 +474,7 @@ pub struct RunnableData { #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub enum RunnableKindData { + Flycheck, Check, Run, TestOne, @@ -511,6 +545,7 @@ impl From for RunnableKind { RunnableKindData::Check => RunnableKind::Check, RunnableKindData::Run => RunnableKind::Run, RunnableKindData::TestOne => RunnableKind::TestOne, + RunnableKindData::Flycheck => RunnableKind::Flycheck, } } } diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs index fa3a79e041e0..8f15f7e1507c 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -1161,6 +1161,8 @@ fn project_json_to_crate_graph( name: Some(name.canonical_name().to_owned()), } } + } else if is_sysroot { + CrateOrigin::Lang(LangCrateOrigin::Dependency) } else { CrateOrigin::Local { repo: None, name: None } }, @@ -1294,6 +1296,8 @@ fn cargo_to_crate_graph( name: Some(Symbol::intern(&pkg_data.name)), } } + } else if cargo.is_sysroot() { + CrateOrigin::Lang(LangCrateOrigin::Dependency) } else { CrateOrigin::Library { repo: pkg_data.repository.clone(), @@ -1717,7 +1721,7 @@ fn extend_crate_graph_with_sysroot( !matches!(lang_crate, LangCrateOrigin::Test | LangCrateOrigin::Alloc), )), LangCrateOrigin::ProcMacro => libproc_macro = Some(cid), - LangCrateOrigin::Other => (), + LangCrateOrigin::Other | LangCrateOrigin::Dependency => (), } } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index a02d1a78564f..1995d3889891 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -91,6 +91,7 @@ impl flags::AnalysisStats { } }, prefill_caches: false, + proc_macro_processes: 1, }; let build_scripts_time = if self.disable_build_scripts { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs index 776069f155f0..575c77f8428c 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs @@ -41,6 +41,7 @@ impl flags::Diagnostics { load_out_dirs_from_check: !self.disable_build_scripts, with_proc_macro_server, prefill_caches: false, + proc_macro_processes: 1, }; let (db, _vfs, _proc_macro) = load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs index f3b0699d5515..e5e238db6361 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs @@ -293,6 +293,7 @@ impl flags::Lsif { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let path = AbsPathBuf::assert_utf8(env::current_dir()?.join(self.path)); let root = ProjectManifest::discover_single(&path)?; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/prime_caches.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/prime_caches.rs index 467d8a53884a..d5da6791797b 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/prime_caches.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/prime_caches.rs @@ -38,6 +38,7 @@ impl flags::PrimeCaches { // we want to ensure that this command, not `load_workspace_at`, // is responsible for that work. prefill_caches: false, + proc_macro_processes: config.proc_macro_num_processes(), }; let root = AbsPathBuf::assert_utf8(std::env::current_dir()?.join(root)); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs index 82ace8c8b315..d4a56d773e7d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs @@ -23,6 +23,7 @@ impl flags::RunTests { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let (ref db, _vfs, _proc_macro) = load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs index 249566d2ac16..e8c6c5f4d4f7 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs @@ -103,6 +103,7 @@ impl Tester { load_out_dirs_from_check: false, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let (db, _vfs, _proc_macro) = load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs index 271d2507bcfe..ed0476697c9c 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs @@ -52,6 +52,7 @@ impl flags::Scip { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, + proc_macro_processes: config.proc_macro_num_processes(), }; let cargo_config = config.cargo(None); let (db, vfs, _) = load_workspace_at( diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs index 39186831459c..5c69bda723fb 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs @@ -20,6 +20,7 @@ impl flags::Ssr { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let (ref db, vfs, _proc_macro) = load_workspace_at( &std::env::current_dir()?, @@ -56,6 +57,7 @@ impl flags::Search { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let (ref db, _vfs, _proc_macro) = load_workspace_at( &std::env::current_dir()?, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs index 294add682d01..49c6fcb91ebf 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs @@ -44,6 +44,7 @@ impl flags::UnresolvedReferences { load_out_dirs_from_check: !self.disable_build_scripts, with_proc_macro_server, prefill_caches: false, + proc_macro_processes: config.proc_macro_num_processes(), }; let (db, vfs, _proc_macro) = load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs index 2f052618cdfa..49ce6db4ea9a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs @@ -10,6 +10,7 @@ use std::{ process::{ChildStderr, ChildStdout, Command, Stdio}, }; +use anyhow::Context; use crossbeam_channel::Sender; use paths::Utf8PathBuf; use process_wrap::std::{StdChildWrapper, StdCommandWrap}; @@ -156,7 +157,7 @@ impl CommandHandle { parser: impl JsonLinesParser, sender: Sender, out_file: Option, - ) -> std::io::Result { + ) -> anyhow::Result { command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); let program = command.get_program().into(); @@ -168,7 +169,10 @@ impl CommandHandle { child.wrap(process_wrap::std::ProcessSession); #[cfg(windows)] child.wrap(process_wrap::std::JobObject); - let mut child = child.spawn().map(JodGroupChild)?; + let mut child = child + .spawn() + .map(JodGroupChild) + .with_context(|| "Failed to spawn command: {child:?}")?; let stdout = child.0.stdout().take().unwrap(); let stderr = child.0.stderr().take().unwrap(); 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..0dda7f3cc276 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -387,6 +387,12 @@ config_data! { /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`. procMacro_enable: bool = true, + /// Number of proc-macro server processes to spawn. + /// + /// Controls how many independent `proc-macro-srv` processes rust-analyzer + /// runs in parallel to handle macro expansion. + procMacro_processes: NumProcesses = NumProcesses::Concrete(1), + /// Internal config, path to proc-macro server executable. procMacro_server: Option = None, @@ -478,33 +484,83 @@ config_data! { typing_triggerChars: Option = Some("=.".to_owned()), - /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`]. + /// Configure a command that rust-analyzer can invoke to + /// obtain configuration. /// - /// [`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`. - /// `progress_label` is used for the title in progress indicators, whereas `files_to_watch` - /// is used to determine which build system-specific files should be watched in order to - /// reload rust-analyzer. + /// This is an alternative to manually generating + /// `rust-project.json`: it enables rust-analyzer to generate + /// rust-project.json on the fly, and regenerate it when + /// switching or modifying projects. + /// + /// This is an object with three fields: + /// + /// * `command`: the shell command to invoke + /// + /// * `filesToWatch`: which build system-specific files should + /// be watched to trigger regenerating the configuration + /// + /// * `progressLabel`: the name of the command, used in + /// progress indicators in the IDE + /// + /// Here's an example of a valid configuration: /// - /// Below is an example of a valid configuration: /// ```json /// "rust-analyzer.workspace.discoverConfig": { /// "command": [ /// "rust-project", - /// "develop-json" + /// "develop-json", + /// "{arg}" /// ], - /// "progressLabel": "rust-analyzer", + /// "progressLabel": "buck2/rust-project", /// "filesToWatch": [ /// "BUCK" /// ] /// } /// ``` /// - /// ## On `DiscoverWorkspaceConfig::command` + /// ## Argument Substitutions + /// + /// If `command` includes the argument `{arg}`, that argument will be substituted + /// with the JSON-serialized form of the following enum: + /// + /// ```norun + /// #[derive(PartialEq, Clone, Debug, Serialize)] + /// #[serde(rename_all = "camelCase")] + /// pub enum DiscoverArgument { + /// Path(AbsPathBuf), + /// Buildfile(AbsPathBuf), + /// } + /// ``` + /// + /// rust-analyzer will use the path invocation to find and + /// generate a `rust-project.json` and therefore a + /// workspace. Example: + /// + /// + /// ```norun + /// rust-project develop-json '{ "path": "myproject/src/main.rs" }' + /// ``` + /// + /// rust-analyzer will use build file invocations to update an + /// existing workspace. Example: + /// + /// Or with a build file and the configuration above: + /// + /// ```norun + /// rust-project develop-json '{ "buildfile": "myproject/BUCK" }' + /// ``` + /// + /// As a reference for implementors, buck2's `rust-project` + /// will likely be useful: + /// . + /// + /// ## Discover Command Output /// /// **Warning**: This format is provisional and subject to change. /// - /// [`DiscoverWorkspaceConfig::command`] *must* return a JSON object corresponding to - /// `DiscoverProjectData::Finished`: + /// The discover command should output JSON objects, one per + /// line (JSONL format). These objects should correspond to + /// this Rust data type: /// /// ```norun /// #[derive(Debug, Clone, Deserialize, Serialize)] @@ -517,7 +573,14 @@ config_data! { /// } /// ``` /// - /// As JSON, `DiscoverProjectData::Finished` is: + /// For example, a progress event: + /// + /// ```json + /// {"kind":"progress","message":"generating rust-project.json"} + /// ``` + /// + /// A finished event can look like this (expanded and + /// commented for readability): /// /// ```json /// { @@ -525,7 +588,7 @@ config_data! { /// "kind": "finished", /// // the file used by a non-Cargo build system to define /// // a package or target. - /// "buildfile": "rust-analyzer/BUILD", + /// "buildfile": "rust-analyzer/BUCK", /// // the contents of a rust-project.json, elided for brevity /// "project": { /// "sysroot": "foo", @@ -534,41 +597,9 @@ config_data! { /// } /// ``` /// - /// It is encouraged, but not required, to use the other variants on `DiscoverProjectData` - /// to provide a more polished end-user experience. - /// - /// `DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`, which will be - /// substituted with the JSON-serialized form of the following enum: - /// - /// ```norun - /// #[derive(PartialEq, Clone, Debug, Serialize)] - /// #[serde(rename_all = "camelCase")] - /// pub enum DiscoverArgument { - /// Path(AbsPathBuf), - /// Buildfile(AbsPathBuf), - /// } - /// ``` - /// - /// The JSON representation of `DiscoverArgument::Path` is: - /// - /// ```json - /// { - /// "path": "src/main.rs" - /// } - /// ``` - /// - /// Similarly, the JSON representation of `DiscoverArgument::Buildfile` is: - /// - /// ```json - /// { - /// "buildfile": "BUILD" - /// } - /// ``` - /// - /// `DiscoverArgument::Path` is used to find and generate a `rust-project.json`, and - /// therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to to update an - /// existing workspace. As a reference for implementors, buck2's `rust-project` will likely - /// be useful: . + /// Only the finished event is required, but the other + /// variants are encouraged to give users more feedback about + /// progress or errors. workspace_discoverConfig: Option = None, } } @@ -870,10 +901,18 @@ config_data! { /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten /// by changing `#rust-analyzer.check.invocationStrategy#`. /// - /// If `$saved_file` is part of the command, rust-analyzer will pass - /// the absolute path of the saved file to the provided command. This is - /// intended to be used with non-Cargo build systems. - /// Note that `$saved_file` is experimental and may be removed in the future. + /// It supports two interpolation syntaxes, both mainly intended to be used with + /// [non-Cargo build systems](./non_cargo_based_projects.md): + /// + /// - If `{saved_file}` is part of the command, rust-analyzer will pass + /// the absolute path of the saved file to the provided command. + /// (A previous version, `$saved_file`, also works.) + /// - If `{label}` is part of the command, rust-analyzer will pass the + /// Cargo package ID, which can be used with `cargo check -p`, or a build label from + /// `rust-project.json`. If `{label}` is included, rust-analyzer behaves much like + /// [`"rust-analyzer.check.workspace": false`](#check.workspace). + /// + /// /// /// An example command would be: /// @@ -1034,6 +1073,7 @@ pub struct Config { /// The workspace roots as registered by the LSP client workspace_roots: Vec, caps: ClientCapabilities, + /// The LSP root path, deprecated in favor of `workspace_roots` root_path: AbsPathBuf, snippets: Vec, client_info: Option, @@ -1357,6 +1397,10 @@ impl Config { self.discovered_projects_from_command.push(ProjectJsonFromCommand { data, buildfile }); } + + pub fn workspace_roots(&self) -> &[AbsPathBuf] { + &self.workspace_roots + } } #[derive(Default, Debug)] @@ -1733,6 +1777,7 @@ impl Config { } pub fn root_path(&self) -> &AbsPathBuf { + // We should probably use `workspace_roots` here if set &self.root_path } @@ -2431,6 +2476,8 @@ impl Config { pub(crate) fn cargo_test_options(&self, source_root: Option) -> CargoOptions { CargoOptions { + // Might be nice to allow users to specify test_command = "nextest" + subcommand: "test".into(), target_tuples: self.cargo_target(source_root).clone().into_iter().collect(), all_targets: false, no_default_features: *self.cargo_noDefaultFeatures(source_root), @@ -2464,9 +2511,9 @@ impl Config { }, } } - Some(_) | None => FlycheckConfig::CargoCommand { - command: self.check_command(source_root).clone(), - options: CargoOptions { + Some(_) | None => FlycheckConfig::Automatic { + cargo_options: CargoOptions { + subcommand: self.check_command(source_root).clone(), target_tuples: self .check_targets(source_root) .clone() @@ -2630,6 +2677,13 @@ impl Config { } } + pub fn proc_macro_num_processes(&self) -> usize { + match self.procMacro_processes() { + NumProcesses::Concrete(0) | NumProcesses::Physical => num_cpus::get_physical(), + &NumProcesses::Concrete(n) => n, + } + } + pub fn main_loop_num_threads(&self) -> usize { match self.numThreads() { Some(NumThreads::Concrete(0)) | None | Some(NumThreads::Physical) => { @@ -3066,6 +3120,14 @@ pub enum NumThreads { Concrete(usize), } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum NumProcesses { + Physical, + #[serde(untagged)] + Concrete(usize), +} + macro_rules! _default_val { ($default:expr, $ty:ty) => {{ let default_: $ty = $default; @@ -3892,6 +3954,22 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json }, ], }, + "NumProcesses" => set! { + "anyOf": [ + { + "type": "number", + "minimum": 0, + "maximum": 255 + }, + { + "type": "string", + "enum": ["physical"], + "enumDescriptions": [ + "Use the number of physical cores", + ], + }, + ], + }, "Option" => set! { "anyOf": [ { @@ -4171,8 +4249,8 @@ mod tests { assert_eq!(config.cargo_targetDir(None), &None); assert!(matches!( config.flycheck(None), - FlycheckConfig::CargoCommand { - options: CargoOptions { target_dir_config: TargetDirectoryConfig::None, .. }, + FlycheckConfig::Automatic { + cargo_options: CargoOptions { target_dir_config: TargetDirectoryConfig::None, .. }, .. } )); @@ -4195,8 +4273,8 @@ mod tests { Utf8PathBuf::from(std::env::var("CARGO_TARGET_DIR").unwrap_or("target".to_owned())); assert!(matches!( config.flycheck(None), - FlycheckConfig::CargoCommand { - options: CargoOptions { target_dir_config, .. }, + FlycheckConfig::Automatic { + cargo_options: CargoOptions { target_dir_config, .. }, .. } if target_dir_config.target_dir(Some(&ws_target_dir)).map(Cow::into_owned) == Some(ws_target_dir.join("rust-analyzer")) @@ -4221,8 +4299,8 @@ mod tests { ); assert!(matches!( config.flycheck(None), - FlycheckConfig::CargoCommand { - options: CargoOptions { target_dir_config, .. }, + FlycheckConfig::Automatic { + cargo_options: CargoOptions { target_dir_config, .. }, .. } if target_dir_config.target_dir(None).map(Cow::into_owned) == Some(Utf8PathBuf::from("other_folder")) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config/patch_old_style.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config/patch_old_style.rs index 389bb7848c01..5dc463eccce4 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config/patch_old_style.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config/patch_old_style.rs @@ -3,7 +3,7 @@ use serde_json::{Value, json}; /// This function patches the json config to the new expected keys. /// That is we try to load old known config keys here and convert them to the new ones. -/// See https://github.com/rust-lang/rust-analyzer/pull/12010 +/// See /// /// We already have an alias system for simple cases, but if we make structural changes /// the alias infra fails down. diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs index 4a247800af9d..8d0f52433e02 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs @@ -3,7 +3,6 @@ pub(crate) mod flycheck_to_proto; use std::mem; -use cargo_metadata::PackageId; use ide::FileId; use ide_db::{FxHashMap, base_db::DbPanicContext}; use itertools::Itertools; @@ -12,10 +11,13 @@ use smallvec::SmallVec; use stdx::iter_eq_by; use triomphe::Arc; -use crate::{global_state::GlobalStateSnapshot, lsp, lsp_ext, main_loop::DiagnosticsTaskKind}; +use crate::{ + flycheck::PackageSpecifier, global_state::GlobalStateSnapshot, lsp, lsp_ext, + main_loop::DiagnosticsTaskKind, +}; pub(crate) type CheckFixes = - Arc>, FxHashMap>>>>; + Arc, FxHashMap>>>>; #[derive(Debug, Default, Clone)] pub struct DiagnosticsMapConfig { @@ -29,7 +31,7 @@ pub(crate) type DiagnosticsGeneration = usize; #[derive(Debug, Clone, Default)] pub(crate) struct WorkspaceFlycheckDiagnostic { - pub(crate) per_package: FxHashMap>, PackageFlycheckDiagnostic>, + pub(crate) per_package: FxHashMap, PackageFlycheckDiagnostic>, } #[derive(Debug, Clone)] @@ -85,7 +87,7 @@ impl DiagnosticCollection { pub(crate) fn clear_check_for_package( &mut self, flycheck_id: usize, - package_id: Arc, + package_id: PackageSpecifier, ) { let Some(check) = self.check.get_mut(flycheck_id) else { return; @@ -124,7 +126,7 @@ impl DiagnosticCollection { pub(crate) fn clear_check_older_than_for_package( &mut self, flycheck_id: usize, - package_id: Arc, + package_id: PackageSpecifier, generation: DiagnosticsGeneration, ) { let Some(check) = self.check.get_mut(flycheck_id) else { @@ -154,7 +156,7 @@ impl DiagnosticCollection { &mut self, flycheck_id: usize, generation: DiagnosticsGeneration, - package_id: &Option>, + package_id: &Option, file_id: FileId, diagnostic: lsp_types::Diagnostic, fix: Option>, @@ -287,34 +289,40 @@ pub(crate) fn fetch_native_diagnostics( let mut diagnostics = subscriptions[slice] .iter() .copied() - .filter_map(|file_id| { - let line_index = snapshot.file_line_index(file_id).ok()?; - let source_root = snapshot.analysis.source_root_id(file_id).ok()?; + .map(|file_id| { + let diagnostics = (|| { + let line_index = snapshot.file_line_index(file_id).ok()?; + let source_root = snapshot.analysis.source_root_id(file_id).ok()?; - let config = &snapshot.config.diagnostics(Some(source_root)); - let diagnostics = match kind { - NativeDiagnosticsFetchKind::Syntax => { - snapshot.analysis.syntax_diagnostics(config, file_id).ok()? - } - - NativeDiagnosticsFetchKind::Semantic if config.enabled => snapshot - .analysis - .semantic_diagnostics(config, ide::AssistResolveStrategy::None, file_id) - .ok()?, - NativeDiagnosticsFetchKind::Semantic => return None, - }; - let diagnostics = diagnostics - .into_iter() - .filter_map(|d| { - if d.range.file_id == file_id { - Some(convert_diagnostic(&line_index, d)) - } else { - odd_ones.push(d); - None + let config = &snapshot.config.diagnostics(Some(source_root)); + let diagnostics = match kind { + NativeDiagnosticsFetchKind::Syntax => { + snapshot.analysis.syntax_diagnostics(config, file_id).ok()? } - }) - .collect::>(); - Some((file_id, diagnostics)) + + NativeDiagnosticsFetchKind::Semantic if config.enabled => snapshot + .analysis + .semantic_diagnostics(config, ide::AssistResolveStrategy::None, file_id) + .ok()?, + NativeDiagnosticsFetchKind::Semantic => return None, + }; + Some( + diagnostics + .into_iter() + .filter_map(|d| { + if d.range.file_id == file_id { + Some(convert_diagnostic(&line_index, d)) + } else { + odd_ones.push(d); + None + } + }) + .collect::>(), + ) + })() + .unwrap_or_default(); + + (file_id, diagnostics) }) .collect::>(); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs index 4aef5b0b7f3d..098b6a4d986d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs @@ -1,6 +1,6 @@ //! Infrastructure for lazy project discovery. Currently only support rust-project.json discovery //! via a custom discover command. -use std::{io, path::Path}; +use std::path::Path; use crossbeam_channel::Sender; use ide_db::FxHashMap; @@ -42,12 +42,12 @@ impl DiscoverCommand { Self { sender, command } } - /// Spawn the command inside [Discover] and report progress, if any. + /// Spawn the command inside `DiscoverCommand` and report progress, if any. pub(crate) fn spawn( &self, discover_arg: DiscoverArgument, current_dir: &Path, - ) -> io::Result { + ) -> anyhow::Result { let command = &self.command[0]; let args = &self.command[1..]; @@ -73,7 +73,7 @@ impl DiscoverCommand { } } -/// A handle to a spawned [Discover]. +/// A handle to a spawned `DiscoverCommand`. #[derive(Debug)] pub(crate) struct DiscoverHandle { pub(crate) handle: CommandHandle, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs index b06264169188..512c231990cb 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs @@ -14,6 +14,7 @@ use ide_db::FxHashSet; use itertools::Itertools; use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf}; use project_model::TargetDirectoryConfig; +use project_model::project_json; use rustc_hash::FxHashMap; use serde::Deserialize as _; use serde_derive::Deserialize; @@ -21,6 +22,7 @@ use serde_derive::Deserialize; pub(crate) use cargo_metadata::diagnostic::{ Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan, }; +use toolchain::DISPLAY_COMMAND_IGNORE_ENVS; use toolchain::Tool; use triomphe::Arc; @@ -36,8 +38,11 @@ pub(crate) enum InvocationStrategy { PerWorkspace, } +/// Data needed to construct a `cargo` command invocation, e.g. for flycheck or running a test. #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct CargoOptions { + /// The cargo subcommand to run, e.g. "check" or "clippy" + pub(crate) subcommand: String, pub(crate) target_tuples: Vec, pub(crate) all_targets: bool, pub(crate) set_test: bool, @@ -89,13 +94,36 @@ impl CargoOptions { } } +/// The flycheck config from a rust-project.json file or discoverConfig JSON output. +#[derive(Debug, Default)] +pub(crate) struct FlycheckConfigJson { + /// The template with [project_json::RunnableKind::Flycheck] + pub single_template: Option, +} + +impl FlycheckConfigJson { + pub(crate) fn any_configured(&self) -> bool { + // self.workspace_template.is_some() || + self.single_template.is_some() + } +} + +/// The flycheck config from rust-analyzer's own configuration. +/// +/// We rely on this when rust-project.json does not specify a flycheck runnable +/// #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum FlycheckConfig { - CargoCommand { - command: String, - options: CargoOptions, + /// Automatically use rust-project.json's flycheck runnable or just use cargo (the common case) + /// + /// We can't have a variant for ProjectJson because that is configured on the fly during + /// discoverConfig. We only know what we can read at config time. + Automatic { + /// If we do use cargo, how to build the check command + cargo_options: CargoOptions, ansi_color_output: bool, }, + /// check_overrideCommand. This overrides both cargo and rust-project.json's flycheck runnable. CustomCommand { command: String, args: Vec, @@ -107,7 +135,7 @@ pub(crate) enum FlycheckConfig { impl FlycheckConfig { pub(crate) fn invocation_strategy(&self) -> InvocationStrategy { match self { - FlycheckConfig::CargoCommand { .. } => InvocationStrategy::PerWorkspace, + FlycheckConfig::Automatic { .. } => InvocationStrategy::PerWorkspace, FlycheckConfig::CustomCommand { invocation_strategy, .. } => { invocation_strategy.clone() } @@ -118,7 +146,9 @@ impl FlycheckConfig { impl fmt::Display for FlycheckConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {command}"), + FlycheckConfig::Automatic { cargo_options, .. } => { + write!(f, "cargo {}", cargo_options.subcommand) + } FlycheckConfig::CustomCommand { command, args, .. } => { // Don't show `my_custom_check --foo $saved_file` literally to the user, as it // looks like we've forgotten to substitute $saved_file. @@ -128,7 +158,7 @@ impl fmt::Display for FlycheckConfig { // in the IDE (e.g. in the VS Code status bar). let display_args = args .iter() - .map(|arg| if arg == SAVED_FILE_PLACEHOLDER { "..." } else { arg }) + .map(|arg| if arg == SAVED_FILE_PLACEHOLDER_DOLLAR { "..." } else { arg }) .collect::>(); write!(f, "{command} {}", display_args.join(" ")) @@ -156,6 +186,7 @@ impl FlycheckHandle { generation: Arc, sender: Sender, config: FlycheckConfig, + config_json: FlycheckConfigJson, sysroot_root: Option, workspace_root: AbsPathBuf, manifest_path: Option, @@ -166,6 +197,7 @@ impl FlycheckHandle { generation.load(Ordering::Relaxed), sender, config, + config_json, sysroot_root, workspace_root, manifest_path, @@ -195,16 +227,17 @@ impl FlycheckHandle { /// Schedule a re-start of the cargo check worker to do a package wide check. pub(crate) fn restart_for_package( &self, - package: Arc, + package: PackageSpecifier, target: Option, - workspace_deps: Option>>, + workspace_deps: Option>, + saved_file: Option, ) { let generation = self.generation.fetch_add(1, Ordering::Relaxed) + 1; self.sender .send(StateChange::Restart { generation, scope: FlycheckScope::Package { package, workspace_deps }, - saved_file: None, + saved_file, target, }) .unwrap(); @@ -233,7 +266,7 @@ pub(crate) enum ClearDiagnosticsKind { #[derive(Debug)] pub(crate) enum ClearScope { Workspace, - Package(Arc), + Package(PackageSpecifier), } pub(crate) enum FlycheckMessage { @@ -243,7 +276,7 @@ pub(crate) enum FlycheckMessage { generation: DiagnosticsGeneration, workspace_root: Arc, diagnostic: Diagnostic, - package_id: Option>, + package_id: Option, }, /// Request clearing all outdated diagnostics. @@ -286,16 +319,56 @@ impl fmt::Debug for FlycheckMessage { #[derive(Debug)] pub(crate) enum Progress { - DidStart, + DidStart { + /// The user sees this in VSCode, etc. May be a shortened version of the command we actually + /// executed, otherwise it is way too long. + user_facing_command: String, + }, DidCheckCrate(String), DidFinish(io::Result<()>), DidCancel, DidFailToRestart(String), } +#[derive(Debug, Clone)] enum FlycheckScope { Workspace, - Package { package: Arc, workspace_deps: Option>> }, + Package { + // Either a cargo package or a $label in rust-project.check.overrideCommand + package: PackageSpecifier, + workspace_deps: Option>, + }, +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub(crate) enum PackageSpecifier { + Cargo { + /// The one in Cargo.toml, assumed to work with `cargo check -p {}` etc + package_id: Arc, + }, + BuildInfo { + /// If a `build` field is present in rust-project.json, its label field + label: String, + }, +} + +impl PackageSpecifier { + pub(crate) fn as_str(&self) -> &str { + match self { + Self::Cargo { package_id } => &package_id.repr, + Self::BuildInfo { label } => label, + } + } +} + +#[derive(Debug)] +enum FlycheckCommandOrigin { + /// Regular cargo invocation + Cargo, + /// Configured via check_overrideCommand + CheckOverrideCommand, + /// From a runnable with [project_json::RunnableKind::Flycheck] + ProjectJsonRunnable, } enum StateChange { @@ -316,6 +389,8 @@ struct FlycheckActor { generation: DiagnosticsGeneration, sender: Sender, config: FlycheckConfig, + config_json: FlycheckConfigJson, + manifest_path: Option, ws_target_dir: Option, /// Either the workspace root of the workspace we are flychecking, @@ -331,7 +406,7 @@ struct FlycheckActor { command_handle: Option>, /// The receiver side of the channel mentioned above. command_receiver: Option>, - diagnostics_cleared_for: FxHashSet>, + diagnostics_cleared_for: FxHashSet, diagnostics_received: DiagnosticsReceived, } @@ -348,7 +423,66 @@ enum Event { CheckEvent(Option), } -pub(crate) const SAVED_FILE_PLACEHOLDER: &str = "$saved_file"; +/// This is stable behaviour. Don't change. +const SAVED_FILE_PLACEHOLDER_DOLLAR: &str = "$saved_file"; +const LABEL_INLINE: &str = "{label}"; +const SAVED_FILE_INLINE: &str = "{saved_file}"; + +struct Substitutions<'a> { + label: Option<&'a str>, + saved_file: Option<&'a str>, +} + +impl<'a> Substitutions<'a> { + /// If you have a runnable, and it has {label} in it somewhere, treat it as a template that + /// may be unsatisfied if you do not provide a label to substitute into it. Returns None in + /// that situation. Otherwise performs the requested substitutions. + /// + /// Same for {saved_file}. + /// + #[allow(clippy::disallowed_types)] /* generic parameter allows for FxHashMap */ + fn substitute( + self, + template: &project_json::Runnable, + extra_env: &std::collections::HashMap, H>, + ) -> Option { + let mut cmd = toolchain::command(&template.program, &template.cwd, extra_env); + for arg in &template.args { + if let Some(ix) = arg.find(LABEL_INLINE) { + if let Some(label) = self.label { + let mut arg = arg.to_string(); + arg.replace_range(ix..ix + LABEL_INLINE.len(), label); + cmd.arg(arg); + continue; + } else { + return None; + } + } + if let Some(ix) = arg.find(SAVED_FILE_INLINE) { + if let Some(saved_file) = self.saved_file { + let mut arg = arg.to_string(); + arg.replace_range(ix..ix + SAVED_FILE_INLINE.len(), saved_file); + cmd.arg(arg); + continue; + } else { + return None; + } + } + // Legacy syntax: full argument match + if arg == SAVED_FILE_PLACEHOLDER_DOLLAR { + if let Some(saved_file) = self.saved_file { + cmd.arg(saved_file); + continue; + } else { + return None; + } + } + cmd.arg(arg); + } + cmd.current_dir(&template.cwd); + Some(cmd) + } +} impl FlycheckActor { fn new( @@ -356,6 +490,7 @@ impl FlycheckActor { generation: DiagnosticsGeneration, sender: Sender, config: FlycheckConfig, + config_json: FlycheckConfigJson, sysroot_root: Option, workspace_root: AbsPathBuf, manifest_path: Option, @@ -367,6 +502,7 @@ impl FlycheckActor { generation, sender, config, + config_json, sysroot_root, root: Arc::new(workspace_root), scope: FlycheckScope::Workspace, @@ -418,27 +554,39 @@ impl FlycheckActor { } let command = self.check_command(&scope, saved_file.as_deref(), target); - self.scope = scope; + self.scope = scope.clone(); self.generation = generation; - let Some(command) = command else { + let Some((command, origin)) = command else { + tracing::debug!(?scope, "failed to build flycheck command"); continue; }; - let formatted_command = format!("{command:?}"); + let debug_command = format!("{command:?}"); + let user_facing_command = match origin { + // Don't show all the --format=json-with-blah-blah args, just the simple + // version + FlycheckCommandOrigin::Cargo => self.config.to_string(), + // show them the full command but pretty printed. advanced user + FlycheckCommandOrigin::ProjectJsonRunnable + | FlycheckCommandOrigin::CheckOverrideCommand => display_command( + &command, + Some(std::path::Path::new(self.root.as_path())), + ), + }; - tracing::debug!(?command, "will restart flycheck"); + tracing::debug!(?origin, ?command, "will restart flycheck"); let (sender, receiver) = unbounded(); match CommandHandle::spawn( command, CargoCheckParser, sender, match &self.config { - FlycheckConfig::CargoCommand { options, .. } => { + FlycheckConfig::Automatic { cargo_options, .. } => { let ws_target_dir = self.ws_target_dir.as_ref().map(Utf8PathBuf::as_path); let target_dir = - options.target_dir_config.target_dir(ws_target_dir); + cargo_options.target_dir_config.target_dir(ws_target_dir); // If `"rust-analyzer.cargo.targetDir": null`, we should use // workspace's target dir instead of hard-coded fallback. @@ -464,14 +612,14 @@ impl FlycheckActor { }, ) { Ok(command_handle) => { - tracing::debug!(command = formatted_command, "did restart flycheck"); + tracing::debug!(?origin, command = %debug_command, "did restart flycheck"); self.command_handle = Some(command_handle); self.command_receiver = Some(receiver); - self.report_progress(Progress::DidStart); + self.report_progress(Progress::DidStart { user_facing_command }); } Err(error) => { self.report_progress(Progress::DidFailToRestart(format!( - "Failed to run the following command: {formatted_command} error={error}" + "Failed to run the following command: {debug_command} origin={origin:?} error={error}" ))); } } @@ -564,7 +712,10 @@ impl FlycheckActor { msg.target.kind.iter().format_with(", ", |kind, f| f(&kind)), ))); let package_id = Arc::new(msg.package_id); - if self.diagnostics_cleared_for.insert(package_id.clone()) { + if self + .diagnostics_cleared_for + .insert(PackageSpecifier::Cargo { package_id: package_id.clone() }) + { tracing::trace!( flycheck_id = self.id, package_id = package_id.repr, @@ -572,7 +723,9 @@ impl FlycheckActor { ); self.send(FlycheckMessage::ClearDiagnostics { id: self.id, - kind: ClearDiagnosticsKind::All(ClearScope::Package(package_id)), + kind: ClearDiagnosticsKind::All(ClearScope::Package( + PackageSpecifier::Cargo { package_id }, + )), }); } } @@ -580,7 +733,7 @@ impl FlycheckActor { tracing::trace!( flycheck_id = self.id, message = diagnostic.message, - package_id = package_id.as_ref().map(|it| &it.repr), + package_id = package_id.as_ref().map(|it| it.as_str()), "diagnostic received" ); if self.diagnostics_received == DiagnosticsReceived::No { @@ -590,7 +743,7 @@ impl FlycheckActor { if self.diagnostics_cleared_for.insert(package_id.clone()) { tracing::trace!( flycheck_id = self.id, - package_id = package_id.repr, + package_id = package_id.as_str(), "clearing diagnostics" ); self.send(FlycheckMessage::ClearDiagnostics { @@ -642,6 +795,29 @@ impl FlycheckActor { self.diagnostics_received = DiagnosticsReceived::No; } + fn explicit_check_command( + &self, + scope: &FlycheckScope, + saved_file: Option<&AbsPath>, + ) -> Option { + let label = match scope { + // We could add a runnable like "RunnableKind::FlycheckWorkspace". But generally + // if you're not running cargo, it's because your workspace is too big to check + // all at once. You can always use `check_overrideCommand` with no {label}. + FlycheckScope::Workspace => return None, + FlycheckScope::Package { package: PackageSpecifier::BuildInfo { label }, .. } => { + label.as_str() + } + FlycheckScope::Package { + package: PackageSpecifier::Cargo { package_id: label }, + .. + } => &label.repr, + }; + let template = self.config_json.single_template.as_ref()?; + let subs = Substitutions { label: Some(label), saved_file: saved_file.map(|x| x.as_str()) }; + subs.substitute(template, &FxHashMap::default()) + } + /// Construct a `Command` object for checking the user's code. If the user /// has specified a custom command with placeholders that we cannot fill, /// return None. @@ -650,23 +826,49 @@ impl FlycheckActor { scope: &FlycheckScope, saved_file: Option<&AbsPath>, target: Option, - ) -> Option { + ) -> Option<(Command, FlycheckCommandOrigin)> { match &self.config { - FlycheckConfig::CargoCommand { command, options, ansi_color_output } => { + FlycheckConfig::Automatic { cargo_options, ansi_color_output } => { + // Only use the rust-project.json's flycheck config when no check_overrideCommand + // is configured. In the FlycheckConcig::CustomCommand branch we will still do + // label substitution, but on the overrideCommand instead. + // + // There needs to be SOME way to override what your discoverConfig tool says, + // because to change the flycheck runnable there you may have to literally + // recompile the tool. + if self.config_json.any_configured() { + // Completely handle according to rust-project.json. + // We don't consider this to be "using cargo" so we will not apply any of the + // CargoOptions to the command. + let cmd = self.explicit_check_command(scope, saved_file)?; + return Some((cmd, FlycheckCommandOrigin::ProjectJsonRunnable)); + } + let mut cmd = - toolchain::command(Tool::Cargo.path(), &*self.root, &options.extra_env); + toolchain::command(Tool::Cargo.path(), &*self.root, &cargo_options.extra_env); if let Some(sysroot_root) = &self.sysroot_root - && !options.extra_env.contains_key("RUSTUP_TOOLCHAIN") + && !cargo_options.extra_env.contains_key("RUSTUP_TOOLCHAIN") && std::env::var_os("RUSTUP_TOOLCHAIN").is_none() { cmd.env("RUSTUP_TOOLCHAIN", AsRef::::as_ref(sysroot_root)); } cmd.env("CARGO_LOG", "cargo::core::compiler::fingerprint=info"); - cmd.arg(command); + cmd.arg(&cargo_options.subcommand); match scope { FlycheckScope::Workspace => cmd.arg("--workspace"), - FlycheckScope::Package { package, .. } => cmd.arg("-p").arg(&package.repr), + FlycheckScope::Package { + package: PackageSpecifier::Cargo { package_id }, + .. + } => cmd.arg("-p").arg(&package_id.repr), + FlycheckScope::Package { + package: PackageSpecifier::BuildInfo { .. }, .. + } => { + // No way to flycheck this single package. All we have is a build label. + // There's no way to really say whether this build label happens to be + // a cargo canonical name, so we won't try. + return None; + } }; if let Some(tgt) = target { @@ -695,12 +897,12 @@ impl FlycheckActor { cmd.arg("--keep-going"); - options.apply_on_command( + cargo_options.apply_on_command( &mut cmd, self.ws_target_dir.as_ref().map(Utf8PathBuf::as_path), ); - cmd.args(&options.extra_args); - Some(cmd) + cmd.args(&cargo_options.extra_args); + Some((cmd, FlycheckCommandOrigin::Cargo)) } FlycheckConfig::CustomCommand { command, args, extra_env, invocation_strategy } => { let root = match invocation_strategy { @@ -710,31 +912,25 @@ impl FlycheckActor { &*self.root } }; - let mut cmd = toolchain::command(command, root, extra_env); + let runnable = project_json::Runnable { + program: command.clone(), + cwd: Utf8Path::to_owned(root.as_ref()), + args: args.clone(), + kind: project_json::RunnableKind::Flycheck, + }; - // If the custom command has a $saved_file placeholder, and - // we're saving a file, replace the placeholder in the arguments. - if let Some(saved_file) = saved_file { - for arg in args { - if arg == SAVED_FILE_PLACEHOLDER { - cmd.arg(saved_file); - } else { - cmd.arg(arg); - } - } - } else { - for arg in args { - if arg == SAVED_FILE_PLACEHOLDER { - // The custom command has a $saved_file placeholder, - // but we had an IDE event that wasn't a file save. Do nothing. - return None; - } + let label = match scope { + FlycheckScope::Workspace => None, + // We support substituting both build labels (e.g. buck, bazel) and cargo package ids. + // With cargo package ids, you get `cargo check -p path+file:///path/to/rust-analyzer/crates/hir#0.0.0`. + // That does work! + FlycheckScope::Package { package, .. } => Some(package.as_str()), + }; - cmd.arg(arg); - } - } + let subs = Substitutions { label, saved_file: saved_file.map(|x| x.as_str()) }; + let cmd = subs.substitute(&runnable, extra_env)?; - Some(cmd) + Some((cmd, FlycheckCommandOrigin::CheckOverrideCommand)) } } } @@ -748,7 +944,7 @@ impl FlycheckActor { #[allow(clippy::large_enum_variant)] enum CargoCheckMessage { CompilerArtifact(cargo_metadata::Artifact), - Diagnostic { diagnostic: Diagnostic, package_id: Option> }, + Diagnostic { diagnostic: Diagnostic, package_id: Option }, } struct CargoCheckParser; @@ -767,7 +963,9 @@ impl JsonLinesParser for CargoCheckParser { cargo_metadata::Message::CompilerMessage(msg) => { Some(CargoCheckMessage::Diagnostic { diagnostic: msg.message, - package_id: Some(Arc::new(msg.package_id)), + package_id: Some(PackageSpecifier::Cargo { + package_id: Arc::new(msg.package_id), + }), }) } _ => None, @@ -794,3 +992,181 @@ enum JsonMessage { Cargo(cargo_metadata::Message), Rustc(Diagnostic), } + +/// Not good enough to execute in a shell, but good enough to show the user without all the noisy +/// quotes +/// +/// Pass implicit_cwd if there is one regarded as the obvious by the user, so we can skip showing it. +/// Compactness is the aim of the game, the output typically gets truncated quite a lot. +fn display_command(c: &Command, implicit_cwd: Option<&std::path::Path>) -> String { + let mut o = String::new(); + use std::fmt::Write; + let lossy = std::ffi::OsStr::to_string_lossy; + if let Some(dir) = c.get_current_dir() { + if Some(dir) == implicit_cwd.map(std::path::Path::new) { + // pass + } else if dir.to_string_lossy().contains(" ") { + write!(o, "cd {:?} && ", dir).unwrap(); + } else { + write!(o, "cd {} && ", dir.display()).unwrap(); + } + } + for (env, val) in c.get_envs() { + let (env, val) = (lossy(env), val.map(lossy).unwrap_or(std::borrow::Cow::Borrowed(""))); + if DISPLAY_COMMAND_IGNORE_ENVS.contains(&env.as_ref()) { + continue; + } + if env.contains(" ") { + write!(o, "\"{}={}\" ", env, val).unwrap(); + } else if val.contains(" ") { + write!(o, "{}=\"{}\" ", env, val).unwrap(); + } else { + write!(o, "{}={} ", env, val).unwrap(); + } + } + let prog = lossy(c.get_program()); + if prog.contains(" ") { + write!(o, "{:?}", prog).unwrap(); + } else { + write!(o, "{}", prog).unwrap(); + } + for arg in c.get_args() { + let arg = lossy(arg); + if arg.contains(" ") { + write!(o, " \"{}\"", arg).unwrap(); + } else { + write!(o, " {}", arg).unwrap(); + } + } + o +} + +#[cfg(test)] +mod tests { + use ide_db::FxHashMap; + use itertools::Itertools; + use paths::Utf8Path; + use project_model::project_json; + + use crate::flycheck::Substitutions; + use crate::flycheck::display_command; + + #[test] + fn test_substitutions() { + let label = ":label"; + let saved_file = "file.rs"; + + // Runnable says it needs both; you need both. + assert_eq!(test_substitute(None, None, "{label} {saved_file}").as_deref(), None); + assert_eq!(test_substitute(Some(label), None, "{label} {saved_file}").as_deref(), None); + assert_eq!( + test_substitute(None, Some(saved_file), "{label} {saved_file}").as_deref(), + None + ); + assert_eq!( + test_substitute(Some(label), Some(saved_file), "{label} {saved_file}").as_deref(), + Some("build :label file.rs") + ); + + // Only need label? only need label. + assert_eq!(test_substitute(None, None, "{label}").as_deref(), None); + assert_eq!(test_substitute(Some(label), None, "{label}").as_deref(), Some("build :label"),); + assert_eq!(test_substitute(None, Some(saved_file), "{label}").as_deref(), None,); + assert_eq!( + test_substitute(Some(label), Some(saved_file), "{label}").as_deref(), + Some("build :label"), + ); + + // Only need saved_file + assert_eq!(test_substitute(None, None, "{saved_file}").as_deref(), None); + assert_eq!(test_substitute(Some(label), None, "{saved_file}").as_deref(), None); + assert_eq!( + test_substitute(None, Some(saved_file), "{saved_file}").as_deref(), + Some("build file.rs") + ); + assert_eq!( + test_substitute(Some(label), Some(saved_file), "{saved_file}").as_deref(), + Some("build file.rs") + ); + + // Need neither + assert_eq!(test_substitute(None, None, "xxx").as_deref(), Some("build xxx")); + assert_eq!(test_substitute(Some(label), None, "xxx").as_deref(), Some("build xxx")); + assert_eq!(test_substitute(None, Some(saved_file), "xxx").as_deref(), Some("build xxx")); + assert_eq!( + test_substitute(Some(label), Some(saved_file), "xxx").as_deref(), + Some("build xxx") + ); + + // {label} mid-argument substitution + assert_eq!( + test_substitute(Some(label), None, "--label={label}").as_deref(), + Some("build --label=:label") + ); + + // {saved_file} mid-argument substitution + assert_eq!( + test_substitute(None, Some(saved_file), "--saved={saved_file}").as_deref(), + Some("build --saved=file.rs") + ); + + // $saved_file legacy support (no mid-argument substitution, we never supported that) + assert_eq!( + test_substitute(None, Some(saved_file), "$saved_file").as_deref(), + Some("build file.rs") + ); + + fn test_substitute( + label: Option<&str>, + saved_file: Option<&str>, + args: &str, + ) -> Option { + Substitutions { label, saved_file } + .substitute( + &project_json::Runnable { + program: "build".to_owned(), + args: Vec::from_iter(args.split_whitespace().map(ToOwned::to_owned)), + cwd: Utf8Path::new("/path").to_owned(), + kind: project_json::RunnableKind::Flycheck, + }, + &FxHashMap::default(), + ) + .map(|command| { + command.get_args().map(|x| x.to_string_lossy()).collect_vec().join(" ") + }) + .map(|args| format!("build {}", args)) + } + } + + #[test] + fn test_display_command() { + use std::path::Path; + let workdir = Path::new("workdir"); + let mut cmd = toolchain::command("command", workdir, &FxHashMap::default()); + assert_eq!(display_command(cmd.arg("--arg"), Some(workdir)), "command --arg"); + assert_eq!( + display_command(cmd.arg("spaced arg"), Some(workdir)), + "command --arg \"spaced arg\"" + ); + assert_eq!( + display_command(cmd.env("ENVIRON", "yeah"), Some(workdir)), + "ENVIRON=yeah command --arg \"spaced arg\"" + ); + assert_eq!( + display_command(cmd.env("OTHER", "spaced env"), Some(workdir)), + "ENVIRON=yeah OTHER=\"spaced env\" command --arg \"spaced arg\"" + ); + assert_eq!( + display_command(cmd.current_dir("/tmp"), Some(workdir)), + "cd /tmp && ENVIRON=yeah OTHER=\"spaced env\" command --arg \"spaced arg\"" + ); + assert_eq!( + display_command(cmd.current_dir("/tmp and/thing"), Some(workdir)), + "cd \"/tmp and/thing\" && ENVIRON=yeah OTHER=\"spaced env\" command --arg \"spaced arg\"" + ); + assert_eq!( + display_command(cmd.current_dir("/tmp and/thing"), Some(Path::new("/tmp and/thing"))), + "ENVIRON=yeah OTHER=\"spaced env\" command --arg \"spaced arg\"" + ); + } +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs index 9beab3c0e45c..afd4162de622 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs @@ -9,7 +9,6 @@ use std::{ time::{Duration, Instant}, }; -use cargo_metadata::PackageId; use crossbeam_channel::{Receiver, Sender, unbounded}; use hir::ChangeWithProcMacros; use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId}; @@ -36,7 +35,7 @@ use crate::{ config::{Config, ConfigChange, ConfigErrors, RatomlFileKind}, diagnostics::{CheckFixes, DiagnosticCollection}, discover, - flycheck::{FlycheckHandle, FlycheckMessage}, + flycheck::{FlycheckHandle, FlycheckMessage, PackageSpecifier}, line_index::{LineEndings, LineIndex}, lsp::{from_proto, to_proto::url_from_abs_path}, lsp_ext, @@ -113,6 +112,7 @@ pub(crate) struct GlobalState { pub(crate) flycheck_sender: Sender, pub(crate) flycheck_receiver: Receiver, pub(crate) last_flycheck_error: Option, + pub(crate) flycheck_formatted_commands: Vec, // Test explorer pub(crate) test_run_session: Option>, @@ -188,7 +188,7 @@ pub(crate) struct GlobalState { /// been called. pub(crate) deferred_task_queue: DeferredTaskQueue, - /// HACK: Workaround for https://github.com/rust-lang/rust-analyzer/issues/19709 + /// HACK: Workaround for /// This is marked true if we failed to load a crate root file at crate graph creation, /// which will usually end up causing a bunch of incorrect diagnostics on startup. pub(crate) incomplete_crate_graph: bool, @@ -289,6 +289,7 @@ impl GlobalState { flycheck_sender, flycheck_receiver, last_flycheck_error: None, + flycheck_formatted_commands: vec![], test_run_session: None, test_run_sender, @@ -825,7 +826,7 @@ impl GlobalStateSnapshot { let Some(krate) = project.crate_by_root(path) else { continue; }; - let Some(build) = krate.build else { + let Some(build) = krate.build.clone() else { continue; }; @@ -833,6 +834,7 @@ impl GlobalStateSnapshot { label: build.label, target_kind: build.target_kind, shell_runnables: project.runnables().to_owned(), + project_root: project.project_root().to_owned(), })); } ProjectWorkspaceKind::DetachedFile { .. } => {} @@ -844,23 +846,43 @@ impl GlobalStateSnapshot { pub(crate) fn all_workspace_dependencies_for_package( &self, - package: &Arc, - ) -> Option>> { - for workspace in self.workspaces.iter() { - match &workspace.kind { - ProjectWorkspaceKind::Cargo { cargo, .. } - | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. } => { - let package = cargo.packages().find(|p| cargo[*p].id == *package)?; + package: &PackageSpecifier, + ) -> Option> { + match package { + PackageSpecifier::Cargo { package_id } => { + self.workspaces.iter().find_map(|workspace| match &workspace.kind { + ProjectWorkspaceKind::Cargo { cargo, .. } + | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. } => { + let package = cargo.packages().find(|p| cargo[*p].id == *package_id)?; - return cargo[package] - .all_member_deps - .as_ref() - .map(|deps| deps.iter().map(|dep| cargo[*dep].id.clone()).collect()); - } - _ => {} + cargo[package].all_member_deps.as_ref().map(|deps| { + deps.iter() + .map(|dep| cargo[*dep].id.clone()) + .map(|p| PackageSpecifier::Cargo { package_id: p }) + .collect() + }) + } + _ => None, + }) + } + PackageSpecifier::BuildInfo { label } => { + self.workspaces.iter().find_map(|workspace| match &workspace.kind { + ProjectWorkspaceKind::Json(p) => { + let krate = p.crate_by_label(label)?; + Some( + krate + .iter_deps() + .filter_map(|dep| p[dep].build.as_ref()) + .map(|build| PackageSpecifier::BuildInfo { + label: build.label.clone(), + }) + .collect(), + ) + } + _ => None, + }) } } - None } pub(crate) fn file_exists(&self, file_id: FileId) -> bool { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs index 10bbb0bb31d9..90deae2d902e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs @@ -101,7 +101,7 @@ impl RequestDispatcher<'_> { } /// Dispatches a non-latency-sensitive request onto the thread pool. When the VFS is marked not - /// ready this will return a default constructed [`R::Result`]. + /// ready this will return a default constructed `R::Result`. pub(crate) fn on( &mut self, f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, @@ -128,7 +128,7 @@ impl RequestDispatcher<'_> { } /// Dispatches a non-latency-sensitive request onto the thread pool. When the VFS is marked not - /// ready this will return a `default` constructed [`R::Result`]. + /// ready this will return a `default` constructed `R::Result`. pub(crate) fn on_with_vfs_default( &mut self, f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, @@ -176,7 +176,7 @@ impl RequestDispatcher<'_> { } /// Dispatches a latency-sensitive request onto the thread pool. When the VFS is marked not - /// ready this will return a default constructed [`R::Result`]. + /// ready this will return a default constructed `R::Result`. pub(crate) fn on_latency_sensitive( &mut self, f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs index 4a6544508ff4..138310b78f62 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs @@ -18,7 +18,7 @@ use vfs::{AbsPathBuf, ChangeKind, VfsPath}; use crate::{ config::{Config, ConfigChange}, - flycheck::{InvocationStrategy, Target}, + flycheck::{InvocationStrategy, PackageSpecifier, Target}, global_state::{FetchWorkspaceRequest, GlobalState}, lsp::{from_proto, utils::apply_document_changes}, lsp_ext::{self, RunFlycheckParams}, @@ -289,11 +289,24 @@ pub(crate) fn handle_did_change_watched_files( state: &mut GlobalState, params: DidChangeWatchedFilesParams, ) -> anyhow::Result<()> { + // we want to trigger flycheck if a file outside of our workspaces has changed, + // as to reduce stale diagnostics when outside changes happen + let mut trigger_flycheck = false; for change in params.changes.iter().unique_by(|&it| &it.uri) { if let Ok(path) = from_proto::abs_path(&change.uri) { + if !trigger_flycheck { + trigger_flycheck = + state.config.workspace_roots().iter().any(|root| !path.starts_with(root)); + } state.loader.handle.invalidate(path); } } + + if trigger_flycheck && state.config.check_on_save(None) { + for flycheck in state.flycheck.iter() { + flycheck.restart_workspace(None); + } + } Ok(()) } @@ -328,22 +341,33 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { } InvocationStrategy::PerWorkspace => { Box::new(move || { - let target = TargetSpec::for_file(&world, file_id)?.and_then(|it| { + let saved_file = vfs_path.as_path().map(ToOwned::to_owned); + let target = TargetSpec::for_file(&world, file_id)?.map(|it| { let tgt_kind = it.target_kind(); let (tgt_name, root, package) = match it { - TargetSpec::Cargo(c) => (c.target, c.workspace_root, c.package_id), - _ => return None, + TargetSpec::Cargo(c) => ( + Some(c.target), + c.workspace_root, + PackageSpecifier::Cargo { package_id: c.package_id }, + ), + TargetSpec::ProjectJson(p) => ( + None, + p.project_root, + PackageSpecifier::BuildInfo { label: p.label.clone() }, + ), }; - let tgt = match tgt_kind { - project_model::TargetKind::Bin => Target::Bin(tgt_name), - project_model::TargetKind::Example => Target::Example(tgt_name), - project_model::TargetKind::Test => Target::Test(tgt_name), - project_model::TargetKind::Bench => Target::Benchmark(tgt_name), - _ => return Some((None, root, package)), - }; + let tgt = tgt_name.and_then(|tgt_name| { + Some(match tgt_kind { + project_model::TargetKind::Bin => Target::Bin(tgt_name), + project_model::TargetKind::Example => Target::Example(tgt_name), + project_model::TargetKind::Test => Target::Test(tgt_name), + project_model::TargetKind::Bench => Target::Benchmark(tgt_name), + _ => return None, + }) + }); - Some((Some(tgt), root, package)) + (tgt, root, package) }); tracing::debug!(?target, "flycheck target"); // we have a specific non-library target, attempt to only check that target, nothing @@ -352,8 +376,10 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { if let Some((target, root, package)) = target { // trigger a package check if we have a non-library target as that can't affect // anything else in the workspace OR if we're not allowed to check the workspace as - // the user opted into package checks then - let package_check_allowed = target.is_some() || !may_flycheck_workspace; + // the user opted into package checks then OR if this is not cargo. + let package_check_allowed = target.is_some() + || !may_flycheck_workspace + || matches!(package, PackageSpecifier::BuildInfo { .. }); if package_check_allowed { package_workspace_idx = world.workspaces.iter().position(|ws| match &ws.kind { @@ -365,16 +391,30 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { cargo: Some((cargo, _, _)), .. } => *cargo.workspace_root() == root, - _ => false, + project_model::ProjectWorkspaceKind::Json(p) => { + *p.project_root() == root + } + project_model::ProjectWorkspaceKind::DetachedFile { + cargo: None, + .. + } => false, }); if let Some(idx) = package_workspace_idx { - let workspace_deps = - world.all_workspace_dependencies_for_package(&package); - world.flycheck[idx].restart_for_package( - package, - target, - workspace_deps, - ); + // flycheck handles are indexed by their ID (which is the workspace index), + // but not all workspaces have flycheck enabled (e.g., JSON projects without + // a flycheck template). Find the flycheck handle by its ID. + if let Some(flycheck) = + world.flycheck.iter().find(|fc| fc.id() == idx) + { + let workspace_deps = + world.all_workspace_dependencies_for_package(&package); + flycheck.restart_for_package( + package, + target, + workspace_deps, + saved_file.clone(), + ); + } } } } @@ -444,7 +484,6 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { ws_contains_file && !is_pkg_ws }); - let saved_file = vfs_path.as_path().map(ToOwned::to_owned); let mut workspace_check_triggered = false; // Find and trigger corresponding flychecks 'flychecks: for flycheck in world.flycheck.iter() { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs index c61825b99fec..d16ca2fb48ac 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -53,6 +53,7 @@ fn integrated_highlighting_benchmark() { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let (db, vfs, _proc_macro) = { @@ -121,6 +122,7 @@ fn integrated_completion_benchmark() { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, + proc_macro_processes: 1, }; let (db, vfs, _proc_macro) = { @@ -322,6 +324,7 @@ fn integrated_diagnostics_benchmark() { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, + proc_macro_processes: 1, }; let (db, vfs, _proc_macro) = { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index 6f0f57725fc7..e5b983dcbf85 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -70,6 +70,7 @@ pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind { | SymbolKind::Attribute | SymbolKind::Derive | SymbolKind::DeriveHelper => lsp_types::SymbolKind::FUNCTION, + SymbolKind::CrateRoot => lsp_types::SymbolKind::PACKAGE, SymbolKind::Module | SymbolKind::ToolModule => lsp_types::SymbolKind::MODULE, SymbolKind::TypeAlias | SymbolKind::TypeParam | SymbolKind::SelfType => { lsp_types::SymbolKind::TYPE_PARAMETER @@ -141,6 +142,7 @@ pub(crate) fn completion_item_kind( SymbolKind::Method => lsp_types::CompletionItemKind::METHOD, SymbolKind::Const => lsp_types::CompletionItemKind::CONSTANT, SymbolKind::ConstParam => lsp_types::CompletionItemKind::TYPE_PARAMETER, + SymbolKind::CrateRoot => lsp_types::CompletionItemKind::MODULE, SymbolKind::Derive => lsp_types::CompletionItemKind::FUNCTION, SymbolKind::DeriveHelper => lsp_types::CompletionItemKind::FUNCTION, SymbolKind::Enum => lsp_types::CompletionItemKind::ENUM, @@ -803,11 +805,16 @@ fn semantic_token_type_and_modifiers( ) -> (lsp_types::SemanticTokenType, semantic_tokens::ModifierSet) { use semantic_tokens::{modifiers as mods, types}; + let mut mods = semantic_tokens::ModifierSet::default(); let ty = match highlight.tag { HlTag::Symbol(symbol) => match symbol { SymbolKind::Attribute => types::DECORATOR, SymbolKind::Derive => types::DERIVE, SymbolKind::DeriveHelper => types::DERIVE_HELPER, + SymbolKind::CrateRoot => { + mods |= mods::CRATE_ROOT; + types::NAMESPACE + } SymbolKind::Module => types::NAMESPACE, SymbolKind::Impl => types::TYPE_ALIAS, SymbolKind::Field => types::PROPERTY, @@ -870,7 +877,6 @@ fn semantic_token_type_and_modifiers( }, }; - let mut mods = semantic_tokens::ModifierSet::default(); for modifier in highlight.mods.iter() { let modifier = match modifier { HlMod::Associated => mods::ASSOCIATED, 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 dd0813c14454..64decc9e0db6 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 @@ -309,10 +309,10 @@ impl GlobalState { let event_dbg_msg = format!("{event:?}"); tracing::debug!(?loop_start, ?event, "handle_event"); - if tracing::enabled!(tracing::Level::INFO) { + if tracing::enabled!(tracing::Level::TRACE) { let task_queue_len = self.task_pool.handle.len(); if task_queue_len > 0 { - tracing::info!("task queue len: {}", task_queue_len); + tracing::trace!("task queue len: {}", task_queue_len); } } @@ -666,31 +666,33 @@ impl GlobalState { move |sender| { // We aren't observing the semantics token cache here let snapshot = AssertUnwindSafe(&snapshot); - let Ok(diags) = std::panic::catch_unwind(|| { + let diags = std::panic::catch_unwind(|| { fetch_native_diagnostics( &snapshot, subscriptions.clone(), slice.clone(), NativeDiagnosticsFetchKind::Syntax, ) - }) else { - return; - }; + }) + .unwrap_or_else(|_| { + subscriptions.iter().map(|&id| (id, Vec::new())).collect::>() + }); sender .send(Task::Diagnostics(DiagnosticsTaskKind::Syntax(generation, diags))) .unwrap(); if fetch_semantic { - let Ok(diags) = std::panic::catch_unwind(|| { + let diags = std::panic::catch_unwind(|| { fetch_native_diagnostics( &snapshot, subscriptions.clone(), slice.clone(), NativeDiagnosticsFetchKind::Semantic, ) - }) else { - return; - }; + }) + .unwrap_or_else(|_| { + subscriptions.iter().map(|&id| (id, Vec::new())).collect::>() + }); sender .send(Task::Diagnostics(DiagnosticsTaskKind::Semantic( generation, diags, @@ -825,33 +827,29 @@ impl GlobalState { } Task::DiscoverLinkedProjects(arg) => { if let Some(cfg) = self.config.discover_workspace_config() { - // the clone is unfortunately necessary to avoid a borrowck error when - // `self.report_progress` is called later - let title = &cfg.progress_label.clone(); let command = cfg.command.clone(); let discover = DiscoverCommand::new(self.discover_sender.clone(), command); - if self.discover_jobs_active == 0 { - self.report_progress(title, Progress::Begin, None, None, None); - } - self.discover_jobs_active += 1; - let arg = match arg { DiscoverProjectParam::Buildfile(it) => DiscoverArgument::Buildfile(it), DiscoverProjectParam::Path(it) => DiscoverArgument::Path(it), }; - let handle = discover - .spawn( - arg, - &std::env::current_dir() - .expect("Failed to get cwd during project discovery"), - ) - .unwrap_or_else(|e| { - panic!("Failed to spawn project discovery command: {e}") - }); - - self.discover_handles.push(handle); + match discover.spawn(arg, self.config.root_path().as_ref()) { + Ok(handle) => { + if self.discover_jobs_active == 0 { + let title = &cfg.progress_label.clone(); + self.report_progress(title, Progress::Begin, None, None, None); + } + self.discover_jobs_active += 1; + self.discover_handles.push(handle) + } + Err(e) => self.show_message( + lsp_types::MessageType::ERROR, + format!("Failed to spawn project discovery command: {e:#}"), + false, + ), + } } } Task::FetchBuildData(progress) => { @@ -1179,8 +1177,24 @@ impl GlobalState { kind: ClearDiagnosticsKind::OlderThan(generation, ClearScope::Package(package_id)), } => self.diagnostics.clear_check_older_than_for_package(id, package_id, generation), FlycheckMessage::Progress { id, progress } => { + let format_with_id = |user_facing_command: String| { + if self.flycheck.len() == 1 { + user_facing_command + } else { + format!("{user_facing_command} (#{})", id + 1) + } + }; + + self.flycheck_formatted_commands + .resize_with(self.flycheck.len().max(id + 1), || { + format_with_id(self.config.flycheck(None).to_string()) + }); + let (state, message) = match progress { - flycheck::Progress::DidStart => (Progress::Begin, None), + flycheck::Progress::DidStart { user_facing_command } => { + self.flycheck_formatted_commands[id] = format_with_id(user_facing_command); + (Progress::Begin, None) + } flycheck::Progress::DidCheckCrate(target) => (Progress::Report, Some(target)), flycheck::Progress::DidCancel => { self.last_flycheck_error = None; @@ -1200,13 +1214,8 @@ impl GlobalState { } }; - // 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. - let title = if self.flycheck.len() == 1 { - format!("{}", self.config.flycheck(None)) - } else { - format!("{} (#{})", self.config.flycheck(None), id + 1) - }; + // Clone because we &mut self for report_progress + let title = self.flycheck_formatted_commands[id].clone(); self.report_progress( &title, state, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index e3a5ee221973..83f4a19b39fa 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -25,7 +25,9 @@ use load_cargo::{ProjectFolders, load_proc_macro}; use lsp_types::FileSystemWatcher; use paths::Utf8Path; use proc_macro_api::ProcMacroClient; -use project_model::{ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts}; +use project_model::{ + ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts, project_json, +}; use stdx::{format_to, thread::ThreadIntent}; use triomphe::Arc; use vfs::{AbsPath, AbsPathBuf, ChangeKind}; @@ -699,15 +701,19 @@ impl GlobalState { _ => Default::default(), }; info!("Using proc-macro server at {path}"); + let num_process = self.config.proc_macro_num_processes(); - Some(ProcMacroClient::spawn(&path, &env, ws.toolchain.as_ref()).map_err(|err| { - tracing::error!( - "Failed to run proc-macro server from path {path}, error: {err:?}", - ); - anyhow::format_err!( - "Failed to run proc-macro server from path {path}, error: {err:?}", - ) - })) + Some( + ProcMacroClient::spawn(&path, &env, ws.toolchain.as_ref(), num_process) + .map_err(|err| { + tracing::error!( + "Failed to run proc-macro server from path {path}, error: {err:?}", + ); + anyhow::format_err!( + "Failed to run proc-macro server from path {path}, error: {err:?}", + ) + }), + ) })) } @@ -875,6 +881,7 @@ impl GlobalState { generation.clone(), sender.clone(), config, + crate::flycheck::FlycheckConfigJson::default(), None, self.config.root_path().clone(), None, @@ -894,16 +901,25 @@ impl GlobalState { cargo: Some((cargo, _, _)), .. } => ( + crate::flycheck::FlycheckConfigJson::default(), cargo.workspace_root(), Some(cargo.manifest_path()), Some(cargo.target_directory()), ), ProjectWorkspaceKind::Json(project) => { + let config_json = crate::flycheck::FlycheckConfigJson { + single_template: project + .runnable_template(project_json::RunnableKind::Flycheck) + .cloned(), + }; // Enable flychecks for json projects if a custom flycheck command was supplied // in the workspace configuration. match config { + _ if config_json.any_configured() => { + (config_json, project.path(), None, None) + } FlycheckConfig::CustomCommand { .. } => { - (project.path(), None, None) + (config_json, project.path(), None, None) } _ => return None, } @@ -913,12 +929,13 @@ impl GlobalState { ws.sysroot.root().map(ToOwned::to_owned), )) }) - .map(|(id, (root, manifest_path, target_dir), sysroot_root)| { + .map(|(id, (config_json, root, manifest_path, target_dir), sysroot_root)| { FlycheckHandle::spawn( id, generation.clone(), sender.clone(), config.clone(), + config_json, sysroot_root, root.to_path_buf(), manifest_path.map(|it| it.to_path_buf()), @@ -929,6 +946,7 @@ impl GlobalState { } } .into(); + self.flycheck_formatted_commands = vec![]; } } 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..b8d9acc02a32 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 @@ -68,6 +68,7 @@ pub(crate) struct ProjectJsonTargetSpec { pub(crate) label: String, pub(crate) target_kind: TargetKind, pub(crate) shell_runnables: Vec, + pub(crate) project_root: AbsPathBuf, } impl ProjectJsonTargetSpec { @@ -76,7 +77,16 @@ impl ProjectJsonTargetSpec { RunnableKind::Bin => { for runnable in &self.shell_runnables { if matches!(runnable.kind, project_model::project_json::RunnableKind::Run) { - return Some(runnable.clone()); + 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); } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/task_pool.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/task_pool.rs index 8b8876b801cf..104cd3d2eae9 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/task_pool.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/task_pool.rs @@ -52,7 +52,7 @@ impl TaskPool { /// `DeferredTaskQueue` holds deferred tasks. /// /// These are tasks that must be run after -/// [`GlobalState::process_changes`] has been called. +/// `GlobalState::process_changes` has been called. pub(crate) struct DeferredTaskQueue { pub(crate) sender: crossbeam_channel::Sender, pub(crate) receiver: crossbeam_channel::Receiver, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs index 7111a15d0246..0d9c8310d858 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs @@ -101,11 +101,11 @@ impl CargoTestHandle { ws_target_dir: Option<&Utf8Path>, test_target: TestTarget, sender: Sender, - ) -> std::io::Result { + ) -> anyhow::Result { let mut cmd = toolchain::command(Tool::Cargo.path(), root, &options.extra_env); cmd.env("RUSTC_BOOTSTRAP", "1"); cmd.arg("--color=always"); - cmd.arg("test"); + cmd.arg(&options.subcommand); // test, usually cmd.arg("--package"); cmd.arg(&test_target.package); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs index eb1b8c5dd0e6..b4a7b44d165a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs @@ -1447,7 +1447,27 @@ foo = { path = "../foo" } .server() .wait_until_workspace_is_loaded(); - server.request::(Default::default(), json!([])); + server.request::( + Default::default(), + json!([ + { + "name": "bar", + "kind": 4, + "location": { + "uri": "file:///[..]bar/src/lib.rs", + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 0 + } + } + } + }]), + ); let server = Project::with_fixture( r#" @@ -1486,7 +1506,27 @@ version = "0.0.0" .server() .wait_until_workspace_is_loaded(); - server.request::(Default::default(), json!([])); + server.request::( + Default::default(), + json!([ + { + "name": "baz", + "kind": 4, + "location": { + "uri": "file:///[..]baz/src/lib.rs", + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 0 + } + } + } + }]), + ); } #[test] diff --git a/src/tools/rust-analyzer/crates/span/src/hygiene.rs b/src/tools/rust-analyzer/crates/span/src/hygiene.rs index fdfa94dfee4e..fe05ef946518 100644 --- a/src/tools/rust-analyzer/crates/span/src/hygiene.rs +++ b/src/tools/rust-analyzer/crates/span/src/hygiene.rs @@ -8,9 +8,9 @@ //! //! # The Expansion Order Hierarchy //! -//! `ExpnData` in rustc, rust-analyzer's version is [`MacroCallLoc`]. Traversing the hierarchy -//! upwards can be achieved by walking up [`MacroCallLoc::kind`]'s contained file id, as -//! [`MacroFile`]s are interned [`MacroCallLoc`]s. +//! `ExpnData` in rustc, rust-analyzer's version is `MacroCallLoc`. Traversing the hierarchy +//! upwards can be achieved by walking up `MacroCallLoc::kind`'s contained file id, as +//! `MacroFile`s are interned `MacroCallLoc`s. //! //! # The Macro Definition Hierarchy //! @@ -18,7 +18,7 @@ //! //! # The Call-site Hierarchy //! -//! `ExpnData::call_site` in rustc, [`MacroCallLoc::call_site`] in rust-analyzer. +//! `ExpnData::call_site` in rustc, `MacroCallLoc::call_site` in rust-analyzer. use crate::Edition; use std::fmt; @@ -241,9 +241,7 @@ const _: () = { edition: zalsa_::interned::Lookup::into_owned(data.2), parent: zalsa_::interned::Lookup::into_owned(data.3), opaque: opaque(zalsa_::FromId::from_id(id)), - opaque_and_semiopaque: opaque_and_semiopaque( - zalsa_::FromId::from_id(id), - ), + opaque_and_semiopaque: opaque_and_semiopaque(zalsa_::FromId::from_id(id)), }, ) } diff --git a/src/tools/rust-analyzer/crates/stdx/src/process.rs b/src/tools/rust-analyzer/crates/stdx/src/process.rs index 2efeed45e44e..7c4ae978b04a 100644 --- a/src/tools/rust-analyzer/crates/stdx/src/process.rs +++ b/src/tools/rust-analyzer/crates/stdx/src/process.rs @@ -76,7 +76,7 @@ pub fn spawn_with_streaming_output( Ok(Output { status, stdout, stderr }) } -#[cfg(unix)] +#[cfg(all(unix, not(target_arch = "wasm32")))] mod imp { use std::{ io::{self, prelude::*}, diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/prec.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/prec.rs index 8c88224a761a..d99cf492616e 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/prec.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/prec.rs @@ -154,6 +154,11 @@ fn check_ancestry(ancestor: &SyntaxNode, descendent: &SyntaxNode) -> bool { bail() } +fn next_token_of(node: &SyntaxNode) -> Option { + let last = node.last_token()?; + skip_trivia_token(last.next_token()?, Direction::Next) +} + impl Expr { pub fn precedence(&self) -> ExprPrecedence { precedence(self) @@ -197,6 +202,8 @@ impl Expr { if is_parent_call_expr && is_field_expr { return true; } + let place_of_parent = + || place_of.ancestors().find(|it| it.parent().is_none_or(|p| &p == parent.syntax())); // Special-case block weirdness if parent.child_is_followed_by_a_block() { @@ -226,15 +233,24 @@ impl Expr { // For `&&`, we avoid introducing ` && ` into a binary chain. if self.precedence() == ExprPrecedence::Jump - && let Some(node) = - place_of.ancestors().find(|it| it.parent().is_none_or(|p| &p == parent.syntax())) - && let Some(next) = - node.last_token().and_then(|t| skip_trivia_token(t.next_token()?, Direction::Next)) + && let Some(node) = place_of_parent() + && let Some(next) = next_token_of(&node) && matches!(next.kind(), T![||] | T![&&]) { return true; } + // Special-case `2 as x < 3` + if let ast::Expr::CastExpr(it) = self + && let Some(ty) = it.ty() + && ty.syntax().last_token().and_then(|it| ast::NameLike::cast(it.parent()?)).is_some() + && let Some(node) = place_of_parent() + && let Some(next) = next_token_of(&node) + && matches!(next.kind(), T![<] | T![<<]) + { + return true; + } + if self.is_paren_like() || parent.is_paren_like() || self.is_prefix() diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 7cf9e2bf14f9..5fe419ad4eb7 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -1578,6 +1578,44 @@ impl SyntaxFactory { pub fn ident(&self, text: &str) -> SyntaxToken { make::tokens::ident(text) } + + pub fn mut_self_param(&self) -> ast::SelfParam { + let ast = make::mut_self_param().clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + + pub fn ret_type(&self, ty: ast::Type) -> ast::RetType { + let ast = make::ret_type(ty.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + ast + } + + pub fn ty_ref(&self, ty: ast::Type, is_mut: bool) -> ast::Type { + let ast = make::ty_ref(ty.clone(), is_mut).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + match &ast { + ast::Type::RefType(ref_ty) => { + builder.map_node(ty.syntax().clone(), ref_ty.ty().unwrap().syntax().clone()); + } + _ => unreachable!(), + } + builder.finish(&mut mapping); + } + ast + } } // `ext` constructors diff --git a/src/tools/rust-analyzer/crates/syntax/src/ptr.rs b/src/tools/rust-analyzer/crates/syntax/src/ptr.rs index 34c07598d200..c4979b8e3ae8 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ptr.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ptr.rs @@ -68,7 +68,7 @@ impl AstPtr { self.raw } - pub fn text_range(&self) -> TextRange { + pub fn text_range(self) -> TextRange { self.raw.text_range() } diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs index 1eaef03197c5..6257bf4e572e 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs @@ -1,6 +1,6 @@ //! Maps syntax elements through disjoint syntax nodes. //! -//! [`SyntaxMappingBuilder`] should be used to create mappings to add to a [`SyntaxEditor`] +//! [`SyntaxMappingBuilder`] should be used to create mappings to add to a `SyntaxEditor` use itertools::Itertools; use rustc_hash::FxHashMap; diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index d81f27d7c3b1..ca68edd88c05 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -37,7 +37,110 @@ use triomphe::Arc; pub const WORKSPACE: base_db::SourceRootId = base_db::SourceRootId(0); +/// A trait for setting up test databases from fixture strings. +/// +/// Fixtures are strings containing Rust source code with optional metadata that describe +/// a project setup. This is the primary way to write tests for rust-analyzer without +/// having to depend on the entire sysroot. +/// +/// # Fixture Syntax +/// +/// ## Basic Structure +/// +/// A fixture without metadata is parsed into a single source file (`/main.rs`). +/// Metadata is added after a `//-` comment prefix. +/// +/// ```text +/// //- /main.rs +/// fn main() { +/// println!("Hello"); +/// } +/// ``` +/// +/// Note that the fixture syntax is optional and can be omitted if the test only requires +/// a simple single file. +/// +/// ## File Metadata +/// +/// Each file can have the following metadata after `//-`: +/// +/// - **Path** (required): Must start with `/`, e.g., `/main.rs`, `/lib.rs`, `/foo/bar.rs` +/// - **`crate:`**: Defines a new crate with this file as its root +/// - Optional version: `crate:foo@0.1.0,https://example.com/repo.git` +/// - **`deps:,`**: Dependencies (requires `crate:`) +/// - **`extern-prelude:,`**: Limits extern prelude to specified crates +/// - **`edition:`**: Rust edition (2015, 2018, 2021, 2024). Defaults to current. +/// - **`cfg:=,`**: Configuration options, e.g., `cfg:test,feature="foo"` +/// - **`env:=`**: Environment variables +/// - **`crate-attr:`**: Crate-level attributes, e.g., `crate-attr:no_std` +/// - **`new_source_root:local|library`**: Starts a new source root +/// - **`library`**: Marks crate as external library (not workspace member) +/// +/// ## Global Meta (must appear at the top, in order) +/// +/// - **`//- toolchain: nightly|stable`**: Sets the Rust toolchain (default: stable) +/// - **`//- target_data_layout: `**: LLVM data layout string +/// - **`//- target_arch: `**: Target architecture (default: x86_64) +/// - **`//- proc_macros: ,`**: Enables predefined test proc macros +/// - **`//- minicore: , `**: Includes subset of libcore +/// +/// ## Cursor Markers +/// +/// Use `$0` to mark cursor position(s) in the fixture: +/// - Single `$0`: marks a position (use with [`with_position`](Self::with_position)) +/// - Two `$0` markers: marks a range (use with [`with_range`](Self::with_range)) +/// - Escape as `\$0` if you need a literal `$0` +/// +/// # Examples +/// +/// ## Single file with cursor position +/// ```text +/// r#" +/// fn main() { +/// let x$0 = 42; +/// } +/// "# +/// ``` +/// +/// ## Multiple crates with dependencies +/// ```text +/// r#" +/// //- /main.rs crate:main deps:helper +/// use helper::greet; +/// fn main() { greet(); } +/// +/// //- /lib.rs crate:helper +/// pub fn greet() {} +/// "# +/// ``` +/// +/// ## Using minicore for lang items +/// ```text +/// r#" +/// //- minicore: option, result, iterator +/// //- /main.rs +/// fn foo() -> Option { Some(42) } +/// "# +/// ``` +/// +/// The available minicore flags are listed at the top of crates\test-utils\src\minicore.rs. +/// +/// ## Using test proc macros +/// ```text +/// r#" +/// //- proc_macros: identity, mirror +/// //- /main.rs crate:main deps:proc_macros +/// use proc_macros::identity; +/// +/// #[identity] +/// fn foo() {} +/// "# +/// ``` +/// +/// Available proc macros: `identity` (attr), `DeriveIdentity` (derive), `input_replace` (attr), +/// `mirror` (bang), `shorten` (bang) pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static { + /// See the trait documentation for more information on fixtures. #[track_caller] fn with_single_file( #[rust_analyzer::rust_fixture] ra_fixture: &str, @@ -50,6 +153,7 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static { (db, file) } + /// See the trait documentation for more information on fixtures. #[track_caller] fn with_many_files( #[rust_analyzer::rust_fixture] ra_fixture: &str, @@ -66,6 +170,7 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static { (db, files) } + /// See the trait documentation for more information on fixtures. #[track_caller] fn with_files(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> Self { let mut db = Self::default(); @@ -75,6 +180,7 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static { db } + /// See the trait documentation for more information on fixtures. #[track_caller] fn with_files_extra_proc_macros( #[rust_analyzer::rust_fixture] ra_fixture: &str, @@ -88,6 +194,7 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static { db } + /// See the trait documentation for more information on fixtures. #[track_caller] fn with_position(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (Self, FilePosition) { let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); @@ -95,6 +202,7 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static { (db, FilePosition { file_id, offset }) } + /// See the trait documentation for more information on fixtures. #[track_caller] fn with_range(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (Self, FileRange) { let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); @@ -102,6 +210,7 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static { (db, FileRange { file_id, range }) } + /// See the trait documentation for more information on fixtures. #[track_caller] fn with_range_or_offset( #[rust_analyzer::rust_fixture] ra_fixture: &str, diff --git a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs index c3429356d9e5..48c3e8952507 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs @@ -58,6 +58,7 @@ //! pin: //! pointee: copy, send, sync, ord, hash, unpin, phantom_data //! range: +//! new_range: //! receiver: deref //! result: //! send: sized @@ -175,7 +176,9 @@ pub mod marker { // region:clone impl Clone for PhantomData { - fn clone(&self) -> Self { Self } + fn clone(&self) -> Self { + Self + } } // endregion:clone @@ -1128,6 +1131,32 @@ pub mod ops { // endregion:dispatch_from_dyn } +// region:new_range +pub mod range { + #[lang = "RangeCopy"] + pub struct Range { + pub start: Idx, + pub end: Idx, + } + + #[lang = "RangeFromCopy"] + pub struct RangeFrom { + pub start: Idx, + } + + #[lang = "RangeInclusiveCopy"] + pub struct RangeInclusive { + pub start: Idx, + pub end: Idx, + } + + #[lang = "RangeToInclusiveCopy"] + pub struct RangeToInclusive { + pub end: Idx, + } +} +// endregion:new_range + // region:eq pub mod cmp { use crate::marker::PointeeSized; @@ -1144,7 +1173,9 @@ pub mod cmp { // region:builtin_impls impl PartialEq for () { - fn eq(&self, other: &()) -> bool { true } + fn eq(&self, other: &()) -> bool { + true + } } // endregion:builtin_impls @@ -1567,10 +1598,7 @@ pub mod pin { } // endregion:dispatch_from_dyn // region:coerce_unsized - impl crate::ops::CoerceUnsized> for Pin where - Ptr: crate::ops::CoerceUnsized - { - } + impl crate::ops::CoerceUnsized> for Pin where Ptr: crate::ops::CoerceUnsized {} // endregion:coerce_unsized } // endregion:pin @@ -1792,9 +1820,9 @@ pub mod iter { fn from_iter>(iter: T) -> Self; } } - pub use self::collect::{IntoIterator, FromIterator}; + pub use self::collect::{FromIterator, IntoIterator}; } - pub use self::traits::{IntoIterator, FromIterator, Iterator}; + pub use self::traits::{FromIterator, IntoIterator, Iterator}; } // endregion:iterator @@ -1880,6 +1908,10 @@ mod arch { pub macro global_asm("assembly template", $(operands,)* $(options($(option),*))?) { /* compiler built-in */ } + #[rustc_builtin_macro] + pub macro naked_asm("assembly template", $(operands,)* $(options($(option),*))?) { + /* compiler built-in */ + } } // endregion:asm @@ -2087,30 +2119,30 @@ macro_rules! column { pub mod prelude { pub mod v1 { pub use crate::{ - clone::Clone, // :clone - cmp::{Eq, PartialEq}, // :eq - cmp::{Ord, PartialOrd}, // :ord - convert::AsMut, // :as_mut - convert::AsRef, // :as_ref - convert::{From, Into, TryFrom, TryInto}, // :from - default::Default, // :default - iter::{IntoIterator, Iterator, FromIterator}, // :iterator - macros::builtin::{derive, derive_const}, // :derive - marker::Copy, // :copy - marker::Send, // :send - marker::Sized, // :sized - marker::Sync, // :sync - mem::drop, // :drop - mem::size_of, // :size_of - ops::Drop, // :drop - ops::{AsyncFn, AsyncFnMut, AsyncFnOnce}, // :async_fn - ops::{Fn, FnMut, FnOnce}, // :fn - option::Option::{self, None, Some}, // :option - panic, // :panic - result::Result::{self, Err, Ok}, // :result - str::FromStr, // :str - fmt::derive::Debug, // :fmt, derive - hash::derive::Hash, // :hash, derive + clone::Clone, // :clone + cmp::{Eq, PartialEq}, // :eq + cmp::{Ord, PartialOrd}, // :ord + convert::AsMut, // :as_mut + convert::AsRef, // :as_ref + convert::{From, Into, TryFrom, TryInto}, // :from + default::Default, // :default + fmt::derive::Debug, // :fmt, derive + hash::derive::Hash, // :hash, derive + iter::{FromIterator, IntoIterator, Iterator}, // :iterator + macros::builtin::{derive, derive_const}, // :derive + marker::Copy, // :copy + marker::Send, // :send + marker::Sized, // :sized + marker::Sync, // :sync + mem::drop, // :drop + mem::size_of, // :size_of + ops::Drop, // :drop + ops::{AsyncFn, AsyncFnMut, AsyncFnOnce}, // :async_fn + ops::{Fn, FnMut, FnOnce}, // :fn + option::Option::{self, None, Some}, // :option + panic, // :panic + result::Result::{self, Err, Ok}, // :result + str::FromStr, // :str }; } diff --git a/src/tools/rust-analyzer/crates/toolchain/src/lib.rs b/src/tools/rust-analyzer/crates/toolchain/src/lib.rs index 39319886cfe4..1a1726983870 100644 --- a/src/tools/rust-analyzer/crates/toolchain/src/lib.rs +++ b/src/tools/rust-analyzer/crates/toolchain/src/lib.rs @@ -74,6 +74,9 @@ impl Tool { // Prevent rustup from automatically installing toolchains, see https://github.com/rust-lang/rust-analyzer/issues/20719. pub const NO_RUSTUP_AUTO_INSTALL_ENV: (&str, &str) = ("RUSTUP_AUTO_INSTALL", "0"); +// These get ignored when displaying what command is running in LSP status messages. +pub const DISPLAY_COMMAND_IGNORE_ENVS: &[&str] = &[NO_RUSTUP_AUTO_INSTALL_ENV.0]; + #[allow(clippy::disallowed_types)] /* generic parameter allows for FxHashMap */ pub fn command( cmd: impl AsRef, diff --git a/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml b/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml index bd6c8331e66c..ce7ea53b5373 100644 --- a/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml +++ b/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml @@ -16,7 +16,7 @@ doctest = false tracing.workspace = true walkdir = "2.5.0" crossbeam-channel.workspace = true -notify = "8.0.0" +notify = "8.2.0" rayon = "1.10.0" stdx.workspace = true 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..8460c2c7d0cf 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -323,10 +323,18 @@ each of them, with the working directory being the workspace root (i.e., the folder containing the `Cargo.toml`). This can be overwritten by changing `#rust-analyzer.check.invocationStrategy#`. -If `$saved_file` is part of the command, rust-analyzer will pass -the absolute path of the saved file to the provided command. This is -intended to be used with non-Cargo build systems. -Note that `$saved_file` is experimental and may be removed in the future. +It supports two interpolation syntaxes, both mainly intended to be used with +[non-Cargo build systems](./non_cargo_based_projects.md): + +- If `{saved_file}` is part of the command, rust-analyzer will pass + the absolute path of the saved file to the provided command. + (A previous version, `$saved_file`, also works.) +- If `{label}` is part of the command, rust-analyzer will pass the + Cargo package ID, which can be used with `cargo check -p`, or a build label from + `rust-project.json`. If `{label}` is included, rust-analyzer behaves much like + [`"rust-analyzer.check.workspace": false`](#check.workspace). + + An example command would be: @@ -1310,6 +1318,16 @@ These proc-macros will be ignored when trying to expand them. This config takes a map of crate names with the exported proc-macro names to ignore as values. +## rust-analyzer.procMacro.processes {#procMacro.processes} + +Default: `1` + +Number of proc-macro server processes to spawn. + +Controls how many independent `proc-macro-srv` processes rust-analyzer +runs in parallel to handle macro expansion. + + ## rust-analyzer.procMacro.server {#procMacro.server} Default: `null` @@ -1611,33 +1629,83 @@ though Cargo might be the eventual consumer. Default: `null` -Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`]. +Configure a command that rust-analyzer can invoke to +obtain configuration. -[`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`. -`progress_label` is used for the title in progress indicators, whereas `files_to_watch` -is used to determine which build system-specific files should be watched in order to -reload rust-analyzer. +This is an alternative to manually generating +`rust-project.json`: it enables rust-analyzer to generate +rust-project.json on the fly, and regenerate it when +switching or modifying projects. + +This is an object with three fields: + +* `command`: the shell command to invoke + +* `filesToWatch`: which build system-specific files should +be watched to trigger regenerating the configuration + +* `progressLabel`: the name of the command, used in +progress indicators in the IDE + +Here's an example of a valid configuration: -Below is an example of a valid configuration: ```json "rust-analyzer.workspace.discoverConfig": { "command": [ "rust-project", - "develop-json" + "develop-json", + "{arg}" ], - "progressLabel": "rust-analyzer", + "progressLabel": "buck2/rust-project", "filesToWatch": [ "BUCK" ] } ``` -## On `DiscoverWorkspaceConfig::command` +## Argument Substitutions + +If `command` includes the argument `{arg}`, that argument will be substituted +with the JSON-serialized form of the following enum: + +```norun +#[derive(PartialEq, Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum DiscoverArgument { + Path(AbsPathBuf), + Buildfile(AbsPathBuf), +} +``` + +rust-analyzer will use the path invocation to find and +generate a `rust-project.json` and therefore a +workspace. Example: + + +```norun +rust-project develop-json '{ "path": "myproject/src/main.rs" }' +``` + +rust-analyzer will use build file invocations to update an +existing workspace. Example: + +Or with a build file and the configuration above: + +```norun +rust-project develop-json '{ "buildfile": "myproject/BUCK" }' +``` + +As a reference for implementors, buck2's `rust-project` +will likely be useful: +. + +## Discover Command Output **Warning**: This format is provisional and subject to change. -[`DiscoverWorkspaceConfig::command`] *must* return a JSON object corresponding to -`DiscoverProjectData::Finished`: +The discover command should output JSON objects, one per +line (JSONL format). These objects should correspond to +this Rust data type: ```norun #[derive(Debug, Clone, Deserialize, Serialize)] @@ -1650,7 +1718,14 @@ enum DiscoverProjectData { } ``` -As JSON, `DiscoverProjectData::Finished` is: +For example, a progress event: + +```json +{"kind":"progress","message":"generating rust-project.json"} +``` + +A finished event can look like this (expanded and +commented for readability): ```json { @@ -1658,7 +1733,7 @@ As JSON, `DiscoverProjectData::Finished` is: "kind": "finished", // the file used by a non-Cargo build system to define // a package or target. - "buildfile": "rust-analyzer/BUILD", + "buildfile": "rust-analyzer/BUCK", // the contents of a rust-project.json, elided for brevity "project": { "sysroot": "foo", @@ -1667,41 +1742,9 @@ As JSON, `DiscoverProjectData::Finished` is: } ``` -It is encouraged, but not required, to use the other variants on `DiscoverProjectData` -to provide a more polished end-user experience. - -`DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`, which will be -substituted with the JSON-serialized form of the following enum: - -```norun -#[derive(PartialEq, Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum DiscoverArgument { - Path(AbsPathBuf), - Buildfile(AbsPathBuf), -} -``` - -The JSON representation of `DiscoverArgument::Path` is: - -```json -{ - "path": "src/main.rs" -} -``` - -Similarly, the JSON representation of `DiscoverArgument::Buildfile` is: - -```json -{ - "buildfile": "BUILD" -} -``` - -`DiscoverArgument::Path` is used to find and generate a `rust-project.json`, and -therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to to update an -existing workspace. As a reference for implementors, buck2's `rust-project` will likely -be useful: . +Only the finished event is required, but the other +variants are encouraged to give users more feedback about +progress or errors. ## rust-analyzer.workspace.symbol.search.excludeImports {#workspace.symbol.search.excludeImports} diff --git a/src/tools/rust-analyzer/docs/book/src/faq.md b/src/tools/rust-analyzer/docs/book/src/faq.md index 8c143ab94935..9eeb2ae55539 100644 --- a/src/tools/rust-analyzer/docs/book/src/faq.md +++ b/src/tools/rust-analyzer/docs/book/src/faq.md @@ -4,7 +4,7 @@ rust-analyzer fails to resolve `None`, and thinks you are binding to a variable named `None`. That's usually a sign of a corrupted sysroot. Try removing and re-installing -it: `rustup component remove rust-src` then `rustup component install rust-src`. +it: `rustup component remove rust-src` then `rustup component add rust-src`. ### Rust Analyzer and Cargo compete over the build lock diff --git a/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md b/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md index e7df4a5d7668..f1f10ae33653 100644 --- a/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md +++ b/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md @@ -204,23 +204,40 @@ interface Runnable { args: string[]; /// The current working directory of the runnable. cwd: string; - /// Used to decide what code lens to offer. + /// Maps a runnable to a piece of rust-analyzer functionality. /// - /// `testOne`: This runnable will be used when the user clicks the 'Run Test' - /// CodeLens above a test. + /// - `testOne`: This runnable will be used when the user clicks the 'Run Test' + /// CodeLens above a test. + /// - `run`: This runnable will be used when the user clicks the 'Run' CodeLens + /// above a main function or triggers a run command. + /// - `flycheck`: This is run to provide check-on-save diagnostics when the user + /// saves a file. It must emit rustc JSON diagnostics that rust-analyzer can + /// parse. If this runnable is not specified, we may try to use `cargo check -p`. + /// This is only run for a single crate that the user saved a file in. The + /// {label} syntax is replaced with `BuildInfo::label`. + /// Alternatively, you may use `{saved_file}` and figure out which crate + /// to produce diagnostics for based on that. /// /// The args for testOne can contain two template strings: /// `{label}` and `{test_id}`. `{label}` will be replaced - /// with the `Build::label` and `{test_id}` will be replaced + /// with the `BuildInfo::label` and `{test_id}` will be replaced /// with the test name. - kind: 'testOne' | string; + kind: 'testOne' | 'run' | 'flycheck' | string; } ``` This format is provisional and subject to change. Specifically, the `roots` setup will be different eventually. -There are three ways to feed `rust-project.json` to rust-analyzer: +### Providing a JSON project to rust-analyzer + +There are four ways to feed `rust-project.json` to rust-analyzer: + +- Use + [`"rust-analyzer.workspace.discoverConfig": … }`](./configuration.md#workspace.discoverConfig) + to specify a workspace discovery command to generate project descriptions + on-the-fly. Please note that the command output is message-oriented and must + output JSONL [as described in the configuration docs](./configuration.md#workspace.discoverConfig). - Place `rust-project.json` file at the root of the project, and rust-analyzer will discover it. @@ -240,19 +257,86 @@ location or (for inline JSON) relative to `rootUri`. You can set the `RA_LOG` environment variable to `rust_analyzer=info` to inspect how rust-analyzer handles config and project loading. -Note that calls to `cargo check` are disabled when using -`rust-project.json` by default, so compilation errors and warnings will -no longer be sent to your LSP client. To enable these compilation errors -you will need to specify explicitly what command rust-analyzer should -run to perform the checks using the -`rust-analyzer.check.overrideCommand` configuration. As an example, the -following configuration explicitly sets `cargo check` as the `check` -command. +### Flycheck support - { "rust-analyzer.check.overrideCommand": ["cargo", "check", "--message-format=json"] } +Rust-analyzer has functionality to run an actual build of a crate when the user saves a file, to +fill in diagnostics it does not implement natively. This is known as "flycheck". -`check.overrideCommand` requires the command specified to output json -error messages for rust-analyzer to consume. The `--message-format=json` -flag does this for `cargo check` so whichever command you use must also -output errors in this format. See the [Configuration](#_configuration) -section for more information. +**Flycheck is disabled when using `rust-project.json` unless explicitly configured**, so compilation +errors and warnings will no longer be sent to your LSP client by default. To enable these +compilation errors you will need to specify explicitly what command rust-analyzer should run to +perform the checks. There are two ways to do this: + +- `rust-project.json` may contain a `runnables` field. The `flycheck` runnable may be used to + configure a check command. See above for documentation. + +- Using the [`rust-analyzer.check.overrideCommand`](./configuration.md#check.overrideCommand) + configuration. This will also override anything in `rust-project.json`. As an example, the + following configuration explicitly sets `cargo check` as the `check` command. + + ```json + { "rust-analyzer.check.overrideCommand": ["cargo", "check", "--message-format=json"] } + ``` + + Note also that this works with cargo projects. + +Either option requires the command specified to output JSON error messages for rust-analyzer to +consume. The `--message-format=json` flag does this for `cargo check` so whichever command you use +must also output errors in this format. + +Either option also supports two syntaxes within each argument: + +- `{label}` will be replaced with the `BuildInfo::label` of the crate + containing a saved file, if `BuildInfo` is provided. In the case of `check.overrideCommand` being + used in a Cargo project, this will be the cargo package ID, which can be used with `cargo check -p`. +- `{saved_file}` will be replaced with an absolute path to the saved file. This can be queried against a + build system to find targets that include the file. + +For example: + +```json +{ "rust-analyzer.check.overrideCommand": ["custom_crate_checker", "{label}"] } +``` + +If you do use `{label}` or `{saved_file}`, the command will not be run unless the relevant value can +be substituted. + + +#### Flycheck considerations + +##### Diagnostic output on error + +A flycheck command using a complex build orchestrator like `"bazel", "build", "{label}"`, even with +a tweak to return JSON messages, is often insufficient. Such a command will typically succeed if +there are warnings, but if there are errors, it might "fail to compile" the diagnostics and not +produce any output. You must build a package in such a way that the build succeeds even if `rustc` +exits with an error, and prints the JSON build messages in every case. + +##### Diagnostics for upstream crates + +`cargo check -p` re-prints any errors and warnings in crates higher up in the dependency graph +than the one requested. We do clear all diagnostics when flychecking, so if you manage to +replicate this behaviour, diagnostics for crates other than the one being checked will show up in +the editor. If you do not, then users may be confused that diagnostics are "stuck" or disappear +entirely when there is a build error in an upstream crate. + +##### Compiler options + +`cargo check` invokes rustc differently from `cargo build`. It turns off codegen (with `rustc +--emit=metadata`), which results in lower latency to get to diagnostics. If your build system can +configure this, it is recommended. + +If your build tool can configure rustc for incremental compiles, this is also recommended. + +##### Locking and pre-emption + +In any good build system, including Cargo, build commands sometimes block each other. Running a +flycheck will (by default) frequently block you from running other build commands. Generally this is +undesirable. Users will have to (unintuitively) press save again in the editor to cancel a +flycheck, so that some other command may proceed. + +If your build system has the ability to isolate any rust-analyzer-driven flychecks and prevent lock +contention, for example a separate build output directory and/or daemon instance, this is +recommended. Alternatively, consider using a feature if available that can set the priority of +various build invocations and automatically cancel lower-priority ones when needed. Flychecks should +be set to a lower priority than general direct build invocations. diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 2157cbd48653..fc20597e8876 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -1213,7 +1213,7 @@ "title": "Check", "properties": { "rust-analyzer.check.overrideCommand": { - "markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option\n(if your client supports the `colorDiagnosticOutput` experimental\ncapability, you can use `--message-format=json-diagnostic-rendered-ansi`).\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.check.invocationStrategy#`.\n\nIf `$saved_file` is part of the command, rust-analyzer will pass\nthe absolute path of the saved file to the provided command. This is\nintended to be used with non-Cargo build systems.\nNote that `$saved_file` is experimental and may be removed in the future.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n\nNote: The option must be specified as an array of command line arguments, with\nthe first argument being the name of the command to run.", + "markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option\n(if your client supports the `colorDiagnosticOutput` experimental\ncapability, you can use `--message-format=json-diagnostic-rendered-ansi`).\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.check.invocationStrategy#`.\n\nIt supports two interpolation syntaxes, both mainly intended to be used with\n[non-Cargo build systems](./non_cargo_based_projects.md):\n\n- If `{saved_file}` is part of the command, rust-analyzer will pass\n the absolute path of the saved file to the provided command.\n (A previous version, `$saved_file`, also works.)\n- If `{label}` is part of the command, rust-analyzer will pass the\n Cargo package ID, which can be used with `cargo check -p`, or a build label from\n `rust-project.json`. If `{label}` is included, rust-analyzer behaves much like\n [`\"rust-analyzer.check.workspace\": false`](#check.workspace).\n\n\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n\nNote: The option must be specified as an array of command line arguments, with\nthe first argument being the name of the command to run.", "default": null, "type": [ "null", @@ -2770,6 +2770,31 @@ } } }, + { + "title": "Proc Macro", + "properties": { + "rust-analyzer.procMacro.processes": { + "markdownDescription": "Number of proc-macro server processes to spawn.\n\nControls how many independent `proc-macro-srv` processes rust-analyzer\nruns in parallel to handle macro expansion.", + "default": 1, + "anyOf": [ + { + "type": "number", + "minimum": 0, + "maximum": 255 + }, + { + "type": "string", + "enum": [ + "physical" + ], + "enumDescriptions": [ + "Use the number of physical cores" + ] + } + ] + } + } + }, { "title": "Proc Macro", "properties": { @@ -3135,7 +3160,7 @@ "title": "Workspace", "properties": { "rust-analyzer.workspace.discoverConfig": { - "markdownDescription": "Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].\n\n[`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`.\n`progress_label` is used for the title in progress indicators, whereas `files_to_watch`\nis used to determine which build system-specific files should be watched in order to\nreload rust-analyzer.\n\nBelow is an example of a valid configuration:\n```json\n\"rust-analyzer.workspace.discoverConfig\": {\n \"command\": [\n \"rust-project\",\n \"develop-json\"\n ],\n \"progressLabel\": \"rust-analyzer\",\n \"filesToWatch\": [\n \"BUCK\"\n ]\n}\n```\n\n## On `DiscoverWorkspaceConfig::command`\n\n**Warning**: This format is provisional and subject to change.\n\n[`DiscoverWorkspaceConfig::command`] *must* return a JSON object corresponding to\n`DiscoverProjectData::Finished`:\n\n```norun\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"kind\")]\n#[serde(rename_all = \"snake_case\")]\nenum DiscoverProjectData {\n Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },\n Error { error: String, source: Option },\n Progress { message: String },\n}\n```\n\nAs JSON, `DiscoverProjectData::Finished` is:\n\n```json\n{\n // the internally-tagged representation of the enum.\n \"kind\": \"finished\",\n // the file used by a non-Cargo build system to define\n // a package or target.\n \"buildfile\": \"rust-analyzer/BUILD\",\n // the contents of a rust-project.json, elided for brevity\n \"project\": {\n \"sysroot\": \"foo\",\n \"crates\": []\n }\n}\n```\n\nIt is encouraged, but not required, to use the other variants on `DiscoverProjectData`\nto provide a more polished end-user experience.\n\n`DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`, which will be\nsubstituted with the JSON-serialized form of the following enum:\n\n```norun\n#[derive(PartialEq, Clone, Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum DiscoverArgument {\n Path(AbsPathBuf),\n Buildfile(AbsPathBuf),\n}\n```\n\nThe JSON representation of `DiscoverArgument::Path` is:\n\n```json\n{\n \"path\": \"src/main.rs\"\n}\n```\n\nSimilarly, the JSON representation of `DiscoverArgument::Buildfile` is:\n\n```json\n{\n \"buildfile\": \"BUILD\"\n}\n```\n\n`DiscoverArgument::Path` is used to find and generate a `rust-project.json`, and\ntherefore, a workspace, whereas `DiscoverArgument::buildfile` is used to to update an\nexisting workspace. As a reference for implementors, buck2's `rust-project` will likely\nbe useful: .", + "markdownDescription": "Configure a command that rust-analyzer can invoke to\nobtain configuration.\n\nThis is an alternative to manually generating\n`rust-project.json`: it enables rust-analyzer to generate\nrust-project.json on the fly, and regenerate it when\nswitching or modifying projects.\n\nThis is an object with three fields:\n\n* `command`: the shell command to invoke\n\n* `filesToWatch`: which build system-specific files should\nbe watched to trigger regenerating the configuration\n\n* `progressLabel`: the name of the command, used in\nprogress indicators in the IDE\n\nHere's an example of a valid configuration:\n\n```json\n\"rust-analyzer.workspace.discoverConfig\": {\n \"command\": [\n \"rust-project\",\n \"develop-json\",\n \"{arg}\"\n ],\n \"progressLabel\": \"buck2/rust-project\",\n \"filesToWatch\": [\n \"BUCK\"\n ]\n}\n```\n\n## Argument Substitutions\n\nIf `command` includes the argument `{arg}`, that argument will be substituted\nwith the JSON-serialized form of the following enum:\n\n```norun\n#[derive(PartialEq, Clone, Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum DiscoverArgument {\n Path(AbsPathBuf),\n Buildfile(AbsPathBuf),\n}\n```\n\nrust-analyzer will use the path invocation to find and\ngenerate a `rust-project.json` and therefore a\nworkspace. Example:\n\n\n```norun\nrust-project develop-json '{ \"path\": \"myproject/src/main.rs\" }'\n```\n\nrust-analyzer will use build file invocations to update an\nexisting workspace. Example:\n\nOr with a build file and the configuration above:\n\n```norun\nrust-project develop-json '{ \"buildfile\": \"myproject/BUCK\" }'\n```\n\nAs a reference for implementors, buck2's `rust-project`\nwill likely be useful:\n.\n\n## Discover Command Output\n\n**Warning**: This format is provisional and subject to change.\n\nThe discover command should output JSON objects, one per\nline (JSONL format). These objects should correspond to\nthis Rust data type:\n\n```norun\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"kind\")]\n#[serde(rename_all = \"snake_case\")]\nenum DiscoverProjectData {\n Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },\n Error { error: String, source: Option },\n Progress { message: String },\n}\n```\n\nFor example, a progress event:\n\n```json\n{\"kind\":\"progress\",\"message\":\"generating rust-project.json\"}\n```\n\nA finished event can look like this (expanded and\ncommented for readability):\n\n```json\n{\n // the internally-tagged representation of the enum.\n \"kind\": \"finished\",\n // the file used by a non-Cargo build system to define\n // a package or target.\n \"buildfile\": \"rust-analyzer/BUCK\",\n // the contents of a rust-project.json, elided for brevity\n \"project\": {\n \"sysroot\": \"foo\",\n \"crates\": []\n }\n}\n```\n\nOnly the finished event is required, but the other\nvariants are encouraged to give users more feedback about\nprogress or errors.", "default": null, "anyOf": [ { diff --git a/src/tools/rust-analyzer/lib/line-index/src/lib.rs b/src/tools/rust-analyzer/lib/line-index/src/lib.rs index 905da330e64b..d5f0584d988f 100644 --- a/src/tools/rust-analyzer/lib/line-index/src/lib.rs +++ b/src/tools/rust-analyzer/lib/line-index/src/lib.rs @@ -207,7 +207,7 @@ impl LineIndex { } } -/// This is adapted from the rustc_span crate, https://github.com/rust-lang/rust/blob/de59844c98f7925242a798a72c59dc3610dd0e2c/compiler/rustc_span/src/analyze_source_file.rs +/// This is adapted from the rustc_span crate, fn analyze_source_file(src: &str) -> (Vec, IntMap>) { assert!(src.len() < !0u32 as usize); let mut lines = vec![]; diff --git a/src/tools/rust-analyzer/lib/smol_str/CHANGELOG.md b/src/tools/rust-analyzer/lib/smol_str/CHANGELOG.md index b7da6d18a440..4aa25fa13446 100644 --- a/src/tools/rust-analyzer/lib/smol_str/CHANGELOG.md +++ b/src/tools/rust-analyzer/lib/smol_str/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.3.5 - 2026-01-08 - Optimise `SmolStr::clone` 4-5x speedup inline, 0.5x heap (slow down). ## 0.3.4 - 2025-10-23 diff --git a/src/tools/rust-analyzer/lib/smol_str/Cargo.toml b/src/tools/rust-analyzer/lib/smol_str/Cargo.toml index 118b25993ffe..4e7844b49e19 100644 --- a/src/tools/rust-analyzer/lib/smol_str/Cargo.toml +++ b/src/tools/rust-analyzer/lib/smol_str/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "smol_str" -version = "0.3.4" +version = "0.3.5" description = "small-string optimized string type with O(1) clone" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/smol_str" diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index 5ffe95a0b54f..a1011c4a0acf 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -e7d44143a12a526488e4f0c0d7ea8e62a4fe9354 +ba284f468cd2cda48420251efc991758ec13d450 diff --git a/src/tools/test-float-parse/src/traits.rs b/src/tools/test-float-parse/src/traits.rs index 65a8721bfa5c..0cb44d297bfb 100644 --- a/src/tools/test-float-parse/src/traits.rs +++ b/src/tools/test-float-parse/src/traits.rs @@ -155,7 +155,7 @@ macro_rules! impl_float { const BITS: u32 = <$ity>::BITS; const MAN_BITS: u32 = Self::MANTISSA_DIGITS - 1; const MAN_MASK: Self::Int = (Self::Int::ONE << Self::MAN_BITS) - Self::Int::ONE; - const SIGN_MASK: Self::Int = Self::Int::ONE << (Self::BITS-1); + const SIGN_MASK: Self::Int = Self::Int::ONE << (::BITS-1); fn from_bits(i: Self::Int) -> Self { Self::from_bits(i) } fn to_bits(self) -> Self::Int { self.to_bits() } fn constants() -> &'static Constants { diff --git a/tests/assembly-llvm/x86_64-bigint-helpers.rs b/tests/assembly-llvm/x86_64-bigint-helpers.rs index 9d998a31cf30..d5d1eba99f39 100644 --- a/tests/assembly-llvm/x86_64-bigint-helpers.rs +++ b/tests/assembly-llvm/x86_64-bigint-helpers.rs @@ -4,7 +4,6 @@ //@ compile-flags: -C llvm-args=-x86-asm-syntax=intel #![no_std] -#![feature(bigint_helper_methods)] // This checks that the `carrying_add` and `borrowing_sub` implementation successfully chain, // to catch issues like diff --git a/tests/codegen-llvm/bigint-helpers.rs b/tests/codegen-llvm/bigint-helpers.rs index ec70a3eabedb..404dc901de8c 100644 --- a/tests/codegen-llvm/bigint-helpers.rs +++ b/tests/codegen-llvm/bigint-helpers.rs @@ -1,7 +1,6 @@ //@ compile-flags: -C opt-level=3 #![crate_type = "lib"] -#![feature(bigint_helper_methods)] // Note that there's also an assembly test for this, which is what checks for // the `ADC` (Add with Carry) instruction on x86 now that the IR we emit uses diff --git a/tests/codegen-llvm/slp-vectorization-mul3.rs b/tests/codegen-llvm/slp-vectorization-mul3.rs new file mode 100644 index 000000000000..38c0949b8271 --- /dev/null +++ b/tests/codegen-llvm/slp-vectorization-mul3.rs @@ -0,0 +1,26 @@ +//! Regression test for #142519 +//@ only-x86_64 +//@ compile-flags: -O +//@ min-llvm-version: 22 + +#![crate_type = "lib"] + +// CHECK-LABEL: @mul3 +// CHECK: phi <4 x i8> +// CHECK: load <4 x i8> +// CHECK: add <4 x i8> +// CHECK: store <4 x i8> + +#[no_mangle] +pub fn mul3(previous: &[[u8; 4]], current: &mut [[u8; 4]]) { + let mut c_bpp = [0u8; 4]; + + for i in 0..previous.len() { + current[i][0] = current[i][0].wrapping_add(c_bpp[0]); + current[i][1] = current[i][1].wrapping_add(c_bpp[1]); + current[i][2] = current[i][2].wrapping_add(c_bpp[2]); + current[i][3] = current[i][3].wrapping_add(c_bpp[3]); + + c_bpp = previous[i]; + } +} diff --git a/tests/coverage/macros/context-mismatch-issue-147339.cov-map b/tests/coverage/macros/context-mismatch-issue-147339.cov-map new file mode 100644 index 000000000000..7aa829cab72b --- /dev/null +++ b/tests/coverage/macros/context-mismatch-issue-147339.cov-map @@ -0,0 +1,40 @@ +Function name: context_mismatch_issue_147339::a (unused) +Raw bytes (14): 0x[01, 01, 00, 02, 00, 0c, 27, 00, 35, 00, 00, 3b, 00, 3c] +Number of files: 1 +- file 0 => $DIR/context-mismatch-issue-147339.rs +Number of expressions: 0 +Number of file 0 mappings: 2 +- Code(Zero) at (prev + 12, 39) to (start + 0, 53) +- Code(Zero) at (prev + 0, 59) to (start + 0, 60) +Highest counter ID seen: (none) + +Function name: context_mismatch_issue_147339::b (unused) +Raw bytes (14): 0x[01, 01, 00, 02, 00, 0c, 27, 00, 35, 00, 00, 3b, 00, 3c] +Number of files: 1 +- file 0 => $DIR/context-mismatch-issue-147339.rs +Number of expressions: 0 +Number of file 0 mappings: 2 +- Code(Zero) at (prev + 12, 39) to (start + 0, 53) +- Code(Zero) at (prev + 0, 59) to (start + 0, 60) +Highest counter ID seen: (none) + +Function name: context_mismatch_issue_147339::c (unused) +Raw bytes (14): 0x[01, 01, 00, 02, 00, 0c, 27, 00, 35, 00, 00, 3b, 00, 3c] +Number of files: 1 +- file 0 => $DIR/context-mismatch-issue-147339.rs +Number of expressions: 0 +Number of file 0 mappings: 2 +- Code(Zero) at (prev + 12, 39) to (start + 0, 53) +- Code(Zero) at (prev + 0, 59) to (start + 0, 60) +Highest counter ID seen: (none) + +Function name: context_mismatch_issue_147339::main +Raw bytes (14): 0x[01, 01, 00, 02, 01, 14, 01, 00, 0a, 01, 00, 0c, 00, 0d] +Number of files: 1 +- file 0 => $DIR/context-mismatch-issue-147339.rs +Number of expressions: 0 +Number of file 0 mappings: 2 +- Code(Counter(0)) at (prev + 20, 1) to (start + 0, 10) +- Code(Counter(0)) at (prev + 0, 12) to (start + 0, 13) +Highest counter ID seen: c0 + diff --git a/tests/coverage/macros/context-mismatch-issue-147339.coverage b/tests/coverage/macros/context-mismatch-issue-147339.coverage new file mode 100644 index 000000000000..9b4fc67b8dff --- /dev/null +++ b/tests/coverage/macros/context-mismatch-issue-147339.coverage @@ -0,0 +1,28 @@ + LL| |//@ edition: 2024 + LL| | + LL| |// These nested macro expansions were found to cause span refinement to produce + LL| |// spans with a context that doesn't match the function body span, triggering + LL| |// a defensive check that discards the span. + LL| |// + LL| |// Reported in . + LL| | + LL| |macro_rules! foo { + LL| | ($($m:ident $($f:ident $v:tt)+),*) => { + LL| | $($(macro_rules! $f { () => { $v } })+)* + LL| 0| $(macro_rules! $m { () => { $(fn $f() -> i32 { $v })+ } })* + ------------------ + | Unexecuted instantiation: context_mismatch_issue_147339::a + ------------------ + | Unexecuted instantiation: context_mismatch_issue_147339::b + ------------------ + | Unexecuted instantiation: context_mismatch_issue_147339::c + ------------------ + LL| | } + LL| |} + LL| | + LL| |foo!(m a 1 b 2, n c 3); + LL| |m!(); + LL| |n!(); + LL| | + LL| 1|fn main() {} + diff --git a/tests/coverage/macros/context-mismatch-issue-147339.rs b/tests/coverage/macros/context-mismatch-issue-147339.rs new file mode 100644 index 000000000000..80e744afc7c6 --- /dev/null +++ b/tests/coverage/macros/context-mismatch-issue-147339.rs @@ -0,0 +1,20 @@ +//@ edition: 2024 + +// These nested macro expansions were found to cause span refinement to produce +// spans with a context that doesn't match the function body span, triggering +// a defensive check that discards the span. +// +// Reported in . + +macro_rules! foo { + ($($m:ident $($f:ident $v:tt)+),*) => { + $($(macro_rules! $f { () => { $v } })+)* + $(macro_rules! $m { () => { $(fn $f() -> i32 { $v })+ } })* + } +} + +foo!(m a 1 b 2, n c 3); +m!(); +n!(); + +fn main() {} diff --git a/tests/crashes/137582.rs b/tests/crashes/137582.rs deleted file mode 100644 index e21b6c9578b7..000000000000 --- a/tests/crashes/137582.rs +++ /dev/null @@ -1,16 +0,0 @@ -//@ known-bug: #137582 -#![feature(adt_const_params)] - -mod lib { - pub type Matrix = [&'static u32]; - - const EMPTY_MATRIX: Matrix = [[0; 4]; 4]; - - pub struct Walk { - _p: (), - } - - impl Walk {} -} - -fn main() {} diff --git a/tests/rustdoc-gui/globals.goml b/tests/rustdoc-gui/globals.goml index 4f12175f6ab2..7fd9c5bfb6f2 100644 --- a/tests/rustdoc-gui/globals.goml +++ b/tests/rustdoc-gui/globals.goml @@ -6,7 +6,7 @@ include: "utils.goml" // URL query go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=sa" wait-for: "#search-tabs" -assert-window-property-false: {"searchIndex": null} +wait-for-window-property-false: {"searchIndex": null} // Form input go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.rs b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.rs new file mode 100644 index 000000000000..babe3813e40b --- /dev/null +++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.rs @@ -0,0 +1,777 @@ +//@ check-fail +// Tests error conditions for specifying diagnostics using #[derive(Diagnostic)] +// This test specifically tests diagnostic derives involving the inline fluent syntax. + +//@ normalize-stderr: "the following other types implement trait `IntoDiagArg`:(?:.*\n){0,9}\s+and \d+ others" -> "normalized in stderr" +//@ normalize-stderr: "(COMPILER_DIR/.*\.rs):[0-9]+:[0-9]+" -> "$1:LL:CC" + +// The proc_macro2 crate handles spans differently when on beta/stable release rather than nightly, +// changing the output of this test. Since Diagnostic is strictly internal to the compiler +// the test is just ignored on stable and beta: +//@ ignore-stage1 +//@ ignore-beta +//@ ignore-stable + +#![feature(rustc_private)] +#![crate_type = "lib"] + +extern crate rustc_span; +use rustc_span::symbol::Ident; +use rustc_span::Span; + +extern crate rustc_fluent_macro; +extern crate rustc_macros; +use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; + +extern crate rustc_middle; +use rustc_middle::ty::Ty; + +extern crate rustc_errors; +use rustc_errors::{Applicability, DiagMessage, ErrCode, MultiSpan, SubdiagMessage}; + +extern crate rustc_session; + +extern crate core; + +// E0123 and E0456 are no longer used, so we define our own constants here just for this test. +const E0123: ErrCode = ErrCode::from_u32(0123); +const E0456: ErrCode = ErrCode::from_u32(0456); + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct Hello {} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +//~^ ERROR unsupported type attribute for diagnostic derive enum +enum DiagnosticOnEnum { + Foo, + //~^ ERROR diagnostic slug not specified + Bar, + //~^ ERROR diagnostic slug not specified +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +#[diag = "E0123"] +//~^ ERROR expected parentheses: #[diag(...)] +struct WrongStructAttrStyle {} + +#[derive(Diagnostic)] +#[nonsense("this is an example message", code = E0123)] +//~^ ERROR `#[nonsense(...)]` is not a valid attribute +//~^^ ERROR diagnostic slug not specified +//~^^^ ERROR cannot find attribute `nonsense` in this scope +struct InvalidStructAttr {} + +#[derive(Diagnostic)] +#[diag(code = E0123)] +//~^ ERROR diagnostic slug not specified +struct InvalidLitNestedAttr {} + +#[derive(Diagnostic)] +#[diag(nonsense("foo"), code = E0123, slug = "foo")] +//~^ ERROR derive(Diagnostic): diagnostic slug not specified +struct InvalidNestedStructAttr1 {} + +#[derive(Diagnostic)] +#[diag(nonsense = "...", code = E0123, slug = "foo")] +//~^ ERROR diagnostic slug not specified +struct InvalidNestedStructAttr2 {} + +#[derive(Diagnostic)] +#[diag(nonsense = 4, code = E0123, slug = "foo")] +//~^ ERROR diagnostic slug not specified +struct InvalidNestedStructAttr3 {} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123, slug = "foo")] +//~^ ERROR unknown argument +struct InvalidNestedStructAttr4 {} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct WrongPlaceField { + #[suggestion = "bar"] + //~^ ERROR `#[suggestion = ...]` is not a valid attribute + sp: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +#[diag("this is an example message", code = E0456)] +//~^ ERROR specified multiple times +struct DiagSpecifiedTwice {} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123, code = E0456)] +//~^ ERROR specified multiple times +struct CodeSpecifiedTwice {} + +#[derive(Diagnostic)] +#[diag("this is an example message", no_crate::example, code = E0123)] +//~^ ERROR diagnostic slug must be the first argument +struct SlugSpecifiedTwice {} + +#[derive(Diagnostic)] +struct KindNotProvided {} //~ ERROR diagnostic slug not specified + +#[derive(Diagnostic)] +#[diag(code = E0123)] +//~^ ERROR diagnostic slug not specified +struct SlugNotProvided {} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct CodeNotProvided {} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct MessageWrongType { + #[primary_span] + //~^ ERROR `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan` + foo: String, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct InvalidPathFieldAttr { + #[nonsense] + //~^ ERROR `#[nonsense]` is not a valid attribute + //~^^ ERROR cannot find attribute `nonsense` in this scope + foo: String, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct ErrorWithField { + name: String, + #[label("with a label")] + span: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct ErrorWithMessageAppliedToField { + #[label("with a label")] + //~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` + name: String, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct ErrorWithNonexistentField { + #[suggestion("with a suggestion", code = "{name}")] + //~^ ERROR `name` doesn't refer to a field on this type + suggestion: (Span, Applicability), +} + +#[derive(Diagnostic)] +//~^ ERROR invalid format string: expected `}` +#[diag("this is an example message", code = E0123)] +struct ErrorMissingClosingBrace { + #[suggestion("with a suggestion", code = "{name")] + suggestion: (Span, Applicability), + name: String, + val: usize, +} + +#[derive(Diagnostic)] +//~^ ERROR invalid format string: unmatched `}` +#[diag("this is an example message", code = E0123)] +struct ErrorMissingOpeningBrace { + #[suggestion("with a suggestion", code = "name}")] + suggestion: (Span, Applicability), + name: String, + val: usize, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct LabelOnSpan { + #[label("with a label")] + sp: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct LabelOnNonSpan { + #[label("with a label")] + //~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` + id: u32, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct Suggest { + #[suggestion("with a suggestion", code = "This is the suggested code")] + #[suggestion("with a suggestion", code = "This is the suggested code", style = "normal")] + #[suggestion("with a suggestion", code = "This is the suggested code", style = "short")] + #[suggestion("with a suggestion", code = "This is the suggested code", style = "hidden")] + #[suggestion("with a suggestion", code = "This is the suggested code", style = "verbose")] + suggestion: (Span, Applicability), +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct SuggestWithoutCode { + #[suggestion("with a suggestion")] + //~^ ERROR suggestion without `code = "..."` + suggestion: (Span, Applicability), +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct SuggestWithBadKey { + #[suggestion("with a suggestion", nonsense = "bar")] + //~^ ERROR invalid nested attribute + //~| ERROR suggestion without `code = "..."` + suggestion: (Span, Applicability), +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct SuggestWithShorthandMsg { + #[suggestion("with a suggestion", msg = "bar")] + //~^ ERROR invalid nested attribute + //~| ERROR suggestion without `code = "..."` + suggestion: (Span, Applicability), +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct SuggestWithoutMsg { + #[suggestion("with a suggestion", code = "bar")] + suggestion: (Span, Applicability), +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct SuggestWithTypesSwapped { + #[suggestion("with a suggestion", code = "This is suggested code")] + suggestion: (Applicability, Span), +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct SuggestWithWrongTypeApplicabilityOnly { + #[suggestion("with a suggestion", code = "This is suggested code")] + //~^ ERROR wrong field type for suggestion + suggestion: Applicability, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct SuggestWithSpanOnly { + #[suggestion("with a suggestion", code = "This is suggested code")] + suggestion: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct SuggestWithDuplicateSpanAndApplicability { + #[suggestion("with a suggestion", code = "This is suggested code")] + suggestion: (Span, Span, Applicability), + //~^ ERROR specified multiple times +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct SuggestWithDuplicateApplicabilityAndSpan { + #[suggestion("with a suggestion", code = "This is suggested code")] + suggestion: (Applicability, Applicability, Span), + //~^ ERROR specified multiple times +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct WrongKindOfAnnotation { + #[label = "bar"] + //~^ ERROR `#[label = ...]` is not a valid attribute + z: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct OptionsInErrors { + #[label("with a label")] + label: Option, + #[suggestion("with a suggestion", code = "...")] + opt_sugg: Option<(Span, Applicability)>, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct MoveOutOfBorrowError<'tcx> { + name: Ident, + ty: Ty<'tcx>, + #[primary_span] + #[label("with a label")] + span: Span, + #[label("with a label")] + other_span: Span, + #[suggestion("with a suggestion", code = "{name}.clone()")] + opt_sugg: Option<(Span, Applicability)>, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct ErrorWithLifetime<'a> { + #[label("with a label")] + span: Span, + name: &'a str, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct ArgFieldWithoutSkip { + #[primary_span] + span: Span, + other: Hello, + //~^ ERROR the trait bound `Hello: IntoDiagArg` is not satisfied +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct ArgFieldWithSkip { + #[primary_span] + span: Span, + // `Hello` does not implement `IntoDiagArg` so this would result in an error if + // not for `#[skip_arg]`. + #[skip_arg] + other: Hello, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct ErrorWithSpannedNote { + #[note("with a note")] + span: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +#[note("with a note")] +struct ErrorWithNote { + val: String, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct ErrorWithSpannedHelpCustom { + #[help("with a help")] + span: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +#[help("with a help")] +struct ErrorWithHelp { + val: String, +} + +#[derive(Diagnostic)] +#[help("with a help")] +#[diag("this is an example message", code = E0123)] +struct ErrorWithHelpWrongOrder { + val: String, +} + +#[derive(Diagnostic)] +#[note("with a note")] +#[diag("this is an example message", code = E0123)] +struct ErrorWithNoteWrongOrder { + val: String, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct ApplicabilityInBoth { + #[suggestion("with a suggestion", code = "...", applicability = "maybe-incorrect")] + //~^ ERROR specified multiple times + suggestion: (Span, Applicability), +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct InvalidApplicability { + #[suggestion("with a suggestion", code = "...", applicability = "batman")] + //~^ ERROR invalid applicability + suggestion: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct ValidApplicability { + #[suggestion("with a suggestion", code = "...", applicability = "maybe-incorrect")] + suggestion: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct NoApplicability { + #[suggestion("with a suggestion", code = "...")] + suggestion: Span, +} + +#[derive(Subdiagnostic)] +#[note("this is an example message")] +struct Note; + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct Subdiagnostic { + #[subdiagnostic] + note: Note, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct VecField { + #[primary_span] + #[label("with a label")] + spans: Vec, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct UnitField { + #[primary_span] + spans: Span, + #[help("with a help")] + bar: (), +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct OptUnitField { + #[primary_span] + spans: Span, + #[help("with a help")] + foo: Option<()>, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct BoolField { + #[primary_span] + spans: Span, + #[help("with a help")] + foo: bool, + #[help("with a help")] + //~^ ERROR the `#[help(...)]` attribute can only be applied to fields of type + // only allow plain 'bool' fields + bar: Option, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct LabelWithTrailingPath { + #[label("with a label", foo)] + //~^ ERROR a diagnostic slug must be the first argument to the attribute + span: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct LabelWithTrailingNameValue { + #[label("with a label", foo = "...")] + //~^ ERROR no nested attribute expected here + span: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct LabelWithTrailingList { + #[label("with a label", foo("..."))] + //~^ ERROR no nested attribute expected here + span: Span, +} + +#[derive(LintDiagnostic)] +#[diag("this is an example message")] +struct LintsGood {} + +#[derive(LintDiagnostic)] +#[diag("this is an example message")] +struct PrimarySpanOnLint { + #[primary_span] + //~^ ERROR `#[primary_span]` is not a valid attribute + span: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct ErrorWithMultiSpan { + #[primary_span] + span: MultiSpan, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +#[warning("with a warning")] +struct ErrorWithWarn { + val: String, +} + +#[derive(Diagnostic)] +#[error("this is an example message", code = E0123)] +//~^ ERROR `#[error(...)]` is not a valid attribute +//~| ERROR diagnostic slug not specified +//~| ERROR cannot find attribute `error` in this scope +struct ErrorAttribute {} + +#[derive(Diagnostic)] +#[warn_("this is an example message", code = E0123)] +//~^ ERROR `#[warn_(...)]` is not a valid attribute +//~| ERROR diagnostic slug not specified +//~| ERROR cannot find attribute `warn_` in this scope +struct WarnAttribute {} + +#[derive(Diagnostic)] +#[lint("this is an example message", code = E0123)] +//~^ ERROR `#[lint(...)]` is not a valid attribute +//~| ERROR diagnostic slug not specified +//~| ERROR cannot find attribute `lint` in this scope +struct LintAttributeOnSessionDiag {} + +#[derive(LintDiagnostic)] +#[lint("this is an example message", code = E0123)] +//~^ ERROR `#[lint(...)]` is not a valid attribute +//~| ERROR diagnostic slug not specified +//~| ERROR cannot find attribute `lint` in this scope +struct LintAttributeOnLintDiag {} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct DuplicatedSuggestionCode { + #[suggestion("with a suggestion", code = "...", code = ",,,")] + //~^ ERROR specified multiple times + suggestion: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct InvalidTypeInSuggestionTuple { + #[suggestion("with a suggestion", code = "...")] + suggestion: (Span, usize), + //~^ ERROR wrong types for suggestion +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct MissingApplicabilityInSuggestionTuple { + #[suggestion("with a suggestion", code = "...")] + suggestion: (Span,), + //~^ ERROR wrong types for suggestion +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct MissingCodeInSuggestion { + #[suggestion("with a suggestion")] + //~^ ERROR suggestion without `code = "..."` + suggestion: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +#[multipart_suggestion("with a suggestion")] +//~^ ERROR `#[multipart_suggestion(...)]` is not a valid attribute +//~| ERROR cannot find attribute `multipart_suggestion` in this scope +#[multipart_suggestion()] +//~^ ERROR cannot find attribute `multipart_suggestion` in this scope +//~| ERROR `#[multipart_suggestion(...)]` is not a valid attribute +struct MultipartSuggestion { + #[multipart_suggestion("with a suggestion")] + //~^ ERROR `#[multipart_suggestion(...)]` is not a valid attribute + //~| ERROR cannot find attribute `multipart_suggestion` in this scope + suggestion: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +#[suggestion("with a suggestion", code = "...")] +//~^ ERROR `#[suggestion(...)]` is not a valid attribute +struct SuggestionOnStruct { + #[primary_span] + suggestion: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +#[label] +//~^ ERROR `#[label]` is not a valid attribute +struct LabelOnStruct { + #[primary_span] + suggestion: Span, +} + +#[derive(Diagnostic)] +enum ExampleEnum { + #[diag("this is an example message")] + Foo { + #[primary_span] + sp: Span, + #[note("with a note")] + note_sp: Span, + }, + #[diag("this is an example message")] + Bar { + #[primary_span] + sp: Span, + }, + #[diag("this is an example message")] + Baz, +} + +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct RawIdentDiagnosticArg { + pub r#type: String, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SubdiagnosticBad { + #[subdiagnostic(bad)] + //~^ ERROR `#[subdiagnostic(...)]` is not a valid attribute + note: Note, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SubdiagnosticBadStr { + #[subdiagnostic = "bad"] + //~^ ERROR `#[subdiagnostic = ...]` is not a valid attribute + note: Note, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SubdiagnosticBadTwice { + #[subdiagnostic(bad, bad)] + //~^ ERROR `#[subdiagnostic(...)]` is not a valid attribute + note: Note, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SubdiagnosticBadLitStr { + #[subdiagnostic("bad")] + //~^ ERROR `#[subdiagnostic(...)]` is not a valid attribute + note: Note, +} + +#[derive(LintDiagnostic)] +#[diag("this is an example message")] +struct SubdiagnosticEagerLint { + #[subdiagnostic(eager)] + //~^ ERROR `#[subdiagnostic(...)]` is not a valid attribute + note: Note, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SubdiagnosticEagerFormerlyCorrect { + #[subdiagnostic(eager)] + //~^ ERROR `#[subdiagnostic(...)]` is not a valid attribute + note: Note, +} + +// Check that formatting of `correct` in suggestion doesn't move the binding for that field, making +// the `arg` call a compile error; and that isn't worked around by moving the `arg` call +// after the `span_suggestion` call - which breaks eager translation. + +#[derive(Subdiagnostic)] +#[suggestion("example message", applicability = "machine-applicable", code = "{correct}")] +pub(crate) struct SubdiagnosticWithSuggestion { + #[primary_span] + span: Span, + invalid: String, + correct: String, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SubdiagnosticEagerSuggestion { + #[subdiagnostic(eager)] + //~^ ERROR `#[subdiagnostic(...)]` is not a valid attribute + sub: SubdiagnosticWithSuggestion, +} + +/// with a doc comment on the type.. +#[derive(Diagnostic)] +#[diag("this is an example message", code = E0123)] +struct WithDocComment { + /// ..and the field + #[primary_span] + span: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SuggestionsGood { + #[suggestion("with a suggestion", code("foo", "bar"))] + sub: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SuggestionsSingleItem { + #[suggestion("with a suggestion", code("foo"))] + sub: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SuggestionsNoItem { + #[suggestion("with a suggestion", code())] + //~^ ERROR expected at least one string literal for `code(...)` + sub: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SuggestionsInvalidItem { + #[suggestion("with a suggestion", code(foo))] + //~^ ERROR `code(...)` must contain only string literals + //~| ERROR unexpected token, expected `)` + sub: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SuggestionsInvalidLiteral { + #[suggestion("with a suggestion", code = 3)] + //~^ ERROR expected string literal + sub: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SuggestionStyleGood { + #[suggestion("with a suggestion", code = "", style = "hidden")] + sub: Span, +} + +#[derive(Diagnostic)] +#[diag("this is an example message")] +struct SuggestionOnVec { + #[suggestion("with a suggestion", code = "")] + //~^ ERROR `#[suggestion(...)]` is not a valid attribute + sub: Vec, +} + +#[derive(Diagnostic)] +#[diag("exists: {$sub}")] +struct VariableExists { + sub: String, +} + +#[derive(Diagnostic)] +#[diag("does not exist: {$nosub}")] +//~^ ERROR Variable `nosub` not found in diagnostic +struct VariableDoesNotExist { + sub: String, +} diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.stderr b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.stderr new file mode 100644 index 000000000000..2ba307940280 --- /dev/null +++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.stderr @@ -0,0 +1,635 @@ +error: derive(Diagnostic): unsupported type attribute for diagnostic derive enum + --> $DIR/diagnostic-derive-inline.rs:45:1 + | +LL | #[diag("this is an example message", code = E0123)] + | ^ + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:48:5 + | +LL | Foo, + | ^^^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:50:5 + | +LL | Bar, + | ^^^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: expected parentheses: #[diag(...)] + --> $DIR/diagnostic-derive-inline.rs:56:8 + | +LL | #[diag = "E0123"] + | ^ + +error: derive(Diagnostic): `#[nonsense(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:61:1 + | +LL | #[nonsense("this is an example message", code = E0123)] + | ^ + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:61:1 + | +LL | #[nonsense("this is an example message", code = E0123)] + | ^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:68:1 + | +LL | #[diag(code = E0123)] + | ^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:73:1 + | +LL | #[diag(nonsense("foo"), code = E0123, slug = "foo")] + | ^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:78:1 + | +LL | #[diag(nonsense = "...", code = E0123, slug = "foo")] + | ^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:83:1 + | +LL | #[diag(nonsense = 4, code = E0123, slug = "foo")] + | ^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): unknown argument + --> $DIR/diagnostic-derive-inline.rs:88:52 + | +LL | #[diag("this is an example message", code = E0123, slug = "foo")] + | ^^^^ + | + = note: only the `code` parameter is valid after the slug + +error: derive(Diagnostic): `#[suggestion = ...]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:95:5 + | +LL | #[suggestion = "bar"] + | ^ + +error: derive(Diagnostic): attribute specified multiple times + --> $DIR/diagnostic-derive-inline.rs:102:38 + | +LL | #[diag("this is an example message", code = E0456)] + | ^^^^ + | +note: previously specified here + --> $DIR/diagnostic-derive-inline.rs:101:38 + | +LL | #[diag("this is an example message", code = E0123)] + | ^^^^ + +error: derive(Diagnostic): attribute specified multiple times + --> $DIR/diagnostic-derive-inline.rs:107:52 + | +LL | #[diag("this is an example message", code = E0123, code = E0456)] + | ^^^^ + | +note: previously specified here + --> $DIR/diagnostic-derive-inline.rs:107:38 + | +LL | #[diag("this is an example message", code = E0123, code = E0456)] + | ^^^^ + +error: derive(Diagnostic): diagnostic slug must be the first argument + --> $DIR/diagnostic-derive-inline.rs:112:38 + | +LL | #[diag("this is an example message", no_crate::example, code = E0123)] + | ^^^^^^^^ + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:117:1 + | +LL | struct KindNotProvided {} + | ^^^^^^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:120:1 + | +LL | #[diag(code = E0123)] + | ^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan` + --> $DIR/diagnostic-derive-inline.rs:131:5 + | +LL | #[primary_span] + | ^ + +error: derive(Diagnostic): `#[nonsense]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:139:5 + | +LL | #[nonsense] + | ^ + +error: derive(Diagnostic): the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` + --> $DIR/diagnostic-derive-inline.rs:156:5 + | +LL | #[label("with a label")] + | ^ + +error: derive(Diagnostic): `name` doesn't refer to a field on this type + --> $DIR/diagnostic-derive-inline.rs:164:46 + | +LL | #[suggestion("with a suggestion", code = "{name}")] + | ^^^^^^^^ + +error: invalid format string: expected `}` but string was terminated + --> $DIR/diagnostic-derive-inline.rs:169:10 + | +LL | #[derive(Diagnostic)] + | ^^^^^^^^^^ expected `}` in format string + | + = note: if you intended to print `{`, you can escape it using `{{` + = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid format string: unmatched `}` found + --> $DIR/diagnostic-derive-inline.rs:179:10 + | +LL | #[derive(Diagnostic)] + | ^^^^^^^^^^ unmatched `}` in format string + | + = note: if you intended to print `}`, you can escape it using `}}` + = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: derive(Diagnostic): the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` + --> $DIR/diagnostic-derive-inline.rs:199:5 + | +LL | #[label("with a label")] + | ^ + +error: derive(Diagnostic): suggestion without `code = "..."` + --> $DIR/diagnostic-derive-inline.rs:218:5 + | +LL | #[suggestion("with a suggestion")] + | ^ + +error: derive(Diagnostic): invalid nested attribute + --> $DIR/diagnostic-derive-inline.rs:226:39 + | +LL | #[suggestion("with a suggestion", nonsense = "bar")] + | ^^^^^^^^ + | + = help: only `style`, `code` and `applicability` are valid nested attributes + +error: derive(Diagnostic): suggestion without `code = "..."` + --> $DIR/diagnostic-derive-inline.rs:226:5 + | +LL | #[suggestion("with a suggestion", nonsense = "bar")] + | ^ + +error: derive(Diagnostic): invalid nested attribute + --> $DIR/diagnostic-derive-inline.rs:235:39 + | +LL | #[suggestion("with a suggestion", msg = "bar")] + | ^^^ + | + = help: only `style`, `code` and `applicability` are valid nested attributes + +error: derive(Diagnostic): suggestion without `code = "..."` + --> $DIR/diagnostic-derive-inline.rs:235:5 + | +LL | #[suggestion("with a suggestion", msg = "bar")] + | ^ + +error: derive(Diagnostic): wrong field type for suggestion + --> $DIR/diagnostic-derive-inline.rs:258:5 + | +LL | #[suggestion("with a suggestion", code = "This is suggested code")] + | ^ + | + = help: `#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)` + +error: derive(Diagnostic): attribute specified multiple times + --> $DIR/diagnostic-derive-inline.rs:274:24 + | +LL | suggestion: (Span, Span, Applicability), + | ^^^^ + | +note: previously specified here + --> $DIR/diagnostic-derive-inline.rs:274:18 + | +LL | suggestion: (Span, Span, Applicability), + | ^^^^ + +error: derive(Diagnostic): attribute specified multiple times + --> $DIR/diagnostic-derive-inline.rs:282:33 + | +LL | suggestion: (Applicability, Applicability, Span), + | ^^^^^^^^^^^^^ + | +note: previously specified here + --> $DIR/diagnostic-derive-inline.rs:282:18 + | +LL | suggestion: (Applicability, Applicability, Span), + | ^^^^^^^^^^^^^ + +error: derive(Diagnostic): `#[label = ...]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:289:5 + | +LL | #[label = "bar"] + | ^ + +error: derive(Diagnostic): attribute specified multiple times + --> $DIR/diagnostic-derive-inline.rs:390:5 + | +LL | #[suggestion("with a suggestion", code = "...", applicability = "maybe-incorrect")] + | ^ + | +note: previously specified here + --> $DIR/diagnostic-derive-inline.rs:392:24 + | +LL | suggestion: (Span, Applicability), + | ^^^^^^^^^^^^^ + +error: derive(Diagnostic): invalid applicability + --> $DIR/diagnostic-derive-inline.rs:398:69 + | +LL | #[suggestion("with a suggestion", code = "...", applicability = "batman")] + | ^^^^^^^^ + +error: derive(Diagnostic): the `#[help(...)]` attribute can only be applied to fields of type `Span`, `MultiSpan`, `bool` or `()` + --> $DIR/diagnostic-derive-inline.rs:461:5 + | +LL | #[help("with a help")] + | ^ + +error: derive(Diagnostic): a diagnostic slug must be the first argument to the attribute + --> $DIR/diagnostic-derive-inline.rs:470:29 + | +LL | #[label("with a label", foo)] + | ^^^ + +error: derive(Diagnostic): no nested attribute expected here + --> $DIR/diagnostic-derive-inline.rs:478:29 + | +LL | #[label("with a label", foo = "...")] + | ^^^ + +error: derive(Diagnostic): no nested attribute expected here + --> $DIR/diagnostic-derive-inline.rs:486:29 + | +LL | #[label("with a label", foo("..."))] + | ^^^ + +error: derive(Diagnostic): `#[primary_span]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:498:5 + | +LL | #[primary_span] + | ^ + | + = help: the `primary_span` field attribute is not valid for lint diagnostics + +error: derive(Diagnostic): `#[error(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:518:1 + | +LL | #[error("this is an example message", code = E0123)] + | ^ + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:518:1 + | +LL | #[error("this is an example message", code = E0123)] + | ^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): `#[warn_(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:525:1 + | +LL | #[warn_("this is an example message", code = E0123)] + | ^ + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:525:1 + | +LL | #[warn_("this is an example message", code = E0123)] + | ^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): `#[lint(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:532:1 + | +LL | #[lint("this is an example message", code = E0123)] + | ^ + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:532:1 + | +LL | #[lint("this is an example message", code = E0123)] + | ^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): `#[lint(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:539:1 + | +LL | #[lint("this is an example message", code = E0123)] + | ^ + +error: derive(Diagnostic): diagnostic slug not specified + --> $DIR/diagnostic-derive-inline.rs:539:1 + | +LL | #[lint("this is an example message", code = E0123)] + | ^ + | + = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(hir_analysis_example_error)]` + +error: derive(Diagnostic): attribute specified multiple times + --> $DIR/diagnostic-derive-inline.rs:548:53 + | +LL | #[suggestion("with a suggestion", code = "...", code = ",,,")] + | ^^^^ + | +note: previously specified here + --> $DIR/diagnostic-derive-inline.rs:548:39 + | +LL | #[suggestion("with a suggestion", code = "...", code = ",,,")] + | ^^^^ + +error: derive(Diagnostic): wrong types for suggestion + --> $DIR/diagnostic-derive-inline.rs:557:24 + | +LL | suggestion: (Span, usize), + | ^^^^^ + | + = help: `#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)` + +error: derive(Diagnostic): wrong types for suggestion + --> $DIR/diagnostic-derive-inline.rs:565:17 + | +LL | suggestion: (Span,), + | ^^^^^^^ + | + = help: `#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)` + +error: derive(Diagnostic): suggestion without `code = "..."` + --> $DIR/diagnostic-derive-inline.rs:572:5 + | +LL | #[suggestion("with a suggestion")] + | ^ + +error: derive(Diagnostic): `#[multipart_suggestion(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:579:1 + | +LL | #[multipart_suggestion("with a suggestion")] + | ^ + | + = help: consider creating a `Subdiagnostic` instead + +error: derive(Diagnostic): `#[multipart_suggestion(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:582:1 + | +LL | #[multipart_suggestion()] + | ^ + | + = help: consider creating a `Subdiagnostic` instead + +error: derive(Diagnostic): `#[multipart_suggestion(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:586:5 + | +LL | #[multipart_suggestion("with a suggestion")] + | ^ + | + = help: consider creating a `Subdiagnostic` instead + +error: derive(Diagnostic): `#[suggestion(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:594:1 + | +LL | #[suggestion("with a suggestion", code = "...")] + | ^ + | + = help: `#[label]` and `#[suggestion]` can only be applied to fields + +error: derive(Diagnostic): `#[label]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:603:1 + | +LL | #[label] + | ^ + | + = help: `#[label]` and `#[suggestion]` can only be applied to fields + +error: derive(Diagnostic): `#[subdiagnostic(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:637:5 + | +LL | #[subdiagnostic(bad)] + | ^ + +error: derive(Diagnostic): `#[subdiagnostic = ...]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:645:5 + | +LL | #[subdiagnostic = "bad"] + | ^ + +error: derive(Diagnostic): `#[subdiagnostic(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:653:5 + | +LL | #[subdiagnostic(bad, bad)] + | ^ + +error: derive(Diagnostic): `#[subdiagnostic(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:661:5 + | +LL | #[subdiagnostic("bad")] + | ^ + +error: derive(Diagnostic): `#[subdiagnostic(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:669:5 + | +LL | #[subdiagnostic(eager)] + | ^ + +error: derive(Diagnostic): `#[subdiagnostic(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:677:5 + | +LL | #[subdiagnostic(eager)] + | ^ + +error: derive(Diagnostic): `#[subdiagnostic(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:698:5 + | +LL | #[subdiagnostic(eager)] + | ^ + +error: derive(Diagnostic): expected at least one string literal for `code(...)` + --> $DIR/diagnostic-derive-inline.rs:729:44 + | +LL | #[suggestion("with a suggestion", code())] + | ^ + +error: derive(Diagnostic): `code(...)` must contain only string literals + --> $DIR/diagnostic-derive-inline.rs:737:44 + | +LL | #[suggestion("with a suggestion", code(foo))] + | ^^^ + +error: unexpected token, expected `)` + --> $DIR/diagnostic-derive-inline.rs:737:44 + | +LL | #[suggestion("with a suggestion", code(foo))] + | ^^^ + +error: expected string literal + --> $DIR/diagnostic-derive-inline.rs:746:46 + | +LL | #[suggestion("with a suggestion", code = 3)] + | ^ + +error: derive(Diagnostic): `#[suggestion(...)]` is not a valid attribute + --> $DIR/diagnostic-derive-inline.rs:761:5 + | +LL | #[suggestion("with a suggestion", code = "")] + | ^ + | + = note: `#[suggestion(...)]` applied to `Vec` field is ambiguous + = help: to show a suggestion consisting of multiple parts, use a `Subdiagnostic` annotated with `#[multipart_suggestion(...)]` + = help: to show a variable set of suggestions, use a `Vec` of `Subdiagnostic`s annotated with `#[suggestion(...)]` + +error: derive(Diagnostic): Variable `nosub` not found in diagnostic + --> $DIR/diagnostic-derive-inline.rs:773:8 + | +LL | #[diag("does not exist: {$nosub}")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: Available fields: "sub" + +error: cannot find attribute `nonsense` in this scope + --> $DIR/diagnostic-derive-inline.rs:61:3 + | +LL | #[nonsense("this is an example message", code = E0123)] + | ^^^^^^^^ + +error: cannot find attribute `nonsense` in this scope + --> $DIR/diagnostic-derive-inline.rs:139:7 + | +LL | #[nonsense] + | ^^^^^^^^ + +error: cannot find attribute `error` in this scope + --> $DIR/diagnostic-derive-inline.rs:518:3 + | +LL | #[error("this is an example message", code = E0123)] + | ^^^^^ + | +help: `error` is an attribute that can be used by the derive macro `Error`, you might be missing a `derive` attribute + | +LL + #[derive(Error)] +LL | struct ErrorAttribute {} + | + +error: cannot find attribute `warn_` in this scope + --> $DIR/diagnostic-derive-inline.rs:525:3 + | +LL | #[warn_("this is an example message", code = E0123)] + | ^^^^^ + | +help: a built-in attribute with a similar name exists + | +LL - #[warn_("this is an example message", code = E0123)] +LL + #[warn("this is an example message", code = E0123)] + | + +error: cannot find attribute `lint` in this scope + --> $DIR/diagnostic-derive-inline.rs:532:3 + | +LL | #[lint("this is an example message", code = E0123)] + | ^^^^ + | +help: a built-in attribute with a similar name exists + | +LL - #[lint("this is an example message", code = E0123)] +LL + #[link("this is an example message", code = E0123)] + | + +error: cannot find attribute `lint` in this scope + --> $DIR/diagnostic-derive-inline.rs:539:3 + | +LL | #[lint("this is an example message", code = E0123)] + | ^^^^ + | +help: a built-in attribute with a similar name exists + | +LL - #[lint("this is an example message", code = E0123)] +LL + #[link("this is an example message", code = E0123)] + | + +error: cannot find attribute `multipart_suggestion` in this scope + --> $DIR/diagnostic-derive-inline.rs:579:3 + | +LL | #[multipart_suggestion("with a suggestion")] + | ^^^^^^^^^^^^^^^^^^^^ + | +help: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute + | +LL + #[derive(Subdiagnostic)] +LL | struct MultipartSuggestion { + | + +error: cannot find attribute `multipart_suggestion` in this scope + --> $DIR/diagnostic-derive-inline.rs:582:3 + | +LL | #[multipart_suggestion()] + | ^^^^^^^^^^^^^^^^^^^^ + | +help: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute + | +LL + #[derive(Subdiagnostic)] +LL | struct MultipartSuggestion { + | + +error: cannot find attribute `multipart_suggestion` in this scope + --> $DIR/diagnostic-derive-inline.rs:586:7 + | +LL | #[multipart_suggestion("with a suggestion")] + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute + +error[E0277]: the trait bound `Hello: IntoDiagArg` is not satisfied + --> $DIR/diagnostic-derive-inline.rs:330:12 + | +LL | #[derive(Diagnostic)] + | ---------- required by a bound introduced by this call +... +LL | other: Hello, + | ^^^^^ unsatisfied trait bound + | +help: the nightly-only, unstable trait `IntoDiagArg` is not implemented for `Hello` + --> $DIR/diagnostic-derive-inline.rs:42:1 + | +LL | struct Hello {} + | ^^^^^^^^^^^^ + = help: normalized in stderr +note: required by a bound in `Diag::<'a, G>::arg` + --> $COMPILER_DIR/rustc_errors/src/diagnostic.rs:LL:CC + ::: $COMPILER_DIR/rustc_errors/src/diagnostic.rs:LL:CC + | + = note: in this macro invocation + = note: this error originates in the macro `with_fn` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 80 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive-inline.rs b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive-inline.rs new file mode 100644 index 000000000000..eaa681d40be5 --- /dev/null +++ b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive-inline.rs @@ -0,0 +1,807 @@ +//@ check-fail +// Tests error conditions for specifying inline subdiagnostics using #[derive(Subdiagnostic)] + +// The proc_macro2 crate handles spans differently when on beta/stable release rather than nightly, +// changing the output of this test. Since Subdiagnostic is strictly internal to the compiler +// the test is just ignored on stable and beta: +//@ ignore-stage1 +//@ ignore-beta +//@ ignore-stable + +#![feature(rustc_private)] +#![crate_type = "lib"] + +extern crate rustc_errors; +extern crate rustc_fluent_macro; +extern crate rustc_macros; +extern crate rustc_session; +extern crate rustc_span; +extern crate core; + +use rustc_errors::{Applicability, DiagMessage, SubdiagMessage}; +use rustc_macros::Subdiagnostic; +use rustc_span::Span; + +#[derive(Subdiagnostic)] +#[label("example message")] +struct A { + #[primary_span] + span: Span, + var: String, +} + +#[derive(Subdiagnostic)] +enum B { + #[label("example message")] + A { + #[primary_span] + span: Span, + var: String, + }, + #[label("example message")] + B { + #[primary_span] + span: Span, + var: String, + }, +} + +#[derive(Subdiagnostic)] +#[label("example message")] +//~^ ERROR label without `#[primary_span]` field +struct C { + var: String, +} + +#[derive(Subdiagnostic)] +#[label] +//~^ ERROR diagnostic slug must be first argument +struct D { + #[primary_span] + span: Span, + var: String, +} + +#[derive(Subdiagnostic)] +#[foo] +//~^ ERROR `#[foo]` is not a valid attribute +//~^^ ERROR cannot find attribute `foo` in this scope +struct E { + #[primary_span] + span: Span, + var: String, +} + +#[derive(Subdiagnostic)] +#[label = "..."] +//~^ ERROR `#[label = ...]` is not a valid attribute +struct F { + #[primary_span] + span: Span, + var: String, +} + +#[derive(Subdiagnostic)] +#[label(bug = "...")] +//~^ ERROR no nested attribute expected here +//~| ERROR diagnostic slug must be first argument +struct G { + #[primary_span] + span: Span, + var: String, +} + +#[derive(Subdiagnostic)] +#[label(slug = 4)] +//~^ ERROR no nested attribute expected here +//~| ERROR diagnostic slug must be first argument +struct J { + #[primary_span] + span: Span, + var: String, +} + +#[derive(Subdiagnostic)] +#[label(slug("..."))] +//~^ ERROR no nested attribute expected here +//~| ERROR diagnostic slug must be first argument +struct K { + #[primary_span] + span: Span, + var: String, +} + +#[derive(Subdiagnostic)] +#[label()] +//~^ ERROR diagnostic slug must be first argument of a `#[label(...)]` attribute +struct M { + #[primary_span] + span: Span, + var: String, +} + +#[derive(Subdiagnostic)] +#[label("example message", code = "...")] +//~^ ERROR no nested attribute expected here +struct N { + #[primary_span] + span: Span, + var: String, +} + +#[derive(Subdiagnostic)] +#[label("example message", applicability = "machine-applicable")] +//~^ ERROR no nested attribute expected here +struct O { + #[primary_span] + span: Span, + var: String, +} + +#[derive(Subdiagnostic)] +#[foo] +//~^ ERROR cannot find attribute `foo` in this scope +//~^^ ERROR unsupported type attribute for subdiagnostic enum +enum P { + #[label("example message")] + A { + #[primary_span] + span: Span, + var: String, + }, +} + +#[derive(Subdiagnostic)] +enum Q { + #[bar] + //~^ ERROR `#[bar]` is not a valid attribute + //~^^ ERROR cannot find attribute `bar` in this scope + A { + #[primary_span] + span: Span, + var: String, + }, +} + +#[derive(Subdiagnostic)] +enum R { + #[bar = "..."] + //~^ ERROR `#[bar = ...]` is not a valid attribute + //~^^ ERROR cannot find attribute `bar` in this scope + A { + #[primary_span] + span: Span, + var: String, + }, +} + +#[derive(Subdiagnostic)] +enum S { + #[bar = 4] + //~^ ERROR `#[bar = ...]` is not a valid attribute + //~^^ ERROR cannot find attribute `bar` in this scope + A { + #[primary_span] + span: Span, + var: String, + }, +} + +#[derive(Subdiagnostic)] +enum T { + #[bar("...")] + //~^ ERROR `#[bar(...)]` is not a valid attribute + //~^^ ERROR cannot find attribute `bar` in this scope + A { + #[primary_span] + span: Span, + var: String, + }, +} + +#[derive(Subdiagnostic)] +enum U { + #[label(code = "...")] + //~^ ERROR diagnostic slug must be first argument of a `#[label(...)]` attribute + //~| ERROR no nested attribute expected here + A { + #[primary_span] + span: Span, + var: String, + }, +} + +#[derive(Subdiagnostic)] +enum V { + #[label("example message")] + A { + #[primary_span] + span: Span, + var: String, + }, + B { + #[primary_span] + span: Span, + var: String, + }, +} + +#[derive(Subdiagnostic)] +#[label("example message")] +//~^ ERROR label without `#[primary_span]` field +struct W { + #[primary_span] + //~^ ERROR the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan` + span: String, +} + +#[derive(Subdiagnostic)] +#[label("example message")] +struct X { + #[primary_span] + span: Span, + #[applicability] + //~^ ERROR `#[applicability]` is only valid on suggestions + applicability: Applicability, +} + +#[derive(Subdiagnostic)] +#[label("example message")] +struct Y { + #[primary_span] + span: Span, + #[bar] + //~^ ERROR `#[bar]` is not a valid attribute + //~^^ ERROR cannot find attribute `bar` in this scope + bar: String, +} + +#[derive(Subdiagnostic)] +#[label("example message")] +struct Z { + #[primary_span] + span: Span, + #[bar = "..."] + //~^ ERROR `#[bar = ...]` is not a valid attribute + //~^^ ERROR cannot find attribute `bar` in this scope + bar: String, +} + +#[derive(Subdiagnostic)] +#[label("example message")] +struct AA { + #[primary_span] + span: Span, + #[bar("...")] + //~^ ERROR `#[bar(...)]` is not a valid attribute + //~^^ ERROR cannot find attribute `bar` in this scope + bar: String, +} + +#[derive(Subdiagnostic)] +#[label("example message")] +struct AB { + #[primary_span] + span: Span, + #[skip_arg] + z: Z, +} + +#[derive(Subdiagnostic)] +union AC { + //~^ ERROR unexpected unsupported untagged union + span: u32, + b: u64, +} + +#[derive(Subdiagnostic)] +#[label("example message")] +#[label("example message")] +struct AD { + #[primary_span] + span: Span, +} + +#[derive(Subdiagnostic)] +#[label("example message", no_crate::example)] +//~^ ERROR a diagnostic slug must be the first argument to the attribute +struct AE { + #[primary_span] + span: Span, +} + +#[derive(Subdiagnostic)] +#[label("example message")] +struct AF { + #[primary_span] + //~^ NOTE previously specified here + span_a: Span, + #[primary_span] + //~^ ERROR specified multiple times + span_b: Span, +} + +#[derive(Subdiagnostic)] +struct AG { + //~^ ERROR subdiagnostic kind not specified + #[primary_span] + span: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "...")] +struct AH { + #[primary_span] + span: Span, + #[applicability] + applicability: Applicability, + var: String, +} + +#[derive(Subdiagnostic)] +enum AI { + #[suggestion("example message", code = "...")] + A { + #[primary_span] + span: Span, + #[applicability] + applicability: Applicability, + var: String, + }, + #[suggestion("example message", code = "...")] + B { + #[primary_span] + span: Span, + #[applicability] + applicability: Applicability, + var: String, + }, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "...", code = "...")] +//~^ ERROR specified multiple times +//~^^ NOTE previously specified here +struct AJ { + #[primary_span] + span: Span, + #[applicability] + applicability: Applicability, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "...")] +struct AK { + #[primary_span] + span: Span, + #[applicability] + //~^ NOTE previously specified here + applicability_a: Applicability, + #[applicability] + //~^ ERROR specified multiple times + applicability_b: Applicability, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "...")] +struct AL { + #[primary_span] + span: Span, + #[applicability] + //~^ ERROR the `#[applicability]` attribute can only be applied to fields of type `Applicability` + applicability: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "...")] +struct AM { + #[primary_span] + span: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message")] +//~^ ERROR suggestion without `code = "..."` +struct AN { + #[primary_span] + span: Span, + #[applicability] + applicability: Applicability, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "...", applicability = "foo")] +//~^ ERROR invalid applicability +struct AO { + #[primary_span] + span: Span, +} + +#[derive(Subdiagnostic)] +#[help("example message")] +struct AP { + var: String, +} + +#[derive(Subdiagnostic)] +#[note("example message")] +struct AQ; + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "...")] +//~^ ERROR suggestion without `#[primary_span]` field +struct AR { + var: String, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "...", applicability = "machine-applicable")] +struct AS { + #[primary_span] + span: Span, +} + +#[derive(Subdiagnostic)] +#[label] +//~^ ERROR unsupported type attribute for subdiagnostic enum +enum AT { + #[label("example message")] + A { + #[primary_span] + span: Span, + var: String, + }, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "{var}", applicability = "machine-applicable")] +struct AU { + #[primary_span] + span: Span, + var: String, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "{var}", applicability = "machine-applicable")] +//~^ ERROR `var` doesn't refer to a field on this type +struct AV { + #[primary_span] + span: Span, +} + +#[derive(Subdiagnostic)] +enum AW { + #[suggestion("example message", code = "{var}", applicability = "machine-applicable")] + A { + #[primary_span] + span: Span, + var: String, + }, +} + +#[derive(Subdiagnostic)] +enum AX { + #[suggestion("example message", code = "{var}", applicability = "machine-applicable")] + //~^ ERROR `var` doesn't refer to a field on this type + A { + #[primary_span] + span: Span, + }, +} + +#[derive(Subdiagnostic)] +#[warning("example message")] +struct AY {} + +#[derive(Subdiagnostic)] +#[warning("example message")] +struct AZ { + #[primary_span] + span: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "...")] +//~^ ERROR suggestion without `#[primary_span]` field +struct BA { + #[suggestion_part] + //~^ ERROR `#[suggestion_part]` is not a valid attribute + span: Span, + #[suggestion_part(code = "...")] + //~^ ERROR `#[suggestion_part(...)]` is not a valid attribute + span2: Span, + #[applicability] + applicability: Applicability, + var: String, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message", code = "...", applicability = "machine-applicable")] +//~^ ERROR multipart suggestion without any `#[suggestion_part(...)]` fields +//~| ERROR invalid nested attribute +struct BBa { + var: String, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message", applicability = "machine-applicable")] +struct BBb { + #[suggestion_part] + //~^ ERROR `#[suggestion_part(...)]` attribute without `code = "..."` + span1: Span, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message", applicability = "machine-applicable")] +struct BBc { + #[suggestion_part()] + //~^ ERROR `#[suggestion_part(...)]` attribute without `code = "..."` + span1: Span, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message")] +//~^ ERROR multipart suggestion without any `#[suggestion_part(...)]` fields +struct BC { + #[primary_span] + //~^ ERROR `#[primary_span]` is not a valid attribute + span: Span, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message")] +struct BD { + #[suggestion_part] + //~^ ERROR `#[suggestion_part(...)]` attribute without `code = "..."` + span1: Span, + #[suggestion_part()] + //~^ ERROR `#[suggestion_part(...)]` attribute without `code = "..."` + span2: Span, + #[suggestion_part(foo = "bar")] + //~^ ERROR `code` is the only valid nested attribute + //~| ERROR expected `,` + span4: Span, + #[suggestion_part(code = "...")] + //~^ ERROR the `#[suggestion_part(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` + s1: String, + #[suggestion_part()] + //~^ ERROR the `#[suggestion_part(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` + s2: String, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message", applicability = "machine-applicable")] +struct BE { + #[suggestion_part(code = "...", code = ",,,")] + //~^ ERROR specified multiple times + //~| NOTE previously specified here + span: Span, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message", applicability = "machine-applicable")] +struct BF { + #[suggestion_part(code = "(")] + first: Span, + #[suggestion_part(code = ")")] + second: Span, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message")] +struct BG { + #[applicability] + appl: Applicability, + #[suggestion_part(code = "(")] + first: Span, + #[suggestion_part(code = ")")] + second: Span, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message", applicability = "machine-applicable")] +struct BH { + #[applicability] + //~^ ERROR `#[applicability]` has no effect + appl: Applicability, + #[suggestion_part(code = "(")] + first: Span, + #[suggestion_part(code = ")")] + second: Span, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message", applicability = "machine-applicable")] +struct BI { + #[suggestion_part(code = "")] + spans: Vec, +} + +#[derive(Subdiagnostic)] +#[label("example message")] +struct BJ { + #[primary_span] + span: Span, + r#type: String, +} + +/// with a doc comment on the type.. +#[derive(Subdiagnostic)] +#[label("example message")] +struct BK { + /// ..and the field + #[primary_span] + span: Span, +} + +/// with a doc comment on the type.. +#[derive(Subdiagnostic)] +enum BL { + /// ..and the variant.. + #[label("example message")] + Foo { + /// ..and the field + #[primary_span] + span: Span, + }, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message")] +struct BM { + #[suggestion_part(code("foo"))] + //~^ ERROR expected exactly one string literal for `code = ...` + //~| ERROR unexpected token, expected `)` + span: Span, + r#type: String, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message")] +struct BN { + #[suggestion_part(code("foo", "bar"))] + //~^ ERROR expected exactly one string literal for `code = ...` + //~| ERROR unexpected token, expected `)` + span: Span, + r#type: String, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message")] +struct BO { + #[suggestion_part(code(3))] + //~^ ERROR expected exactly one string literal for `code = ...` + //~| ERROR unexpected token, expected `)` + span: Span, + r#type: String, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message")] +struct BP { + #[suggestion_part(code())] + //~^ ERROR expected exactly one string literal for `code = ...` + span: Span, + r#type: String, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("example message")] +struct BQ { + #[suggestion_part(code = 3)] + //~^ ERROR expected string literal + span: Span, + r#type: String, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "")] +struct SuggestionStyleDefault { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "", style = "short")] +struct SuggestionStyleShort { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "", style = "hidden")] +struct SuggestionStyleHidden { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "", style = "verbose")] +struct SuggestionStyleVerbose { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "", style = "tool-only")] +struct SuggestionStyleToolOnly { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "", style = "hidden", style = "normal")] +//~^ ERROR specified multiple times +//~| NOTE previously specified here +struct SuggestionStyleTwice { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion_hidden("example message", code = "")] +//~^ ERROR #[suggestion_hidden(...)]` is not a valid attribute +struct SuggestionStyleOldSyntax { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion_hidden("example message", code = "", style = "normal")] +//~^ ERROR #[suggestion_hidden(...)]` is not a valid attribute +struct SuggestionStyleOldAndNewSyntax { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "", style = "foo")] +//~^ ERROR invalid suggestion style +struct SuggestionStyleInvalid1 { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "", style = 42)] +//~^ ERROR expected string literal +struct SuggestionStyleInvalid2 { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "", style)] +//~^ ERROR a diagnostic slug must be the first argument to the attribute +struct SuggestionStyleInvalid3 { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "", style("foo"))] +//~^ ERROR expected `=` +struct SuggestionStyleInvalid4 { + #[primary_span] + sub: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion("example message", code = "")] +//~^ ERROR suggestion without `#[primary_span]` field +struct PrimarySpanOnVec { + #[primary_span] + //~^ ERROR `#[primary_span]` is not a valid attribute + //~| NOTE there must be exactly one primary span + sub: Vec, +} + +#[derive(Subdiagnostic)] +struct NestedParent { + #[subdiagnostic] + single_sub: A, + #[subdiagnostic] + option_sub: Option, + #[subdiagnostic] + vec_sub: Vec, +} diff --git a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive-inline.stderr b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive-inline.stderr new file mode 100644 index 000000000000..11753b949bc7 --- /dev/null +++ b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive-inline.stderr @@ -0,0 +1,549 @@ +error: derive(Diagnostic): label without `#[primary_span]` field + --> $DIR/subdiagnostic-derive-inline.rs:50:1 + | +LL | #[label("example message")] + | ^ + +error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label(...)]` attribute + --> $DIR/subdiagnostic-derive-inline.rs:57:1 + | +LL | #[label] + | ^ + +error: derive(Diagnostic): `#[foo]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:66:1 + | +LL | #[foo] + | ^ + +error: derive(Diagnostic): `#[label = ...]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:76:1 + | +LL | #[label = "..."] + | ^ + +error: derive(Diagnostic): no nested attribute expected here + --> $DIR/subdiagnostic-derive-inline.rs:85:9 + | +LL | #[label(bug = "...")] + | ^^^ + +error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label(...)]` attribute + --> $DIR/subdiagnostic-derive-inline.rs:85:1 + | +LL | #[label(bug = "...")] + | ^ + +error: derive(Diagnostic): no nested attribute expected here + --> $DIR/subdiagnostic-derive-inline.rs:95:9 + | +LL | #[label(slug = 4)] + | ^^^^ + +error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label(...)]` attribute + --> $DIR/subdiagnostic-derive-inline.rs:95:1 + | +LL | #[label(slug = 4)] + | ^ + +error: derive(Diagnostic): no nested attribute expected here + --> $DIR/subdiagnostic-derive-inline.rs:105:9 + | +LL | #[label(slug("..."))] + | ^^^^ + +error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label(...)]` attribute + --> $DIR/subdiagnostic-derive-inline.rs:105:1 + | +LL | #[label(slug("..."))] + | ^ + +error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label(...)]` attribute + --> $DIR/subdiagnostic-derive-inline.rs:115:1 + | +LL | #[label()] + | ^ + +error: derive(Diagnostic): no nested attribute expected here + --> $DIR/subdiagnostic-derive-inline.rs:124:28 + | +LL | #[label("example message", code = "...")] + | ^^^^ + +error: derive(Diagnostic): no nested attribute expected here + --> $DIR/subdiagnostic-derive-inline.rs:133:28 + | +LL | #[label("example message", applicability = "machine-applicable")] + | ^^^^^^^^^^^^^ + +error: derive(Diagnostic): unsupported type attribute for subdiagnostic enum + --> $DIR/subdiagnostic-derive-inline.rs:142:1 + | +LL | #[foo] + | ^ + +error: derive(Diagnostic): `#[bar]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:156:5 + | +LL | #[bar] + | ^ + +error: derive(Diagnostic): `#[bar = ...]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:168:5 + | +LL | #[bar = "..."] + | ^ + +error: derive(Diagnostic): `#[bar = ...]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:180:5 + | +LL | #[bar = 4] + | ^ + +error: derive(Diagnostic): `#[bar(...)]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:192:5 + | +LL | #[bar("...")] + | ^ + +error: derive(Diagnostic): no nested attribute expected here + --> $DIR/subdiagnostic-derive-inline.rs:204:13 + | +LL | #[label(code = "...")] + | ^^^^ + +error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label(...)]` attribute + --> $DIR/subdiagnostic-derive-inline.rs:204:5 + | +LL | #[label(code = "...")] + | ^ + +error: derive(Diagnostic): the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan` + --> $DIR/subdiagnostic-derive-inline.rs:233:5 + | +LL | #[primary_span] + | ^ + +error: derive(Diagnostic): label without `#[primary_span]` field + --> $DIR/subdiagnostic-derive-inline.rs:230:1 + | +LL | #[label("example message")] + | ^ + +error: derive(Diagnostic): `#[applicability]` is only valid on suggestions + --> $DIR/subdiagnostic-derive-inline.rs:243:5 + | +LL | #[applicability] + | ^ + +error: derive(Diagnostic): `#[bar]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:253:5 + | +LL | #[bar] + | ^ + | + = help: only `primary_span`, `applicability` and `skip_arg` are valid field attributes + +error: derive(Diagnostic): `#[bar = ...]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:264:5 + | +LL | #[bar = "..."] + | ^ + +error: derive(Diagnostic): `#[bar(...)]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:275:5 + | +LL | #[bar("...")] + | ^ + | + = help: only `primary_span`, `applicability` and `skip_arg` are valid field attributes + +error: unexpected unsupported untagged union + --> $DIR/subdiagnostic-derive-inline.rs:291:1 + | +LL | / union AC { +LL | | +LL | | span: u32, +LL | | b: u64, +LL | | } + | |_^ + +error: derive(Diagnostic): a diagnostic slug must be the first argument to the attribute + --> $DIR/subdiagnostic-derive-inline.rs:306:28 + | +LL | #[label("example message", no_crate::example)] + | ^^^^^^^^ + +error: derive(Diagnostic): attribute specified multiple times + --> $DIR/subdiagnostic-derive-inline.rs:319:5 + | +LL | #[primary_span] + | ^ + | +note: previously specified here + --> $DIR/subdiagnostic-derive-inline.rs:316:5 + | +LL | #[primary_span] + | ^ + +error: derive(Diagnostic): subdiagnostic kind not specified + --> $DIR/subdiagnostic-derive-inline.rs:325:8 + | +LL | struct AG { + | ^^ + +error: derive(Diagnostic): attribute specified multiple times + --> $DIR/subdiagnostic-derive-inline.rs:362:47 + | +LL | #[suggestion("example message", code = "...", code = "...")] + | ^^^^ + | +note: previously specified here + --> $DIR/subdiagnostic-derive-inline.rs:362:33 + | +LL | #[suggestion("example message", code = "...", code = "...")] + | ^^^^ + +error: derive(Diagnostic): attribute specified multiple times + --> $DIR/subdiagnostic-derive-inline.rs:380:5 + | +LL | #[applicability] + | ^ + | +note: previously specified here + --> $DIR/subdiagnostic-derive-inline.rs:377:5 + | +LL | #[applicability] + | ^ + +error: derive(Diagnostic): the `#[applicability]` attribute can only be applied to fields of type `Applicability` + --> $DIR/subdiagnostic-derive-inline.rs:390:5 + | +LL | #[applicability] + | ^ + +error: derive(Diagnostic): suggestion without `code = "..."` + --> $DIR/subdiagnostic-derive-inline.rs:403:1 + | +LL | #[suggestion("example message")] + | ^ + +error: derive(Diagnostic): invalid applicability + --> $DIR/subdiagnostic-derive-inline.rs:413:63 + | +LL | #[suggestion("example message", code = "...", applicability = "foo")] + | ^^^^^ + +error: derive(Diagnostic): suggestion without `#[primary_span]` field + --> $DIR/subdiagnostic-derive-inline.rs:431:1 + | +LL | #[suggestion("example message", code = "...")] + | ^ + +error: derive(Diagnostic): unsupported type attribute for subdiagnostic enum + --> $DIR/subdiagnostic-derive-inline.rs:445:1 + | +LL | #[label] + | ^ + +error: derive(Diagnostic): `var` doesn't refer to a field on this type + --> $DIR/subdiagnostic-derive-inline.rs:465:40 + | +LL | #[suggestion("example message", code = "{var}", applicability = "machine-applicable")] + | ^^^^^^^ + +error: derive(Diagnostic): `var` doesn't refer to a field on this type + --> $DIR/subdiagnostic-derive-inline.rs:484:44 + | +LL | #[suggestion("example message", code = "{var}", applicability = "machine-applicable")] + | ^^^^^^^ + +error: derive(Diagnostic): `#[suggestion_part]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:507:5 + | +LL | #[suggestion_part] + | ^ + | + = help: `#[suggestion_part(...)]` is only valid in multipart suggestions, use `#[primary_span]` instead + +error: derive(Diagnostic): `#[suggestion_part(...)]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:510:5 + | +LL | #[suggestion_part(code = "...")] + | ^ + | + = help: `#[suggestion_part(...)]` is only valid in multipart suggestions + +error: derive(Diagnostic): suggestion without `#[primary_span]` field + --> $DIR/subdiagnostic-derive-inline.rs:504:1 + | +LL | #[suggestion("example message", code = "...")] + | ^ + +error: derive(Diagnostic): invalid nested attribute + --> $DIR/subdiagnostic-derive-inline.rs:519:43 + | +LL | #[multipart_suggestion("example message", code = "...", applicability = "machine-applicable")] + | ^^^^ + | + = help: only `style` and `applicability` are valid nested attributes + +error: derive(Diagnostic): multipart suggestion without any `#[suggestion_part(...)]` fields + --> $DIR/subdiagnostic-derive-inline.rs:519:1 + | +LL | #[multipart_suggestion("example message", code = "...", applicability = "machine-applicable")] + | ^ + +error: derive(Diagnostic): `#[suggestion_part(...)]` attribute without `code = "..."` + --> $DIR/subdiagnostic-derive-inline.rs:529:5 + | +LL | #[suggestion_part] + | ^ + +error: derive(Diagnostic): `#[suggestion_part(...)]` attribute without `code = "..."` + --> $DIR/subdiagnostic-derive-inline.rs:537:5 + | +LL | #[suggestion_part()] + | ^ + +error: derive(Diagnostic): `#[primary_span]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:546:5 + | +LL | #[primary_span] + | ^ + | + = help: multipart suggestions use one or more `#[suggestion_part]`s rather than one `#[primary_span]` + +error: derive(Diagnostic): multipart suggestion without any `#[suggestion_part(...)]` fields + --> $DIR/subdiagnostic-derive-inline.rs:543:1 + | +LL | #[multipart_suggestion("example message")] + | ^ + +error: derive(Diagnostic): `#[suggestion_part(...)]` attribute without `code = "..."` + --> $DIR/subdiagnostic-derive-inline.rs:554:5 + | +LL | #[suggestion_part] + | ^ + +error: derive(Diagnostic): `#[suggestion_part(...)]` attribute without `code = "..."` + --> $DIR/subdiagnostic-derive-inline.rs:557:5 + | +LL | #[suggestion_part()] + | ^ + +error: derive(Diagnostic): `code` is the only valid nested attribute + --> $DIR/subdiagnostic-derive-inline.rs:560:23 + | +LL | #[suggestion_part(foo = "bar")] + | ^^^ + +error: derive(Diagnostic): the `#[suggestion_part(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` + --> $DIR/subdiagnostic-derive-inline.rs:564:5 + | +LL | #[suggestion_part(code = "...")] + | ^ + +error: derive(Diagnostic): the `#[suggestion_part(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` + --> $DIR/subdiagnostic-derive-inline.rs:567:5 + | +LL | #[suggestion_part()] + | ^ + +error: expected `,` + --> $DIR/subdiagnostic-derive-inline.rs:560:27 + | +LL | #[suggestion_part(foo = "bar")] + | ^ + +error: derive(Diagnostic): attribute specified multiple times + --> $DIR/subdiagnostic-derive-inline.rs:575:37 + | +LL | #[suggestion_part(code = "...", code = ",,,")] + | ^^^^ + | +note: previously specified here + --> $DIR/subdiagnostic-derive-inline.rs:575:23 + | +LL | #[suggestion_part(code = "...", code = ",,,")] + | ^^^^ + +error: derive(Diagnostic): `#[applicability]` has no effect if all `#[suggestion]`/`#[multipart_suggestion]` attributes have a static `applicability = "..."` + --> $DIR/subdiagnostic-derive-inline.rs:604:5 + | +LL | #[applicability] + | ^ + +error: derive(Diagnostic): expected exactly one string literal for `code = ...` + --> $DIR/subdiagnostic-derive-inline.rs:652:28 + | +LL | #[suggestion_part(code("foo"))] + | ^^^^^ + +error: unexpected token, expected `)` + --> $DIR/subdiagnostic-derive-inline.rs:652:28 + | +LL | #[suggestion_part(code("foo"))] + | ^^^^^ + +error: derive(Diagnostic): expected exactly one string literal for `code = ...` + --> $DIR/subdiagnostic-derive-inline.rs:662:28 + | +LL | #[suggestion_part(code("foo", "bar"))] + | ^^^^^ + +error: unexpected token, expected `)` + --> $DIR/subdiagnostic-derive-inline.rs:662:28 + | +LL | #[suggestion_part(code("foo", "bar"))] + | ^^^^^ + +error: derive(Diagnostic): expected exactly one string literal for `code = ...` + --> $DIR/subdiagnostic-derive-inline.rs:672:28 + | +LL | #[suggestion_part(code(3))] + | ^ + +error: unexpected token, expected `)` + --> $DIR/subdiagnostic-derive-inline.rs:672:28 + | +LL | #[suggestion_part(code(3))] + | ^ + +error: derive(Diagnostic): expected exactly one string literal for `code = ...` + --> $DIR/subdiagnostic-derive-inline.rs:682:28 + | +LL | #[suggestion_part(code())] + | ^ + +error: expected string literal + --> $DIR/subdiagnostic-derive-inline.rs:691:30 + | +LL | #[suggestion_part(code = 3)] + | ^ + +error: derive(Diagnostic): attribute specified multiple times + --> $DIR/subdiagnostic-derive-inline.rs:733:1 + | +LL | #[suggestion("example message", code = "", style = "hidden", style = "normal")] + | ^ + | +note: previously specified here + --> $DIR/subdiagnostic-derive-inline.rs:733:1 + | +LL | #[suggestion("example message", code = "", style = "hidden", style = "normal")] + | ^ + +error: derive(Diagnostic): `#[suggestion_hidden(...)]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:742:1 + | +LL | #[suggestion_hidden("example message", code = "")] + | ^ + | + = help: Use `#[suggestion(..., style = "hidden")]` instead + +error: derive(Diagnostic): `#[suggestion_hidden(...)]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:750:1 + | +LL | #[suggestion_hidden("example message", code = "", style = "normal")] + | ^ + | + = help: Use `#[suggestion(..., style = "hidden")]` instead + +error: derive(Diagnostic): invalid suggestion style + --> $DIR/subdiagnostic-derive-inline.rs:758:52 + | +LL | #[suggestion("example message", code = "", style = "foo")] + | ^^^^^ + | + = help: valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only` + +error: expected string literal + --> $DIR/subdiagnostic-derive-inline.rs:766:52 + | +LL | #[suggestion("example message", code = "", style = 42)] + | ^^ + +error: derive(Diagnostic): a diagnostic slug must be the first argument to the attribute + --> $DIR/subdiagnostic-derive-inline.rs:774:44 + | +LL | #[suggestion("example message", code = "", style)] + | ^^^^^ + +error: expected `=` + --> $DIR/subdiagnostic-derive-inline.rs:782:49 + | +LL | #[suggestion("example message", code = "", style("foo"))] + | ^ + +error: derive(Diagnostic): `#[primary_span]` is not a valid attribute + --> $DIR/subdiagnostic-derive-inline.rs:793:5 + | +LL | #[primary_span] + | ^ + | + = note: there must be exactly one primary span + = help: to create a suggestion with multiple spans, use `#[multipart_suggestion]` instead + +error: derive(Diagnostic): suggestion without `#[primary_span]` field + --> $DIR/subdiagnostic-derive-inline.rs:790:1 + | +LL | #[suggestion("example message", code = "")] + | ^ + +error: cannot find attribute `foo` in this scope + --> $DIR/subdiagnostic-derive-inline.rs:66:3 + | +LL | #[foo] + | ^^^ + +error: cannot find attribute `foo` in this scope + --> $DIR/subdiagnostic-derive-inline.rs:142:3 + | +LL | #[foo] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive-inline.rs:156:7 + | +LL | #[bar] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive-inline.rs:168:7 + | +LL | #[bar = "..."] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive-inline.rs:180:7 + | +LL | #[bar = 4] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive-inline.rs:192:7 + | +LL | #[bar("...")] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive-inline.rs:253:7 + | +LL | #[bar] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive-inline.rs:264:7 + | +LL | #[bar = "..."] + | ^^^ + +error: cannot find attribute `bar` in this scope + --> $DIR/subdiagnostic-derive-inline.rs:275:7 + | +LL | #[bar("...")] + | ^^^ + +error: aborting due to 82 previous errors + diff --git a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs index b2e7b4c61daa..c06ea451b9bf 100644 --- a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs +++ b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs @@ -93,15 +93,6 @@ struct G { var: String, } -#[derive(Subdiagnostic)] -#[label("...")] -//~^ ERROR expected identifier -struct H { - #[primary_span] - span: Span, - var: String, -} - #[derive(Subdiagnostic)] #[label(slug = 4)] //~^ ERROR no nested attribute expected here diff --git a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr index 63634741e934..9f18f7ffabcc 100644 --- a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr +++ b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr @@ -34,116 +34,110 @@ error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label( LL | #[label(bug = "...")] | ^ -error: expected identifier +error: derive(Diagnostic): no nested attribute expected here --> $DIR/subdiagnostic-derive.rs:97:9 | -LL | #[label("...")] - | ^^^^^ - -error: derive(Diagnostic): no nested attribute expected here - --> $DIR/subdiagnostic-derive.rs:106:9 - | LL | #[label(slug = 4)] | ^^^^ error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label(...)]` attribute - --> $DIR/subdiagnostic-derive.rs:106:1 + --> $DIR/subdiagnostic-derive.rs:97:1 | LL | #[label(slug = 4)] | ^ error: derive(Diagnostic): no nested attribute expected here - --> $DIR/subdiagnostic-derive.rs:116:9 + --> $DIR/subdiagnostic-derive.rs:107:9 | LL | #[label(slug("..."))] | ^^^^ error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label(...)]` attribute - --> $DIR/subdiagnostic-derive.rs:116:1 + --> $DIR/subdiagnostic-derive.rs:107:1 | LL | #[label(slug("..."))] | ^ error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label(...)]` attribute - --> $DIR/subdiagnostic-derive.rs:126:1 + --> $DIR/subdiagnostic-derive.rs:117:1 | LL | #[label()] | ^ error: derive(Diagnostic): no nested attribute expected here - --> $DIR/subdiagnostic-derive.rs:135:27 + --> $DIR/subdiagnostic-derive.rs:126:27 | LL | #[label(no_crate_example, code = "...")] | ^^^^ error: derive(Diagnostic): no nested attribute expected here - --> $DIR/subdiagnostic-derive.rs:144:27 + --> $DIR/subdiagnostic-derive.rs:135:27 | LL | #[label(no_crate_example, applicability = "machine-applicable")] | ^^^^^^^^^^^^^ error: derive(Diagnostic): unsupported type attribute for subdiagnostic enum - --> $DIR/subdiagnostic-derive.rs:153:1 + --> $DIR/subdiagnostic-derive.rs:144:1 | LL | #[foo] | ^ error: derive(Diagnostic): `#[bar]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:167:5 + --> $DIR/subdiagnostic-derive.rs:158:5 | LL | #[bar] | ^ error: derive(Diagnostic): `#[bar = ...]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:179:5 + --> $DIR/subdiagnostic-derive.rs:170:5 | LL | #[bar = "..."] | ^ error: derive(Diagnostic): `#[bar = ...]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:191:5 + --> $DIR/subdiagnostic-derive.rs:182:5 | LL | #[bar = 4] | ^ error: derive(Diagnostic): `#[bar(...)]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:203:5 + --> $DIR/subdiagnostic-derive.rs:194:5 | LL | #[bar("...")] | ^ error: derive(Diagnostic): no nested attribute expected here - --> $DIR/subdiagnostic-derive.rs:215:13 + --> $DIR/subdiagnostic-derive.rs:206:13 | LL | #[label(code = "...")] | ^^^^ error: derive(Diagnostic): diagnostic slug must be first argument of a `#[label(...)]` attribute - --> $DIR/subdiagnostic-derive.rs:215:5 + --> $DIR/subdiagnostic-derive.rs:206:5 | LL | #[label(code = "...")] | ^ error: derive(Diagnostic): the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan` - --> $DIR/subdiagnostic-derive.rs:244:5 + --> $DIR/subdiagnostic-derive.rs:235:5 | LL | #[primary_span] | ^ error: derive(Diagnostic): label without `#[primary_span]` field - --> $DIR/subdiagnostic-derive.rs:241:1 + --> $DIR/subdiagnostic-derive.rs:232:1 | LL | #[label(no_crate_example)] | ^ error: derive(Diagnostic): `#[applicability]` is only valid on suggestions - --> $DIR/subdiagnostic-derive.rs:254:5 + --> $DIR/subdiagnostic-derive.rs:245:5 | LL | #[applicability] | ^ error: derive(Diagnostic): `#[bar]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:264:5 + --> $DIR/subdiagnostic-derive.rs:255:5 | LL | #[bar] | ^ @@ -151,13 +145,13 @@ LL | #[bar] = help: only `primary_span`, `applicability` and `skip_arg` are valid field attributes error: derive(Diagnostic): `#[bar = ...]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:275:5 + --> $DIR/subdiagnostic-derive.rs:266:5 | LL | #[bar = "..."] | ^ error: derive(Diagnostic): `#[bar(...)]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:286:5 + --> $DIR/subdiagnostic-derive.rs:277:5 | LL | #[bar("...")] | ^ @@ -165,7 +159,7 @@ LL | #[bar("...")] = help: only `primary_span`, `applicability` and `skip_arg` are valid field attributes error: unexpected unsupported untagged union - --> $DIR/subdiagnostic-derive.rs:302:1 + --> $DIR/subdiagnostic-derive.rs:293:1 | LL | / union AC { LL | | @@ -175,97 +169,97 @@ LL | | } | |_^ error: derive(Diagnostic): a diagnostic slug must be the first argument to the attribute - --> $DIR/subdiagnostic-derive.rs:317:27 + --> $DIR/subdiagnostic-derive.rs:308:27 | LL | #[label(no_crate_example, no_crate::example)] | ^^^^^^^^ error: derive(Diagnostic): attribute specified multiple times - --> $DIR/subdiagnostic-derive.rs:330:5 + --> $DIR/subdiagnostic-derive.rs:321:5 | LL | #[primary_span] | ^ | note: previously specified here - --> $DIR/subdiagnostic-derive.rs:327:5 + --> $DIR/subdiagnostic-derive.rs:318:5 | LL | #[primary_span] | ^ error: derive(Diagnostic): subdiagnostic kind not specified - --> $DIR/subdiagnostic-derive.rs:336:8 + --> $DIR/subdiagnostic-derive.rs:327:8 | LL | struct AG { | ^^ error: derive(Diagnostic): attribute specified multiple times - --> $DIR/subdiagnostic-derive.rs:373:46 + --> $DIR/subdiagnostic-derive.rs:364:46 | LL | #[suggestion(no_crate_example, code = "...", code = "...")] | ^^^^ | note: previously specified here - --> $DIR/subdiagnostic-derive.rs:373:32 + --> $DIR/subdiagnostic-derive.rs:364:32 | LL | #[suggestion(no_crate_example, code = "...", code = "...")] | ^^^^ error: derive(Diagnostic): attribute specified multiple times - --> $DIR/subdiagnostic-derive.rs:391:5 + --> $DIR/subdiagnostic-derive.rs:382:5 | LL | #[applicability] | ^ | note: previously specified here - --> $DIR/subdiagnostic-derive.rs:388:5 + --> $DIR/subdiagnostic-derive.rs:379:5 | LL | #[applicability] | ^ error: derive(Diagnostic): the `#[applicability]` attribute can only be applied to fields of type `Applicability` - --> $DIR/subdiagnostic-derive.rs:401:5 + --> $DIR/subdiagnostic-derive.rs:392:5 | LL | #[applicability] | ^ error: derive(Diagnostic): suggestion without `code = "..."` - --> $DIR/subdiagnostic-derive.rs:414:1 + --> $DIR/subdiagnostic-derive.rs:405:1 | LL | #[suggestion(no_crate_example)] | ^ error: derive(Diagnostic): invalid applicability - --> $DIR/subdiagnostic-derive.rs:424:62 + --> $DIR/subdiagnostic-derive.rs:415:62 | LL | #[suggestion(no_crate_example, code = "...", applicability = "foo")] | ^^^^^ error: derive(Diagnostic): suggestion without `#[primary_span]` field - --> $DIR/subdiagnostic-derive.rs:442:1 + --> $DIR/subdiagnostic-derive.rs:433:1 | LL | #[suggestion(no_crate_example, code = "...")] | ^ error: derive(Diagnostic): unsupported type attribute for subdiagnostic enum - --> $DIR/subdiagnostic-derive.rs:456:1 + --> $DIR/subdiagnostic-derive.rs:447:1 | LL | #[label] | ^ error: derive(Diagnostic): `var` doesn't refer to a field on this type - --> $DIR/subdiagnostic-derive.rs:476:39 + --> $DIR/subdiagnostic-derive.rs:467:39 | LL | #[suggestion(no_crate_example, code = "{var}", applicability = "machine-applicable")] | ^^^^^^^ error: derive(Diagnostic): `var` doesn't refer to a field on this type - --> $DIR/subdiagnostic-derive.rs:495:43 + --> $DIR/subdiagnostic-derive.rs:486:43 | LL | #[suggestion(no_crate_example, code = "{var}", applicability = "machine-applicable")] | ^^^^^^^ error: derive(Diagnostic): `#[suggestion_part]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:518:5 + --> $DIR/subdiagnostic-derive.rs:509:5 | LL | #[suggestion_part] | ^ @@ -273,7 +267,7 @@ LL | #[suggestion_part] = help: `#[suggestion_part(...)]` is only valid in multipart suggestions, use `#[primary_span]` instead error: derive(Diagnostic): `#[suggestion_part(...)]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:521:5 + --> $DIR/subdiagnostic-derive.rs:512:5 | LL | #[suggestion_part(code = "...")] | ^ @@ -281,13 +275,13 @@ LL | #[suggestion_part(code = "...")] = help: `#[suggestion_part(...)]` is only valid in multipart suggestions error: derive(Diagnostic): suggestion without `#[primary_span]` field - --> $DIR/subdiagnostic-derive.rs:515:1 + --> $DIR/subdiagnostic-derive.rs:506:1 | LL | #[suggestion(no_crate_example, code = "...")] | ^ error: derive(Diagnostic): invalid nested attribute - --> $DIR/subdiagnostic-derive.rs:530:42 + --> $DIR/subdiagnostic-derive.rs:521:42 | LL | #[multipart_suggestion(no_crate_example, code = "...", applicability = "machine-applicable")] | ^^^^ @@ -295,25 +289,25 @@ LL | #[multipart_suggestion(no_crate_example, code = "...", applicability = "mac = help: only `style` and `applicability` are valid nested attributes error: derive(Diagnostic): multipart suggestion without any `#[suggestion_part(...)]` fields - --> $DIR/subdiagnostic-derive.rs:530:1 + --> $DIR/subdiagnostic-derive.rs:521:1 | LL | #[multipart_suggestion(no_crate_example, code = "...", applicability = "machine-applicable")] | ^ error: derive(Diagnostic): `#[suggestion_part(...)]` attribute without `code = "..."` - --> $DIR/subdiagnostic-derive.rs:540:5 + --> $DIR/subdiagnostic-derive.rs:531:5 | LL | #[suggestion_part] | ^ error: derive(Diagnostic): `#[suggestion_part(...)]` attribute without `code = "..."` - --> $DIR/subdiagnostic-derive.rs:548:5 + --> $DIR/subdiagnostic-derive.rs:539:5 | LL | #[suggestion_part()] | ^ error: derive(Diagnostic): `#[primary_span]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:557:5 + --> $DIR/subdiagnostic-derive.rs:548:5 | LL | #[primary_span] | ^ @@ -321,127 +315,127 @@ LL | #[primary_span] = help: multipart suggestions use one or more `#[suggestion_part]`s rather than one `#[primary_span]` error: derive(Diagnostic): multipart suggestion without any `#[suggestion_part(...)]` fields - --> $DIR/subdiagnostic-derive.rs:554:1 + --> $DIR/subdiagnostic-derive.rs:545:1 | LL | #[multipart_suggestion(no_crate_example)] | ^ error: derive(Diagnostic): `#[suggestion_part(...)]` attribute without `code = "..."` - --> $DIR/subdiagnostic-derive.rs:565:5 + --> $DIR/subdiagnostic-derive.rs:556:5 | LL | #[suggestion_part] | ^ error: derive(Diagnostic): `#[suggestion_part(...)]` attribute without `code = "..."` - --> $DIR/subdiagnostic-derive.rs:568:5 + --> $DIR/subdiagnostic-derive.rs:559:5 | LL | #[suggestion_part()] | ^ error: derive(Diagnostic): `code` is the only valid nested attribute - --> $DIR/subdiagnostic-derive.rs:571:23 + --> $DIR/subdiagnostic-derive.rs:562:23 | LL | #[suggestion_part(foo = "bar")] | ^^^ error: derive(Diagnostic): the `#[suggestion_part(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` - --> $DIR/subdiagnostic-derive.rs:575:5 + --> $DIR/subdiagnostic-derive.rs:566:5 | LL | #[suggestion_part(code = "...")] | ^ error: derive(Diagnostic): the `#[suggestion_part(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` - --> $DIR/subdiagnostic-derive.rs:578:5 + --> $DIR/subdiagnostic-derive.rs:569:5 | LL | #[suggestion_part()] | ^ error: expected `,` - --> $DIR/subdiagnostic-derive.rs:571:27 + --> $DIR/subdiagnostic-derive.rs:562:27 | LL | #[suggestion_part(foo = "bar")] | ^ error: derive(Diagnostic): attribute specified multiple times - --> $DIR/subdiagnostic-derive.rs:586:37 + --> $DIR/subdiagnostic-derive.rs:577:37 | LL | #[suggestion_part(code = "...", code = ",,,")] | ^^^^ | note: previously specified here - --> $DIR/subdiagnostic-derive.rs:586:23 + --> $DIR/subdiagnostic-derive.rs:577:23 | LL | #[suggestion_part(code = "...", code = ",,,")] | ^^^^ error: derive(Diagnostic): `#[applicability]` has no effect if all `#[suggestion]`/`#[multipart_suggestion]` attributes have a static `applicability = "..."` - --> $DIR/subdiagnostic-derive.rs:615:5 + --> $DIR/subdiagnostic-derive.rs:606:5 | LL | #[applicability] | ^ error: derive(Diagnostic): expected exactly one string literal for `code = ...` - --> $DIR/subdiagnostic-derive.rs:663:28 + --> $DIR/subdiagnostic-derive.rs:654:28 | LL | #[suggestion_part(code("foo"))] | ^^^^^ error: unexpected token, expected `)` - --> $DIR/subdiagnostic-derive.rs:663:28 + --> $DIR/subdiagnostic-derive.rs:654:28 | LL | #[suggestion_part(code("foo"))] | ^^^^^ error: derive(Diagnostic): expected exactly one string literal for `code = ...` - --> $DIR/subdiagnostic-derive.rs:673:28 + --> $DIR/subdiagnostic-derive.rs:664:28 | LL | #[suggestion_part(code("foo", "bar"))] | ^^^^^ error: unexpected token, expected `)` - --> $DIR/subdiagnostic-derive.rs:673:28 + --> $DIR/subdiagnostic-derive.rs:664:28 | LL | #[suggestion_part(code("foo", "bar"))] | ^^^^^ error: derive(Diagnostic): expected exactly one string literal for `code = ...` - --> $DIR/subdiagnostic-derive.rs:683:28 + --> $DIR/subdiagnostic-derive.rs:674:28 | LL | #[suggestion_part(code(3))] | ^ error: unexpected token, expected `)` - --> $DIR/subdiagnostic-derive.rs:683:28 + --> $DIR/subdiagnostic-derive.rs:674:28 | LL | #[suggestion_part(code(3))] | ^ error: derive(Diagnostic): expected exactly one string literal for `code = ...` - --> $DIR/subdiagnostic-derive.rs:693:28 + --> $DIR/subdiagnostic-derive.rs:684:28 | LL | #[suggestion_part(code())] | ^ error: expected string literal - --> $DIR/subdiagnostic-derive.rs:702:30 + --> $DIR/subdiagnostic-derive.rs:693:30 | LL | #[suggestion_part(code = 3)] | ^ error: derive(Diagnostic): attribute specified multiple times - --> $DIR/subdiagnostic-derive.rs:744:1 + --> $DIR/subdiagnostic-derive.rs:735:1 | LL | #[suggestion(no_crate_example, code = "", style = "hidden", style = "normal")] | ^ | note: previously specified here - --> $DIR/subdiagnostic-derive.rs:744:1 + --> $DIR/subdiagnostic-derive.rs:735:1 | LL | #[suggestion(no_crate_example, code = "", style = "hidden", style = "normal")] | ^ error: derive(Diagnostic): `#[suggestion_hidden(...)]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:753:1 + --> $DIR/subdiagnostic-derive.rs:744:1 | LL | #[suggestion_hidden(no_crate_example, code = "")] | ^ @@ -449,7 +443,7 @@ LL | #[suggestion_hidden(no_crate_example, code = "")] = help: Use `#[suggestion(..., style = "hidden")]` instead error: derive(Diagnostic): `#[suggestion_hidden(...)]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:761:1 + --> $DIR/subdiagnostic-derive.rs:752:1 | LL | #[suggestion_hidden(no_crate_example, code = "", style = "normal")] | ^ @@ -457,7 +451,7 @@ LL | #[suggestion_hidden(no_crate_example, code = "", style = "normal")] = help: Use `#[suggestion(..., style = "hidden")]` instead error: derive(Diagnostic): invalid suggestion style - --> $DIR/subdiagnostic-derive.rs:769:51 + --> $DIR/subdiagnostic-derive.rs:760:51 | LL | #[suggestion(no_crate_example, code = "", style = "foo")] | ^^^^^ @@ -465,25 +459,25 @@ LL | #[suggestion(no_crate_example, code = "", style = "foo")] = help: valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only` error: expected string literal - --> $DIR/subdiagnostic-derive.rs:777:51 + --> $DIR/subdiagnostic-derive.rs:768:51 | LL | #[suggestion(no_crate_example, code = "", style = 42)] | ^^ error: derive(Diagnostic): a diagnostic slug must be the first argument to the attribute - --> $DIR/subdiagnostic-derive.rs:785:43 + --> $DIR/subdiagnostic-derive.rs:776:43 | LL | #[suggestion(no_crate_example, code = "", style)] | ^^^^^ error: expected `=` - --> $DIR/subdiagnostic-derive.rs:793:48 + --> $DIR/subdiagnostic-derive.rs:784:48 | LL | #[suggestion(no_crate_example, code = "", style("foo"))] | ^ error: derive(Diagnostic): `#[primary_span]` is not a valid attribute - --> $DIR/subdiagnostic-derive.rs:804:5 + --> $DIR/subdiagnostic-derive.rs:795:5 | LL | #[primary_span] | ^ @@ -492,7 +486,7 @@ LL | #[primary_span] = help: to create a suggestion with multiple spans, use `#[multipart_suggestion]` instead error: derive(Diagnostic): suggestion without `#[primary_span]` field - --> $DIR/subdiagnostic-derive.rs:801:1 + --> $DIR/subdiagnostic-derive.rs:792:1 | LL | #[suggestion(no_crate_example, code = "")] | ^ @@ -504,52 +498,52 @@ LL | #[foo] | ^^^ error: cannot find attribute `foo` in this scope - --> $DIR/subdiagnostic-derive.rs:153:3 + --> $DIR/subdiagnostic-derive.rs:144:3 | LL | #[foo] | ^^^ error: cannot find attribute `bar` in this scope - --> $DIR/subdiagnostic-derive.rs:167:7 + --> $DIR/subdiagnostic-derive.rs:158:7 | LL | #[bar] | ^^^ error: cannot find attribute `bar` in this scope - --> $DIR/subdiagnostic-derive.rs:179:7 + --> $DIR/subdiagnostic-derive.rs:170:7 | LL | #[bar = "..."] | ^^^ error: cannot find attribute `bar` in this scope - --> $DIR/subdiagnostic-derive.rs:191:7 + --> $DIR/subdiagnostic-derive.rs:182:7 | LL | #[bar = 4] | ^^^ error: cannot find attribute `bar` in this scope - --> $DIR/subdiagnostic-derive.rs:203:7 + --> $DIR/subdiagnostic-derive.rs:194:7 | LL | #[bar("...")] | ^^^ error: cannot find attribute `bar` in this scope - --> $DIR/subdiagnostic-derive.rs:264:7 + --> $DIR/subdiagnostic-derive.rs:255:7 | LL | #[bar] | ^^^ error: cannot find attribute `bar` in this scope - --> $DIR/subdiagnostic-derive.rs:275:7 + --> $DIR/subdiagnostic-derive.rs:266:7 | LL | #[bar = "..."] | ^^^ error: cannot find attribute `bar` in this scope - --> $DIR/subdiagnostic-derive.rs:286:7 + --> $DIR/subdiagnostic-derive.rs:277:7 | LL | #[bar("...")] | ^^^ -error: aborting due to 83 previous errors +error: aborting due to 82 previous errors diff --git a/tests/ui/argument-suggestions/disjoint-spans-issue-151607.rs b/tests/ui/argument-suggestions/disjoint-spans-issue-151607.rs new file mode 100644 index 000000000000..31390faa488b --- /dev/null +++ b/tests/ui/argument-suggestions/disjoint-spans-issue-151607.rs @@ -0,0 +1,16 @@ +// Regression test for #151607 +// The ICE was "all spans must be disjoint" when emitting diagnostics +// with overlapping suggestion spans. + +struct B; +struct D; +struct F; +fn foo(g: F, y: F, e: &E) { + //~^ ERROR cannot find type `E` in this scope + foo(B, g, D, E, F, G) + //~^ ERROR this function takes 3 arguments but 6 arguments were supplied + //~| ERROR cannot find value `E` in this scope + //~| ERROR cannot find value `G` in this scope +} + +fn main() {} diff --git a/tests/ui/argument-suggestions/disjoint-spans-issue-151607.stderr b/tests/ui/argument-suggestions/disjoint-spans-issue-151607.stderr new file mode 100644 index 000000000000..f1bda1203347 --- /dev/null +++ b/tests/ui/argument-suggestions/disjoint-spans-issue-151607.stderr @@ -0,0 +1,85 @@ +error[E0425]: cannot find type `E` in this scope + --> $DIR/disjoint-spans-issue-151607.rs:8:24 + | +LL | struct B; + | --------- similarly named struct `B` defined here +... +LL | fn foo(g: F, y: F, e: &E) { + | ^ + | +help: a struct with a similar name exists + | +LL - fn foo(g: F, y: F, e: &E) { +LL + fn foo(g: F, y: F, e: &B) { + | +help: you might be missing a type parameter + | +LL | fn foo(g: F, y: F, e: &E) { + | +++ + +error[E0425]: cannot find value `E` in this scope + --> $DIR/disjoint-spans-issue-151607.rs:10:18 + | +LL | foo(B, g, D, E, F, G) + | ^ + | +help: a local variable with a similar name exists + | +LL - foo(B, g, D, E, F, G) +LL + foo(B, g, D, e, F, G) + | +help: consider importing one of these constants + | +LL + use std::f128::consts::E; + | +LL + use std::f16::consts::E; + | +LL + use std::f32::consts::E; + | +LL + use std::f64::consts::E; + | + +error[E0425]: cannot find value `G` in this scope + --> $DIR/disjoint-spans-issue-151607.rs:10:24 + | +LL | foo(B, g, D, E, F, G) + | ^ + | +help: a local variable with a similar name exists + | +LL - foo(B, g, D, E, F, G) +LL + foo(B, g, D, E, F, g) + | +help: you might be missing a const parameter + | +LL | fn foo(g: F, y: F, e: &E) { + | +++++++++++++++++++++ + +error[E0061]: this function takes 3 arguments but 6 arguments were supplied + --> $DIR/disjoint-spans-issue-151607.rs:10:5 + | +LL | foo(B, g, D, E, F, G) + | ^^^ - - - unexpected argument #4 + | | | + | | unexpected argument #3 of type `D` + | unexpected argument #1 of type `B` + | +note: function defined here + --> $DIR/disjoint-spans-issue-151607.rs:8:4 + | +LL | fn foo(g: F, y: F, e: &E) { + | ^^^ ----- +help: consider borrowing here + | +LL | foo(B, g, D, E, F, &G) + | + +help: remove the extra arguments + | +LL - foo(B, g, D, E, F, G) +LL + foo(, g, F, G) + | + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0061, E0425. +For more information about an error, try `rustc --explain E0061`. diff --git a/tests/ui/asm/aarch64v8r.rs b/tests/ui/asm/aarch64v8r.rs index 6b582bb730f0..abc254ad5f8e 100644 --- a/tests/ui/asm/aarch64v8r.rs +++ b/tests/ui/asm/aarch64v8r.rs @@ -1,11 +1,15 @@ // Codegen test of mandatory Armv8-R AArch64 extensions +// The Cortex-R82 CPU is an implementation of the Arm v8-R AArch64 ISA so +// it also implements the ISA-level mandatory extensions. We check that with a revision //@ add-minicore -//@ revisions: hf sf +//@ revisions: hf sf r82 //@ [hf] compile-flags: --target aarch64v8r-unknown-none //@ [hf] needs-llvm-components: aarch64 //@ [sf] compile-flags: --target aarch64v8r-unknown-none-softfloat //@ [sf] needs-llvm-components: aarch64 +//@ [r82] compile-flags: --target aarch64v8r-unknown-none -C target-cpu=cortex-r82 +//@ [r82] needs-llvm-components: aarch64 //@ build-pass //@ ignore-backends: gcc diff --git a/tests/ui/asm/cortex-r82.rs b/tests/ui/asm/cortex-r82.rs new file mode 100644 index 000000000000..74313e5cb48c --- /dev/null +++ b/tests/ui/asm/cortex-r82.rs @@ -0,0 +1,180 @@ +// Codegen test of mandatory Cortex-R82 extensions + +//@ add-minicore +//@ compile-flags: --target aarch64v8r-unknown-none -C target-cpu=cortex-r82 +//@ needs-llvm-components: aarch64 +//@ build-pass +//@ ignore-backends: gcc + +#![deny(dead_code)] +#![feature(no_core)] +#![no_core] +#![no_main] +#![crate_type = "rlib"] + +extern crate minicore; +use minicore::*; + +/* # Mandatory extensions + * + * A `//` comment indicates that the extension has no associated assembly instruction and cannot + * be codegen tested + * A `/* */` comment indicates that the extension is being tested in the ISA level codegen test + * (`tests/ui/asm/aarch64v8r.rs`) + * + * Note that as we use the hard-float `aarch64v8r-unknown-none` target as the base, the neon + * extension is present (`NEON_FPm=1`). This affects which R82-specific extensions are enabled + * (see "when `NEON_FPm == 1`" note in Cortex-R82 Processor Technical Reference Manual) + * + * ## References: + * + * - Arm Cortex-R82 Processor Technical Reference Manual Revision r3p1 (102670_0301_06_en Issue 6) + * section 3.2.1 has the list of mandatory extensions + * - Arm Architecture Reference Manual for A-profile architecture (ARM DDI 0487) -- has the + * mapping from features to instructions + * - Feature names in A-profile architecture (109697_0100_02_en Version 1.0) -- overview of what + * each extension mean + * */ +pub fn mandatory_extensions() { + // FEAT_GICv3 + // FEAT_GICv3p1 + // FEAT_GICv3_TDIR + feat_pmuv3(); + // FEAT_ETMv4 + // FEAT_ETMv4p1 + // FEAT_ETMv4p2 + // FEAT_ETMv4p3 + // FEAT_ETMv4p4 + // FEAT_ETMv4p5 + /* FEAT_RAS */ + // FEAT_PCSRv8 + feat_ssbs(); + feat_ssbs2(); + // FEAT_CSV2 + // FEAT_CSV2_1p1 + // FEAT_CSV3 + feat_sb(); + feat_specres(); + feat_dgh(); + // FEAT_nTLBPA + /* FEAT_CRC32 */ + /* FEAT_LSE */ + feat_rdm(); // mandatory given that NEON_FPm=1 + /* FEAT_HPDS */ + /* FEAT_PAN */ + // FEAT_HAFDBS + // FEAT_PMUv3p1 + // FEAT_TTCNP + // FEAT_XNX + /* FEAT_UAO */ + feat_pan2(); + feat_dpb(); + /* FEAT_Debugv8p2 */ + /* FEAT_ASMv8p2 */ + // FEAT_IESB + feat_fp16(); // mandatory given that NEON_FPm=1 + // FEAT_PCSRv8p2 + feat_dotprod(); // mandatory given that NEON_FPm=1 + feat_fhm(); // mandatory given that NEON_FPm=1 + feat_dpb2(); + /* FEAT_PAuth */ + // FEAT_PACQARMA3 + // FEAT_PAuth2 + // FEAT_FPAC + // FEAT_FPACCOMBINE + // FEAT_CONSTPACFIELD + feat_jscvt(); // mandatory given that NEON_FPm=1 + /* FEAT_LRCPC */ + feat_fcma(); // mandatory given that NEON_FPm=1 + // FEAT_DoPD + // FEAT_SEL2 + /* FEAT_S2FWB */ + /* FEAT_DIT */ + /* FEAT_IDST */ + /* FEAT_FlagM */ + /* FEAT_LSE2 */ + /* FEAT_LRCPC2 */ + /* FEAT_TLBIOS */ + /* FEAT_TLBIRANGE */ + /* FEAT_TTL */ + // FEAT_BBM + // FEAT_CNTSC + feat_rasv1p1(); + // FEAT_Debugv8p4 + feat_pmuv3p4(); + feat_trf(); + // FEAT_TTST + // FEAT_E0PD +} + +fn feat_pmuv3() { + unsafe { asm!("mrs x0, PMCCFILTR_EL0") } +} + +fn feat_ssbs() { + unsafe { asm!("msr SSBS, 1") } +} + +fn feat_ssbs2() { + unsafe { asm!("mrs x0, SSBS") } +} + +fn feat_sb() { + unsafe { asm!("sb") } +} + +fn feat_specres() { + unsafe { asm!("cfp rctx, x0") } +} + +fn feat_dgh() { + unsafe { asm!("dgh") } +} + +fn feat_rdm() { + unsafe { asm!("sqrdmlah v0.4h, v1.4h, v2.4h") } +} + +fn feat_pan2() { + unsafe { asm!("AT S1E1RP, x0") } +} + +fn feat_dpb() { + unsafe { asm!("DC CVAP, x0") } +} + +fn feat_fp16() { + unsafe { asm!("fmulx h0, h1, h2") } +} + +fn feat_dotprod() { + unsafe { asm!("sdot V0.4S, V1.16B, V2.16B") } +} + +fn feat_fhm() { + unsafe { asm!("fmlal v0.2s, v1.2h, v2.2h") } +} + +fn feat_dpb2() { + unsafe { asm!("DC CVADP, x0") } +} + +fn feat_jscvt() { + unsafe { asm!("fjcvtzs w0, d1") } +} + +fn feat_fcma() { + unsafe { asm!("fcadd v0.4h, v1.4h, v2.4h, #90") } +} + +fn feat_rasv1p1() { + unsafe { asm!("mrs x0, ERXMISC2_EL1") } +} + +fn feat_pmuv3p4() { + unsafe { asm!("mrs x0, PMMIR_EL1") } +} + +fn feat_trf() { + unsafe { asm!("tsb csync") } +} diff --git a/tests/ui/issues/issue-3026.rs b/tests/ui/borrowck/borrow-box-in-map-3026.rs similarity index 73% rename from tests/ui/issues/issue-3026.rs rename to tests/ui/borrowck/borrow-box-in-map-3026.rs index 05dc46c3cc09..dd63075eecba 100644 --- a/tests/ui/issues/issue-3026.rs +++ b/tests/ui/borrowck/borrow-box-in-map-3026.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/3026 + //@ run-pass use std::collections::HashMap; diff --git a/tests/ui/closures/local-enums-in-closure-2074.rs b/tests/ui/closures/local-enums-in-closure-2074.rs new file mode 100644 index 000000000000..29cd6f13e3bb --- /dev/null +++ b/tests/ui/closures/local-enums-in-closure-2074.rs @@ -0,0 +1,22 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/2074 + +//@ run-pass + +#![allow(non_camel_case_types)] + +pub fn main() { + let one = || { + enum r { + a, + } + r::a as usize + }; + let two = || { + enum r { + a, + } + r::a as usize + }; + one(); + two(); +} diff --git a/tests/ui/compiletest-self-test/auxiliary/no_prefer_dynamic_lib.rs b/tests/ui/compiletest-self-test/auxiliary/no_prefer_dynamic_lib.rs new file mode 100644 index 000000000000..6688dadbab24 --- /dev/null +++ b/tests/ui/compiletest-self-test/auxiliary/no_prefer_dynamic_lib.rs @@ -0,0 +1,10 @@ +//@ no-prefer-dynamic + +//! Since this is `no-prefer-dynamic` we expect compiletest to _not_ look for +//! this create as `libno_prefer_dynamic_lib.so`. + +#![crate_type = "rlib"] + +pub fn return_42() -> i32 { + 42 +} diff --git a/tests/ui/compiletest-self-test/no-prefer-dynamic-means-no-so.rs b/tests/ui/compiletest-self-test/no-prefer-dynamic-means-no-so.rs new file mode 100644 index 000000000000..b7e8fae506c3 --- /dev/null +++ b/tests/ui/compiletest-self-test/no-prefer-dynamic-means-no-so.rs @@ -0,0 +1,10 @@ +//! Since we and our `aux-crate` is `no-prefer-dynamic`, we expect compiletest +//! to _not_ look for `libno_prefer_dynamic_lib.so`. + +//@ check-pass +//@ no-prefer-dynamic +//@ aux-crate: no_prefer_dynamic_lib=no_prefer_dynamic_lib.rs + +fn main() { + no_prefer_dynamic_lib::return_42(); +} diff --git a/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-1.rs b/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-1.rs new file mode 100644 index 000000000000..17910d52d3d7 --- /dev/null +++ b/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-1.rs @@ -0,0 +1,22 @@ +// regression test for issue #137582, where constant evaluating an unsized AnonConst would ICE + +#![feature(adt_const_params)] + +mod lib { + pub type Matrix = [&'static u32]; + + const EMPTY_MATRIX: Matrix = [[0; 4]; 4]; + //~^ ERROR the size for values of type `[&'static u32]` cannot be known at compilation time + //~| ERROR mismatched types + //~| ERROR mismatched types + + pub struct Walk { + //~^ ERROR use of unstable library feature `unsized_const_params` + _p: (), + } + + impl Walk {} + //~^ ERROR the size for values of type `[&'static u32]` cannot be known at compilation time +} + +fn main() {} diff --git a/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-1.stderr b/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-1.stderr new file mode 100644 index 000000000000..daea55efbbc7 --- /dev/null +++ b/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-1.stderr @@ -0,0 +1,44 @@ +error[E0277]: the size for values of type `[&'static u32]` cannot be known at compilation time + --> $DIR/unsized-anon-const-err-1.rs:8:25 + | +LL | const EMPTY_MATRIX: Matrix = [[0; 4]; 4]; + | ^^^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[&'static u32]` + = note: statics and constants must have a statically known size + +error[E0658]: use of unstable library feature `unsized_const_params` + --> $DIR/unsized-anon-const-err-1.rs:13:43 + | +LL | pub struct Walk { + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(unsized_const_params)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = note: required for `[&'static u32]` to implement `ConstParamTy_` + +error[E0277]: the size for values of type `[&'static u32]` cannot be known at compilation time + --> $DIR/unsized-anon-const-err-1.rs:18:46 + | +LL | impl Walk {} + | ^^^^^^^^^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[&'static u32]` + = note: statics and constants must have a statically known size + +error[E0308]: mismatched types + --> $DIR/unsized-anon-const-err-1.rs:8:35 + | +LL | const EMPTY_MATRIX: Matrix = [[0; 4]; 4]; + | ^^^^^^ expected `&u32`, found `[{integer}; 4]` + +error[E0308]: mismatched types + --> $DIR/unsized-anon-const-err-1.rs:8:34 + | +LL | const EMPTY_MATRIX: Matrix = [[0; 4]; 4]; + | ^^^^^^^^^^^ expected `[&u32]`, found `[&u32; 4]` + +error: aborting due to 5 previous errors + +Some errors have detailed explanations: E0277, E0308, E0658. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-2.rs b/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-2.rs new file mode 100644 index 000000000000..81b1ec6a7719 --- /dev/null +++ b/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-2.rs @@ -0,0 +1,21 @@ +// regression test for issue #151591, where constant evaluating an unsized AnonConst would ICE + +#![feature(adt_const_params)] +#![feature(unsized_const_params)] +//~^ WARN the feature `unsized_const_params` is incomplete and may not be safe to use and/or cause compiler crashes + +#[derive(Clone)] +struct S; + +const A: [u8]; +//~^ ERROR free constant item without body +//~| ERROR the size for values of type `[u8]` cannot be known at compilation time + +impl Copy for S {} +//~^ ERROR the size for values of type `[u8]` cannot be known at compilation time +//~| ERROR the const parameter `N` is not constrained by the impl trait, self type, or predicates +impl Copy for S {} +//~^ ERROR the size for values of type `[u8]` cannot be known at compilation time +//~| ERROR the const parameter `M` is not constrained by the impl trait, self type, or predicates + +fn main() {} diff --git a/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-2.stderr b/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-2.stderr new file mode 100644 index 000000000000..d538bb0af09a --- /dev/null +++ b/tests/ui/const-generics/adt_const_params/unsized-anon-const-err-2.stderr @@ -0,0 +1,66 @@ +error: free constant item without body + --> $DIR/unsized-anon-const-err-2.rs:10:1 + | +LL | const A: [u8]; + | ^^^^^^^^^^^^^- + | | + | help: provide a definition for the constant: `= ;` + +warning: the feature `unsized_const_params` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/unsized-anon-const-err-2.rs:4:12 + | +LL | #![feature(unsized_const_params)] + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #95174 for more information + = note: `#[warn(incomplete_features)]` on by default + +error[E0277]: the size for values of type `[u8]` cannot be known at compilation time + --> $DIR/unsized-anon-const-err-2.rs:10:10 + | +LL | const A: [u8]; + | ^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[u8]` + = note: statics and constants must have a statically known size + +error[E0277]: the size for values of type `[u8]` cannot be known at compilation time + --> $DIR/unsized-anon-const-err-2.rs:14:31 + | +LL | impl Copy for S {} + | ^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[u8]` + = note: statics and constants must have a statically known size + +error[E0277]: the size for values of type `[u8]` cannot be known at compilation time + --> $DIR/unsized-anon-const-err-2.rs:17:33 + | +LL | impl Copy for S {} + | ^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[u8]` + = note: statics and constants must have a statically known size + +error[E0207]: the const parameter `N` is not constrained by the impl trait, self type, or predicates + --> $DIR/unsized-anon-const-err-2.rs:14:6 + | +LL | impl Copy for S {} + | ^^^^^^^^^^^^ unconstrained const parameter + | + = note: expressions using a const parameter must map each value to a distinct output value + = note: proving the result of expressions other than the parameter are unique is not supported + +error[E0207]: the const parameter `M` is not constrained by the impl trait, self type, or predicates + --> $DIR/unsized-anon-const-err-2.rs:17:6 + | +LL | impl Copy for S {} + | ^^^^^^^^^^^^^^ unconstrained const parameter + | + = note: expressions using a const parameter must map each value to a distinct output value + = note: proving the result of expressions other than the parameter are unique is not supported + +error: aborting due to 6 previous errors; 1 warning emitted + +Some errors have detailed explanations: E0207, E0277. +For more information about an error, try `rustc --explain E0207`. diff --git a/tests/ui/const-generics/adt_const_params/unsized-anon-const-func-err.rs b/tests/ui/const-generics/adt_const_params/unsized-anon-const-func-err.rs new file mode 100644 index 000000000000..a299cc29fcc5 --- /dev/null +++ b/tests/ui/const-generics/adt_const_params/unsized-anon-const-func-err.rs @@ -0,0 +1,16 @@ +// manually reduced reproduction of issue #137582, where constant evaluating an unsized AnonConst +// would ICE + +#![feature(adt_const_params)] + +fn func() {} +//~^ ERROR use of unstable library feature `unsized_const_params` + +const VALUE: [u32] = [0; 4]; +//~^ ERROR mismatched types +//~| ERROR the size for values of type `[u32]` cannot be known at compilation time + +fn main() { + func::(); + //~^ ERROR the size for values of type `[u32]` cannot be known at compilation time +} diff --git a/tests/ui/const-generics/adt_const_params/unsized-anon-const-func-err.stderr b/tests/ui/const-generics/adt_const_params/unsized-anon-const-func-err.stderr new file mode 100644 index 000000000000..14a41e87e4aa --- /dev/null +++ b/tests/ui/const-generics/adt_const_params/unsized-anon-const-func-err.stderr @@ -0,0 +1,38 @@ +error[E0658]: use of unstable library feature `unsized_const_params` + --> $DIR/unsized-anon-const-func-err.rs:6:9 + | +LL | fn func() {} + | ^^^^^^^^^^^^^^ + | + = help: add `#![feature(unsized_const_params)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = note: required for `[u32]` to implement `ConstParamTy_` + +error[E0277]: the size for values of type `[u32]` cannot be known at compilation time + --> $DIR/unsized-anon-const-func-err.rs:9:14 + | +LL | const VALUE: [u32] = [0; 4]; + | ^^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[u32]` + = note: statics and constants must have a statically known size + +error[E0308]: mismatched types + --> $DIR/unsized-anon-const-func-err.rs:9:22 + | +LL | const VALUE: [u32] = [0; 4]; + | ^^^^^^ expected `[u32]`, found `[u32; 4]` + +error[E0277]: the size for values of type `[u32]` cannot be known at compilation time + --> $DIR/unsized-anon-const-func-err.rs:14:12 + | +LL | func::(); + | ^^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[u32]` + = note: statics and constants must have a statically known size + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0277, E0308, E0658. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui/const-generics/adt_const_params/unsized-anon-const-struct-err.rs b/tests/ui/const-generics/adt_const_params/unsized-anon-const-struct-err.rs new file mode 100644 index 000000000000..35407d02f6ef --- /dev/null +++ b/tests/ui/const-generics/adt_const_params/unsized-anon-const-struct-err.rs @@ -0,0 +1,16 @@ +// manually reduced reproduction of issue #137582, where constant evaluating an unsized AnonConst +// would ICE + +#![feature(adt_const_params)] + +const VALUE: [u32] = [0; 4]; +//~^ ERROR the size for values of type `[u32]` cannot be known at compilation time +//~| ERROR mismatched types + +struct SomeStruct {} +//~^ ERROR use of unstable library feature `unsized_const_params` + +impl SomeStruct {} +//~^ ERROR the size for values of type `[u32]` cannot be known at compilation time + +fn main() {} diff --git a/tests/ui/const-generics/adt_const_params/unsized-anon-const-struct-err.stderr b/tests/ui/const-generics/adt_const_params/unsized-anon-const-struct-err.stderr new file mode 100644 index 000000000000..d9a13976d68a --- /dev/null +++ b/tests/ui/const-generics/adt_const_params/unsized-anon-const-struct-err.stderr @@ -0,0 +1,38 @@ +error[E0277]: the size for values of type `[u32]` cannot be known at compilation time + --> $DIR/unsized-anon-const-struct-err.rs:6:14 + | +LL | const VALUE: [u32] = [0; 4]; + | ^^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[u32]` + = note: statics and constants must have a statically known size + +error[E0658]: use of unstable library feature `unsized_const_params` + --> $DIR/unsized-anon-const-struct-err.rs:10:19 + | +LL | struct SomeStruct {} + | ^^^^^^^^^^^^^^ + | + = help: add `#![feature(unsized_const_params)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = note: required for `[u32]` to implement `ConstParamTy_` + +error[E0277]: the size for values of type `[u32]` cannot be known at compilation time + --> $DIR/unsized-anon-const-struct-err.rs:13:17 + | +LL | impl SomeStruct {} + | ^^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[u32]` + = note: statics and constants must have a statically known size + +error[E0308]: mismatched types + --> $DIR/unsized-anon-const-struct-err.rs:6:22 + | +LL | const VALUE: [u32] = [0; 4]; + | ^^^^^^ expected `[u32]`, found `[u32; 4]` + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0277, E0308, E0658. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui/const-generics/mgca/type-const-used-in-trait.rs b/tests/ui/const-generics/mgca/type-const-used-in-trait.rs new file mode 100644 index 000000000000..c98c14775a0f --- /dev/null +++ b/tests/ui/const-generics/mgca/type-const-used-in-trait.rs @@ -0,0 +1,13 @@ +//@ check-pass + +#![feature(min_generic_const_args)] +#![expect(incomplete_features)] + +#[type_const] +const N: usize = 2; + +trait CollectArray { + fn inner_array(&mut self) -> [A; N]; +} + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.current.stderr b/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.current.stderr index 43205bd395fc..075e4bf0384d 100644 --- a/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.current.stderr +++ b/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.current.stderr @@ -1,8 +1,8 @@ warning: `#[diagnostic::do_not_recommend]` does not expect any arguments --> $DIR/does_not_acccept_args.rs:12:1 | -LL | #[diagnostic::do_not_recommend(not_accepted)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[diagnostic::do_not_recommend(if, crate, do yeet, false, dyn, abstract, gen, not_accepted)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default diff --git a/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.next.stderr b/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.next.stderr index 43205bd395fc..075e4bf0384d 100644 --- a/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.next.stderr +++ b/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.next.stderr @@ -1,8 +1,8 @@ warning: `#[diagnostic::do_not_recommend]` does not expect any arguments --> $DIR/does_not_acccept_args.rs:12:1 | -LL | #[diagnostic::do_not_recommend(not_accepted)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[diagnostic::do_not_recommend(if, crate, do yeet, false, dyn, abstract, gen, not_accepted)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default diff --git a/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.rs b/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.rs index 918bf5a0113e..943b5a37f938 100644 --- a/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.rs +++ b/tests/ui/diagnostic_namespace/do_not_recommend/does_not_acccept_args.rs @@ -9,7 +9,7 @@ trait Bar {} trait Baz {} trait Boo {} -#[diagnostic::do_not_recommend(not_accepted)] +#[diagnostic::do_not_recommend(if, crate, do yeet, false, dyn, abstract, gen, not_accepted)] //~^ WARNING `#[diagnostic::do_not_recommend]` does not expect any arguments impl Foo for T where T: Send {} diff --git a/tests/ui/explain/ensure-color-always-works.rs b/tests/ui/explain/ensure-color-always-works.rs new file mode 100644 index 000000000000..81ee716e78b4 --- /dev/null +++ b/tests/ui/explain/ensure-color-always-works.rs @@ -0,0 +1,2 @@ +//@ compile-flags: --explain E0591 --color always --error-format=human +//@ check-pass diff --git a/tests/ui/explain/ensure-color-always-works.stdout b/tests/ui/explain/ensure-color-always-works.stdout new file mode 100644 index 000000000000..7e5358bcfb7e --- /dev/null +++ b/tests/ui/explain/ensure-color-always-works.stdout @@ -0,0 +1,50 @@ +Per ]8;;https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md\RFC 401]8;;\, if you have a function declaration foo: + +struct S; + +// For the purposes of this explanation, all of these +// different kinds of `fn` declarations are equivalent: + +fn foo(x: S) { /* ... */ } +extern "C" { + fn foo(x: S); +} +impl S { + fn foo(self) { /* ... */ } +} + +the type of foo is not fn(S), as one might expect. Rather, it is a unique, zero-sized marker type written here as typeof(foo). However, typeof(foo) + can be coerced to a function pointer fn(S), so you rarely notice this: + +let x: fn(S) = foo; // OK, coerces + +The reason that this matter is that the type fn(S) is not specific to any particular function: it's a function pointer. So calling x() +results in a virtual call, whereas foo() is statically dispatched, because the type of foo tells us precisely what function is being called. + +As noted above, coercions mean that most code doesn't have to be concerned with this distinction. However, you can tell the difference when +using transmute to convert a fn item into a fn pointer. + +This is sometimes done as part of an FFI: + +extern "C" fn foo(userdata: Box<i32>) { + /* ... */ +} + +unsafe { + let f: extern "C" fn(*mut i32) = transmute(foo); + callback(f); +} + +Here, transmute is being used to convert the types of the fn arguments. This pattern is incorrect because the type of foo is a function item +(typeof(foo)), which is zero-sized, and the target type (fn()) is a function pointer, which is not zero-sized. This pattern should be +rewritten. There are a few possible ways to do this: + +* change the original fn declaration to match the expected signature, and do the cast in the fn body (the preferred option) +* cast the fn item of a fn pointer before calling transmute, as shown here: + +let f: extern "C" fn(*mut i32) = transmute(foo as extern "C" fn(_)); +let f: extern "C" fn(*mut i32) = transmute(foo as usize); // works too + +The same applies to transmutes to *mut fn(), which were observed in practice. Note though that use of this type is generally incorrect. The +intention is typically to describe a function pointer, but just fn() alone suffices for that. *mut fn() is a pointer to a fn pointer. (Since these +values are typically just passed to C code, however, this rarely makes a difference in practice.) diff --git a/tests/ui/imports/ambiguous-import-visibility-macro.rs b/tests/ui/imports/ambiguous-import-visibility-macro.rs new file mode 100644 index 000000000000..e1861cc5d4e0 --- /dev/null +++ b/tests/ui/imports/ambiguous-import-visibility-macro.rs @@ -0,0 +1,19 @@ +//@ check-pass +//@ edition:2018 +//@ proc-macro: same-res-ambigious-extern-macro.rs + +macro_rules! globbing{ + () => { + pub use same_res_ambigious_extern_macro::*; + } +} + +#[macro_use] // this imports the `RustEmbed` macro with `pub(crate)` visibility +extern crate same_res_ambigious_extern_macro; +globbing! {} // this imports the same `RustEmbed` macro with `pub` visibility + +pub trait RustEmbed {} + +pub use RustEmbed as Embed; //~ WARN ambiguous import visibility + //~| WARN this was previously accepted +fn main() {} diff --git a/tests/ui/imports/ambiguous-import-visibility-macro.stderr b/tests/ui/imports/ambiguous-import-visibility-macro.stderr new file mode 100644 index 000000000000..ed6eb6f893af --- /dev/null +++ b/tests/ui/imports/ambiguous-import-visibility-macro.stderr @@ -0,0 +1,29 @@ +warning: ambiguous import visibility: pub(crate) or pub + --> $DIR/ambiguous-import-visibility-macro.rs:17:9 + | +LL | pub use RustEmbed as Embed; + | ^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #149145 + = note: ambiguous because of a conflict between a name from a glob import and an outer scope during import or macro resolution +note: `RustEmbed` could refer to the derive macro imported here + --> $DIR/ambiguous-import-visibility-macro.rs:7:17 + | +LL | pub use same_res_ambigious_extern_macro::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | globbing! {} // this imports the same `RustEmbed` macro with `pub` visibility + | ------------ in this macro invocation + = help: consider adding an explicit import of `RustEmbed` to disambiguate + = help: or use `crate::RustEmbed` to refer to this derive macro unambiguously +note: `RustEmbed` could also refer to the derive macro imported here + --> $DIR/ambiguous-import-visibility-macro.rs:11:1 + | +LL | #[macro_use] // this imports the `RustEmbed` macro with `pub(crate)` visibility + | ^^^^^^^^^^^^ + = note: `#[warn(ambiguous_import_visibilities)]` (part of `#[warn(future_incompatible)]`) on by default + = note: this warning originates in the macro `globbing` (in Nightly builds, run with -Z macro-backtrace for more info) + +warning: 1 warning emitted + diff --git a/tests/ui/imports/ambiguous-import-visibility-module.rs b/tests/ui/imports/ambiguous-import-visibility-module.rs new file mode 100644 index 000000000000..35c6da8b21a4 --- /dev/null +++ b/tests/ui/imports/ambiguous-import-visibility-module.rs @@ -0,0 +1,24 @@ +//@ check-pass +//@ edition:2018.. + +mod reexport { + mod m { + pub struct S {} + } + + macro_rules! mac { + () => { + use m::S; + }; + } + + pub use m::*; + mac!(); + + pub use S as Z; //~ WARN ambiguous import visibility + //~| WARN this was previously accepted +} + +fn main() { + reexport::Z {}; +} diff --git a/tests/ui/imports/ambiguous-import-visibility-module.stderr b/tests/ui/imports/ambiguous-import-visibility-module.stderr new file mode 100644 index 000000000000..a97070c20a62 --- /dev/null +++ b/tests/ui/imports/ambiguous-import-visibility-module.stderr @@ -0,0 +1,29 @@ +warning: ambiguous import visibility: pub or pub(in crate::reexport) + --> $DIR/ambiguous-import-visibility-module.rs:18:13 + | +LL | pub use S as Z; + | ^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #149145 + = note: ambiguous because of a conflict between a macro-expanded name and a less macro-expanded name from outer scope during import or macro resolution +note: `S` could refer to the struct imported here + --> $DIR/ambiguous-import-visibility-module.rs:11:17 + | +LL | use m::S; + | ^^^^ +... +LL | mac!(); + | ------ in this macro invocation + = help: use `self::S` to refer to this struct unambiguously +note: `S` could also refer to the struct imported here + --> $DIR/ambiguous-import-visibility-module.rs:15:13 + | +LL | pub use m::*; + | ^^^^ + = help: use `self::S` to refer to this struct unambiguously + = note: `#[warn(ambiguous_import_visibilities)]` (part of `#[warn(future_incompatible)]`) on by default + = note: this warning originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info) + +warning: 1 warning emitted + diff --git a/tests/ui/imports/ambiguous-import-visibility.rs b/tests/ui/imports/ambiguous-import-visibility.rs new file mode 100644 index 000000000000..4cb8b763fbc9 --- /dev/null +++ b/tests/ui/imports/ambiguous-import-visibility.rs @@ -0,0 +1,14 @@ +//@ check-pass +//@ edition:2018 +//@ proc-macro: same-res-ambigious-extern-macro.rs + +#[macro_use] // this imports the `RustEmbed` macro with `pub(crate)` visibility +extern crate same_res_ambigious_extern_macro; +// this imports the same `RustEmbed` macro with `pub` visibility +pub use same_res_ambigious_extern_macro::*; + +pub trait RustEmbed {} + +pub use RustEmbed as Embed; //~ WARN ambiguous import visibility + //~| WARN this was previously accepted +fn main() {} diff --git a/tests/ui/imports/ambiguous-import-visibility.stderr b/tests/ui/imports/ambiguous-import-visibility.stderr new file mode 100644 index 000000000000..30cddca4697d --- /dev/null +++ b/tests/ui/imports/ambiguous-import-visibility.stderr @@ -0,0 +1,25 @@ +warning: ambiguous import visibility: pub(crate) or pub + --> $DIR/ambiguous-import-visibility.rs:12:9 + | +LL | pub use RustEmbed as Embed; + | ^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #149145 + = note: ambiguous because of a conflict between a name from a glob import and an outer scope during import or macro resolution +note: `RustEmbed` could refer to the derive macro imported here + --> $DIR/ambiguous-import-visibility.rs:8:9 + | +LL | pub use same_res_ambigious_extern_macro::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider adding an explicit import of `RustEmbed` to disambiguate + = help: or use `crate::RustEmbed` to refer to this derive macro unambiguously +note: `RustEmbed` could also refer to the derive macro imported here + --> $DIR/ambiguous-import-visibility.rs:5:1 + | +LL | #[macro_use] // this imports the `RustEmbed` macro with `pub(crate)` visibility + | ^^^^^^^^^^^^ + = note: `#[warn(ambiguous_import_visibilities)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/issues/issue-2074.rs b/tests/ui/issues/issue-2074.rs deleted file mode 100644 index b6e3fb1fa23a..000000000000 --- a/tests/ui/issues/issue-2074.rs +++ /dev/null @@ -1,15 +0,0 @@ -//@ run-pass - -#![allow(non_camel_case_types)] - -pub fn main() { - let one = || { - enum r { a } - r::a as usize - }; - let two = || { - enum r { a } - r::a as usize - }; - one(); two(); -} diff --git a/tests/ui/issues/issue-2445-b.rs b/tests/ui/issues/issue-2445-b.rs deleted file mode 100644 index 3a54c62a771b..000000000000 --- a/tests/ui/issues/issue-2445-b.rs +++ /dev/null @@ -1,30 +0,0 @@ -//@ run-pass -#![allow(dead_code)] -#![allow(non_camel_case_types)] - - -struct c1 { - x: T, -} - -impl c1 { - pub fn f1(&self, _x: isize) { - } -} - -fn c1(x: T) -> c1 { - c1 { - x: x - } -} - -impl c1 { - pub fn f2(&self, _x: isize) { - } -} - - -pub fn main() { - c1::(3).f1(4); - c1::(3).f2(4); -} diff --git a/tests/ui/issues/issue-2463.rs b/tests/ui/issues/issue-2463.rs deleted file mode 100644 index 8fff9763bd9e..000000000000 --- a/tests/ui/issues/issue-2463.rs +++ /dev/null @@ -1,24 +0,0 @@ -//@ run-pass -#![allow(dead_code)] - -struct Pair { f: isize, g: isize } - -pub fn main() { - - let x = Pair { - f: 0, - g: 0, - }; - - let _y = Pair { - f: 1, - g: 1, - .. x - }; - - let _z = Pair { - f: 1, - .. x - }; - -} diff --git a/tests/ui/issues/issue-2904.rs b/tests/ui/issues/issue-2904.rs deleted file mode 100644 index 1ae3a8ad656e..000000000000 --- a/tests/ui/issues/issue-2904.rs +++ /dev/null @@ -1,79 +0,0 @@ -//@ build-pass -#![allow(unused_must_use)] -#![allow(dead_code)] -#![allow(unused_mut)] -#![allow(non_camel_case_types)] - -// Map representation - -use std::fmt; -use std::io::prelude::*; -use square::{bot, wall, rock, lambda, closed_lift, open_lift, earth, empty}; - -enum square { - bot, - wall, - rock, - lambda, - closed_lift, - open_lift, - earth, - empty -} - -impl fmt::Debug for square { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", match *self { - bot => { "R".to_string() } - wall => { "#".to_string() } - rock => { "*".to_string() } - lambda => { "\\".to_string() } - closed_lift => { "L".to_string() } - open_lift => { "O".to_string() } - earth => { ".".to_string() } - empty => { " ".to_string() } - }) - } -} - -fn square_from_char(c: char) -> square { - match c { - 'R' => { bot } - '#' => { wall } - '*' => { rock } - '\\' => { lambda } - 'L' => { closed_lift } - 'O' => { open_lift } - '.' => { earth } - ' ' => { empty } - _ => { - println!("invalid square: {}", c); - panic!() - } - } -} - -fn read_board_grid(mut input: rdr) - -> Vec> { - let mut input: &mut dyn Read = &mut input; - let mut grid = Vec::new(); - let mut line = [0; 10]; - input.read(&mut line); - let mut row = Vec::new(); - for c in &line { - row.push(square_from_char(*c as char)) - } - grid.push(row); - let width = grid[0].len(); - for row in &grid { assert_eq!(row.len(), width) } - grid -} - -mod test { - #[test] - pub fn trivial_to_string() { - assert_eq!(lambda.to_string(), "\\") - } -} - -pub fn main() {} diff --git a/tests/ui/issues/issue-3121.rs b/tests/ui/issues/issue-3121.rs deleted file mode 100644 index aa150f11cf40..000000000000 --- a/tests/ui/issues/issue-3121.rs +++ /dev/null @@ -1,24 +0,0 @@ -//@ run-pass -#![allow(dead_code)] -#![allow(non_camel_case_types)] - -#[derive(Copy, Clone)] -enum side { mayo, catsup, vinegar } -#[derive(Copy, Clone)] -enum order { hamburger, fries(side), shake } -#[derive(Copy, Clone)] -enum meal { to_go(order), for_here(order) } - -fn foo(m: Box, cond: bool) { - match *m { - meal::to_go(_) => { } - meal::for_here(_) if cond => {} - meal::for_here(order::hamburger) => {} - meal::for_here(order::fries(_s)) => {} - meal::for_here(order::shake) => {} - } -} - -pub fn main() { - foo(Box::new(meal::for_here(order::hamburger)), true) -} diff --git a/tests/ui/lint/unused/match_with_guard.rs b/tests/ui/lint/unused/match_with_guard.rs new file mode 100644 index 000000000000..61e4321f6735 --- /dev/null +++ b/tests/ui/lint/unused/match_with_guard.rs @@ -0,0 +1,10 @@ +//! The mere presence of a match guard should not deem bound variables "used". +//! Regression test for https://github.com/rust-lang/rust/issues/151983 +//@ check-pass +#![warn(unused)] +fn main() { + match Some(42) { + Some(unused) if true => (), //~WARN unused variable: `unused` + _ => (), + } +} diff --git a/tests/ui/lint/unused/match_with_guard.stderr b/tests/ui/lint/unused/match_with_guard.stderr new file mode 100644 index 000000000000..6a509e568b68 --- /dev/null +++ b/tests/ui/lint/unused/match_with_guard.stderr @@ -0,0 +1,15 @@ +warning: unused variable: `unused` + --> $DIR/match_with_guard.rs:7:14 + | +LL | Some(unused) if true => (), + | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused` + | +note: the lint level is defined here + --> $DIR/match_with_guard.rs:4:9 + | +LL | #![warn(unused)] + | ^^^^^^ + = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` + +warning: 1 warning emitted + diff --git a/tests/ui/macros/tokenstream-ice-issue-149954.rs b/tests/ui/macros/tokenstream-ice-issue-149954.rs new file mode 100644 index 000000000000..958a86cbde8b --- /dev/null +++ b/tests/ui/macros/tokenstream-ice-issue-149954.rs @@ -0,0 +1,22 @@ +// Regression test for ICE https://github.com/rust-lang/rust/issues/149954 +//@ edition: 2024 + +enum A { + A + const A: A = { //~ ERROR expected one of `(`, `,`, `=`, `{`, or `}`, found keyword `const` + #[derive(Debug)] + struct A + where + A: A<{ struct A> ; enum A } + //~^ ERROR malformed `cfg` attribute input + //~| ERROR malformed `cfg` attribute input + //~| ERROR expected trait, found struct `A` + //~| ERROR expected trait, found type parameter `A` + //~| ERROR expected trait, found struct `A` + //~| ERROR expected trait, found type parameter `A` + //~| ERROR expected one of `<`, `where`, or `{`, found `}` + //~| ERROR expected one of `<`, `where`, or `{`, found `}` + //~| ERROR expected one of `,`, `>`, or `}`, found `` + } + >; +}; //~ ERROR `main` function not found in crate diff --git a/tests/ui/macros/tokenstream-ice-issue-149954.stderr b/tests/ui/macros/tokenstream-ice-issue-149954.stderr new file mode 100644 index 000000000000..750f3efcc612 --- /dev/null +++ b/tests/ui/macros/tokenstream-ice-issue-149954.stderr @@ -0,0 +1,110 @@ +error: expected one of `(`, `,`, `=`, `{`, or `}`, found keyword `const` + --> $DIR/tokenstream-ice-issue-149954.rs:6:5 + | +LL | A + | - expected one of `(`, `,`, `=`, `{`, or `}` +LL | const A: A = { + | ^^^^^ unexpected token + | + = help: enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }` + +error: expected one of `<`, `where`, or `{`, found `}` + --> $DIR/tokenstream-ice-issue-149954.rs:10:60 + | +LL | A: A<{ struct A> ; enum A } + | - ^ expected one of `<`, `where`, or `{` + | | + | while parsing this enum + +error: expected one of `<`, `where`, or `{`, found `}` + --> $DIR/tokenstream-ice-issue-149954.rs:10:60 + | +LL | A: A<{ struct A> ; enum A } + | - ^ expected one of `<`, `where`, or `{` + | | + | while parsing this enum + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: expected one of `,`, `>`, or `}`, found `` + --> $DIR/tokenstream-ice-issue-149954.rs:10:60 + | +LL | A: A<{ struct A> ; enum A } + | ^ expected one of `,`, `>`, or `}` + | +help: you might have meant to end the type parameters here + | +LL | A: A<{ struct A> ; enum A }> + | + + +error[E0539]: malformed `cfg` attribute input + --> $DIR/tokenstream-ice-issue-149954.rs:10:36 + | +LL | A: A<{ struct A> ; enum A } + | ^^^^^^ + | | + | expected this to be a list + | help: must be of the form: `#[cfg(predicate)]` + | + = note: for more information, visit + +error[E0539]: malformed `cfg` attribute input + --> $DIR/tokenstream-ice-issue-149954.rs:10:36 + | +LL | A: A<{ struct A> ; enum A } + | ^^^^^^ + | | + | expected this to be a list + | help: must be of the form: `#[cfg(predicate)]` + | + = note: for more information, visit + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0404]: expected trait, found struct `A` + --> $DIR/tokenstream-ice-issue-149954.rs:10:16 + | +LL | A: A<{ struct A> ; enum A } + | ________________^ +... | +LL | | >; + | |_________^ not a trait + +error[E0404]: expected trait, found type parameter `A` + --> $DIR/tokenstream-ice-issue-149954.rs:10:32 + | +LL | A: A<{ struct A> ; enum A } + | - ^^^^^^^^^^^^^^^^ not a trait + | | + | found this type parameter + +error[E0404]: expected trait, found struct `A` + --> $DIR/tokenstream-ice-issue-149954.rs:10:16 + | +LL | A: A<{ struct A> ; enum A } + | ________________^ +... | +LL | | >; + | |_________^ not a trait + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0404]: expected trait, found type parameter `A` + --> $DIR/tokenstream-ice-issue-149954.rs:10:32 + | +LL | A: A<{ struct A> ; enum A } + | - ^^^^^^^^^^^^^^^^ not a trait + | | + | found this type parameter + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0601]: `main` function not found in crate `tokenstream_ice_issue_149954` + --> $DIR/tokenstream-ice-issue-149954.rs:22:3 + | +LL | }; + | ^ consider adding a `main` function to `$DIR/tokenstream-ice-issue-149954.rs` + +error: aborting due to 11 previous errors + +Some errors have detailed explanations: E0404, E0539, E0601. +For more information about an error, try `rustc --explain E0404`. diff --git a/tests/ui/match/match-nested-enum-box-3121.rs b/tests/ui/match/match-nested-enum-box-3121.rs new file mode 100644 index 000000000000..f2ab4bf08075 --- /dev/null +++ b/tests/ui/match/match-nested-enum-box-3121.rs @@ -0,0 +1,37 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/3121 + +//@ run-pass +#![allow(dead_code)] +#![allow(non_camel_case_types)] + +#[derive(Copy, Clone)] +enum side { + mayo, + catsup, + vinegar, +} +#[derive(Copy, Clone)] +enum order { + hamburger, + fries(side), + shake, +} +#[derive(Copy, Clone)] +enum meal { + to_go(order), + for_here(order), +} + +fn foo(m: Box, cond: bool) { + match *m { + meal::to_go(_) => {} + meal::for_here(_) if cond => {} + meal::for_here(order::hamburger) => {} + meal::for_here(order::fries(_s)) => {} + meal::for_here(order::shake) => {} + } +} + +pub fn main() { + foo(Box::new(meal::for_here(order::hamburger)), true) +} diff --git a/tests/ui/mir/gvn-opt-138225.rs b/tests/ui/mir/gvn-opt-138225.rs new file mode 100644 index 000000000000..46359e044e2e --- /dev/null +++ b/tests/ui/mir/gvn-opt-138225.rs @@ -0,0 +1,16 @@ +//! Regression test for + +pub struct A { + name: NestedOption>, + //~^ ERROR cannot find type `NestedOption` in this scope +} + +impl A { + pub async fn func1() -> &'static A { + //~^ ERROR `async fn` is not permitted in Rust 2015 + static RES: A = A { name: None }; + &RES + } +} + +fn main() {} diff --git a/tests/ui/mir/gvn-opt-138225.stderr b/tests/ui/mir/gvn-opt-138225.stderr new file mode 100644 index 000000000000..b2e3d4476bf8 --- /dev/null +++ b/tests/ui/mir/gvn-opt-138225.stderr @@ -0,0 +1,19 @@ +error[E0670]: `async fn` is not permitted in Rust 2015 + --> $DIR/gvn-opt-138225.rs:9:9 + | +LL | pub async fn func1() -> &'static A { + | ^^^^^ to use `async fn`, switch to Rust 2018 or later + | + = help: pass `--edition 2024` to `rustc` + = note: for more on editions, read https://doc.rust-lang.org/edition-guide + +error[E0425]: cannot find type `NestedOption` in this scope + --> $DIR/gvn-opt-138225.rs:4:11 + | +LL | name: NestedOption>, + | ^^^^^^^^^^^^ not found in this scope + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0425, E0670. +For more information about an error, try `rustc --explain E0425`. diff --git a/tests/ui/issues/issue-3029.rs b/tests/ui/panics/vec-extend-after-panic-3029.rs similarity index 74% rename from tests/ui/issues/issue-3029.rs rename to tests/ui/panics/vec-extend-after-panic-3029.rs index 22d0906ccf70..3ae708d91e19 100644 --- a/tests/ui/issues/issue-3029.rs +++ b/tests/ui/panics/vec-extend-after-panic-3029.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/3029 + //@ run-fail //@ error-pattern:so long //@ needs-subprocess diff --git a/tests/ui/privacy/pub-priv-dep/auxiliary/pm.rs b/tests/ui/privacy/pub-priv-dep/auxiliary/pm.rs index 9e2aa898afe8..d45f2639d182 100644 --- a/tests/ui/privacy/pub-priv-dep/auxiliary/pm.rs +++ b/tests/ui/privacy/pub-priv-dep/auxiliary/pm.rs @@ -1,8 +1,3 @@ -//@ force-host -//@ no-prefer-dynamic - -#![crate_type = "proc-macro"] - extern crate proc_macro; use proc_macro::TokenStream; diff --git a/tests/ui/privacy/pub-priv-dep/pub-priv1.rs b/tests/ui/privacy/pub-priv-dep/pub-priv1.rs index ae86be19cf96..ba947de7f182 100644 --- a/tests/ui/privacy/pub-priv-dep/pub-priv1.rs +++ b/tests/ui/privacy/pub-priv-dep/pub-priv1.rs @@ -1,6 +1,6 @@ //@ aux-crate:priv:priv_dep=priv_dep.rs //@ aux-build:pub_dep.rs -//@ aux-crate:priv:pm=pm.rs +//@ proc-macro:priv:pm.rs //@ compile-flags: -Zunstable-options // Basic behavior check of exported_private_dependencies from either a public diff --git a/tests/ui/range/range-negative-literal-unsigned-type.rs b/tests/ui/range/range-negative-literal-unsigned-type.rs new file mode 100644 index 000000000000..b6152abb340e --- /dev/null +++ b/tests/ui/range/range-negative-literal-unsigned-type.rs @@ -0,0 +1,21 @@ +// Regression tests for: https://github.com/rust-lang/rust/issues/136514 + +#![allow(unreachable_patterns)] +fn main() { + match 0u8 { + -1..=2 => {} + //~^ ERROR the trait bound `u8: Neg` is not satisfied + -0..=0 => {} + //~^ ERROR the trait bound `u8: Neg` is not satisfied + -256..=2 => {} + //~^ ERROR the trait bound `u8: Neg` is not satisfied + -255..=2 => {} + //~^ ERROR the trait bound `u8: Neg` is not satisfied + 0..=-1 => {} + //~^ ERROR the trait bound `u8: Neg` is not satisfied + -2..=-1 => {} + //~^ ERROR the trait bound `u8: Neg` is not satisfied + //~| ERROR the trait bound `u8: Neg` is not satisfied + _ => {} + } +} diff --git a/tests/ui/range/range-negative-literal-unsigned-type.stderr b/tests/ui/range/range-negative-literal-unsigned-type.stderr new file mode 100644 index 000000000000..7ca14c3d7790 --- /dev/null +++ b/tests/ui/range/range-negative-literal-unsigned-type.stderr @@ -0,0 +1,122 @@ +error[E0277]: the trait bound `u8: Neg` is not satisfied + --> $DIR/range-negative-literal-unsigned-type.rs:6:9 + | +LL | -1..=2 => {} + | ^^ the trait `Neg` is not implemented for `u8` + | + = help: the following other types implement trait `Neg`: + &f128 + &f16 + &f32 + &f64 + &i128 + &i16 + &i32 + &i64 + and 12 others + +error[E0277]: the trait bound `u8: Neg` is not satisfied + --> $DIR/range-negative-literal-unsigned-type.rs:8:9 + | +LL | -0..=0 => {} + | ^^ the trait `Neg` is not implemented for `u8` + | + = help: the following other types implement trait `Neg`: + &f128 + &f16 + &f32 + &f64 + &i128 + &i16 + &i32 + &i64 + and 12 others + +error[E0277]: the trait bound `u8: Neg` is not satisfied + --> $DIR/range-negative-literal-unsigned-type.rs:10:9 + | +LL | -256..=2 => {} + | ^^^^ the trait `Neg` is not implemented for `u8` + | + = help: the following other types implement trait `Neg`: + &f128 + &f16 + &f32 + &f64 + &i128 + &i16 + &i32 + &i64 + and 12 others + +error[E0277]: the trait bound `u8: Neg` is not satisfied + --> $DIR/range-negative-literal-unsigned-type.rs:12:9 + | +LL | -255..=2 => {} + | ^^^^ the trait `Neg` is not implemented for `u8` + | + = help: the following other types implement trait `Neg`: + &f128 + &f16 + &f32 + &f64 + &i128 + &i16 + &i32 + &i64 + and 12 others + +error[E0277]: the trait bound `u8: Neg` is not satisfied + --> $DIR/range-negative-literal-unsigned-type.rs:14:13 + | +LL | 0..=-1 => {} + | ^^ the trait `Neg` is not implemented for `u8` + | + = help: the following other types implement trait `Neg`: + &f128 + &f16 + &f32 + &f64 + &i128 + &i16 + &i32 + &i64 + and 12 others + +error[E0277]: the trait bound `u8: Neg` is not satisfied + --> $DIR/range-negative-literal-unsigned-type.rs:16:9 + | +LL | -2..=-1 => {} + | ^^ the trait `Neg` is not implemented for `u8` + | + = help: the following other types implement trait `Neg`: + &f128 + &f16 + &f32 + &f64 + &i128 + &i16 + &i32 + &i64 + and 12 others + +error[E0277]: the trait bound `u8: Neg` is not satisfied + --> $DIR/range-negative-literal-unsigned-type.rs:16:14 + | +LL | -2..=-1 => {} + | ^^ the trait `Neg` is not implemented for `u8` + | + = help: the following other types implement trait `Neg`: + &f128 + &f16 + &f32 + &f64 + &i128 + &i16 + &i32 + &i64 + and 12 others + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/resolve/enum-variant-import-2904.rs b/tests/ui/resolve/enum-variant-import-2904.rs new file mode 100644 index 000000000000..3272ee5fb500 --- /dev/null +++ b/tests/ui/resolve/enum-variant-import-2904.rs @@ -0,0 +1,101 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/2904 + +//@ build-pass +#![allow(unused_must_use)] +#![allow(dead_code)] +#![allow(unused_mut)] + +// Map representation + +use Square::{Bot, ClosedLift, Earth, Empty, Lambda, OpenLift, Rock, Wall}; +use std::fmt; +use std::io::prelude::*; + +enum Square { + Bot, + Wall, + Rock, + Lambda, + ClosedLift, + OpenLift, + Earth, + Empty, +} + +impl fmt::Debug for Square { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + match *self { + Bot => { + "R".to_string() + } + Wall => { + "#".to_string() + } + Rock => { + "*".to_string() + } + Lambda => { + "\\".to_string() + } + ClosedLift => { + "L".to_string() + } + OpenLift => { + "O".to_string() + } + Earth => { + ".".to_string() + } + Empty => { + " ".to_string() + } + } + ) + } +} + +fn square_from_char(c: char) -> Square { + match c { + 'R' => Bot, + '#' => Wall, + '*' => Rock, + '\\' => Lambda, + 'L' => ClosedLift, + 'O' => OpenLift, + '.' => Earth, + ' ' => Empty, + _ => { + println!("invalid Square: {}", c); + panic!() + } + } +} + +fn read_board_grid(mut input: Rdr) -> Vec> { + let mut input: &mut dyn Read = &mut input; + let mut grid = Vec::new(); + let mut line = [0; 10]; + input.read(&mut line); + let mut row = Vec::new(); + for c in &line { + row.push(square_from_char(*c as char)) + } + grid.push(row); + let width = grid[0].len(); + for row in &grid { + assert_eq!(row.len(), width) + } + grid +} + +mod test { + #[test] + pub fn trivial_to_string() { + assert_eq!(Lambda.to_string(), "\\") + } +} + +pub fn main() {} diff --git a/tests/ui/issues/issue-2445.rs b/tests/ui/resolve/struct-function-same-name-2445.rs similarity index 78% rename from tests/ui/issues/issue-2445.rs rename to tests/ui/resolve/struct-function-same-name-2445.rs index e6c33a8fd016..8a0490efa3aa 100644 --- a/tests/ui/issues/issue-2445.rs +++ b/tests/ui/resolve/struct-function-same-name-2445.rs @@ -1,8 +1,9 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/2445 + //@ run-pass #![allow(dead_code)] #![allow(non_camel_case_types)] - struct c1 { x: T, } @@ -12,16 +13,13 @@ impl c1 { } fn c1(x: T) -> c1 { - c1 { - x: x - } + c1 { x } } impl c1 { pub fn f2(&self, _x: T) {} } - pub fn main() { c1::(3).f1(4); c1::(3).f2(4); diff --git a/tests/ui/issues/issue-2487-a.rs b/tests/ui/resolve/struct-function-same-name-2487.rs similarity index 64% rename from tests/ui/issues/issue-2487-a.rs rename to tests/ui/resolve/struct-function-same-name-2487.rs index d38616929fae..5f9a61c3260b 100644 --- a/tests/ui/issues/issue-2487-a.rs +++ b/tests/ui/resolve/struct-function-same-name-2487.rs @@ -2,10 +2,8 @@ #![allow(dead_code)] #![allow(non_camel_case_types)] - struct socket { sock: isize, - } impl Drop for socket { @@ -13,19 +11,22 @@ impl Drop for socket { } impl socket { - pub fn set_identity(&self) { + pub fn set_identity(&self) { closure(|| setsockopt_bytes(self.sock.clone())) } } fn socket() -> socket { - socket { - sock: 1 - } + socket { sock: 1 } } -fn closure(f: F) where F: FnOnce() { f() } +fn closure(f: F) +where + F: FnOnce(), +{ + f() +} -fn setsockopt_bytes(_sock: isize) { } +fn setsockopt_bytes(_sock: isize) {} pub fn main() {} diff --git a/tests/ui/issues/issue-2502.rs b/tests/ui/resolve/struct-function-same-name-2502.rs similarity index 51% rename from tests/ui/issues/issue-2502.rs rename to tests/ui/resolve/struct-function-same-name-2502.rs index 98a52a3b5a7d..5305c7d0a96f 100644 --- a/tests/ui/issues/issue-2502.rs +++ b/tests/ui/resolve/struct-function-same-name-2502.rs @@ -1,11 +1,11 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/2502 + //@ check-pass #![allow(dead_code)] #![allow(non_camel_case_types)] - - struct font<'a> { - fontbuf: &'a Vec , + fontbuf: &'a Vec, } impl<'a> font<'a> { @@ -14,10 +14,8 @@ impl<'a> font<'a> { } } -fn font(fontbuf: &Vec ) -> font<'_> { - font { - fontbuf: fontbuf - } +fn font(fontbuf: &Vec) -> font<'_> { + font { fontbuf } } -pub fn main() { } +pub fn main() {} diff --git a/tests/ui/issues/issue-2550.rs b/tests/ui/resolve/struct-function-same-name-2550.rs similarity index 59% rename from tests/ui/issues/issue-2550.rs rename to tests/ui/resolve/struct-function-same-name-2550.rs index 450db9be627e..c96f58374c6d 100644 --- a/tests/ui/issues/issue-2550.rs +++ b/tests/ui/resolve/struct-function-same-name-2550.rs @@ -1,20 +1,18 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/2550 + //@ run-pass #![allow(dead_code)] #![allow(non_snake_case)] - struct C { x: usize, } fn C(x: usize) -> C { - C { - x: x - } + C { x } } -fn f(_x: T) { -} +fn f(_x: T) {} pub fn main() { f(C(1)); diff --git a/tests/ui/issues/issue-2708.rs b/tests/ui/resolve/struct-function-same-name-2708.rs similarity index 69% rename from tests/ui/issues/issue-2708.rs rename to tests/ui/resolve/struct-function-same-name-2708.rs index 09d19f87aa64..729a5819ae4e 100644 --- a/tests/ui/issues/issue-2708.rs +++ b/tests/ui/resolve/struct-function-same-name-2708.rs @@ -1,15 +1,13 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/2708 + //@ run-pass #![allow(dead_code)] #![allow(non_snake_case)] - - - struct Font { fontbuf: usize, cairo_font: usize, font_dtor: usize, - } impl Drop for Font { @@ -17,11 +15,7 @@ impl Drop for Font { } fn Font() -> Font { - Font { - fontbuf: 0, - cairo_font: 0, - font_dtor: 0 - } + Font { fontbuf: 0, cairo_font: 0, font_dtor: 0 } } pub fn main() { diff --git a/tests/ui/sanitizer/cfi/assoc-const-projection-issue-151878.rs b/tests/ui/sanitizer/cfi/assoc-const-projection-issue-151878.rs new file mode 100644 index 000000000000..50530e4f0db0 --- /dev/null +++ b/tests/ui/sanitizer/cfi/assoc-const-projection-issue-151878.rs @@ -0,0 +1,20 @@ +//@ compile-flags: -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer -Ccodegen-units=1 -Clto +//@ needs-rustc-debug-assertions +//@ needs-sanitizer-cfi +//@ build-pass +//@ no-prefer-dynamic + +#![feature(min_generic_const_args)] +#![expect(incomplete_features)] + +trait Trait { + #[type_const] + const N: usize = 0; + fn process(&self, _: [u8; Self::N]) {} +} + +impl Trait for () {} + +fn main() { + let _x: &dyn Trait = &(); +} diff --git a/tests/ui/issues/issue-2895.rs b/tests/ui/structs/struct-size-with-drop-2895.rs similarity index 84% rename from tests/ui/issues/issue-2895.rs rename to tests/ui/structs/struct-size-with-drop-2895.rs index 6301a8637534..9540d340ff68 100644 --- a/tests/ui/issues/issue-2895.rs +++ b/tests/ui/structs/struct-size-with-drop-2895.rs @@ -1,10 +1,12 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/2895 + //@ run-pass #![allow(dead_code)] use std::mem; struct Cat { - x: isize + x: isize, } struct Kitty { diff --git a/tests/ui/structs/struct-update-syntax-2463.rs b/tests/ui/structs/struct-update-syntax-2463.rs new file mode 100644 index 000000000000..5b2a90a5adf9 --- /dev/null +++ b/tests/ui/structs/struct-update-syntax-2463.rs @@ -0,0 +1,17 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/2463 + +//@ run-pass +#![allow(dead_code)] + +struct Pair { + f: isize, + g: isize, +} + +pub fn main() { + let x = Pair { f: 0, g: 0 }; + + let _y = Pair { f: 1, g: 1, ..x }; + + let _z = Pair { f: 1, ..x }; +} diff --git a/tests/ui/traits/next-solver/generalize/relate-alias-in-lub.rs b/tests/ui/traits/next-solver/generalize/relate-alias-in-lub.rs new file mode 100644 index 000000000000..a81e18559a39 --- /dev/null +++ b/tests/ui/traits/next-solver/generalize/relate-alias-in-lub.rs @@ -0,0 +1,30 @@ +//@ revisions: old next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) +//@ check-pass +// Reproduces https://github.com/rust-lang/rust/pull/151746#issuecomment-3822930803. +// +// The change we tried to make there caused relating a type variable with an alias inside lub, +// In 5bd20bbd0ba6c0285664e55a1ffc677d7487c98b, we moved around code +// that adds an alias-relate predicate to be earlier, from one shared codepath into several +// distinct code paths. However, we forgot the codepath in `LatticeOp`, causing an ICE in serde. +// In the end we dropped said commit, but the reproducer is still a useful as test. + +use std::marker::PhantomData; + +pub trait Trait { + type Error; +} + +pub struct Struct(PhantomData); + +impl Trait for () { + type Error = (); +} + +fn main() { + let _: Struct<<() as Trait>::Error> = match loop {} { + b => loop {}, + a => Struct(PhantomData), + }; +} diff --git a/tests/ui/issues/issue-2935.rs b/tests/ui/traits/trait-object-method-call-2935.rs similarity index 60% rename from tests/ui/issues/issue-2935.rs rename to tests/ui/traits/trait-object-method-call-2935.rs index bcc25f6187b5..ea24aae89462 100644 --- a/tests/ui/issues/issue-2935.rs +++ b/tests/ui/traits/trait-object-method-call-2935.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/2935 + //@ run-pass #![allow(dead_code)] #![allow(non_camel_case_types)] @@ -11,14 +13,14 @@ trait it { } impl it for t { - fn f(&self) { } + fn f(&self) {} } pub fn main() { - // let x = ({a: 4} as it); - // let y = box ({a: 4}); - // let z = box ({a: 4} as it); - // let z = box ({a: true} as it); + // let x = ({a: 4} as it); + // let y = box ({a: 4}); + // let z = box ({a: 4} as it); + // let z = box ({a: true} as it); let z: Box<_> = Box::new(Box::new(true) as Box); // x.f(); // y.f(); diff --git a/tests/ui/issues/issue-3052.rs b/tests/ui/traits/trait-object-type-alias-3052.rs similarity index 69% rename from tests/ui/issues/issue-3052.rs rename to tests/ui/traits/trait-object-type-alias-3052.rs index ab3519fe7147..e601c76713dc 100644 --- a/tests/ui/issues/issue-3052.rs +++ b/tests/ui/traits/trait-object-type-alias-3052.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/3052 + //@ run-pass #![allow(dead_code)] @@ -8,5 +10,4 @@ fn f() -> Option { Some(mock_connection) } -pub fn main() { -} +pub fn main() {} diff --git a/tests/ui/try-trait/bad-interconversion.stderr b/tests/ui/try-trait/bad-interconversion.stderr index f8c0deba99ba..a566800da53e 100644 --- a/tests/ui/try-trait/bad-interconversion.stderr +++ b/tests/ui/try-trait/bad-interconversion.stderr @@ -22,7 +22,7 @@ help: the following other types implement trait `From` ::: $SRC_DIR/core/src/ascii/ascii_char.rs:LL:COL | = note: in this macro invocation - = note: this error originates in the macro `impl_from` which comes from the expansion of the macro `into_int_impl` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `impl_from_bool` which comes from the expansion of the macro `into_int_impl` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the `?` operator can only be used on `Result`s, not `Option`s, in a function that returns `Result` --> $DIR/bad-interconversion.rs:9:12 diff --git a/tests/ui/type-alias-impl-trait/opaque-alias-relate-issue-151331.rs b/tests/ui/type-alias-impl-trait/opaque-alias-relate-issue-151331.rs new file mode 100644 index 000000000000..684f2498d584 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/opaque-alias-relate-issue-151331.rs @@ -0,0 +1,10 @@ +//@ compile-flags: -Znext-solver=globally +#![feature(type_alias_impl_trait)] + +type Foo = Vec; + +#[define_opaque(Foo)] +fn make_foo() -> Foo {} +//~^ ERROR type mismatch resolving + +fn main() {} diff --git a/tests/ui/type-alias-impl-trait/opaque-alias-relate-issue-151331.stderr b/tests/ui/type-alias-impl-trait/opaque-alias-relate-issue-151331.stderr new file mode 100644 index 000000000000..dd73ed1a247c --- /dev/null +++ b/tests/ui/type-alias-impl-trait/opaque-alias-relate-issue-151331.stderr @@ -0,0 +1,9 @@ +error[E0271]: type mismatch resolving `Foo == ()` + --> $DIR/opaque-alias-relate-issue-151331.rs:7:18 + | +LL | fn make_foo() -> Foo {} + | ^^^ types differ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0271`.