Skip to content

Commit bac60db

Browse files
committed
give some suggestion for returning anonymous enum
1 parent a9bb589 commit bac60db

File tree

6 files changed

+203
-9
lines changed

6 files changed

+203
-9
lines changed

compiler/rustc_parse/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments");
3030
pub mod parser;
3131
use parser::{emit_unclosed_delims, make_unclosed_delims_error, Parser};
3232
pub mod lexer;
33+
mod utils;
3334
pub mod validate_attr;
3435

3536
// A bunch of utility functions of the form `parse_<thing>_from_<source>`

compiler/rustc_parse/src/parser/diagnostics.rs

+91
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,97 @@ impl<'a> Parser<'a> {
880880
}
881881
}
882882

883+
pub(super) fn check_anonymous_enum(&mut self, span: Span, tys: &[P<Ty>]) -> PResult<'a, ()> {
884+
use std::fmt::Write;
885+
if tys.len() <= 1 {
886+
return Ok(());
887+
}
888+
889+
fn variant_name_and_ty<'a>(
890+
ty: &'a Ty,
891+
lifetimes: &mut FxHashSet<&'a str>,
892+
) -> Option<(String, String)> {
893+
match &ty.kind {
894+
TyKind::Path(_, path) => {
895+
let mut name = String::new();
896+
let mut ty_string = String::new();
897+
898+
if let Some(seg) = path.segments.iter().last() {
899+
name.push_str(seg.ident.name.as_str());
900+
ty_string.push_str(seg.ident.name.as_str());
901+
902+
if let Some(_args) = &seg.args {
903+
ty_string.push('<');
904+
ty_string.push_str("...");
905+
ty_string.push('>');
906+
}
907+
}
908+
909+
Some((name, ty_string))
910+
}
911+
TyKind::Rptr(lifetime, ty) => {
912+
if let Some((mut name, ty)) = variant_name_and_ty(&ty.ty, lifetimes) {
913+
name.push_str("Ref");
914+
let lifetime =
915+
lifetime.as_ref().map(|l| l.ident.name.as_str()).unwrap_or("'lifetime");
916+
lifetimes.insert(lifetime);
917+
Some((name, format!("&{} {}", lifetime, ty)))
918+
} else {
919+
None
920+
}
921+
}
922+
_ => None,
923+
}
924+
}
925+
926+
let mut err = self.struct_span_err(span, "anonymous enums are not supported");
927+
let mut variant_content = String::new();
928+
let mut variant_name_set = FxHashSet::default();
929+
let mut lifetimes = FxHashSet::default();
930+
tys.iter().for_each(|ty| {
931+
let name_ty = variant_name_and_ty(ty, &mut lifetimes);
932+
if let Some((variant_name, ty)) = name_ty {
933+
let variant_name = crate::utils::to_camel_case(&variant_name);
934+
if !variant_name_set.contains(&variant_name) {
935+
let _ = writeln!(
936+
&mut variant_content,
937+
" {variant_name}({ty})",
938+
variant_name = variant_name,
939+
ty = ty
940+
);
941+
variant_name_set.insert(variant_name);
942+
}
943+
}
944+
});
945+
946+
let mut suggestion_code = String::new();
947+
suggestion_code.push_str("enum SomeEnum");
948+
if lifetimes.len() > 0 {
949+
suggestion_code.push_str("<");
950+
#[allow(rustc::potential_query_instability)]
951+
let mut iter = lifetimes.into_iter();
952+
if let Some(lifetime) = iter.next() {
953+
suggestion_code.push_str(lifetime);
954+
while let Some(lifetime) = iter.next() {
955+
suggestion_code.push_str(",");
956+
suggestion_code.push_str(lifetime);
957+
}
958+
}
959+
suggestion_code.push_str(">");
960+
}
961+
suggestion_code.push_str("{\n");
962+
suggestion_code.push_str(&variant_content);
963+
suggestion_code.push_str("}\n");
964+
err.span_suggestion(
965+
span,
966+
"consider using enum as return type",
967+
"SomeEnum",
968+
Applicability::HasPlaceholders,
969+
)
970+
.note(suggestion_code);
971+
Err(err)
972+
}
973+
883974
/// Eats and discards tokens until one of `kets` is encountered. Respects token trees,
884975
/// passes through any errors encountered. Used for error recovery.
885976
pub(super) fn eat_to_tokens(&mut self, kets: &[&TokenKind]) {

compiler/rustc_parse/src/parser/ty.rs

+27-9
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub(super) enum AllowPlus {
3838
No,
3939
}
4040

41-
#[derive(PartialEq)]
41+
#[derive(PartialEq, Clone, Copy)]
4242
pub(super) enum RecoverQPath {
4343
Yes,
4444
No,
@@ -199,14 +199,32 @@ impl<'a> Parser<'a> {
199199
) -> PResult<'a, FnRetTy> {
200200
Ok(if self.eat(&token::RArrow) {
201201
// FIXME(Centril): Can we unconditionally `allow_plus`?
202-
let ty = self.parse_ty_common(
203-
allow_plus,
204-
AllowCVariadic::No,
205-
recover_qpath,
206-
recover_return_sign,
207-
None,
208-
RecoverQuestionMark::Yes,
209-
)?;
202+
203+
let mut tys = vec![];
204+
let lo = self.token.span;
205+
loop {
206+
let ty = self.parse_ty_common(
207+
allow_plus,
208+
AllowCVariadic::No,
209+
recover_qpath,
210+
recover_return_sign,
211+
None,
212+
RecoverQuestionMark::Yes,
213+
)?;
214+
tys.push(ty);
215+
// maybe a `|` for fn type of closure, `|a: &dyn Fn(i32) -> bool| { ... }`
216+
if self.check_noexpect(&token::BinOp(token::Or))
217+
&& self.look_ahead(1, |tok| tok == &token::OpenDelim(token::Delimiter::Brace))
218+
{
219+
break;
220+
}
221+
if !self.eat_noexpect(&token::BinOp(token::Or)) {
222+
break;
223+
}
224+
}
225+
let span = lo.to(self.prev_token.span);
226+
self.check_anonymous_enum(span, &tys)?;
227+
let ty = tys.into_iter().next().unwrap();
210228
FnRetTy::Ty(ty)
211229
} else if recover_return_sign.can_recover(&self.token.kind) {
212230
// Don't `eat` to prevent `=>` from being added as an expected token which isn't

compiler/rustc_parse/src/utils.rs

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/// copied from rustc_lint
2+
3+
/// Some unicode characters *have* case, are considered upper case or lower case, but they *can't*
4+
/// be upper cased or lower cased. For the purposes of the lint suggestion, we care about being able
5+
/// to change the char's case.
6+
fn char_has_case(c: char) -> bool {
7+
let mut l = c.to_lowercase();
8+
let mut u = c.to_uppercase();
9+
while let Some(l) = l.next() {
10+
match u.next() {
11+
Some(u) if l != u => return true,
12+
_ => {}
13+
}
14+
}
15+
u.next().is_some()
16+
}
17+
18+
pub fn to_camel_case(s: &str) -> String {
19+
s.trim_matches('_')
20+
.split('_')
21+
.filter(|component| !component.is_empty())
22+
.map(|component| {
23+
let mut camel_cased_component = String::new();
24+
25+
let mut new_word = true;
26+
let mut prev_is_lower_case = true;
27+
28+
for c in component.chars() {
29+
// Preserve the case if an uppercase letter follows a lowercase letter, so that
30+
// `camelCase` is converted to `CamelCase`.
31+
if prev_is_lower_case && c.is_uppercase() {
32+
new_word = true;
33+
}
34+
35+
if new_word {
36+
camel_cased_component.extend(c.to_uppercase());
37+
} else {
38+
camel_cased_component.extend(c.to_lowercase());
39+
}
40+
41+
prev_is_lower_case = c.is_lowercase();
42+
new_word = false;
43+
}
44+
45+
camel_cased_component
46+
})
47+
.fold((String::new(), None), |(acc, prev): (String, Option<String>), next| {
48+
// separate two components with an underscore if their boundary cannot
49+
// be distinguished using an uppercase/lowercase case distinction
50+
let join = if let Some(prev) = prev {
51+
let l = prev.chars().last().unwrap();
52+
let f = next.chars().next().unwrap();
53+
!char_has_case(l) && !char_has_case(f)
54+
} else {
55+
false
56+
};
57+
(acc + if join { "_" } else { "" } + &next, Some(next))
58+
})
59+
.0
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
struct Foo {}
2+
3+
fn foo<'a>() -> i32 | Vec<i32> | &str | &'a String | Foo {
4+
//~^ ERROR: anonymous enums are not supported
5+
}
6+
7+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: anonymous enums are not supported
2+
--> $DIR/issue-100741-return-anonymous-enum.rs:3:17
3+
|
4+
LL | fn foo<'a>() -> i32 | Vec<i32> | &str | &'a String | Foo {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using enum as return type: `SomeEnum`
6+
|
7+
= note: enum SomeEnum<'lifetime,'a>{
8+
I32(i32)
9+
Vec(Vec<...>)
10+
StrRef(&'lifetime str)
11+
StringRef(&'a String)
12+
Foo(Foo)
13+
}
14+
15+
16+
error: aborting due to previous error
17+

0 commit comments

Comments
 (0)