aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/add_custom_impl.rs152
-rw-r--r--crates/assists/src/handlers/convert_integer_literal.rs206
-rw-r--r--crates/syntax/src/ast.rs2
-rw-r--r--crates/syntax/src/ast/expr_ext.rs65
4 files changed, 251 insertions, 174 deletions
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs
index 8757fa33f..669dd9b21 100644
--- a/crates/assists/src/handlers/add_custom_impl.rs
+++ b/crates/assists/src/handlers/add_custom_impl.rs
@@ -1,13 +1,16 @@
1use ide_db::imports_locator;
1use itertools::Itertools; 2use itertools::Itertools;
2use syntax::{ 3use syntax::{
3 ast::{self, AstNode}, 4 ast::{self, make, AstNode},
4 Direction, SmolStr, 5 Direction, SmolStr,
5 SyntaxKind::{IDENT, WHITESPACE}, 6 SyntaxKind::{IDENT, WHITESPACE},
6 TextRange, TextSize, 7 TextRange, TextSize,
7}; 8};
8 9
9use crate::{ 10use crate::{
10 assist_context::{AssistContext, Assists}, 11 assist_config::SnippetCap,
12 assist_context::{AssistBuilder, AssistContext, Assists},
13 utils::mod_path_to_ast,
11 AssistId, AssistKind, 14 AssistId, AssistKind,
12}; 15};
13 16
@@ -30,72 +33,116 @@ use crate::{
30// ``` 33// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 34pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let attr = ctx.find_node_at_offset::<ast::Attr>()?; 35 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
33 let input = attr.token_tree()?;
34 36
35 let attr_name = attr 37 let attr_name = attr
36 .syntax() 38 .syntax()
37 .descendants_with_tokens() 39 .descendants_with_tokens()
38 .filter(|t| t.kind() == IDENT) 40 .filter(|t| t.kind() == IDENT)
39 .find_map(|i| i.into_token()) 41 .find_map(syntax::NodeOrToken::into_token)
40 .filter(|t| *t.text() == "derive")? 42 .filter(|t| t.text() == "derive")?
41 .text() 43 .text()
42 .clone(); 44 .clone();
43 45
44 let trait_token = 46 let trait_token =
45 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?; 47 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
48 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
46 49
47 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; 50 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
48 let annotated_name = annotated.syntax().text().to_string(); 51 let annotated_name = annotated.syntax().text().to_string();
49 let start_offset = annotated.syntax().parent()?.text_range().end(); 52 let insert_pos = annotated.syntax().parent()?.text_range().end();
53
54 let current_module = ctx.sema.scope(annotated.syntax()).module()?;
55 let current_crate = current_module.krate();
56
57 let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
58 .into_iter()
59 .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
60 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
61 _ => None,
62 })
63 .flat_map(|trait_| {
64 current_module
65 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
66 .as_ref()
67 .map(mod_path_to_ast)
68 .zip(Some(trait_))
69 });
50 70
51 let label = 71 let mut no_traits_found = true;
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); 72 for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) {
73 add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
74 }
75 if no_traits_found {
76 add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
77 }
78 Some(())
79}
53 80
81fn add_assist(
82 acc: &mut Assists,
83 snippet_cap: Option<SnippetCap>,
84 attr: &ast::Attr,
85 trait_path: &ast::Path,
86 annotated_name: &str,
87 insert_pos: TextSize,
88) -> Option<()> {
54 let target = attr.syntax().text_range(); 89 let target = attr.syntax().text_range();
55 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| { 90 let input = attr.token_tree()?;
56 let new_attr_input = input 91 let label = format!("Add custom impl `{}` for `{}`", trait_path, annotated_name);
57 .syntax() 92 let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
58 .descendants_with_tokens()
59 .filter(|t| t.kind() == IDENT)
60 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
61 .filter(|t| t != trait_token.text())
62 .collect::<Vec<SmolStr>>();
63 let has_more_derives = !new_attr_input.is_empty();
64
65 if has_more_derives {
66 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
67 builder.replace(input.syntax().text_range(), new_attr_input);
68 } else {
69 let attr_range = attr.syntax().text_range();
70 builder.delete(attr_range);
71
72 let line_break_range = attr
73 .syntax()
74 .next_sibling_or_token()
75 .filter(|t| t.kind() == WHITESPACE)
76 .map(|t| t.text_range())
77 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
78 builder.delete(line_break_range);
79 }
80 93
81 match ctx.config.snippet_cap { 94 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
95 update_attribute(builder, &input, &trait_name, &attr);
96 match snippet_cap {
82 Some(cap) => { 97 Some(cap) => {
83 builder.insert_snippet( 98 builder.insert_snippet(
84 cap, 99 cap,
85 start_offset, 100 insert_pos,
86 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name), 101 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name),
87 ); 102 );
88 } 103 }
89 None => { 104 None => {
90 builder.insert( 105 builder.insert(
91 start_offset, 106 insert_pos,
92 format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name), 107 format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
93 ); 108 );
94 } 109 }
95 } 110 }
96 }) 111 })
97} 112}
98 113
114fn update_attribute(
115 builder: &mut AssistBuilder,
116 input: &ast::TokenTree,
117 trait_name: &ast::NameRef,
118 attr: &ast::Attr,
119) {
120 let new_attr_input = input
121 .syntax()
122 .descendants_with_tokens()
123 .filter(|t| t.kind() == IDENT)
124 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
125 .filter(|t| t != trait_name.text())
126 .collect::<Vec<SmolStr>>();
127 let has_more_derives = !new_attr_input.is_empty();
128
129 if has_more_derives {
130 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
131 builder.replace(input.syntax().text_range(), new_attr_input);
132 } else {
133 let attr_range = attr.syntax().text_range();
134 builder.delete(attr_range);
135
136 let line_break_range = attr
137 .syntax()
138 .next_sibling_or_token()
139 .filter(|t| t.kind() == WHITESPACE)
140 .map(|t| t.text_range())
141 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
142 builder.delete(line_break_range);
143 }
144}
145
99#[cfg(test)] 146#[cfg(test)]
100mod tests { 147mod tests {
101 use crate::tests::{check_assist, check_assist_not_applicable}; 148 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -103,6 +150,35 @@ mod tests {
103 use super::*; 150 use super::*;
104 151
105 #[test] 152 #[test]
153 fn add_custom_impl_qualified() {
154 check_assist(
155 add_custom_impl,
156 "
157mod fmt {
158 pub trait Debug {}
159}
160
161#[derive(Debu<|>g)]
162struct Foo {
163 bar: String,
164}
165",
166 "
167mod fmt {
168 pub trait Debug {}
169}
170
171struct Foo {
172 bar: String,
173}
174
175impl fmt::Debug for Foo {
176 $0
177}
178",
179 )
180 }
181 #[test]
106 fn add_custom_impl_for_unique_input() { 182 fn add_custom_impl_for_unique_input() {
107 check_assist( 183 check_assist(
108 add_custom_impl, 184 add_custom_impl,
diff --git a/crates/assists/src/handlers/convert_integer_literal.rs b/crates/assists/src/handlers/convert_integer_literal.rs
index ea35e833a..c8af80701 100644
--- a/crates/assists/src/handlers/convert_integer_literal.rs
+++ b/crates/assists/src/handlers/convert_integer_literal.rs
@@ -1,4 +1,4 @@
1use syntax::{ast, AstNode, SmolStr}; 1use syntax::{ast, ast::Radix, AstNode};
2 2
3use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; 3use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
4 4
@@ -15,37 +15,34 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
15// ``` 15// ```
16pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 16pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
17 let literal = ctx.find_node_at_offset::<ast::Literal>()?; 17 let literal = ctx.find_node_at_offset::<ast::Literal>()?;
18 let (radix, value) = literal.int_value()?;
19
18 let range = literal.syntax().text_range(); 20 let range = literal.syntax().text_range();
19 let group_id = GroupLabel("Convert integer base".into()); 21 let group_id = GroupLabel("Convert integer base".into());
20
21 let suffix = match literal.kind() { 22 let suffix = match literal.kind() {
22 ast::LiteralKind::IntNumber { suffix } => suffix, 23 ast::LiteralKind::IntNumber { suffix } => suffix,
23 _ => return None, 24 _ => return None,
24 }; 25 };
25 let suffix_len = suffix.as_ref().map(|s| s.len()).unwrap_or(0); 26
26 let raw_literal_text = literal.syntax().to_string(); 27 for &target_radix in Radix::ALL {
27 28 if target_radix == radix {
28 // Gets the literal's text without the type suffix and without underscores.
29 let literal_text = raw_literal_text
30 .chars()
31 .take(raw_literal_text.len() - suffix_len)
32 .filter(|c| *c != '_')
33 .collect::<SmolStr>();
34 let literal_base = IntegerLiteralBase::identify(&literal_text)?;
35
36 for base in IntegerLiteralBase::bases() {
37 if *base == literal_base {
38 continue; 29 continue;
39 } 30 }
40 31
41 let mut converted = literal_base.convert(&literal_text, base); 32 let mut converted = match target_radix {
42 33 Radix::Binary => format!("0b{:b}", value),
43 let label = if let Some(suffix) = &suffix { 34 Radix::Octal => format!("0o{:o}", value),
44 format!("Convert {} ({}) to {}", &literal_text, suffix, &converted) 35 Radix::Decimal => value.to_string(),
45 } else { 36 Radix::Hexadecimal => format!("0x{:X}", value),
46 format!("Convert {} to {}", &literal_text, &converted)
47 }; 37 };
48 38
39 let label = format!(
40 "Convert {} to {}{}",
41 literal,
42 converted,
43 suffix.as_deref().unwrap_or_default()
44 );
45
49 // Appends the type suffix back into the new literal if it exists. 46 // Appends the type suffix back into the new literal if it exists.
50 if let Some(suffix) = &suffix { 47 if let Some(suffix) = &suffix {
51 converted.push_str(&suffix); 48 converted.push_str(&suffix);
@@ -63,79 +60,11 @@ pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) ->
63 Some(()) 60 Some(())
64} 61}
65 62
66#[derive(Debug, PartialEq, Eq)]
67enum IntegerLiteralBase {
68 Binary,
69 Octal,
70 Decimal,
71 Hexadecimal,
72}
73
74impl IntegerLiteralBase {
75 fn identify(literal_text: &str) -> Option<Self> {
76 // We cannot express a literal in anything other than decimal in under 3 characters, so we return here if possible.
77 if literal_text.len() < 3 && literal_text.chars().all(|c| c.is_digit(10)) {
78 return Some(Self::Decimal);
79 }
80
81 let base = match &literal_text[..2] {
82 "0b" => Self::Binary,
83 "0o" => Self::Octal,
84 "0x" => Self::Hexadecimal,
85 _ => Self::Decimal,
86 };
87
88 // Checks that all characters after the base prefix are all valid digits for that base.
89 if literal_text[base.prefix_len()..].chars().all(|c| c.is_digit(base.base())) {
90 Some(base)
91 } else {
92 None
93 }
94 }
95
96 fn convert(&self, literal_text: &str, to: &IntegerLiteralBase) -> String {
97 let digits = &literal_text[self.prefix_len()..];
98 let value = u128::from_str_radix(digits, self.base()).unwrap();
99
100 match to {
101 Self::Binary => format!("0b{:b}", value),
102 Self::Octal => format!("0o{:o}", value),
103 Self::Decimal => value.to_string(),
104 Self::Hexadecimal => format!("0x{:X}", value),
105 }
106 }
107
108 const fn base(&self) -> u32 {
109 match self {
110 Self::Binary => 2,
111 Self::Octal => 8,
112 Self::Decimal => 10,
113 Self::Hexadecimal => 16,
114 }
115 }
116
117 const fn prefix_len(&self) -> usize {
118 match self {
119 Self::Decimal => 0,
120 _ => 2,
121 }
122 }
123
124 const fn bases() -> &'static [IntegerLiteralBase] {
125 &[
126 IntegerLiteralBase::Binary,
127 IntegerLiteralBase::Octal,
128 IntegerLiteralBase::Decimal,
129 IntegerLiteralBase::Hexadecimal,
130 ]
131 }
132}
133
134#[cfg(test)] 63#[cfg(test)]
135mod tests { 64mod tests {
65 use crate::tests::{check_assist_by_label, check_assist_not_applicable, check_assist_target};
136 66
137 use super::*; 67 use super::*;
138 use crate::tests::{check_assist_by_label, check_assist_target};
139 68
140 #[test] 69 #[test]
141 fn binary_target() { 70 fn binary_target() {
@@ -317,21 +246,21 @@ mod tests {
317 convert_integer_literal, 246 convert_integer_literal,
318 before, 247 before,
319 "const _: i32 = 0b1111101000;", 248 "const _: i32 = 0b1111101000;",
320 "Convert 1000 to 0b1111101000", 249 "Convert 1_00_0 to 0b1111101000",
321 ); 250 );
322 251
323 check_assist_by_label( 252 check_assist_by_label(
324 convert_integer_literal, 253 convert_integer_literal,
325 before, 254 before,
326 "const _: i32 = 0o1750;", 255 "const _: i32 = 0o1750;",
327 "Convert 1000 to 0o1750", 256 "Convert 1_00_0 to 0o1750",
328 ); 257 );
329 258
330 check_assist_by_label( 259 check_assist_by_label(
331 convert_integer_literal, 260 convert_integer_literal,
332 before, 261 before,
333 "const _: i32 = 0x3E8;", 262 "const _: i32 = 0x3E8;",
334 "Convert 1000 to 0x3E8", 263 "Convert 1_00_0 to 0x3E8",
335 ); 264 );
336 } 265 }
337 266
@@ -343,21 +272,21 @@ mod tests {
343 convert_integer_literal, 272 convert_integer_literal,
344 before, 273 before,
345 "const _: i32 = 0b1010;", 274 "const _: i32 = 0b1010;",
346 "Convert 10 to 0b1010", 275 "Convert 1_0 to 0b1010",
347 ); 276 );
348 277
349 check_assist_by_label( 278 check_assist_by_label(
350 convert_integer_literal, 279 convert_integer_literal,
351 before, 280 before,
352 "const _: i32 = 0o12;", 281 "const _: i32 = 0o12;",
353 "Convert 10 to 0o12", 282 "Convert 1_0 to 0o12",
354 ); 283 );
355 284
356 check_assist_by_label( 285 check_assist_by_label(
357 convert_integer_literal, 286 convert_integer_literal,
358 before, 287 before,
359 "const _: i32 = 0xA;", 288 "const _: i32 = 0xA;",
360 "Convert 10 to 0xA", 289 "Convert 1_0 to 0xA",
361 ); 290 );
362 } 291 }
363 292
@@ -369,21 +298,21 @@ mod tests {
369 convert_integer_literal, 298 convert_integer_literal,
370 before, 299 before,
371 "const _: i32 = 0b11111111;", 300 "const _: i32 = 0b11111111;",
372 "Convert 0xFF to 0b11111111", 301 "Convert 0x_F_F to 0b11111111",
373 ); 302 );
374 303
375 check_assist_by_label( 304 check_assist_by_label(
376 convert_integer_literal, 305 convert_integer_literal,
377 before, 306 before,
378 "const _: i32 = 0o377;", 307 "const _: i32 = 0o377;",
379 "Convert 0xFF to 0o377", 308 "Convert 0x_F_F to 0o377",
380 ); 309 );
381 310
382 check_assist_by_label( 311 check_assist_by_label(
383 convert_integer_literal, 312 convert_integer_literal,
384 before, 313 before,
385 "const _: i32 = 255;", 314 "const _: i32 = 255;",
386 "Convert 0xFF to 255", 315 "Convert 0x_F_F to 255",
387 ); 316 );
388 } 317 }
389 318
@@ -395,21 +324,21 @@ mod tests {
395 convert_integer_literal, 324 convert_integer_literal,
396 before, 325 before,
397 "const _: i32 = 0o377;", 326 "const _: i32 = 0o377;",
398 "Convert 0b11111111 to 0o377", 327 "Convert 0b1111_1111 to 0o377",
399 ); 328 );
400 329
401 check_assist_by_label( 330 check_assist_by_label(
402 convert_integer_literal, 331 convert_integer_literal,
403 before, 332 before,
404 "const _: i32 = 255;", 333 "const _: i32 = 255;",
405 "Convert 0b11111111 to 255", 334 "Convert 0b1111_1111 to 255",
406 ); 335 );
407 336
408 check_assist_by_label( 337 check_assist_by_label(
409 convert_integer_literal, 338 convert_integer_literal,
410 before, 339 before,
411 "const _: i32 = 0xFF;", 340 "const _: i32 = 0xFF;",
412 "Convert 0b11111111 to 0xFF", 341 "Convert 0b1111_1111 to 0xFF",
413 ); 342 );
414 } 343 }
415 344
@@ -421,21 +350,21 @@ mod tests {
421 convert_integer_literal, 350 convert_integer_literal,
422 before, 351 before,
423 "const _: i32 = 0b11111111;", 352 "const _: i32 = 0b11111111;",
424 "Convert 0o377 to 0b11111111", 353 "Convert 0o3_77 to 0b11111111",
425 ); 354 );
426 355
427 check_assist_by_label( 356 check_assist_by_label(
428 convert_integer_literal, 357 convert_integer_literal,
429 before, 358 before,
430 "const _: i32 = 255;", 359 "const _: i32 = 255;",
431 "Convert 0o377 to 255", 360 "Convert 0o3_77 to 255",
432 ); 361 );
433 362
434 check_assist_by_label( 363 check_assist_by_label(
435 convert_integer_literal, 364 convert_integer_literal,
436 before, 365 before,
437 "const _: i32 = 0xFF;", 366 "const _: i32 = 0xFF;",
438 "Convert 0o377 to 0xFF", 367 "Convert 0o3_77 to 0xFF",
439 ); 368 );
440 } 369 }
441 370
@@ -447,21 +376,21 @@ mod tests {
447 convert_integer_literal, 376 convert_integer_literal,
448 before, 377 before,
449 "const _: i32 = 0b1111101000i32;", 378 "const _: i32 = 0b1111101000i32;",
450 "Convert 1000 (i32) to 0b1111101000", 379 "Convert 1000i32 to 0b1111101000i32",
451 ); 380 );
452 381
453 check_assist_by_label( 382 check_assist_by_label(
454 convert_integer_literal, 383 convert_integer_literal,
455 before, 384 before,
456 "const _: i32 = 0o1750i32;", 385 "const _: i32 = 0o1750i32;",
457 "Convert 1000 (i32) to 0o1750", 386 "Convert 1000i32 to 0o1750i32",
458 ); 387 );
459 388
460 check_assist_by_label( 389 check_assist_by_label(
461 convert_integer_literal, 390 convert_integer_literal,
462 before, 391 before,
463 "const _: i32 = 0x3E8i32;", 392 "const _: i32 = 0x3E8i32;",
464 "Convert 1000 (i32) to 0x3E8", 393 "Convert 1000i32 to 0x3E8i32",
465 ); 394 );
466 } 395 }
467 396
@@ -473,21 +402,21 @@ mod tests {
473 convert_integer_literal, 402 convert_integer_literal,
474 before, 403 before,
475 "const _: i32 = 0b1010i32;", 404 "const _: i32 = 0b1010i32;",
476 "Convert 10 (i32) to 0b1010", 405 "Convert 10i32 to 0b1010i32",
477 ); 406 );
478 407
479 check_assist_by_label( 408 check_assist_by_label(
480 convert_integer_literal, 409 convert_integer_literal,
481 before, 410 before,
482 "const _: i32 = 0o12i32;", 411 "const _: i32 = 0o12i32;",
483 "Convert 10 (i32) to 0o12", 412 "Convert 10i32 to 0o12i32",
484 ); 413 );
485 414
486 check_assist_by_label( 415 check_assist_by_label(
487 convert_integer_literal, 416 convert_integer_literal,
488 before, 417 before,
489 "const _: i32 = 0xAi32;", 418 "const _: i32 = 0xAi32;",
490 "Convert 10 (i32) to 0xA", 419 "Convert 10i32 to 0xAi32",
491 ); 420 );
492 } 421 }
493 422
@@ -499,21 +428,21 @@ mod tests {
499 convert_integer_literal, 428 convert_integer_literal,
500 before, 429 before,
501 "const _: i32 = 0b11111111i32;", 430 "const _: i32 = 0b11111111i32;",
502 "Convert 0xFF (i32) to 0b11111111", 431 "Convert 0xFFi32 to 0b11111111i32",
503 ); 432 );
504 433
505 check_assist_by_label( 434 check_assist_by_label(
506 convert_integer_literal, 435 convert_integer_literal,
507 before, 436 before,
508 "const _: i32 = 0o377i32;", 437 "const _: i32 = 0o377i32;",
509 "Convert 0xFF (i32) to 0o377", 438 "Convert 0xFFi32 to 0o377i32",
510 ); 439 );
511 440
512 check_assist_by_label( 441 check_assist_by_label(
513 convert_integer_literal, 442 convert_integer_literal,
514 before, 443 before,
515 "const _: i32 = 255i32;", 444 "const _: i32 = 255i32;",
516 "Convert 0xFF (i32) to 255", 445 "Convert 0xFFi32 to 255i32",
517 ); 446 );
518 } 447 }
519 448
@@ -525,21 +454,21 @@ mod tests {
525 convert_integer_literal, 454 convert_integer_literal,
526 before, 455 before,
527 "const _: i32 = 0o377i32;", 456 "const _: i32 = 0o377i32;",
528 "Convert 0b11111111 (i32) to 0o377", 457 "Convert 0b11111111i32 to 0o377i32",
529 ); 458 );
530 459
531 check_assist_by_label( 460 check_assist_by_label(
532 convert_integer_literal, 461 convert_integer_literal,
533 before, 462 before,
534 "const _: i32 = 255i32;", 463 "const _: i32 = 255i32;",
535 "Convert 0b11111111 (i32) to 255", 464 "Convert 0b11111111i32 to 255i32",
536 ); 465 );
537 466
538 check_assist_by_label( 467 check_assist_by_label(
539 convert_integer_literal, 468 convert_integer_literal,
540 before, 469 before,
541 "const _: i32 = 0xFFi32;", 470 "const _: i32 = 0xFFi32;",
542 "Convert 0b11111111 (i32) to 0xFF", 471 "Convert 0b11111111i32 to 0xFFi32",
543 ); 472 );
544 } 473 }
545 474
@@ -551,21 +480,21 @@ mod tests {
551 convert_integer_literal, 480 convert_integer_literal,
552 before, 481 before,
553 "const _: i32 = 0b11111111i32;", 482 "const _: i32 = 0b11111111i32;",
554 "Convert 0o377 (i32) to 0b11111111", 483 "Convert 0o377i32 to 0b11111111i32",
555 ); 484 );
556 485
557 check_assist_by_label( 486 check_assist_by_label(
558 convert_integer_literal, 487 convert_integer_literal,
559 before, 488 before,
560 "const _: i32 = 255i32;", 489 "const _: i32 = 255i32;",
561 "Convert 0o377 (i32) to 255", 490 "Convert 0o377i32 to 255i32",
562 ); 491 );
563 492
564 check_assist_by_label( 493 check_assist_by_label(
565 convert_integer_literal, 494 convert_integer_literal,
566 before, 495 before,
567 "const _: i32 = 0xFFi32;", 496 "const _: i32 = 0xFFi32;",
568 "Convert 0o377 (i32) to 0xFF", 497 "Convert 0o377i32 to 0xFFi32",
569 ); 498 );
570 } 499 }
571 500
@@ -577,21 +506,21 @@ mod tests {
577 convert_integer_literal, 506 convert_integer_literal,
578 before, 507 before,
579 "const _: i32 = 0b1111101000i32;", 508 "const _: i32 = 0b1111101000i32;",
580 "Convert 1000 (i32) to 0b1111101000", 509 "Convert 1_00_0i32 to 0b1111101000i32",
581 ); 510 );
582 511
583 check_assist_by_label( 512 check_assist_by_label(
584 convert_integer_literal, 513 convert_integer_literal,
585 before, 514 before,
586 "const _: i32 = 0o1750i32;", 515 "const _: i32 = 0o1750i32;",
587 "Convert 1000 (i32) to 0o1750", 516 "Convert 1_00_0i32 to 0o1750i32",
588 ); 517 );
589 518
590 check_assist_by_label( 519 check_assist_by_label(
591 convert_integer_literal, 520 convert_integer_literal,
592 before, 521 before,
593 "const _: i32 = 0x3E8i32;", 522 "const _: i32 = 0x3E8i32;",
594 "Convert 1000 (i32) to 0x3E8", 523 "Convert 1_00_0i32 to 0x3E8i32",
595 ); 524 );
596 } 525 }
597 526
@@ -603,21 +532,21 @@ mod tests {
603 convert_integer_literal, 532 convert_integer_literal,
604 before, 533 before,
605 "const _: i32 = 0b1010i32;", 534 "const _: i32 = 0b1010i32;",
606 "Convert 10 (i32) to 0b1010", 535 "Convert 1_0i32 to 0b1010i32",
607 ); 536 );
608 537
609 check_assist_by_label( 538 check_assist_by_label(
610 convert_integer_literal, 539 convert_integer_literal,
611 before, 540 before,
612 "const _: i32 = 0o12i32;", 541 "const _: i32 = 0o12i32;",
613 "Convert 10 (i32) to 0o12", 542 "Convert 1_0i32 to 0o12i32",
614 ); 543 );
615 544
616 check_assist_by_label( 545 check_assist_by_label(
617 convert_integer_literal, 546 convert_integer_literal,
618 before, 547 before,
619 "const _: i32 = 0xAi32;", 548 "const _: i32 = 0xAi32;",
620 "Convert 10 (i32) to 0xA", 549 "Convert 1_0i32 to 0xAi32",
621 ); 550 );
622 } 551 }
623 552
@@ -629,21 +558,21 @@ mod tests {
629 convert_integer_literal, 558 convert_integer_literal,
630 before, 559 before,
631 "const _: i32 = 0b11111111i32;", 560 "const _: i32 = 0b11111111i32;",
632 "Convert 0xFF (i32) to 0b11111111", 561 "Convert 0x_F_Fi32 to 0b11111111i32",
633 ); 562 );
634 563
635 check_assist_by_label( 564 check_assist_by_label(
636 convert_integer_literal, 565 convert_integer_literal,
637 before, 566 before,
638 "const _: i32 = 0o377i32;", 567 "const _: i32 = 0o377i32;",
639 "Convert 0xFF (i32) to 0o377", 568 "Convert 0x_F_Fi32 to 0o377i32",
640 ); 569 );
641 570
642 check_assist_by_label( 571 check_assist_by_label(
643 convert_integer_literal, 572 convert_integer_literal,
644 before, 573 before,
645 "const _: i32 = 255i32;", 574 "const _: i32 = 255i32;",
646 "Convert 0xFF (i32) to 255", 575 "Convert 0x_F_Fi32 to 255i32",
647 ); 576 );
648 } 577 }
649 578
@@ -655,21 +584,21 @@ mod tests {
655 convert_integer_literal, 584 convert_integer_literal,
656 before, 585 before,
657 "const _: i32 = 0o377i32;", 586 "const _: i32 = 0o377i32;",
658 "Convert 0b11111111 (i32) to 0o377", 587 "Convert 0b1111_1111i32 to 0o377i32",
659 ); 588 );
660 589
661 check_assist_by_label( 590 check_assist_by_label(
662 convert_integer_literal, 591 convert_integer_literal,
663 before, 592 before,
664 "const _: i32 = 255i32;", 593 "const _: i32 = 255i32;",
665 "Convert 0b11111111 (i32) to 255", 594 "Convert 0b1111_1111i32 to 255i32",
666 ); 595 );
667 596
668 check_assist_by_label( 597 check_assist_by_label(
669 convert_integer_literal, 598 convert_integer_literal,
670 before, 599 before,
671 "const _: i32 = 0xFFi32;", 600 "const _: i32 = 0xFFi32;",
672 "Convert 0b11111111 (i32) to 0xFF", 601 "Convert 0b1111_1111i32 to 0xFFi32",
673 ); 602 );
674 } 603 }
675 604
@@ -681,21 +610,28 @@ mod tests {
681 convert_integer_literal, 610 convert_integer_literal,
682 before, 611 before,
683 "const _: i32 = 0b11111111i32;", 612 "const _: i32 = 0b11111111i32;",
684 "Convert 0o377 (i32) to 0b11111111", 613 "Convert 0o3_77i32 to 0b11111111i32",
685 ); 614 );
686 615
687 check_assist_by_label( 616 check_assist_by_label(
688 convert_integer_literal, 617 convert_integer_literal,
689 before, 618 before,
690 "const _: i32 = 255i32;", 619 "const _: i32 = 255i32;",
691 "Convert 0o377 (i32) to 255", 620 "Convert 0o3_77i32 to 255i32",
692 ); 621 );
693 622
694 check_assist_by_label( 623 check_assist_by_label(
695 convert_integer_literal, 624 convert_integer_literal,
696 before, 625 before,
697 "const _: i32 = 0xFFi32;", 626 "const _: i32 = 0xFFi32;",
698 "Convert 0o377 (i32) to 0xFF", 627 "Convert 0o3_77i32 to 0xFFi32",
699 ); 628 );
700 } 629 }
630
631 #[test]
632 fn convert_overflowing_literal() {
633 let before = "const _: i32 =
634 111111111111111111111111111111111111111111111111111111111111111111111111<|>;";
635 check_assist_not_applicable(convert_integer_literal, before);
636 }
701} 637}
diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs
index 8a0e3d27b..a16ac6a7c 100644
--- a/crates/syntax/src/ast.rs
+++ b/crates/syntax/src/ast.rs
@@ -16,7 +16,7 @@ use crate::{
16}; 16};
17 17
18pub use self::{ 18pub use self::{
19 expr_ext::{ArrayExprKind, BinOp, Effect, ElseBranch, LiteralKind, PrefixOp, RangeOp}, 19 expr_ext::{ArrayExprKind, BinOp, Effect, ElseBranch, LiteralKind, PrefixOp, Radix, RangeOp},
20 generated::{nodes::*, tokens::*}, 20 generated::{nodes::*, tokens::*},
21 node_ext::{ 21 node_ext::{
22 AttrKind, FieldKind, NameOrNameRef, PathSegmentKind, SelfParamKind, SlicePatComponents, 22 AttrKind, FieldKind, NameOrNameRef, PathSegmentKind, SelfParamKind, SlicePatComponents,
diff --git a/crates/syntax/src/ast/expr_ext.rs b/crates/syntax/src/ast/expr_ext.rs
index f5ba87223..3aff01e83 100644
--- a/crates/syntax/src/ast/expr_ext.rs
+++ b/crates/syntax/src/ast/expr_ext.rs
@@ -358,6 +358,71 @@ impl ast::Literal {
358 _ => unreachable!(), 358 _ => unreachable!(),
359 } 359 }
360 } 360 }
361
362 // FIXME: should probably introduce string token type?
363 // https://github.com/rust-analyzer/rust-analyzer/issues/6308
364 pub fn int_value(&self) -> Option<(Radix, u128)> {
365 let suffix = match self.kind() {
366 LiteralKind::IntNumber { suffix } => suffix,
367 _ => return None,
368 };
369
370 let token = self.token();
371 let mut text = token.text().as_str();
372 text = &text[..text.len() - suffix.map_or(0, |it| it.len())];
373
374 let buf;
375 if text.contains("_") {
376 buf = text.replace('_', "");
377 text = buf.as_str();
378 };
379
380 let radix = Radix::identify(text)?;
381 let digits = &text[radix.prefix_len()..];
382 let value = u128::from_str_radix(digits, radix as u32).ok()?;
383 Some((radix, value))
384 }
385}
386
387#[derive(Debug, PartialEq, Eq, Copy, Clone)]
388pub enum Radix {
389 Binary = 2,
390 Octal = 8,
391 Decimal = 10,
392 Hexadecimal = 16,
393}
394
395impl Radix {
396 pub const ALL: &'static [Radix] =
397 &[Radix::Binary, Radix::Octal, Radix::Decimal, Radix::Hexadecimal];
398
399 fn identify(literal_text: &str) -> Option<Self> {
400 // We cannot express a literal in anything other than decimal in under 3 characters, so we return here if possible.
401 if literal_text.len() < 3 && literal_text.chars().all(|c| c.is_digit(10)) {
402 return Some(Self::Decimal);
403 }
404
405 let res = match &literal_text[..2] {
406 "0b" => Radix::Binary,
407 "0o" => Radix::Octal,
408 "0x" => Radix::Hexadecimal,
409 _ => Radix::Decimal,
410 };
411
412 // Checks that all characters after the base prefix are all valid digits for that base.
413 if literal_text[res.prefix_len()..].chars().all(|c| c.is_digit(res as u32)) {
414 Some(res)
415 } else {
416 None
417 }
418 }
419
420 const fn prefix_len(&self) -> usize {
421 match self {
422 Self::Decimal => 0,
423 _ => 2,
424 }
425 }
361} 426}
362 427
363#[derive(Debug, Clone, PartialEq, Eq)] 428#[derive(Debug, Clone, PartialEq, Eq)]