diff options
| author | bors <bors@rust-lang.org> | 2022-08-31 07:57:09 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2022-08-31 07:57:09 +0000 |
| commit | 12e4fd0755d7d976d4ee0f2004dc938290752ff7 (patch) | |
| tree | 2a88229f6a91bb1741d22d923002d1b253346689 /compiler | |
| parent | 7f442f8ba174fd4233a14ef4d7b577aa907db594 (diff) | |
| parent | 49ed325759ca51034fb965b161a06edf9a33e73c (diff) | |
| download | rust-12e4fd0755d7d976d4ee0f2004dc938290752ff7.tar.gz rust-12e4fd0755d7d976d4ee0f2004dc938290752ff7.zip | |
Auto merge of #101225 - matthiaskrgr:rollup-9s1chas, r=matthiaskrgr
Rollup of 8 pull requests Successful merges: - #100970 (Allow deriving multipart suggestions) - #100984 (Reinstate preloading of some dll imports) - #101011 (Use getentropy when possible on all Apple platforms) - #101025 (Add tier-3 support for powerpc64 and riscv64 openbsd) - #101049 (Remove span fatal from ast lowering) - #101100 (Make call suggestions more general and more accurate) - #101171 (Fix UB from misalignment and provenance widening in `std::sys::windows`) - #101185 (Tweak `WellFormedLoc`s a bit) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
Diffstat (limited to 'compiler')
21 files changed, 873 insertions, 551 deletions
diff --git a/compiler/rustc_ast_lowering/src/errors.rs b/compiler/rustc_ast_lowering/src/errors.rs index 59f1b7180e4..4adeaef9bbf 100644 --- a/compiler/rustc_ast_lowering/src/errors.rs +++ b/compiler/rustc_ast_lowering/src/errors.rs @@ -327,3 +327,10 @@ pub struct ArbitraryExpressionInPattern { #[primary_span] pub span: Span, } + +#[derive(SessionDiagnostic, Clone, Copy)] +#[diag(ast_lowering::inclusive_range_with_no_end)] +pub struct InclusiveRangeWithNoEnd { + #[primary_span] + pub span: Span, +} diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 61f8c0216f1..17604761688 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1,8 +1,8 @@ use super::errors::{ AsyncGeneratorsNotSupported, AsyncNonMoveClosureNotSupported, AwaitOnlyInAsyncFnAndBlocks, BaseExpressionDoubleDot, ClosureCannotBeStatic, FunctionalRecordUpdateDestructuringAssignemnt, - GeneratorTooManyParameters, NotSupportedForLifetimeBinderAsyncClosure, RustcBoxAttributeError, - UnderscoreExprLhsAssign, + GeneratorTooManyParameters, InclusiveRangeWithNoEnd, NotSupportedForLifetimeBinderAsyncClosure, + RustcBoxAttributeError, UnderscoreExprLhsAssign, }; use super::ResolverAstLoweringExt; use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericArgs}; @@ -1264,7 +1264,13 @@ impl<'hir> LoweringContext<'_, 'hir> { (Some(..), Some(..), HalfOpen) => hir::LangItem::Range, (None, Some(..), Closed) => hir::LangItem::RangeToInclusive, (Some(..), Some(..), Closed) => unreachable!(), - (_, None, Closed) => self.diagnostic().span_fatal(span, "inclusive range with no end"), + (start, None, Closed) => { + self.tcx.sess.emit_err(InclusiveRangeWithNoEnd { span }); + match start { + Some(..) => hir::LangItem::RangeFrom, + None => hir::LangItem::RangeFull, + } + } }; let fields = self.arena.alloc_from_iter( diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 5e9e8aa553d..30a87740c7f 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -36,6 +36,8 @@ #![feature(never_type)] #![recursion_limit = "256"] #![allow(rustc::potential_query_instability)] +#![deny(rustc::untranslatable_diagnostic)] +#![deny(rustc::diagnostic_outside_of_impl)] #[macro_use] extern crate tracing; diff --git a/compiler/rustc_error_messages/locales/en-US/ast_lowering.ftl b/compiler/rustc_error_messages/locales/en-US/ast_lowering.ftl index dcb1e2b0830..f2790531aba 100644 --- a/compiler/rustc_error_messages/locales/en-US/ast_lowering.ftl +++ b/compiler/rustc_error_messages/locales/en-US/ast_lowering.ftl @@ -129,3 +129,5 @@ ast_lowering_not_supported_for_lifetime_binder_async_closure = ast_lowering_arbitrary_expression_in_pattern = arbitrary expressions aren't allowed in patterns + +ast_lowering_inclusive_range_with_no_end = inclusive range with no end diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 506198df4d8..f75e2596f36 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -686,19 +686,12 @@ impl Diagnostic { suggestion: Vec<(Span, String)>, applicability: Applicability, ) -> &mut Self { - assert!(!suggestion.is_empty()); - self.push_suggestion(CodeSuggestion { - substitutions: vec![Substitution { - parts: suggestion - .into_iter() - .map(|(span, snippet)| SubstitutionPart { snippet, span }) - .collect(), - }], - msg: self.subdiagnostic_message_to_diagnostic_message(msg), - style: SuggestionStyle::CompletelyHidden, + self.multipart_suggestion_with_style( + msg, + suggestion, applicability, - }); - self + SuggestionStyle::CompletelyHidden, + ) } /// Prints out a message with a suggested edit of the code. diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 7b0f4354afd..68abdd0bad1 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -1249,9 +1249,13 @@ impl HandlerInner { } fn treat_err_as_bug(&self) -> bool { - self.flags - .treat_err_as_bug - .map_or(false, |c| self.err_count() + self.lint_err_count >= c.get()) + self.flags.treat_err_as_bug.map_or(false, |c| { + self.err_count() + + self.lint_err_count + + self.delayed_span_bugs.len() + + self.delayed_good_path_bugs.len() + >= c.get() + }) } fn print_error_count(&mut self, registry: &Registry) { @@ -1407,7 +1411,14 @@ impl HandlerInner { // This is technically `self.treat_err_as_bug()` but `delay_span_bug` is called before // incrementing `err_count` by one, so we need to +1 the comparing. // FIXME: Would be nice to increment err_count in a more coherent way. - if self.flags.treat_err_as_bug.map_or(false, |c| self.err_count() + 1 >= c.get()) { + if self.flags.treat_err_as_bug.map_or(false, |c| { + self.err_count() + + self.lint_err_count + + self.delayed_span_bugs.len() + + self.delayed_good_path_bugs.len() + + 1 + >= c.get() + }) { // FIXME: don't abort here if report_delayed_bugs is off self.span_bug(sp, msg); } diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 9e7cbba9511..9c6530c8a08 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -41,7 +41,8 @@ macro_rules! pluralize { /// All suggestions are marked with an `Applicability`. Tools use the applicability of a suggestion /// to determine whether it should be automatically applied or if the user should be consulted /// before applying the suggestion. -#[derive(Copy, Clone, Debug, PartialEq, Hash, Encodable, Decodable, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Hash, Encodable, Decodable, Serialize, Deserialize)] +#[derive(PartialEq, Eq, PartialOrd, Ord)] pub enum Applicability { /// The suggestion is definitely what the user intended, or maintains the exact meaning of the code. /// This suggestion should be automatically applied. diff --git a/compiler/rustc_llvm/build.rs b/compiler/rustc_llvm/build.rs index abb68f3afe5..28e092c1eb7 100644 --- a/compiler/rustc_llvm/build.rs +++ b/compiler/rustc_llvm/build.rs @@ -342,10 +342,10 @@ fn main() { }; // RISC-V GCC erroneously requires libatomic for sub-word - // atomic operations. FreeBSD uses Clang as its system + // atomic operations. Some BSD uses Clang as its system // compiler and provides no libatomic in its base system so // does not want this. - if !target.contains("freebsd") && target.starts_with("riscv") { + if target.starts_with("riscv") && !target.contains("freebsd") && !target.contains("openbsd") { println!("cargo:rustc-link-lib=atomic"); } diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index 666dbc23c28..8b40e295bd8 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -12,7 +12,7 @@ use quote::{format_ident, quote}; use std::collections::HashMap; use std::fmt; use std::str::FromStr; -use syn::{parse_quote, spanned::Spanned, Meta, MetaList, MetaNameValue, NestedMeta, Path}; +use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path}; use synstructure::{BindingInfo, Structure, VariantInfo}; /// Which kind of suggestion is being created? @@ -28,8 +28,41 @@ enum SubdiagnosticSuggestionKind { Verbose, } +impl FromStr for SubdiagnosticSuggestionKind { + type Err = (); + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "" => Ok(SubdiagnosticSuggestionKind::Normal), + "_short" => Ok(SubdiagnosticSuggestionKind::Short), + "_hidden" => Ok(SubdiagnosticSuggestionKind::Hidden), + "_verbose" => Ok(SubdiagnosticSuggestionKind::Verbose), + _ => Err(()), + } + } +} + +impl SubdiagnosticSuggestionKind { + pub fn to_suggestion_style(&self) -> TokenStream { + match self { + SubdiagnosticSuggestionKind::Normal => { + quote! { rustc_errors::SuggestionStyle::ShowCode } + } + SubdiagnosticSuggestionKind::Short => { + quote! { rustc_errors::SuggestionStyle::HideCodeInline } + } + SubdiagnosticSuggestionKind::Hidden => { + quote! { rustc_errors::SuggestionStyle::HideCodeAlways } + } + SubdiagnosticSuggestionKind::Verbose => { + quote! { rustc_errors::SuggestionStyle::ShowAlways } + } + } + } +} + /// Which kind of subdiagnostic is being created from a variant? -#[derive(Clone, Copy)] +#[derive(Clone)] enum SubdiagnosticKind { /// `#[label(...)]` Label, @@ -40,31 +73,9 @@ enum SubdiagnosticKind { /// `#[warning(...)]` Warn, /// `#[suggestion{,_short,_hidden,_verbose}]` - Suggestion(SubdiagnosticSuggestionKind), -} - -impl FromStr for SubdiagnosticKind { - type Err = (); - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - "label" => Ok(SubdiagnosticKind::Label), - "note" => Ok(SubdiagnosticKind::Note), - "help" => Ok(SubdiagnosticKind::Help), - "warning" => Ok(SubdiagnosticKind::Warn), - "suggestion" => Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal)), - "suggestion_short" => { - Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short)) - } - "suggestion_hidden" => { - Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden)) - } - "suggestion_verbose" => { - Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose)) - } - _ => Err(()), - } - } + Suggestion { suggestion_kind: SubdiagnosticSuggestionKind, code: TokenStream }, + /// `#[multipart_suggestion{,_short,_hidden,_verbose}]` + MultipartSuggestion { suggestion_kind: SubdiagnosticSuggestionKind }, } impl quote::IdentFragment for SubdiagnosticKind { @@ -74,17 +85,9 @@ impl quote::IdentFragment for SubdiagnosticKind { SubdiagnosticKind::Note => write!(f, "note"), SubdiagnosticKind::Help => write!(f, "help"), SubdiagnosticKind::Warn => write!(f, "warn"), - SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal) => { - write!(f, "suggestion") - } - SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short) => { - write!(f, "suggestion_short") - } - SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden) => { - write!(f, "suggestion_hidden") - } - SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose) => { - write!(f, "suggestion_verbose") + SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestion_with_style"), + SubdiagnosticKind::MultipartSuggestion { .. } => { + write!(f, "multipart_suggestion_with_style") } } } @@ -148,11 +151,9 @@ impl<'a> SessionSubdiagnosticDerive<'a> { variant, span, fields: fields_map, - kind: None, - slug: None, - code: None, span_field: None, applicability: None, + has_suggestion_parts: false, }; builder.into_tokens().unwrap_or_else(|v| v.to_compile_error()) }); @@ -193,21 +194,15 @@ struct SessionSubdiagnosticDeriveBuilder<'a> { /// derive builder. fields: HashMap<String, TokenStream>, - /// Subdiagnostic kind of the type/variant. - kind: Option<(SubdiagnosticKind, proc_macro::Span)>, - - /// Slug of the subdiagnostic - corresponds to the Fluent identifier for the message - from the - /// `#[kind(slug)]` attribute on the type or variant. - slug: Option<(Path, proc_macro::Span)>, - /// If a suggestion, the code to suggest as a replacement - from the `#[kind(code = "...")]` - /// attribute on the type or variant. - code: Option<(TokenStream, proc_macro::Span)>, - /// Identifier for the binding to the `#[primary_span]` field. span_field: Option<(proc_macro2::Ident, proc_macro::Span)>, /// If a suggestion, the identifier for the binding to the `#[applicability]` field or a /// `rustc_errors::Applicability::*` variant directly. applicability: Option<(TokenStream, proc_macro::Span)>, + + /// Set to true when a `#[suggestion_part]` field is encountered, used to generate an error + /// during finalization if still `false`. + has_suggestion_parts: bool, } impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> { @@ -217,7 +212,11 @@ impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> { } impl<'a> SessionSubdiagnosticDeriveBuilder<'a> { - fn identify_kind(&mut self) -> Result<(), DiagnosticDeriveError> { + fn identify_kind( + &mut self, + ) -> Result<Option<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> { + let mut kind_slug = None; + for attr in self.variant.ast().attrs { let span = attr.span().unwrap(); @@ -225,116 +224,121 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> { let name = name.as_str(); let meta = attr.parse_meta()?; - let kind = match meta { - Meta::List(MetaList { ref nested, .. }) => { - let mut nested_iter = nested.into_iter(); - if let Some(nested_attr) = nested_iter.next() { - match nested_attr { - NestedMeta::Meta(Meta::Path(path)) => { - self.slug.set_once((path.clone(), span)); - } - NestedMeta::Meta(meta @ Meta::NameValue(_)) - if matches!( - meta.path().segments.last().unwrap().ident.to_string().as_str(), - "code" | "applicability" - ) => - { - // don't error for valid follow-up attributes - } - nested_attr => { - throw_invalid_nested_attr!(attr, &nested_attr, |diag| { - diag.help( - "first argument of the attribute should be the diagnostic \ - slug", - ) - }) - } - }; - } + let Meta::List(MetaList { ref nested, .. }) = meta else { + throw_invalid_attr!(attr, &meta); + }; - for nested_attr in nested_iter { - let meta = match nested_attr { - NestedMeta::Meta(ref meta) => meta, - _ => throw_invalid_nested_attr!(attr, &nested_attr), - }; - - let span = meta.span().unwrap(); - let nested_name = meta.path().segments.last().unwrap().ident.to_string(); - let nested_name = nested_name.as_str(); - - match meta { - Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => { - match nested_name { - "code" => { - let formatted_str = self.build_format(&s.value(), s.span()); - self.code.set_once((formatted_str, span)); - } - "applicability" => { - let value = match Applicability::from_str(&s.value()) { - Ok(v) => v, - Err(()) => { - span_err(span, "invalid applicability").emit(); - Applicability::Unspecified - } - }; - self.applicability.set_once((quote! { #value }, span)); - } - _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { - diag.help( - "only `code` and `applicability` are valid nested \ - attributes", - ) - }), - } - } - _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { - if matches!(meta, Meta::Path(_)) { - diag.help( - "a diagnostic slug must be the first argument to the \ - attribute", - ) - } else { - diag - } - }), - } + let mut kind = match name { + "label" => SubdiagnosticKind::Label, + "note" => SubdiagnosticKind::Note, + "help" => SubdiagnosticKind::Help, + "warning" => SubdiagnosticKind::Warn, + _ => { + if let Some(suggestion_kind) = + name.strip_prefix("suggestion").and_then(|s| s.parse().ok()) + { + SubdiagnosticKind::Suggestion { suggestion_kind, code: TokenStream::new() } + } else if let Some(suggestion_kind) = + name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok()) + { + SubdiagnosticKind::MultipartSuggestion { suggestion_kind } + } else { + throw_invalid_attr!(attr, &meta); } - - let Ok(kind) = SubdiagnosticKind::from_str(name) else { - throw_invalid_attr!(attr, &meta) - }; - - kind } - _ => throw_invalid_attr!(attr, &meta), }; - if matches!( - kind, - SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note - ) && self.code.is_some() - { - throw_span_err!( - span, - &format!("`code` is not a valid nested attribute of a `{}` attribute", name) - ); + let mut slug = None; + let mut code = None; + + let mut nested_iter = nested.into_iter(); + if let Some(nested_attr) = nested_iter.next() { + match nested_attr { + NestedMeta::Meta(Meta::Path(path)) => { + slug.set_once((path.clone(), span)); + } + NestedMeta::Meta(meta @ Meta::NameValue(_)) + if matches!( + meta.path().segments.last().unwrap().ident.to_string().as_str(), + "code" | "applicability" + ) => + { + // Don't error for valid follow-up attributes. + } + nested_attr => { + throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help( + "first argument of the attribute should be the diagnostic \ + slug", + ) + }) + } + }; } - if matches!( - kind, - SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note - ) && self.applicability.is_some() - { - throw_span_err!( - span, - &format!( - "`applicability` is not a valid nested attribute of a `{}` attribute", - name - ) - ); + for nested_attr in nested_iter { + let meta = match nested_attr { + NestedMeta::Meta(ref meta) => meta, + _ => throw_invalid_nested_attr!(attr, &nested_attr), + }; + + let span = meta.span().unwrap(); + let nested_name = meta.path().segments.last().unwrap().ident.to_string(); + let nested_name = nested_name.as_str(); + + let value = match meta { + Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => value, + Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help("a diagnostic slug must be the first argument to the attribute") + }), + _ => throw_invalid_nested_attr!(attr, &nested_attr), + }; + + match nested_name { + "code" => { + if matches!(kind, SubdiagnosticKind::Suggestion { .. }) { + let formatted_str = self.build_format(&value.value(), value.span()); + code.set_once((formatted_str, span)); + } else { + span_err( + span, + &format!( + "`code` is not a valid nested attribute of a `{}` attribute", + name + ), + ) + .emit(); + } + } + "applicability" => { + if matches!( + kind, + SubdiagnosticKind::Suggestion { .. } + | SubdiagnosticKind::MultipartSuggestion { .. } + ) { + let value = + Applicability::from_str(&value.value()).unwrap_or_else(|()| { + span_err(span, "invalid applicability").emit(); + Applicability::Unspecified + }); + self.applicability.set_once((quote! { #value }, span)); + } else { + span_err( + span, + &format!( + "`applicability` is not a valid nested attribute of a `{}` attribute", + name + ) + ).emit(); + } + } + _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help("only `code` and `applicability` are valid nested attributes") + }), + } } - if self.slug.is_none() { + let Some((slug, _)) = slug else { throw_span_err!( span, &format!( @@ -342,150 +346,338 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> { name ) ); + }; + + match kind { + SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => { + let Some((code, _)) = code else { + throw_span_err!(span, "suggestion without `code = \"...\"`"); + }; + *code_field = code; + } + SubdiagnosticKind::Label + | SubdiagnosticKind::Note + | SubdiagnosticKind::Help + | SubdiagnosticKind::Warn + | SubdiagnosticKind::MultipartSuggestion { .. } => {} } - self.kind.set_once((kind, span)); + kind_slug.set_once(((kind, slug), span)) } - Ok(()) + Ok(kind_slug.map(|(kind_slug, _)| kind_slug)) + } + + /// Generates the code for a field with no attributes. + fn generate_field_set_arg(&mut self, binding: &BindingInfo<'_>) -> TokenStream { + let ast = binding.ast(); + assert_eq!(ast.attrs.len(), 0, "field with attribute used as diagnostic arg"); + + let diag = &self.diag; + let ident = ast.ident.as_ref().unwrap(); + quote! { + #diag.set_arg( + stringify!(#ident), + #binding + ); + } } - fn generate_field_code( + /// Generates the necessary code for all attributes on a field. + fn generate_field_attr_code( &mut self, binding: &BindingInfo<'_>, - is_suggestion: bool, - ) -> Result<TokenStream, DiagnosticDeriveError> { + kind: &SubdiagnosticKind, + ) -> TokenStream { let ast = binding.ast(); + assert!(ast.attrs.len() > 0, "field without attributes generating attr code"); + // Abstract over `Vec<T>` and `Option<T>` fields using `FieldInnerTy`, which will + // apply the generated code on each element in the `Vec` or `Option`. let inner_ty = FieldInnerTy::from_type(&ast.ty); - let info = FieldInfo { - binding: binding, - ty: inner_ty.inner_type().unwrap_or(&ast.ty), - span: &ast.span(), - }; + ast.attrs + .iter() + .map(|attr| { + let info = FieldInfo { + binding, + ty: inner_ty.inner_type().unwrap_or(&ast.ty), + span: &ast.span(), + }; - for attr in &ast.attrs { - let name = attr.path.segments.last().unwrap().ident.to_string(); - let name = name.as_str(); - let span = attr.span().unwrap(); + let generated = self + .generate_field_code_inner(kind, attr, info) + .unwrap_or_else(|v| v.to_compile_error()); - let meta = attr.parse_meta()?; - match meta { - Meta::Path(_) => match name { - "primary_span" => { - report_error_if_not_applied_to_span(attr, &info)?; - self.span_field.set_once((binding.binding.clone(), span)); - return Ok(quote! {}); - } - "applicability" if is_suggestion => { - report_error_if_not_applied_to_applicability(attr, &info)?; - let binding = binding.binding.clone(); - self.applicability.set_once((quote! { #binding }, span)); - return Ok(quote! {}); - } - "applicability" => { - span_err(span, "`#[applicability]` is only valid on suggestions").emit(); - return Ok(quote! {}); - } - "skip_arg" => { - return Ok(quote! {}); - } - _ => throw_invalid_attr!(attr, &meta, |diag| { + inner_ty.with(binding, generated) + }) + .collect() + } + + fn generate_field_code_inner( + &mut self, + kind: &SubdiagnosticKind, + attr: &Attribute, + info: FieldInfo<'_>, + ) -> Result<TokenStream, DiagnosticDeriveError> { + let meta = attr.parse_meta()?; + match meta { + Meta::Path(path) => self.generate_field_code_inner_path(kind, attr, info, path), + Meta::List(list @ MetaList { .. }) => { + self.generate_field_code_inner_list(kind, attr, info, list) + } + _ => throw_invalid_attr!(attr, &meta), + } + } + + /// Generates the code for a `[Meta::Path]`-like attribute on a field (e.g. `#[primary_span]`). + fn generate_field_code_inner_path( + &mut self, + kind: &SubdiagnosticKind, + attr: &Attribute, + info: FieldInfo<'_>, + path: Path, + ) -> Result<TokenStream, DiagnosticDeriveError> { + let span = attr.span().unwrap(); + let ident = &path.segments.last().unwrap().ident; + let name = ident.to_string(); + let name = name.as_str(); + + match name { + "skip_arg" => Ok(quote! {}), + "primary_span" => { + if matches!(kind, SubdiagnosticKind::MultipartSuggestion { .. }) { + throw_invalid_attr!(attr, &Meta::Path(path), |diag| { diag.help( - "only `primary_span`, `applicability` and `skip_arg` are valid field \ - attributes", + "multipart suggestions use one or more `#[suggestion_part]`s rather \ + than one `#[primary_span]`", ) - }), - }, - _ => throw_invalid_attr!(attr, &meta), + }) + } + + report_error_if_not_applied_to_span(attr, &info)?; + + let binding = info.binding.binding.clone(); + self.span_field.set_once((binding, span)); + + Ok(quote! {}) } + "suggestion_part" => { + self.has_suggestion_parts = true; + + match kind { + SubdiagnosticKind::MultipartSuggestion { .. } => { + span_err( + span, + "`#[suggestion_part(...)]` attribute without `code = \"...\"`", + ) + .emit(); + Ok(quote! {}) + } + SubdiagnosticKind::Label + | SubdiagnosticKind::Note + | SubdiagnosticKind::Help + | SubdiagnosticKind::Warn + | SubdiagnosticKind::Suggestion { .. } => { + throw_invalid_attr!(attr, &Meta::Path(path), |diag| { + diag.help( + "`#[suggestion_part(...)]` is only valid in multipart suggestions, use `#[primary_span]` instead", + ) + }); + } + } + } + "applicability" => { + if let SubdiagnosticKind::Suggestion { .. } + | SubdiagnosticKind::MultipartSuggestion { .. } = kind + { + report_error_if_not_applied_to_applicability(attr, &info)?; + + let binding = info.binding.binding.clone(); + self.applicability.set_once((quote! { #binding }, span)); + } else { + span_err(span, "`#[applicability]` is only valid on suggestions").emit(); + } + + Ok(quote! {}) + } + _ => throw_invalid_attr!(attr, &Meta::Path(path), |diag| { + let span_attr = if let SubdiagnosticKind::MultipartSuggestion { .. } = kind { + "suggestion_part" + } else { + "primary_span" + }; + diag.help(format!( + "only `{span_attr}`, `applicability` and `skip_arg` are valid field attributes", + )) + }), } + } - let ident = ast.ident.as_ref().unwrap(); + /// Generates the code for a `[Meta::List]`-like attribute on a field (e.g. + /// `#[suggestion_part(code = "...")]`). + fn generate_field_code_inner_list( + &mut self, + kind: &SubdiagnosticKind, + attr: &Attribute, + info: FieldInfo<'_>, + list: MetaList, + ) -> Result<TokenStream, DiagnosticDeriveError> { + let span = attr.span().unwrap(); + let ident = &list.path.segments.last().unwrap().ident; + let name = ident.to_string(); + let name = name.as_str(); + + match name { + "suggestion_part" => { + if !matches!(kind, SubdiagnosticKind::MultipartSuggestion { .. }) { + throw_invalid_attr!(attr, &Meta::List(list), |diag| { + diag.help( + "`#[suggestion_part(...)]` is only valid in multipart suggestions", + ) + }) + } - let diag = &self.diag; - let generated = quote! { - #diag.set_arg( - stringify!(#ident), - #binding - ); - }; + self.has_suggestion_parts = true; + + report_error_if_not_applied_to_span(attr, &info)?; + + let mut code = None; + for nested_attr in list.nested.iter() { + let NestedMeta::Meta(ref meta) = nested_attr else { + throw_invalid_nested_attr!(attr, &nested_attr); + }; + + let span = meta.span().unwrap(); + let nested_name = meta.path().segments.last().unwrap().ident.to_string(); + let nested_name = nested_name.as_str(); + + let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) = meta else { + throw_invalid_nested_attr!(attr, &nested_attr); + }; + + match nested_name { + "code" => { + let formatted_str = self.build_format(&value.value(), value.span()); + code.set_once((formatted_str, span)); + } + _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help("`code` is the only valid nested attribute") + }), + } + } + + let Some((code, _)) = code else { + span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`") + .emit(); + return Ok(quote! {}); + }; + let binding = info.binding; - Ok(inner_ty.with(binding, generated)) + Ok(quote! { suggestions.push((#binding, #code)); }) + } + _ => throw_invalid_attr!(attr, &Meta::List(list), |diag| { + let span_attr = if let SubdiagnosticKind::MultipartSuggestion { .. } = kind { + "suggestion_part" + } else { + "primary_span" + }; + diag.help(format!( + "only `{span_attr}`, `applicability` and `skip_arg` are valid field attributes", + )) + }), + } } - fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> { - self.identify_kind()?; - let Some(kind) = self.kind.map(|(kind, _)| kind) else { + pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> { + let Some((kind, slug)) = self.identify_kind()? else { throw_span_err!( self.variant.ast().ident.span().unwrap(), "subdiagnostic kind not specified" ); }; - let is_suggestion = matches!(kind, SubdiagnosticKind::Suggestion(_)); - - let mut args = TokenStream::new(); - for binding in self.variant.bindings() { - let arg = self - .generate_field_code(binding, is_suggestion) - .unwrap_or_else(|v| v.to_compile_error()); - args.extend(arg); - } - - // Missing slug errors will already have been reported. - let slug = self - .slug - .as_ref() - .map(|(slug, _)| slug.clone()) - .unwrap_or_else(|| parse_quote! { you::need::to::specify::a::slug }); - let code = match self.code.as_ref() { - Some((code, _)) => Some(quote! { #code }), - None if is_suggestion => { - span_err(self.span, "suggestion without `code = \"...\"`").emit(); - Some(quote! { /* macro error */ "..." }) + let init = match &kind { + SubdiagnosticKind::Label + | SubdiagnosticKind::Note + | SubdiagnosticKind::Help + | SubdiagnosticKind::Warn + | SubdiagnosticKind::Suggestion { .. } => quote! {}, + SubdiagnosticKind::MultipartSuggestion { .. } => { + quote! { let mut suggestions = Vec::new(); } } - None => None, }; + let attr_args: TokenStream = self + .variant + .bindings() + .iter() + .filter(|binding| !binding.ast().attrs.is_empty()) + .map(|binding| self.generate_field_attr_code(binding, &kind)) + .collect(); + let span_field = self.span_field.as_ref().map(|(span, _)| span); - let applicability = match self.applicability.clone() { - Some((applicability, _)) => Some(applicability), - None if is_suggestion => { - span_err(self.span, "suggestion without `applicability`").emit(); - Some(quote! { rustc_errors::Applicability::Unspecified }) - } - None => None, - }; + let applicability = self.applicability.take().map_or_else( + || quote! { rustc_errors::Applicability::Unspecified }, + |(applicability, _)| applicability, + ); let diag = &self.diag; let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind); let message = quote! { rustc_errors::fluent::#slug }; - let call = if matches!(kind, SubdiagnosticKind::Suggestion(..)) { - if let Some(span) = span_field { - quote! { #diag.#name(#span, #message, #code, #applicability); } - } else { - span_err(self.span, "suggestion without `#[primary_span]` field").emit(); - quote! { unreachable!(); } + let call = match kind { + SubdiagnosticKind::Suggestion { suggestion_kind, code } => { + if let Some(span) = span_field { + let style = suggestion_kind.to_suggestion_style(); + + quote! { #diag.#name(#span, #message, #code, #applicability, #style); } + } else { + span_err(self.span, "suggestion without `#[primary_span]` field").emit(); + quote! { unreachable!(); } + } } - } else if matches!(kind, SubdiagnosticKind::Label) { - if let Some(span) = span_field { - quote! { #diag.#name(#span, #message); } - } else { - span_err(self.span, "label without `#[primary_span]` field").emit(); - quote! { unreachable!(); } + SubdiagnosticKind::MultipartSuggestion { suggestion_kind } => { + if !self.has_suggestion_parts { + span_err( + self.span, + "multipart suggestion without any `#[suggestion_part(...)]` fields", + ) + .emit(); + } + + let style = suggestion_kind.to_suggestion_style(); + + quote! { #diag.#name(#message, suggestions, #applicability, #style); } } - } else { - if let Some(span) = span_field { - quote! { #diag.#name(#span, #message); } - } else { - quote! { #diag.#name(#message); } + SubdiagnosticKind::Label => { + if let Some(span) = span_field { + quote! { #diag.#name(#span, #message); } + } else { + span_err(self.span, "label without `#[primary_span]` field").emit(); + quote! { unreachable!(); } + } + } + _ => { + if let Some(span) = span_field { + quote! { #diag.#name(#span, #message); } + } else { + quote! { #diag.#name(#message); } + } } }; + let plain_args: TokenStream = self + .variant + .bindings() + .iter() + .filter(|binding| binding.ast().attrs.is_empty()) + .map(|binding| self.generate_field_set_arg(binding)) + .collect(); + Ok(quote! { + #init + #attr_args #call - #args + #plain_args }) } } diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index 8faac8ef36a..20ee5dfc727 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -171,8 +171,13 @@ decl_derive!( suggestion_short, suggestion_hidden, suggestion_verbose, + multipart_suggestion, + multipart_suggestion_short, + multipart_suggestion_hidden, + multipart_suggestion_verbose, // field attributes skip_arg, primary_span, + suggestion_part, applicability)] => diagnostics::session_subdiagnostic_derive ); diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs index 7660a2f3af6..57555433f55 100644 --- a/compiler/rustc_middle/src/ty/structural_impls.rs +++ b/compiler/rustc_middle/src/ty/structural_impls.rs @@ -844,6 +844,12 @@ impl<'tcx, T: TypeVisitable<'tcx>> TypeVisitable<'tcx> for Vec<T> { } } +impl<'tcx, T: TypeVisitable<'tcx>> TypeVisitable<'tcx> for &[T] { + fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> { + self.iter().try_for_each(|t| t.visit_with(visitor)) + } +} + impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for Box<[T]> { fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> { self.try_map_id(|t| t.try_fold_with(folder)) diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 8d00129b1b1..aa73625ff81 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -944,9 +944,11 @@ supported_targets! { ("aarch64-unknown-openbsd", aarch64_unknown_openbsd), ("i686-unknown-openbsd", i686_unknown_openbsd), + ("powerpc-unknown-openbsd", powerpc_unknown_openbsd), + ("powerpc64-unknown-openbsd", powerpc64_unknown_openbsd), + ("riscv64gc-unknown-openbsd", riscv64gc_unknown_openbsd), ("sparc64-unknown-openbsd", sparc64_unknown_openbsd), ("x86_64-unknown-openbsd", x86_64_unknown_openbsd), - ("powerpc-unknown-openbsd", powerpc_unknown_openbsd), ("aarch64-unknown-netbsd", aarch64_unknown_netbsd), ("armv6-unknown-netbsd-eabihf", armv6_unknown_netbsd_eabihf), diff --git a/compiler/rustc_target/src/spec/powerpc64_unknown_openbsd.rs b/compiler/rustc_target/src/spec/powerpc64_unknown_openbsd.rs new file mode 100644 index 00000000000..9cb3a67dc58 --- /dev/null +++ b/compiler/rustc_target/src/spec/powerpc64_unknown_openbsd.rs @@ -0,0 +1,17 @@ +use crate::abi::Endian; +use crate::spec::{LinkerFlavor, Target, TargetOptions}; + +pub fn target() -> Target { + let mut base = super::openbsd_base::opts(); + base.cpu = "ppc64".into(); + base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]); + base.max_atomic_width = Some(64); + + Target { + llvm_target: "powerpc64-unknown-openbsd".into(), + pointer_width: 64, + data_layout: "E-m:e-i64:64-n32:64".into(), + arch: "powerpc64".into(), + options: TargetOptions { endian: Endian::Big, mcount: "_mcount".into(), ..base }, + } +} diff --git a/compiler/rustc_target/src/spec/riscv64gc_unknown_openbsd.rs b/compiler/rustc_target/src/spec/riscv64gc_unknown_openbsd.rs new file mode 100644 index 00000000000..cd10f3afaac --- /dev/null +++ b/compiler/rustc_target/src/spec/riscv64gc_unknown_openbsd.rs @@ -0,0 +1,18 @@ +use crate::spec::{CodeModel, Target, TargetOptions}; + +pub fn target() -> Target { + Target { + llvm_target: "riscv64-unknown-openbsd".into(), + pointer_width: 64, + data_layout: "e-m:e-p:64:64-i64:64-i128:128-n64-S128".into(), + arch: "riscv64".into(), + options: TargetOptions { + code_model: Some(CodeModel::Medium), + cpu: "generic-rv64".into(), + features: "+m,+a,+f,+d,+c".into(), + llvm_abiname: "lp64d".into(), + max_atomic_width: Some(64), + ..super::openbsd_base::opts() + }, + } +} diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs index 33d74249e7b..f6596950c2a 100644 --- a/compiler/rustc_typeck/src/check/expr.rs +++ b/compiler/rustc_typeck/src/check/expr.rs @@ -21,7 +21,6 @@ use crate::errors::{ }; use crate::type_error_struct; -use super::suggest_call_constructor; use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive}; use rustc_ast as ast; use rustc_data_structures::fx::FxHashMap; @@ -44,7 +43,7 @@ use rustc_middle::middle::stability; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase}; use rustc_middle::ty::error::TypeError::FieldMisMatch; use rustc_middle::ty::subst::SubstsRef; -use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TypeVisitable}; +use rustc_middle::ty::{self, AdtKind, Ty, TypeVisitable}; use rustc_session::parse::feature_err; use rustc_span::hygiene::DesugaringKind; use rustc_span::lev_distance::find_best_match_for_name; @@ -2141,15 +2140,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { field: Ident, ) -> Ty<'tcx> { debug!("check_field(expr: {:?}, base: {:?}, field: {:?})", expr, base, field); - let expr_t = self.check_expr(base); - let expr_t = self.structurally_resolved_type(base.span, expr_t); + let base_ty = self.check_expr(base); + let base_ty = self.structurally_resolved_type(base.span, base_ty); let mut private_candidate = None; - let mut autoderef = self.autoderef(expr.span, expr_t); - while let Some((base_t, _)) = autoderef.next() { - debug!("base_t: {:?}", base_t); - match base_t.kind() { + let mut autoderef = self.autoderef(expr.span, base_ty); + while let Some((deref_base_ty, _)) = autoderef.next() { + debug!("deref_base_ty: {:?}", deref_base_ty); + match deref_base_ty.kind() { ty::Adt(base_def, substs) if !base_def.is_enum() => { - debug!("struct named {:?}", base_t); + debug!("struct named {:?}", deref_base_ty); let (ident, def_scope) = self.tcx.adjust_ident_and_get_scope(field, base_def.did(), self.body_id); let fields = &base_def.non_enum_variant().fields; @@ -2197,23 +2196,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // (#90483) apply adjustments to avoid ExprUseVisitor from // creating erroneous projection. self.apply_adjustments(base, adjustments); - self.ban_private_field_access(expr, expr_t, field, did); + self.ban_private_field_access(expr, base_ty, field, did); return field_ty; } if field.name == kw::Empty { - } else if self.method_exists(field, expr_t, expr.hir_id, true) { - self.ban_take_value_of_method(expr, expr_t, field); - } else if !expr_t.is_primitive_ty() { - self.ban_nonexisting_field(field, base, expr, expr_t); + } else if self.method_exists(field, base_ty, expr.hir_id, true) { + self.ban_take_value_of_method(expr, base_ty, field); + } else if !base_ty.is_primitive_ty() { + self.ban_nonexisting_field(field, base, expr, base_ty); } else { let field_name = field.to_string(); let mut err = type_error_struct!( self.tcx().sess, field.span, - expr_t, + base_ty, E0610, - "`{expr_t}` is a primitive type and therefore doesn't have fields", + "`{base_ty}` is a primitive type and therefore doesn't have fields", ); let is_valid_suffix = |field: &str| { if field == "f32" || field == "f64" { @@ -2251,7 +2250,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { None } }; - if let ty::Infer(ty::IntVar(_)) = expr_t.kind() + if let ty::Infer(ty::IntVar(_)) = base_ty.kind() && let ExprKind::Lit(Spanned { node: ast::LitKind::Int(_, ast::LitIntType::Unsuffixed), .. @@ -2280,35 +2279,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx().ty_error() } - fn check_call_constructor( - &self, - err: &mut Diagnostic, - base: &'tcx hir::Expr<'tcx>, - def_id: DefId, - ) { - if let Some(local_id) = def_id.as_local() { - let hir_id = self.tcx.hir().local_def_id_to_hir_id(local_id); - let node = self.tcx.hir().get(hir_id); - - if let Some(fields) = node.tuple_fields() { - let kind = match self.tcx.opt_def_kind(local_id) { - Some(DefKind::Ctor(of, _)) => of, - _ => return, - }; - - suggest_call_constructor(base.span, kind, fields.len(), err); - } - } else { - // The logic here isn't smart but `associated_item_def_ids` - // doesn't work nicely on local. - if let DefKind::Ctor(of, _) = self.tcx.def_kind(def_id) { - let parent_def_id = self.tcx.parent(def_id); - let fields = self.tcx.associated_item_def_ids(parent_def_id); - suggest_call_constructor(base.span, of, fields.len(), err); - } - } - } - fn suggest_await_on_field_access( &self, err: &mut Diagnostic, @@ -2351,40 +2321,52 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn ban_nonexisting_field( &self, - field: Ident, + ident: Ident, base: &'tcx hir::Expr<'tcx>, expr: &'tcx hir::Expr<'tcx>, - expr_t: Ty<'tcx>, + base_ty: Ty<'tcx>, ) { debug!( - "ban_nonexisting_field: field={:?}, base={:?}, expr={:?}, expr_ty={:?}", - field, base, expr, expr_t + "ban_nonexisting_field: field={:?}, base={:?}, expr={:?}, base_ty={:?}", + ident, base, expr, base_ty ); - let mut err = self.no_such_field_err(field, expr_t, base.hir_id); + let mut err = self.no_such_field_err(ident, base_ty, base.hir_id); - match *expr_t.peel_refs().kind() { + match *base_ty.peel_refs().kind() { ty::Array(_, len) => { - self.maybe_suggest_array_indexing(&mut err, expr, base, field, len); + self.maybe_suggest_array_indexing(&mut err, expr, base, ident, len); } ty::RawPtr(..) => { - self.suggest_first_deref_field(&mut err, expr, base, field); + self.suggest_first_deref_field(&mut err, expr, base, ident); } ty::Adt(def, _) if !def.is_enum() => { - self.suggest_fields_on_recordish(&mut err, def, field, expr.span); + self.suggest_fields_on_recordish(&mut err, def, ident, expr.span); } ty::Param(param_ty) => { self.point_at_param_definition(&mut err, param_ty); } ty::Opaque(_, _) => { - self.suggest_await_on_field_access(&mut err, field, base, expr_t.peel_refs()); - } - ty::FnDef(def_id, _) => { - self.check_call_constructor(&mut err, base, def_id); + self.suggest_await_on_field_access(&mut err, ident, base, base_ty.peel_refs()); } _ => {} } - if field.name == kw::Await { + self.suggest_fn_call(&mut err, base, base_ty, |output_ty| { + if let ty::Adt(def, _) = output_ty.kind() && !def.is_enum() { + def.non_enum_variant().fields.iter().any(|field| { + field.ident(self.tcx) == ident + && field.vis.is_accessible_from(expr.hir_id.owner.to_def_id(), self.tcx) + }) + } else if let ty::Tuple(tys) = output_ty.kind() + && let Ok(idx) = ident.as_str().parse::<usize>() + { + idx < tys.len() + } else { + false + } + }); + + if ident.name == kw::Await { // We know by construction that `<expr>.await` is either on Rust 2015 // or results in `ExprKind::Await`. Suggest switching the edition to 2018. err.note("to `.await` a `Future`, switch to Rust 2018 or later"); diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs index 64d261285c5..939f4612d44 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs @@ -2,6 +2,7 @@ use super::FnCtxt; use crate::astconv::AstConv; use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel}; +use hir::def_id::DefId; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::{Applicability, Diagnostic, MultiSpan}; use rustc_hir as hir; @@ -61,70 +62,51 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pointing_at_return_type } - /// When encountering an fn-like ctor that needs to unify with a value, check whether calling - /// the ctor would successfully solve the type mismatch and if so, suggest it: + /// When encountering an fn-like type, try accessing the output of the type + /// // and suggesting calling it if it satisfies a predicate (i.e. if the + /// output has a method or a field): /// ```compile_fail,E0308 /// fn foo(x: usize) -> usize { x } /// let x: usize = foo; // suggest calling the `foo` function: `foo(42)` /// ``` - fn suggest_fn_call( + pub(crate) fn suggest_fn_call( &self, err: &mut Diagnostic, expr: &hir::Expr<'_>, - expected: Ty<'tcx>, found: Ty<'tcx>, + can_satisfy: impl FnOnce(Ty<'tcx>) -> bool, ) -> bool { - let (def_id, output, inputs) = match *found.kind() { - ty::FnDef(def_id, _) => { - let fn_sig = found.fn_sig(self.tcx); - (def_id, fn_sig.output(), fn_sig.inputs().skip_binder().len()) - } - ty::Closure(def_id, substs) => { - let fn_sig = substs.as_closure().sig(); - (def_id, fn_sig.output(), fn_sig.inputs().skip_binder().len() - 1) - } - ty::Opaque(def_id, substs) => { - let sig = self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| { - if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder() - && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output() - // args tuple will always be substs[1] - && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() - { - Some(( - pred.kind().rebind(proj.term.ty().unwrap()), - args.len(), - )) - } else { - None - } - }); - if let Some((output, inputs)) = sig { - (def_id, output, inputs) - } else { - return false; - } - } - _ => return false, - }; - - let output = self.replace_bound_vars_with_fresh_vars(expr.span, infer::FnCall, output); - let output = self.normalize_associated_types_in(expr.span, output); - if !output.is_ty_var() && self.can_coerce(output, expected) { - let (sugg_call, mut applicability) = match inputs { + let Some((def_id_or_name, output, inputs)) = self.extract_callable_info(expr, found) + else { return false; }; + if can_satisfy(output) { + let (sugg_call, mut applicability) = match inputs.len() { 0 => ("".to_string(), Applicability::MachineApplicable), 1..=4 => ( - (0..inputs).map(|_| "_").collect::<Vec<_>>().join(", "), - Applicability::MachineApplicable, + inputs + .iter() + .map(|ty| { + if ty.is_suggestable(self.tcx, false) { + format!("/* {ty} */") + } else { + "".to_string() + } + }) + .collect::<Vec<_>>() + .join(", "), + Applicability::HasPlaceholders, ), - _ => ("...".to_string(), Applicability::HasPlaceholders), + _ => ("/* ... */".to_string(), Applicability::HasPlaceholders), }; - let msg = match self.tcx.def_kind(def_id) { - DefKind::Fn => "call this function", - DefKind::Closure | DefKind::OpaqueTy => "call this closure", - DefKind::Ctor(CtorOf::Struct, _) => "instantiate this tuple struct", - DefKind::Ctor(CtorOf::Variant, _) => "instantiate this tuple variant", - _ => "call this function", + let msg = match def_id_or_name { + DefIdOrName::DefId(def_id) => match self.tcx.def_kind(def_id) { + DefKind::Ctor(CtorOf::Struct, _) => "instantiate this tuple struct".to_string(), + DefKind::Ctor(CtorOf::Variant, _) => { + "instantiate this tuple variant".to_string() + } + kind => format!("call this {}", kind.descr(def_id)), + }, + DefIdOrName::Name(name) => format!("call this {name}"), }; let sugg = match expr.kind { @@ -161,6 +143,179 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false } + fn extract_callable_info( + &self, + expr: &Expr<'_>, + found: Ty<'tcx>, + ) -> Option<(DefIdOrName, Ty<'tcx>, Vec<Ty<'tcx>>)> { + // Autoderef is useful here because sometimes we box callables, etc. + let Some((def_id_or_name, output, inputs)) = self.autoderef(expr.span, found).silence_errors().find_map(|(found, _)| { + match *found.kind() { + ty::FnPtr(fn_sig) => + Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.inputs())), + ty::FnDef(def_id, _) => { + let fn_sig = found.fn_sig(self.tcx); + Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs())) + } + ty::Closure(def_id, substs) => { + let fn_sig = substs.as_closure().sig(); + Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs().map_bound(|inputs| &inputs[1..]))) + } + ty::Opaque(def_id, substs) => { + self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| { + if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder() + && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output() + // args tuple will always be substs[1] + && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() + { + Some(( + DefIdOrName::DefId(def_id), + pred.kind().rebind(proj.term.ty().unwrap()), + pred.kind().rebind(args.as_slice()), + )) + } else { + None + } + }) + } + ty::Dynamic(data, _) => { + data.iter().find_map(|pred| { + if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder() + && Some(proj.item_def_id) == self.tcx.lang_items().fn_once_output() + // for existential projection, substs are shifted over by 1 + && let ty::Tuple(args) = proj.substs.type_at(0).kind() + { + Some(( + DefIdOrName::Name("trait object"), + pred.rebind(proj.term.ty().unwrap()), + pred.rebind(args.as_slice()), + )) + } else { + None + } + }) + } + ty::Param(param) => { + let def_id = self.tcx.generics_of(self.body_id.owner).type_param(¶m, self.tcx).def_id; + self.tcx.predicates_of(self.body_id.owner).predicates.iter().find_map(|(pred, _)| { + if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder() + && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output() + && proj.projection_ty.self_ty() == found + // args tuple will always be substs[1] + && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() + { + Some(( + DefIdOrName::DefId(def_id), + pred.kind().rebind(proj.term.ty().unwrap()), + pred.kind().rebind(args.as_slice()), + )) + } else { + None + } + }) + } + _ => None, + } + }) else { return None; }; + + let output = self.replace_bound_vars_with_fresh_vars(expr.span, infer::FnCall, output); + let inputs = inputs + .skip_binder() + .iter() + .map(|ty| { + self.replace_bound_vars_with_fresh_vars( + expr.span, + infer::FnCall, + inputs.rebind(*ty), + ) + }) + .collect(); + + // We don't want to register any extra obligations, which should be + // implied by wf, but also because that would possibly result in + // erroneous errors later on. + let infer::InferOk { value: output, obligations: _ } = + self.normalize_associated_types_in_as_infer_ok(expr.span, output); + + if output.is_ty_var() { None } else { Some((def_id_or_name, output, inputs)) } + } + + pub fn suggest_two_fn_call( + &self, + err: &mut Diagnostic, + lhs_expr: &'tcx hir::Expr<'tcx>, + lhs_ty: Ty<'tcx>, + rhs_expr: &'tcx hir::Expr<'tcx>, + rhs_ty: Ty<'tcx>, + can_satisfy: impl FnOnce(Ty<'tcx>, Ty<'tcx>) -> bool, + ) -> bool { + let Some((_, lhs_output_ty, lhs_inputs)) = self.extract_callable_info(lhs_expr, lhs_ty) + else { return false; }; + let Some((_, rhs_output_ty, rhs_inputs)) = self.extract_callable_info(rhs_expr, rhs_ty) + else { return false; }; + + if can_satisfy(lhs_output_ty, rhs_output_ty) { + let mut sugg = vec![]; + let mut applicability = Applicability::MachineApplicable; + + for (expr, inputs) in [(lhs_expr, lhs_inputs), (rhs_expr, rhs_inputs)] { + let (sugg_call, this_applicability) = match inputs.len() { + 0 => ("".to_string(), Applicability::MachineApplicable), + 1..=4 => ( + inputs + .iter() + .map(|ty| { + if ty.is_suggestable(self.tcx, false) { + format!("/* {ty} */") + } else { + "/* value */".to_string() + } + }) + .collect::<Vec<_>>() + .join(", "), + Applicability::HasPlaceholders, + ), + _ => ("/* ... */".to_string(), Applicability::HasPlaceholders), + }; + + applicability = applicability.max(this_applicability); + + match expr.kind { + hir::ExprKind::Call(..) + | hir::ExprKind::Path(..) + | hir::ExprKind::Index(..) + | hir::ExprKind::Lit(..) => { + sugg.extend([(expr.span.shrink_to_hi(), format!("({sugg_call})"))]); + } + hir::ExprKind::Closure { .. } => { + // Might be `{ expr } || { bool }` + applicability = Applicability::MaybeIncorrect; + sugg.extend([ + (expr.span.shrink_to_lo(), "(".to_string()), + (expr.span.shrink_to_hi(), format!(")({sugg_call})")), + ]); + } + _ => { + sugg.extend([ + (expr.span.shrink_to_lo(), "(".to_string()), + (expr.span.shrink_to_hi(), format!(")({sugg_call})")), + ]); + } + } + } + + err.multipart_suggestion_verbose( + format!("use parentheses to call these"), + sugg, + applicability, + ); + + true + } else { + false + } + } + pub fn suggest_deref_ref_or_into( &self, err: &mut Diagnostic, @@ -178,12 +333,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { err.span_suggestion(sp, &msg, suggestion, applicability); } - } else if let (ty::FnDef(def_id, ..), true) = - (&found.kind(), self.suggest_fn_call(err, expr, expected, found)) + } else if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected)) + && let ty::FnDef(def_id, ..) = &found.kind() + && let Some(sp) = self.tcx.hir().span_if_local(*def_id) { - if let Some(sp) = self.tcx.hir().span_if_local(*def_id) { - err.span_label(sp, format!("{found} defined here")); - } + err.span_label(sp, format!("{found} defined here")); } else if !self.check_for_cast(err, expr, found, expected, expected_ty_expr) { let methods = self.get_conversion_methods(expr.span, expected, found, expr.hir_id); if !methods.is_empty() { @@ -911,3 +1065,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } } + +enum DefIdOrName { + DefId(DefId), + Name(&'static str), +} diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs index e532f392157..e99782fdc65 100644 --- a/compiler/rustc_typeck/src/check/method/suggest.rs +++ b/compiler/rustc_typeck/src/check/method/suggest.rs @@ -31,7 +31,7 @@ use std::cmp::Ordering; use std::iter; use super::probe::{Mode, ProbeScope}; -use super::{super::suggest_call_constructor, CandidateSource, MethodError, NoMatchData}; +use super::{CandidateSource, MethodError, NoMatchData}; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn is_fn_ty(&self, ty: Ty<'tcx>, span: Span) -> bool { @@ -363,44 +363,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } - if self.is_fn_ty(rcvr_ty, span) { - if let SelfSource::MethodCall(expr) = source { - let suggest = if let ty::FnDef(def_id, _) = rcvr_ty.kind() { - if let Some(local_id) = def_id.as_local() { - let hir_id = tcx.hir().local_def_id_to_hir_id(local_id); - let node = tcx.hir().get(hir_id); - let fields = node.tuple_fields(); - if let Some(fields) = fields - && let Some(DefKind::Ctor(of, _)) = self.tcx.opt_def_kind(local_id) { - Some((fields.len(), of)) - } else { - None - } - } else { - // The logic here isn't smart but `associated_item_def_ids` - // doesn't work nicely on local. - if let DefKind::Ctor(of, _) = tcx.def_kind(def_id) { - let parent_def_id = tcx.parent(*def_id); - Some((tcx.associated_item_def_ids(parent_def_id).len(), of)) - } else { - None - } - } - } else { - None - }; - - // If the function is a tuple constructor, we recommend that they call it - if let Some((fields, kind)) = suggest { - suggest_call_constructor(expr.span, kind, fields, &mut err); - } else { - // General case - err.span_label( - expr.span, - "this is a function, perhaps you wish to call it", - ); - } - } + if let SelfSource::MethodCall(rcvr_expr) = source { + self.suggest_fn_call(&mut err, rcvr_expr, rcvr_ty, |output_ty| { + let call_expr = self + .tcx + .hir() + .expect_expr(self.tcx.hir().get_parent_node(rcvr_expr.hir_id)); + let probe = self.lookup_probe( + span, + item_name, + output_ty, + call_expr, + ProbeScope::AllTraits, + ); + probe.is_ok() + }); } let mut custom_span_label = false; diff --git a/compiler/rustc_typeck/src/check/mod.rs b/compiler/rustc_typeck/src/check/mod.rs index f8d839b6483..3281dd8298b 100644 --- a/compiler/rustc_typeck/src/check/mod.rs +++ b/compiler/rustc_typeck/src/check/mod.rs @@ -96,7 +96,6 @@ use check::{check_abi, check_fn, check_mod_item_types}; pub use diverges::Diverges; pub use expectation::Expectation; pub use fn_ctxt::*; -use hir::def::CtorOf; pub use inherited::{Inherited, InheritedBuilder}; use crate::astconv::AstConv; @@ -960,31 +959,3 @@ fn has_expected_num_generic_args<'tcx>( generics.count() == expected + if generics.has_self { 1 } else { 0 } }) } - -/// Suggests calling the constructor of a tuple struct or enum variant -/// -/// * `snippet` - The snippet of code that references the constructor -/// * `span` - The span of the snippet -/// * `params` - The number of parameters the constructor accepts -/// * `err` - A mutable diagnostic builder to add the suggestion to -fn suggest_call_constructor(span: Span, kind: CtorOf, params: usize, err: &mut Diagnostic) { - // Note: tuple-structs don't have named fields, so just use placeholders - let args = vec!["_"; params].join(", "); - let applicable = if params > 0 { - Applicability::HasPlaceholders - } else { - // When n = 0, it's an empty-tuple struct/enum variant - // so we trivially know how to construct it - Applicability::MachineApplicable - }; - let kind = match kind { - CtorOf::Struct => "a struct", - CtorOf::Variant => "an enum variant", - }; - err.span_label(span, &format!("this is the constructor of {kind}")); - err.multipart_suggestion( - "call the constructor", - vec![(span.shrink_to_lo(), "(".to_string()), (span.shrink_to_hi(), format!(")({args})"))], - applicable, - ); -} diff --git a/compiler/rustc_typeck/src/check/op.rs b/compiler/rustc_typeck/src/check/op.rs index eb0c51bb2f9..952086e898f 100644 --- a/compiler/rustc_typeck/src/check/op.rs +++ b/compiler/rustc_typeck/src/check/op.rs @@ -410,26 +410,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let mut err = struct_span_err!(self.tcx.sess, op.span, E0369, "{message}"); if !lhs_expr.span.eq(&rhs_expr.span) { - self.add_type_neq_err_label( - &mut err, - lhs_expr.span, - lhs_ty, - rhs_ty, - rhs_expr, - op, - is_assign, - expected, - ); - self.add_type_neq_err_label( - &mut err, - rhs_expr.span, - rhs_ty, - lhs_ty, - lhs_expr, - op, - is_assign, - expected, - ); + err.span_label(lhs_expr.span, lhs_ty.to_string()); + err.span_label(rhs_expr.span, rhs_ty.to_string()); } self.note_unmet_impls_on_type(&mut err, errors); (err, missing_trait, use_output) @@ -468,17 +450,50 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; + let is_compatible = |lhs_ty, rhs_ty| { + self.lookup_op_method( + lhs_ty, + Some(rhs_ty), + Some(rhs_expr), + Op::Binary(op, is_assign), + expected, + ) + .is_ok() + }; + // We should suggest `a + b` => `*a + b` if `a` is copy, and suggest // `a += b` => `*a += b` if a is a mut ref. - if is_assign == IsAssign::Yes - && let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) { - suggest_deref_binop(lhs_deref_ty); + if !op.span.can_be_used_for_suggestions() { + // Suppress suggestions when lhs and rhs are not in the same span as the error + } else if is_assign == IsAssign::Yes + && let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) + { + suggest_deref_binop(lhs_deref_ty); } else if is_assign == IsAssign::No - && let Ref(_, lhs_deref_ty, _) = lhs_ty.kind() { - if self.type_is_copy_modulo_regions(self.param_env, *lhs_deref_ty, lhs_expr.span) { + && let Ref(_, lhs_deref_ty, _) = lhs_ty.kind() + { + if self.type_is_copy_modulo_regions( + self.param_env, + *lhs_deref_ty, + lhs_expr.span, + ) { suggest_deref_binop(*lhs_deref_ty); } + } else if self.suggest_fn_call(&mut err, lhs_expr, lhs_ty, |lhs_ty| { + is_compatible(lhs_ty, rhs_ty) + }) || self.suggest_fn_call(&mut err, rhs_expr, rhs_ty, |rhs_ty| { + is_compatible(lhs_ty, rhs_ty) + }) || self.suggest_two_fn_call( + &mut err, + rhs_expr, + rhs_ty, + lhs_expr, + lhs_ty, + |lhs_ty, rhs_ty| is_compatible(lhs_ty, rhs_ty), + ) { + // Cool } + if let Some(missing_trait) = missing_trait { let mut visitor = TypeParamVisitor(vec![]); visitor.visit_ty(lhs_ty); @@ -548,69 +563,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { (lhs_ty, rhs_ty, return_ty) } - /// If one of the types is an uncalled function and calling it would yield the other type, - /// suggest calling the function. Returns `true` if suggestion would apply (even if not given). - fn add_type_neq_err_label( - &self, - err: &mut Diagnostic, - span: Span, - ty: Ty<'tcx>, - other_ty: Ty<'tcx>, - other_expr: &'tcx hir::Expr<'tcx>, - op: hir::BinOp, - is_assign: IsAssign, - expected: Expectation<'tcx>, - ) -> bool /* did we suggest to call a function because of missing parentheses? */ { - err.span_label(span, ty.to_string()); - if let FnDef(def_id, _) = *ty.kind() { - if !self.tcx.has_typeck_results(def_id) { - return false; - } - // FIXME: Instead of exiting early when encountering bound vars in - // the function signature, consider keeping the binder here and - // propagating it downwards. - let Some(fn_sig) = self.tcx.fn_sig(def_id).no_bound_vars() else { - return false; - }; - - let other_ty = if let FnDef(def_id, _) = *other_ty.kind() { - if !self.tcx.has_typeck_results(def_id) { - return false; - } - // We're emitting a suggestion, so we can just ignore regions - self.tcx.fn_sig(def_id).skip_binder().output() - } else { - other_ty - }; - - if self - .lookup_op_method( - fn_sig.output(), - Some(other_ty), - Some(other_expr), - Op::Binary(op, is_assign), - expected, - ) - .is_ok() - { - let (variable_snippet, applicability) = if !fn_sig.inputs().is_empty() { - ("( /* arguments */ )", Applicability::HasPlaceholders) - } else { - ("()", Applicability::MaybeIncorrect) - }; - - err.span_suggestion_verbose( - span.shrink_to_hi(), - "you might have forgotten to call this function", - variable_snippet, - applicability, - ); - return true; - } - } - false - } - /// Provide actionable suggestions when trying to add two strings with incorrect types, /// like `&str + &str`, `String + String` and `&str + &String`. /// diff --git a/compiler/rustc_typeck/src/check/wfcheck.rs b/compiler/rustc_typeck/src/check/wfcheck.rs index ce42647c837..ba42453bd60 100644 --- a/compiler/rustc_typeck/src/check/wfcheck.rs +++ b/compiler/rustc_typeck/src/check/wfcheck.rs @@ -1262,7 +1262,11 @@ fn check_impl<'tcx>( } None => { let self_ty = tcx.type_of(item.def_id); - let self_ty = wfcx.normalize(item.span, None, self_ty); + let self_ty = wfcx.normalize( + item.span, + Some(WellFormedLoc::Ty(item.hir_id().expect_owner())), + self_ty, + ); wfcx.register_wf_obligation( ast_self_ty.span, Some(WellFormedLoc::Ty(item.hir_id().expect_owner())), @@ -1307,7 +1311,11 @@ fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, span: Span, def_id // parameter includes another (e.g., `<T, U = T>`). In those cases, we can't // be sure if it will error or not as user might always specify the other. if !ty.needs_subst() { - wfcx.register_wf_obligation(tcx.def_span(param.def_id), None, ty.into()); + wfcx.register_wf_obligation( + tcx.def_span(param.def_id), + Some(WellFormedLoc::Ty(param.def_id.expect_local())), + ty.into(), + ); } } } @@ -1512,7 +1520,14 @@ fn check_fn_or_method<'tcx>( ); } - wfcx.register_wf_obligation(hir_decl.output.span(), None, sig.output().into()); + wfcx.register_wf_obligation( + hir_decl.output.span(), + Some(WellFormedLoc::Param { + function: def_id, + param_idx: sig.inputs().len().try_into().unwrap(), + }), + sig.output().into(), + ); check_where_clauses(wfcx, span, def_id); } diff --git a/compiler/rustc_typeck/src/hir_wf_check.rs b/compiler/rustc_typeck/src/hir_wf_check.rs index fd9715e6ca3..7b080dc2942 100644 --- a/compiler/rustc_typeck/src/hir_wf_check.rs +++ b/compiler/rustc_typeck/src/hir_wf_check.rs @@ -140,6 +140,10 @@ fn diagnostic_hir_wf_check<'tcx>( hir::Node::ForeignItem(ForeignItem { kind: ForeignItemKind::Static(ty, _), .. }) => Some(*ty), + hir::Node::GenericParam(hir::GenericParam { + kind: hir::GenericParamKind::Type { default: Some(ty), .. }, + .. + }) => Some(*ty), ref node => bug!("Unexpected node {:?}", node), }, WellFormedLoc::Param { function: _, param_idx } => { |
