From bac60dbadb73358144a8e79ccfd512dc91f9c62d Mon Sep 17 00:00:00 2001 From: yifei Date: Thu, 25 Aug 2022 15:51:03 +0800 Subject: [PATCH] give some suggestion for returning anonymous enum --- compiler/rustc_parse/src/lib.rs | 1 + .../rustc_parse/src/parser/diagnostics.rs | 91 +++++++++++++++++++ compiler/rustc_parse/src/parser/ty.rs | 36 ++++++-- compiler/rustc_parse/src/utils.rs | 60 ++++++++++++ .../issue-100741-return-anonymous-enum.rs | 7 ++ .../issue-100741-return-anonymous-enum.stderr | 17 ++++ 6 files changed, 203 insertions(+), 9 deletions(-) create mode 100644 compiler/rustc_parse/src/utils.rs create mode 100644 src/test/ui/return/issue-100741-return-anonymous-enum.rs create mode 100644 src/test/ui/return/issue-100741-return-anonymous-enum.stderr diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index 3691d82a835a9..fc92b3e902ba8 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -30,6 +30,7 @@ pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments"); pub mod parser; use parser::{emit_unclosed_delims, make_unclosed_delims_error, Parser}; pub mod lexer; +mod utils; pub mod validate_attr; // A bunch of utility functions of the form `parse__from_` diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index d7facb29714bc..f93127bc64750 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -880,6 +880,97 @@ impl<'a> Parser<'a> { } } + pub(super) fn check_anonymous_enum(&mut self, span: Span, tys: &[P]) -> PResult<'a, ()> { + use std::fmt::Write; + if tys.len() <= 1 { + return Ok(()); + } + + fn variant_name_and_ty<'a>( + ty: &'a Ty, + lifetimes: &mut FxHashSet<&'a str>, + ) -> Option<(String, String)> { + match &ty.kind { + TyKind::Path(_, path) => { + let mut name = String::new(); + let mut ty_string = String::new(); + + if let Some(seg) = path.segments.iter().last() { + name.push_str(seg.ident.name.as_str()); + ty_string.push_str(seg.ident.name.as_str()); + + if let Some(_args) = &seg.args { + ty_string.push('<'); + ty_string.push_str("..."); + ty_string.push('>'); + } + } + + Some((name, ty_string)) + } + TyKind::Rptr(lifetime, ty) => { + if let Some((mut name, ty)) = variant_name_and_ty(&ty.ty, lifetimes) { + name.push_str("Ref"); + let lifetime = + lifetime.as_ref().map(|l| l.ident.name.as_str()).unwrap_or("'lifetime"); + lifetimes.insert(lifetime); + Some((name, format!("&{} {}", lifetime, ty))) + } else { + None + } + } + _ => None, + } + } + + let mut err = self.struct_span_err(span, "anonymous enums are not supported"); + let mut variant_content = String::new(); + let mut variant_name_set = FxHashSet::default(); + let mut lifetimes = FxHashSet::default(); + tys.iter().for_each(|ty| { + let name_ty = variant_name_and_ty(ty, &mut lifetimes); + if let Some((variant_name, ty)) = name_ty { + let variant_name = crate::utils::to_camel_case(&variant_name); + if !variant_name_set.contains(&variant_name) { + let _ = writeln!( + &mut variant_content, + " {variant_name}({ty})", + variant_name = variant_name, + ty = ty + ); + variant_name_set.insert(variant_name); + } + } + }); + + let mut suggestion_code = String::new(); + suggestion_code.push_str("enum SomeEnum"); + if lifetimes.len() > 0 { + suggestion_code.push_str("<"); + #[allow(rustc::potential_query_instability)] + let mut iter = lifetimes.into_iter(); + if let Some(lifetime) = iter.next() { + suggestion_code.push_str(lifetime); + while let Some(lifetime) = iter.next() { + suggestion_code.push_str(","); + suggestion_code.push_str(lifetime); + } + } + suggestion_code.push_str(">"); + } + suggestion_code.push_str("{\n"); + suggestion_code.push_str(&variant_content); + suggestion_code.push_str("}\n"); + err.span_suggestion( + span, + "consider using enum as return type", + "SomeEnum", + Applicability::HasPlaceholders, + ) + .note(suggestion_code); + Err(err) + } + /// Eats and discards tokens until one of `kets` is encountered. Respects token trees, /// passes through any errors encountered. Used for error recovery. pub(super) fn eat_to_tokens(&mut self, kets: &[&TokenKind]) { diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 76b710095d798..f33cb73f28f42 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -38,7 +38,7 @@ pub(super) enum AllowPlus { No, } -#[derive(PartialEq)] +#[derive(PartialEq, Clone, Copy)] pub(super) enum RecoverQPath { Yes, No, @@ -199,14 +199,32 @@ impl<'a> Parser<'a> { ) -> PResult<'a, FnRetTy> { Ok(if self.eat(&token::RArrow) { // FIXME(Centril): Can we unconditionally `allow_plus`? - let ty = self.parse_ty_common( - allow_plus, - AllowCVariadic::No, - recover_qpath, - recover_return_sign, - None, - RecoverQuestionMark::Yes, - )?; + + let mut tys = vec![]; + let lo = self.token.span; + loop { + let ty = self.parse_ty_common( + allow_plus, + AllowCVariadic::No, + recover_qpath, + recover_return_sign, + None, + RecoverQuestionMark::Yes, + )?; + tys.push(ty); + // maybe a `|` for fn type of closure, `|a: &dyn Fn(i32) -> bool| { ... }` + if self.check_noexpect(&token::BinOp(token::Or)) + && self.look_ahead(1, |tok| tok == &token::OpenDelim(token::Delimiter::Brace)) + { + break; + } + if !self.eat_noexpect(&token::BinOp(token::Or)) { + break; + } + } + let span = lo.to(self.prev_token.span); + self.check_anonymous_enum(span, &tys)?; + let ty = tys.into_iter().next().unwrap(); FnRetTy::Ty(ty) } else if recover_return_sign.can_recover(&self.token.kind) { // Don't `eat` to prevent `=>` from being added as an expected token which isn't diff --git a/compiler/rustc_parse/src/utils.rs b/compiler/rustc_parse/src/utils.rs new file mode 100644 index 0000000000000..78d10ccb99640 --- /dev/null +++ b/compiler/rustc_parse/src/utils.rs @@ -0,0 +1,60 @@ +/// copied from rustc_lint + +/// Some unicode characters *have* case, are considered upper case or lower case, but they *can't* +/// be upper cased or lower cased. For the purposes of the lint suggestion, we care about being able +/// to change the char's case. +fn char_has_case(c: char) -> bool { + let mut l = c.to_lowercase(); + let mut u = c.to_uppercase(); + while let Some(l) = l.next() { + match u.next() { + Some(u) if l != u => return true, + _ => {} + } + } + u.next().is_some() +} + +pub fn to_camel_case(s: &str) -> String { + s.trim_matches('_') + .split('_') + .filter(|component| !component.is_empty()) + .map(|component| { + let mut camel_cased_component = String::new(); + + let mut new_word = true; + let mut prev_is_lower_case = true; + + for c in component.chars() { + // Preserve the case if an uppercase letter follows a lowercase letter, so that + // `camelCase` is converted to `CamelCase`. + if prev_is_lower_case && c.is_uppercase() { + new_word = true; + } + + if new_word { + camel_cased_component.extend(c.to_uppercase()); + } else { + camel_cased_component.extend(c.to_lowercase()); + } + + prev_is_lower_case = c.is_lowercase(); + new_word = false; + } + + camel_cased_component + }) + .fold((String::new(), None), |(acc, prev): (String, Option), next| { + // separate two components with an underscore if their boundary cannot + // be distinguished using an uppercase/lowercase case distinction + let join = if let Some(prev) = prev { + let l = prev.chars().last().unwrap(); + let f = next.chars().next().unwrap(); + !char_has_case(l) && !char_has_case(f) + } else { + false + }; + (acc + if join { "_" } else { "" } + &next, Some(next)) + }) + .0 +} diff --git a/src/test/ui/return/issue-100741-return-anonymous-enum.rs b/src/test/ui/return/issue-100741-return-anonymous-enum.rs new file mode 100644 index 0000000000000..020f99d7f0afb --- /dev/null +++ b/src/test/ui/return/issue-100741-return-anonymous-enum.rs @@ -0,0 +1,7 @@ +struct Foo {} + +fn foo<'a>() -> i32 | Vec | &str | &'a String | Foo { + //~^ ERROR: anonymous enums are not supported +} + +fn main() {} diff --git a/src/test/ui/return/issue-100741-return-anonymous-enum.stderr b/src/test/ui/return/issue-100741-return-anonymous-enum.stderr new file mode 100644 index 0000000000000..a2751260b77d5 --- /dev/null +++ b/src/test/ui/return/issue-100741-return-anonymous-enum.stderr @@ -0,0 +1,17 @@ +error: anonymous enums are not supported + --> $DIR/issue-100741-return-anonymous-enum.rs:3:17 + | +LL | fn foo<'a>() -> i32 | Vec | &str | &'a String | Foo { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using enum as return type: `SomeEnum` + | + = note: enum SomeEnum<'lifetime,'a>{ + I32(i32) + Vec(Vec<...>) + StrRef(&'lifetime str) + StringRef(&'a String) + Foo(Foo) + } + + +error: aborting due to previous error +