Skip to content

Commit

Permalink
give some suggestion for returning anonymous enum
Browse files Browse the repository at this point in the history
  • Loading branch information
LYF1999 committed Aug 25, 2022
1 parent a9bb589 commit bac60db
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 9 deletions.
1 change: 1 addition & 0 deletions compiler/rustc_parse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_<thing>_from_<source>`
Expand Down
91 changes: 91 additions & 0 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,97 @@ impl<'a> Parser<'a> {
}
}

pub(super) fn check_anonymous_enum(&mut self, span: Span, tys: &[P<Ty>]) -> 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]) {
Expand Down
36 changes: 27 additions & 9 deletions compiler/rustc_parse/src/parser/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub(super) enum AllowPlus {
No,
}

#[derive(PartialEq)]
#[derive(PartialEq, Clone, Copy)]
pub(super) enum RecoverQPath {
Yes,
No,
Expand Down Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions compiler/rustc_parse/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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<String>), 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
}
7 changes: 7 additions & 0 deletions src/test/ui/return/issue-100741-return-anonymous-enum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
struct Foo {}

fn foo<'a>() -> i32 | Vec<i32> | &str | &'a String | Foo {
//~^ ERROR: anonymous enums are not supported
}

fn main() {}
17 changes: 17 additions & 0 deletions src/test/ui/return/issue-100741-return-anonymous-enum.stderr
Original file line number Diff line number Diff line change
@@ -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<i32> | &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

0 comments on commit bac60db

Please sign in to comment.