diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/assists/src/handlers/add_custom_impl.rs | 152 | ||||
-rw-r--r-- | crates/assists/src/handlers/convert_integer_literal.rs | 206 | ||||
-rw-r--r-- | crates/syntax/src/ast.rs | 2 | ||||
-rw-r--r-- | crates/syntax/src/ast/expr_ext.rs | 65 |
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 @@ | |||
1 | use ide_db::imports_locator; | ||
1 | use itertools::Itertools; | 2 | use itertools::Itertools; |
2 | use syntax::{ | 3 | use 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 | ||
9 | use crate::{ | 10 | use 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 | // ``` |
31 | pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 34 | pub(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 | ||
81 | fn 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 | ||
114 | fn 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)] |
100 | mod tests { | 147 | mod 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 | " | ||
157 | mod fmt { | ||
158 | pub trait Debug {} | ||
159 | } | ||
160 | |||
161 | #[derive(Debu<|>g)] | ||
162 | struct Foo { | ||
163 | bar: String, | ||
164 | } | ||
165 | ", | ||
166 | " | ||
167 | mod fmt { | ||
168 | pub trait Debug {} | ||
169 | } | ||
170 | |||
171 | struct Foo { | ||
172 | bar: String, | ||
173 | } | ||
174 | |||
175 | impl 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 @@ | |||
1 | use syntax::{ast, AstNode, SmolStr}; | 1 | use syntax::{ast, ast::Radix, AstNode}; |
2 | 2 | ||
3 | use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; | 3 | use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; |
4 | 4 | ||
@@ -15,37 +15,34 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; | |||
15 | // ``` | 15 | // ``` |
16 | pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 16 | pub(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)] | ||
67 | enum IntegerLiteralBase { | ||
68 | Binary, | ||
69 | Octal, | ||
70 | Decimal, | ||
71 | Hexadecimal, | ||
72 | } | ||
73 | |||
74 | impl 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)] |
135 | mod tests { | 64 | mod 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 | ||
18 | pub use self::{ | 18 | pub 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)] | ||
388 | pub enum Radix { | ||
389 | Binary = 2, | ||
390 | Octal = 8, | ||
391 | Decimal = 10, | ||
392 | Hexadecimal = 16, | ||
393 | } | ||
394 | |||
395 | impl 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)] |