use syntax::{
ast::{self, AstNode},
- TextRange, TextSize, T,
+ SyntaxElement, TextRange, TextSize, T,
};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// ```
pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
+ let new_contents = adjusted_macro_contents(¯o_call)?;
- if !is_valid_macrocall(¯o_call, "dbg")? {
- return None;
- }
-
- let is_leaf = macro_call.syntax().next_sibling().is_none();
-
+ let macro_text_range = macro_call.syntax().text_range();
let macro_end = if macro_call.semicolon_token().is_some() {
- macro_call.syntax().text_range().end() - TextSize::of(';')
+ macro_text_range.end() - TextSize::of(';')
} else {
- macro_call.syntax().text_range().end()
+ macro_text_range.end()
};
- // macro_range determines what will be deleted and replaced with macro_content
- let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end);
- let paste_instead_of_dbg = {
- let text = macro_call.token_tree()?.syntax().text();
-
- // leafiness determines if we should include the parenthesis or not
- let slice_index: TextRange = if is_leaf {
- // leaf means - we can extract the contents of the dbg! in text
- TextRange::new(TextSize::of('('), text.len() - TextSize::of(')'))
- } else {
- // not leaf - means we should keep the parens
- TextRange::up_to(text.len())
- };
- text.slice(slice_index).to_string()
- };
+ acc.add(
+ AssistId("remove_dbg", AssistKind::Refactor),
+ "Remove dbg!()",
+ macro_text_range,
+ |builder| {
+ builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents);
+ },
+ )
+}
- let target = macro_call.syntax().text_range();
- acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| {
- builder.replace(macro_range, paste_instead_of_dbg);
- })
+fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
+ let contents = get_valid_macrocall_contents(¯o_call, "dbg")?;
+ let is_leaf = macro_call.syntax().next_sibling().is_none();
+ let macro_text_with_brackets = macro_call.token_tree()?.syntax().text();
+ let slice_index = if is_leaf || !needs_parentheses_around_macro_contents(contents) {
+ TextRange::new(TextSize::of('('), macro_text_with_brackets.len() - TextSize::of(')'))
+ } else {
+ // leave parenthesis around macro contents to preserve the semantics
+ TextRange::up_to(macro_text_with_brackets.len())
+ };
+ Some(macro_text_with_brackets.slice(slice_index).to_string())
}
/// Verifies that the given macro_call actually matches the given name
-/// and contains proper ending tokens
-fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> {
+/// and contains proper ending tokens, then returns the contents between the ending tokens
+fn get_valid_macrocall_contents(
+ macro_call: &ast::MacroCall,
+ macro_name: &str,
+) -> Option<Vec<SyntaxElement>> {
let path = macro_call.path()?;
let name_ref = path.segment()?.name_ref()?;
// Make sure it is actually a dbg-macro call, dbg followed by !
let excl = path.syntax().next_sibling_or_token()?;
-
if name_ref.text() != macro_name || excl.kind() != T![!] {
return None;
}
- let node = macro_call.token_tree()?.syntax().clone();
- let first_child = node.first_child_or_token()?;
- let last_child = node.last_child_or_token()?;
+ let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens();
+ let first_child = children_with_tokens.next()?;
+ let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>();
+ let last_child = contents_between_brackets.pop()?;
+
+ if contents_between_brackets.is_empty() {
+ None
+ } else {
+ match (first_child.kind(), last_child.kind()) {
+ (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => {
+ Some(contents_between_brackets)
+ }
+ _ => None,
+ }
+ }
+}
+
+fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -> bool {
+ if macro_contents.len() < 2 {
+ return false;
+ }
+
+ let mut macro_contents_kind_not_in_brackets = Vec::with_capacity(macro_contents.len());
- match (first_child.kind(), last_child.kind()) {
- (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true),
- _ => Some(false),
+ let mut first_bracket_in_macro = None;
+ let mut unpaired_brackets_in_contents = Vec::new();
+ for element in macro_contents {
+ match element.kind() {
+ T!['('] | T!['['] | T!['{'] => {
+ if let None = first_bracket_in_macro {
+ first_bracket_in_macro = Some(element.clone())
+ }
+ unpaired_brackets_in_contents.push(element);
+ }
+ T![')'] => {
+ if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['('])
+ {
+ return true;
+ }
+ }
+ T![']'] => {
+ if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['['])
+ {
+ return true;
+ }
+ }
+ T!['}'] => {
+ if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{'])
+ {
+ return true;
+ }
+ }
+ other_kind => {
+ if unpaired_brackets_in_contents.is_empty() {
+ macro_contents_kind_not_in_brackets.push(other_kind);
+ }
+ }
+ }
}
+
+ !unpaired_brackets_in_contents.is_empty()
+ || matches!(first_bracket_in_macro, Some(bracket) if bracket.kind() != T!['('])
+ || macro_contents_kind_not_in_brackets
+ .into_iter()
+ .any(|macro_contents_kind| macro_contents_kind.is_punct())
}
#[cfg(test)]
);
}
+ #[test]
+ fn remove_dbg_from_non_leaf_simple_expression() {
+ check_assist(
+ remove_dbg,
+ "
+fn main() {
+ let mut a = 1;
+ while dbg!<|>(a) < 10000 {
+ a += 1;
+ }
+}
+",
+ "
+fn main() {
+ let mut a = 1;
+ while a < 10000 {
+ a += 1;
+ }
+}
+",
+ );
+ }
+
#[test]
fn test_remove_dbg_keep_expression() {
check_assist(
r#"let res = <|>dbg!(a + b).foo();"#,
r#"let res = (a + b).foo();"#,
);
+
+ check_assist(remove_dbg, r#"let res = <|>dbg!(2 + 2) * 5"#, r#"let res = (2 + 2) * 5"#);
}
#[test]
self.imp.resolve_record_field(field)
}
- pub fn resolve_record_field_pat(&self, field: &ast::RecordPatField) -> Option<Field> {
- self.imp.resolve_record_field_pat(field)
+ pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<Field> {
+ self.imp.resolve_record_pat_field(field)
}
pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
self.analyze(field.syntax()).resolve_record_field(self.db, field)
}
- fn resolve_record_field_pat(&self, field: &ast::RecordPatField) -> Option<Field> {
- self.analyze(field.syntax()).resolve_record_field_pat(self.db, field)
+ fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<Field> {
+ self.analyze(field.syntax()).resolve_record_pat_field(self.db, field)
}
fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
Some((struct_field.into(), local))
}
- pub(crate) fn resolve_record_field_pat(
+ pub(crate) fn resolve_record_pat_field(
&self,
_db: &dyn HirDatabase,
field: &ast::RecordPatField,
) -> Option<Field> {
let pat_id = self.pat_id(&field.pat()?)?;
- let struct_field = self.infer.as_ref()?.record_field_pat_resolution(pat_id)?;
+ let struct_field = self.infer.as_ref()?.record_pat_field_resolution(pat_id)?;
Some(struct_field.into())
}
field_resolutions: FxHashMap<ExprId, FieldId>,
/// For each field in record literal, records the field it resolves to.
record_field_resolutions: FxHashMap<ExprId, FieldId>,
- record_field_pat_resolutions: FxHashMap<PatId, FieldId>,
+ record_pat_field_resolutions: FxHashMap<PatId, FieldId>,
/// For each struct literal, records the variant it resolves to.
variant_resolutions: FxHashMap<ExprOrPatId, VariantId>,
/// For each associated item record what it resolves to
pub fn record_field_resolution(&self, expr: ExprId) -> Option<FieldId> {
self.record_field_resolutions.get(&expr).copied()
}
- pub fn record_field_pat_resolution(&self, pat: PatId) -> Option<FieldId> {
- self.record_field_pat_resolutions.get(&pat).copied()
+ pub fn record_pat_field_resolution(&self, pat: PatId) -> Option<FieldId> {
+ self.record_pat_field_resolutions.get(&pat).copied()
}
pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option<VariantId> {
self.variant_resolutions.get(&id.into()).copied()
let matching_field = var_data.as_ref().and_then(|it| it.field(&subpat.name));
if let Some(local_id) = matching_field {
let field_def = FieldId { parent: def.unwrap(), local_id };
- self.result.record_field_pat_resolutions.insert(subpat.pat, field_def);
+ self.result.record_pat_field_resolutions.insert(subpat.pat, field_def);
}
let expected_ty =
ast::IdentPat(it) => {
let local = sema.to_def(&it)?;
- if let Some(record_field_pat) = it.syntax().parent().and_then(ast::RecordPatField::cast) {
- if record_field_pat.name_ref().is_none() {
- if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) {
+ if let Some(record_pat_field) = it.syntax().parent().and_then(ast::RecordPatField::cast) {
+ if record_pat_field.name_ref().is_none() {
+ if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) {
let field = Definition::Field(field);
return Some(NameClass::FieldShorthand { local, field });
}
}
}
- if let Some(record_field_pat) = ast::RecordPatField::cast(parent.clone()) {
- if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) {
+ if let Some(record_pat_field) = ast::RecordPatField::cast(parent.clone()) {
+ if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) {
let field = Definition::Field(field);
return Some(NameRefClass::Definition(field));
}
p.expect(T![')']);
}
-// test record_field_pat_list
+// test record_pat_field_list
// fn foo() {
// let S {} = ();
// let S { f, ref mut g } = ();
c => {
let m = p.start();
match c {
- // test record_field_pat
+ // test record_pat_field
// fn foo() {
// let S { 0: 1 } = ();
// let S { x: 1 } = ();
+++ /dev/null
-SOURCE_FILE@0..119
- FN@0..118
- FN_KW@0..2 "fn"
- WHITESPACE@2..3 " "
- NAME@3..6
- IDENT@3..6 "foo"
- PARAM_LIST@6..8
- L_PAREN@6..7 "("
- R_PAREN@7..8 ")"
- WHITESPACE@8..9 " "
- BLOCK_EXPR@9..118
- L_CURLY@9..10 "{"
- WHITESPACE@10..15 "\n "
- LET_STMT@15..29
- LET_KW@15..18 "let"
- WHITESPACE@18..19 " "
- RECORD_PAT@19..23
- PATH@19..20
- PATH_SEGMENT@19..20
- NAME_REF@19..20
- IDENT@19..20 "S"
- WHITESPACE@20..21 " "
- RECORD_PAT_FIELD_LIST@21..23
- L_CURLY@21..22 "{"
- R_CURLY@22..23 "}"
- WHITESPACE@23..24 " "
- EQ@24..25 "="
- WHITESPACE@25..26 " "
- TUPLE_EXPR@26..28
- L_PAREN@26..27 "("
- R_PAREN@27..28 ")"
- SEMICOLON@28..29 ";"
- WHITESPACE@29..34 "\n "
- LET_STMT@34..62
- LET_KW@34..37 "let"
- WHITESPACE@37..38 " "
- RECORD_PAT@38..56
- PATH@38..39
- PATH_SEGMENT@38..39
- NAME_REF@38..39
- IDENT@38..39 "S"
- WHITESPACE@39..40 " "
- RECORD_PAT_FIELD_LIST@40..56
- L_CURLY@40..41 "{"
- WHITESPACE@41..42 " "
- RECORD_PAT_FIELD@42..43
- IDENT_PAT@42..43
- NAME@42..43
- IDENT@42..43 "f"
- COMMA@43..44 ","
- WHITESPACE@44..45 " "
- RECORD_PAT_FIELD@45..54
- IDENT_PAT@45..54
- REF_KW@45..48 "ref"
- WHITESPACE@48..49 " "
- MUT_KW@49..52 "mut"
- WHITESPACE@52..53 " "
- NAME@53..54
- IDENT@53..54 "g"
- WHITESPACE@54..55 " "
- R_CURLY@55..56 "}"
- WHITESPACE@56..57 " "
- EQ@57..58 "="
- WHITESPACE@58..59 " "
- TUPLE_EXPR@59..61
- L_PAREN@59..60 "("
- R_PAREN@60..61 ")"
- SEMICOLON@61..62 ";"
- WHITESPACE@62..67 "\n "
- LET_STMT@67..90
- LET_KW@67..70 "let"
- WHITESPACE@70..71 " "
- RECORD_PAT@71..84
- PATH@71..72
- PATH_SEGMENT@71..72
- NAME_REF@71..72
- IDENT@71..72 "S"
- WHITESPACE@72..73 " "
- RECORD_PAT_FIELD_LIST@73..84
- L_CURLY@73..74 "{"
- WHITESPACE@74..75 " "
- RECORD_PAT_FIELD@75..79
- NAME_REF@75..76
- IDENT@75..76 "h"
- COLON@76..77 ":"
- WHITESPACE@77..78 " "
- WILDCARD_PAT@78..79
- UNDERSCORE@78..79 "_"
- COMMA@79..80 ","
- WHITESPACE@80..81 " "
- DOT2@81..83 ".."
- R_CURLY@83..84 "}"
- WHITESPACE@84..85 " "
- EQ@85..86 "="
- WHITESPACE@86..87 " "
- TUPLE_EXPR@87..89
- L_PAREN@87..88 "("
- R_PAREN@88..89 ")"
- SEMICOLON@89..90 ";"
- WHITESPACE@90..95 "\n "
- LET_STMT@95..116
- LET_KW@95..98 "let"
- WHITESPACE@98..99 " "
- RECORD_PAT@99..110
- PATH@99..100
- PATH_SEGMENT@99..100
- NAME_REF@99..100
- IDENT@99..100 "S"
- WHITESPACE@100..101 " "
- RECORD_PAT_FIELD_LIST@101..110
- L_CURLY@101..102 "{"
- WHITESPACE@102..103 " "
- RECORD_PAT_FIELD@103..107
- NAME_REF@103..104
- IDENT@103..104 "h"
- COLON@104..105 ":"
- WHITESPACE@105..106 " "
- WILDCARD_PAT@106..107
- UNDERSCORE@106..107 "_"
- COMMA@107..108 ","
- WHITESPACE@108..109 " "
- R_CURLY@109..110 "}"
- WHITESPACE@110..111 " "
- EQ@111..112 "="
- WHITESPACE@112..113 " "
- TUPLE_EXPR@113..115
- L_PAREN@113..114 "("
- R_PAREN@114..115 ")"
- SEMICOLON@115..116 ";"
- WHITESPACE@116..117 "\n"
- R_CURLY@117..118 "}"
- WHITESPACE@118..119 "\n"
+++ /dev/null
-fn foo() {
- let S {} = ();
- let S { f, ref mut g } = ();
- let S { h: _, ..} = ();
- let S { h: _, } = ();
-}
--- /dev/null
+SOURCE_FILE@0..119
+ FN@0..118
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..6
+ IDENT@3..6 "foo"
+ PARAM_LIST@6..8
+ L_PAREN@6..7 "("
+ R_PAREN@7..8 ")"
+ WHITESPACE@8..9 " "
+ BLOCK_EXPR@9..118
+ L_CURLY@9..10 "{"
+ WHITESPACE@10..15 "\n "
+ LET_STMT@15..29
+ LET_KW@15..18 "let"
+ WHITESPACE@18..19 " "
+ RECORD_PAT@19..23
+ PATH@19..20
+ PATH_SEGMENT@19..20
+ NAME_REF@19..20
+ IDENT@19..20 "S"
+ WHITESPACE@20..21 " "
+ RECORD_PAT_FIELD_LIST@21..23
+ L_CURLY@21..22 "{"
+ R_CURLY@22..23 "}"
+ WHITESPACE@23..24 " "
+ EQ@24..25 "="
+ WHITESPACE@25..26 " "
+ TUPLE_EXPR@26..28
+ L_PAREN@26..27 "("
+ R_PAREN@27..28 ")"
+ SEMICOLON@28..29 ";"
+ WHITESPACE@29..34 "\n "
+ LET_STMT@34..62
+ LET_KW@34..37 "let"
+ WHITESPACE@37..38 " "
+ RECORD_PAT@38..56
+ PATH@38..39
+ PATH_SEGMENT@38..39
+ NAME_REF@38..39
+ IDENT@38..39 "S"
+ WHITESPACE@39..40 " "
+ RECORD_PAT_FIELD_LIST@40..56
+ L_CURLY@40..41 "{"
+ WHITESPACE@41..42 " "
+ RECORD_PAT_FIELD@42..43
+ IDENT_PAT@42..43
+ NAME@42..43
+ IDENT@42..43 "f"
+ COMMA@43..44 ","
+ WHITESPACE@44..45 " "
+ RECORD_PAT_FIELD@45..54
+ IDENT_PAT@45..54
+ REF_KW@45..48 "ref"
+ WHITESPACE@48..49 " "
+ MUT_KW@49..52 "mut"
+ WHITESPACE@52..53 " "
+ NAME@53..54
+ IDENT@53..54 "g"
+ WHITESPACE@54..55 " "
+ R_CURLY@55..56 "}"
+ WHITESPACE@56..57 " "
+ EQ@57..58 "="
+ WHITESPACE@58..59 " "
+ TUPLE_EXPR@59..61
+ L_PAREN@59..60 "("
+ R_PAREN@60..61 ")"
+ SEMICOLON@61..62 ";"
+ WHITESPACE@62..67 "\n "
+ LET_STMT@67..90
+ LET_KW@67..70 "let"
+ WHITESPACE@70..71 " "
+ RECORD_PAT@71..84
+ PATH@71..72
+ PATH_SEGMENT@71..72
+ NAME_REF@71..72
+ IDENT@71..72 "S"
+ WHITESPACE@72..73 " "
+ RECORD_PAT_FIELD_LIST@73..84
+ L_CURLY@73..74 "{"
+ WHITESPACE@74..75 " "
+ RECORD_PAT_FIELD@75..79
+ NAME_REF@75..76
+ IDENT@75..76 "h"
+ COLON@76..77 ":"
+ WHITESPACE@77..78 " "
+ WILDCARD_PAT@78..79
+ UNDERSCORE@78..79 "_"
+ COMMA@79..80 ","
+ WHITESPACE@80..81 " "
+ DOT2@81..83 ".."
+ R_CURLY@83..84 "}"
+ WHITESPACE@84..85 " "
+ EQ@85..86 "="
+ WHITESPACE@86..87 " "
+ TUPLE_EXPR@87..89
+ L_PAREN@87..88 "("
+ R_PAREN@88..89 ")"
+ SEMICOLON@89..90 ";"
+ WHITESPACE@90..95 "\n "
+ LET_STMT@95..116
+ LET_KW@95..98 "let"
+ WHITESPACE@98..99 " "
+ RECORD_PAT@99..110
+ PATH@99..100
+ PATH_SEGMENT@99..100
+ NAME_REF@99..100
+ IDENT@99..100 "S"
+ WHITESPACE@100..101 " "
+ RECORD_PAT_FIELD_LIST@101..110
+ L_CURLY@101..102 "{"
+ WHITESPACE@102..103 " "
+ RECORD_PAT_FIELD@103..107
+ NAME_REF@103..104
+ IDENT@103..104 "h"
+ COLON@104..105 ":"
+ WHITESPACE@105..106 " "
+ WILDCARD_PAT@106..107
+ UNDERSCORE@106..107 "_"
+ COMMA@107..108 ","
+ WHITESPACE@108..109 " "
+ R_CURLY@109..110 "}"
+ WHITESPACE@110..111 " "
+ EQ@111..112 "="
+ WHITESPACE@112..113 " "
+ TUPLE_EXPR@113..115
+ L_PAREN@113..114 "("
+ R_PAREN@114..115 ")"
+ SEMICOLON@115..116 ";"
+ WHITESPACE@116..117 "\n"
+ R_CURLY@117..118 "}"
+ WHITESPACE@118..119 "\n"
--- /dev/null
+fn foo() {
+ let S {} = ();
+ let S { f, ref mut g } = ();
+ let S { h: _, ..} = ();
+ let S { h: _, } = ();
+}
+++ /dev/null
-SOURCE_FILE@0..63
- FN@0..62
- FN_KW@0..2 "fn"
- WHITESPACE@2..3 " "
- NAME@3..6
- IDENT@3..6 "foo"
- PARAM_LIST@6..8
- L_PAREN@6..7 "("
- R_PAREN@7..8 ")"
- WHITESPACE@8..9 " "
- BLOCK_EXPR@9..62
- L_CURLY@9..10 "{"
- WHITESPACE@10..15 "\n "
- LET_STMT@15..35
- LET_KW@15..18 "let"
- WHITESPACE@18..19 " "
- RECORD_PAT@19..29
- PATH@19..20
- PATH_SEGMENT@19..20
- NAME_REF@19..20
- IDENT@19..20 "S"
- WHITESPACE@20..21 " "
- RECORD_PAT_FIELD_LIST@21..29
- L_CURLY@21..22 "{"
- WHITESPACE@22..23 " "
- RECORD_PAT_FIELD@23..27
- NAME_REF@23..24
- INT_NUMBER@23..24 "0"
- COLON@24..25 ":"
- WHITESPACE@25..26 " "
- LITERAL_PAT@26..27
- LITERAL@26..27
- INT_NUMBER@26..27 "1"
- WHITESPACE@27..28 " "
- R_CURLY@28..29 "}"
- WHITESPACE@29..30 " "
- EQ@30..31 "="
- WHITESPACE@31..32 " "
- TUPLE_EXPR@32..34
- L_PAREN@32..33 "("
- R_PAREN@33..34 ")"
- SEMICOLON@34..35 ";"
- WHITESPACE@35..40 "\n "
- LET_STMT@40..60
- LET_KW@40..43 "let"
- WHITESPACE@43..44 " "
- RECORD_PAT@44..54
- PATH@44..45
- PATH_SEGMENT@44..45
- NAME_REF@44..45
- IDENT@44..45 "S"
- WHITESPACE@45..46 " "
- RECORD_PAT_FIELD_LIST@46..54
- L_CURLY@46..47 "{"
- WHITESPACE@47..48 " "
- RECORD_PAT_FIELD@48..52
- NAME_REF@48..49
- IDENT@48..49 "x"
- COLON@49..50 ":"
- WHITESPACE@50..51 " "
- LITERAL_PAT@51..52
- LITERAL@51..52
- INT_NUMBER@51..52 "1"
- WHITESPACE@52..53 " "
- R_CURLY@53..54 "}"
- WHITESPACE@54..55 " "
- EQ@55..56 "="
- WHITESPACE@56..57 " "
- TUPLE_EXPR@57..59
- L_PAREN@57..58 "("
- R_PAREN@58..59 ")"
- SEMICOLON@59..60 ";"
- WHITESPACE@60..61 "\n"
- R_CURLY@61..62 "}"
- WHITESPACE@62..63 "\n"
+++ /dev/null
-fn foo() {
- let S { 0: 1 } = ();
- let S { x: 1 } = ();
-}
--- /dev/null
+SOURCE_FILE@0..63
+ FN@0..62
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..6
+ IDENT@3..6 "foo"
+ PARAM_LIST@6..8
+ L_PAREN@6..7 "("
+ R_PAREN@7..8 ")"
+ WHITESPACE@8..9 " "
+ BLOCK_EXPR@9..62
+ L_CURLY@9..10 "{"
+ WHITESPACE@10..15 "\n "
+ LET_STMT@15..35
+ LET_KW@15..18 "let"
+ WHITESPACE@18..19 " "
+ RECORD_PAT@19..29
+ PATH@19..20
+ PATH_SEGMENT@19..20
+ NAME_REF@19..20
+ IDENT@19..20 "S"
+ WHITESPACE@20..21 " "
+ RECORD_PAT_FIELD_LIST@21..29
+ L_CURLY@21..22 "{"
+ WHITESPACE@22..23 " "
+ RECORD_PAT_FIELD@23..27
+ NAME_REF@23..24
+ INT_NUMBER@23..24 "0"
+ COLON@24..25 ":"
+ WHITESPACE@25..26 " "
+ LITERAL_PAT@26..27
+ LITERAL@26..27
+ INT_NUMBER@26..27 "1"
+ WHITESPACE@27..28 " "
+ R_CURLY@28..29 "}"
+ WHITESPACE@29..30 " "
+ EQ@30..31 "="
+ WHITESPACE@31..32 " "
+ TUPLE_EXPR@32..34
+ L_PAREN@32..33 "("
+ R_PAREN@33..34 ")"
+ SEMICOLON@34..35 ";"
+ WHITESPACE@35..40 "\n "
+ LET_STMT@40..60
+ LET_KW@40..43 "let"
+ WHITESPACE@43..44 " "
+ RECORD_PAT@44..54
+ PATH@44..45
+ PATH_SEGMENT@44..45
+ NAME_REF@44..45
+ IDENT@44..45 "S"
+ WHITESPACE@45..46 " "
+ RECORD_PAT_FIELD_LIST@46..54
+ L_CURLY@46..47 "{"
+ WHITESPACE@47..48 " "
+ RECORD_PAT_FIELD@48..52
+ NAME_REF@48..49
+ IDENT@48..49 "x"
+ COLON@49..50 ":"
+ WHITESPACE@50..51 " "
+ LITERAL_PAT@51..52
+ LITERAL@51..52
+ INT_NUMBER@51..52 "1"
+ WHITESPACE@52..53 " "
+ R_CURLY@53..54 "}"
+ WHITESPACE@54..55 " "
+ EQ@55..56 "="
+ WHITESPACE@56..57 " "
+ TUPLE_EXPR@57..59
+ L_PAREN@57..58 "("
+ R_PAREN@58..59 ")"
+ SEMICOLON@59..60 ";"
+ WHITESPACE@60..61 "\n"
+ R_CURLY@61..62 "}"
+ WHITESPACE@62..63 "\n"
--- /dev/null
+fn foo() {
+ let S { 0: 1 } = ();
+ let S { x: 1 } = ();
+}