From ca40ca93a55ffa08d3e699fc877e7e189b526c66 Mon Sep 17 00:00:00 2001 From: robojumper Date: Fri, 5 Apr 2019 22:34:45 +0200 Subject: Parse and infer tuple indices --- crates/ra_hir/src/expr.rs | 5 +- crates/ra_hir/src/name.rs | 9 ++++ crates/ra_hir/src/ty/tests.rs | 59 ++++++++++++++++++++++ crates/ra_parser/src/grammar/expressions.rs | 13 ++++- crates/ra_syntax/src/ast.rs | 2 +- crates/ra_syntax/src/ast/extensions.rs | 34 +++++++++++-- crates/ra_syntax/src/syntax_error.rs | 4 ++ crates/ra_syntax/src/validation.rs | 2 + crates/ra_syntax/src/validation/field_expr.rs | 12 +++++ .../parser/inline/err/0010_bad_tuple_index_expr.rs | 5 ++ .../inline/err/0010_bad_tuple_index_expr.txt | 51 +++++++++++++++++++ .../tests/data/parser/inline/ok/0011_field_expr.rs | 1 + .../data/parser/inline/ok/0011_field_expr.txt | 27 +++++++--- 13 files changed, 210 insertions(+), 14 deletions(-) create mode 100644 crates/ra_syntax/src/validation/field_expr.rs create mode 100644 crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.rs create mode 100644 crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.txt (limited to 'crates') diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs index b2a237ece..cfa824458 100644 --- a/crates/ra_hir/src/expr.rs +++ b/crates/ra_hir/src/expr.rs @@ -671,7 +671,10 @@ impl ExprCollector { } ast::ExprKind::FieldExpr(e) => { let expr = self.collect_expr_opt(e.expr()); - let name = e.name_ref().map(|nr| nr.as_name()).unwrap_or_else(Name::missing); + let name = match e.field_access() { + Some(kind) => kind.as_name(), + _ => Name::missing(), + }; self.alloc_expr(Expr::Field { expr, name }, syntax_ptr) } ast::ExprKind::TryExpr(e) => { diff --git a/crates/ra_hir/src/name.rs b/crates/ra_hir/src/name.rs index 677d18efc..283f37845 100644 --- a/crates/ra_hir/src/name.rs +++ b/crates/ra_hir/src/name.rs @@ -90,6 +90,15 @@ impl AsName for ast::Name { } } +impl<'a> AsName for ast::FieldKind<'a> { + fn as_name(&self) -> Name { + match self { + ast::FieldKind::Name(nr) => nr.as_name(), + ast::FieldKind::Index(idx) => Name::new(idx.text().clone()), + } + } +} + impl AsName for ra_db::Dependency { fn as_name(&self) -> Name { Name::new(self.name.clone()) diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index 0b7c841df..f0164f7ea 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs @@ -2242,6 +2242,65 @@ static B: u64 = { let x = 1; x }; ); } +#[test] +fn tuple_struct_fields() { + assert_snapshot_matches!( + infer(r#" +struct S(i32, u64); +fn test() -> u64 { + let a = S(4, 6); + let b = a.0; + a.1 +} +"#), + @r###" +[38; 87) '{ ... a.1 }': u64 +[48; 49) 'a': S +[52; 53) 'S': S(i32, u64) -> S +[52; 59) 'S(4, 6)': S +[54; 55) '4': i32 +[57; 58) '6': u64 +[69; 70) 'b': i32 +[73; 74) 'a': S +[73; 76) 'a.0': i32 +[82; 83) 'a': S +[82; 85) 'a.1': u64"### + ); +} + +#[test] +fn tuple_struct_with_fn() { + assert_snapshot_matches!( + infer(r#" +struct S(fn(u32) -> u64); +fn test() -> u64 { + let a = S(|i| 2*i); + let b = a.0(4); + a.0(2) +} +"#), + @r###" +[44; 102) '{ ...0(2) }': u64 +[54; 55) 'a': S +[58; 59) 'S': S(fn(u32) -> u64) -> S +[58; 68) 'S(|i| 2*i)': S +[60; 67) '|i| 2*i': fn(u32) -> u64 +[61; 62) 'i': i32 +[64; 65) '2': i32 +[64; 67) '2*i': i32 +[66; 67) 'i': i32 +[78; 79) 'b': u64 +[82; 83) 'a': S +[82; 85) 'a.0': fn(u32) -> u64 +[82; 88) 'a.0(4)': u64 +[86; 87) '4': u32 +[94; 95) 'a': S +[94; 97) 'a.0': fn(u32) -> u64 +[94; 100) 'a.0(2)': u64 +[98; 99) '2': u32"### + ); +} + fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String { let func = source_binder::function_from_position(db, pos).unwrap(); let body_source_map = func.body_source_map(db); diff --git a/crates/ra_parser/src/grammar/expressions.rs b/crates/ra_parser/src/grammar/expressions.rs index bf5d6544d..9b38b0a31 100644 --- a/crates/ra_parser/src/grammar/expressions.rs +++ b/crates/ra_parser/src/grammar/expressions.rs @@ -379,6 +379,14 @@ fn method_call_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker { // fn foo() { // x.foo; // x.0.bar; +// x.0(); +// } + +// test_err bad_tuple_index_expr +// fn foo() { +// x.0.; +// x.1i32; +// x.0x01; // } fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker { assert!(p.at(DOT)); @@ -387,7 +395,10 @@ fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker { if p.at(IDENT) { name_ref(p) } else if p.at(INT_NUMBER) { - p.bump() + p.bump(); + } else if p.at(FLOAT_NUMBER) { + // FIXME: How to recover and instead parse INT + DOT? + p.bump(); } else { p.error("expected field name or number") } diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index 9f5c41b0c..a06a6375d 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs @@ -17,7 +17,7 @@ pub use self::{ generated::*, traits::*, tokens::*, - extensions::{PathSegmentKind, StructKind, SelfParamKind}, + extensions::{PathSegmentKind, StructKind, FieldKind, SelfParamKind}, expr_extensions::{ElseBranch, PrefixOp, BinOp, LiteralKind}, }; diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs index aec57c380..ca33b43e7 100644 --- a/crates/ra_syntax/src/ast/extensions.rs +++ b/crates/ra_syntax/src/ast/extensions.rs @@ -3,11 +3,8 @@ use itertools::Itertools; -use crate::{ - SmolStr, SyntaxToken, - ast::{self, AstNode, children, child_opt}, - SyntaxKind::*, -}; +use crate::{SmolStr, SyntaxToken, ast::{self, AstNode, children, child_opt}, SyntaxKind::*, SyntaxElement}; +use ra_parser::SyntaxKind; impl ast::Name { pub fn text(&self) -> &SmolStr { @@ -217,6 +214,33 @@ impl ast::ExprStmt { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FieldKind<'a> { + Name(&'a ast::NameRef), + Index(SyntaxToken<'a>), +} + +impl ast::FieldExpr { + pub fn index_token(&self) -> Option { + self.syntax + .children_with_tokens() + // FIXME: Accepting floats here to reject them in validation later + .find(|c| c.kind() == SyntaxKind::INT_NUMBER || c.kind() == SyntaxKind::FLOAT_NUMBER) + .as_ref() + .and_then(SyntaxElement::as_token) + } + + pub fn field_access(&self) -> Option { + if let Some(nr) = self.name_ref() { + Some(FieldKind::Name(nr)) + } else if let Some(tok) = self.index_token() { + Some(FieldKind::Index(tok)) + } else { + None + } + } +} + impl ast::RefPat { pub fn is_mut(&self) -> bool { self.syntax().children_with_tokens().any(|n| n.kind() == MUT_KW) diff --git a/crates/ra_syntax/src/syntax_error.rs b/crates/ra_syntax/src/syntax_error.rs index 4b8c22a57..4198eefdb 100644 --- a/crates/ra_syntax/src/syntax_error.rs +++ b/crates/ra_syntax/src/syntax_error.rs @@ -95,6 +95,7 @@ pub enum SyntaxErrorKind { InvalidSuffix, InvalidBlockAttr, InvalidMatchInnerAttr, + InvalidTupleIndexFormat, } impl fmt::Display for SyntaxErrorKind { @@ -139,6 +140,9 @@ impl fmt::Display for SyntaxErrorKind { InvalidMatchInnerAttr => { write!(f, "Inner attributes are only allowed directly after the opening brace of the match expression") } + InvalidTupleIndexFormat => { + write!(f, "Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix") + } ParseError(msg) => write!(f, "{}", msg.0), } } diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs index fc534df83..c2f545173 100644 --- a/crates/ra_syntax/src/validation.rs +++ b/crates/ra_syntax/src/validation.rs @@ -3,6 +3,7 @@ mod byte_string; mod char; mod string; mod block; +mod field_expr; use crate::{ SourceFile, SyntaxError, AstNode, SyntaxNode, @@ -17,6 +18,7 @@ pub(crate) fn validate(file: &SourceFile) -> Vec { let _ = visitor_ctx(&mut errors) .visit::(validate_literal) .visit::(block::validate_block_node) + .visit::(field_expr::validate_field_expr_node) .accept(node); } errors diff --git a/crates/ra_syntax/src/validation/field_expr.rs b/crates/ra_syntax/src/validation/field_expr.rs new file mode 100644 index 000000000..2b405062e --- /dev/null +++ b/crates/ra_syntax/src/validation/field_expr.rs @@ -0,0 +1,12 @@ +use crate::{ast::{self, FieldKind}, + SyntaxError, + SyntaxErrorKind::*, +}; + +pub(crate) fn validate_field_expr_node(node: &ast::FieldExpr, errors: &mut Vec) { + if let Some(FieldKind::Index(idx)) = node.field_access() { + if idx.text().chars().any(|c| c < '0' || c > '9') { + errors.push(SyntaxError::new(InvalidTupleIndexFormat, idx.range())); + } + } +} diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.rs b/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.rs new file mode 100644 index 000000000..30cc49138 --- /dev/null +++ b/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.rs @@ -0,0 +1,5 @@ +fn foo() { + x.0.; + x.1i32; + x.0x01; +} diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.txt b/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.txt new file mode 100644 index 000000000..c111f60ea --- /dev/null +++ b/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.txt @@ -0,0 +1,51 @@ +SOURCE_FILE@[0; 47) + FN_DEF@[0; 46) + 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@[9; 46) + L_CURLY@[9; 10) "{" + WHITESPACE@[10; 15) "\n " + EXPR_STMT@[15; 20) + FIELD_EXPR@[15; 19) + PATH_EXPR@[15; 16) + PATH@[15; 16) + PATH_SEGMENT@[15; 16) + NAME_REF@[15; 16) + IDENT@[15; 16) "x" + DOT@[16; 17) "." + err: `Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix` + FLOAT_NUMBER@[17; 19) "0." + SEMI@[19; 20) ";" + WHITESPACE@[20; 25) "\n " + EXPR_STMT@[25; 32) + FIELD_EXPR@[25; 31) + PATH_EXPR@[25; 26) + PATH@[25; 26) + PATH_SEGMENT@[25; 26) + NAME_REF@[25; 26) + IDENT@[25; 26) "x" + DOT@[26; 27) "." + err: `Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix` + INT_NUMBER@[27; 31) "1i32" + SEMI@[31; 32) ";" + WHITESPACE@[32; 37) "\n " + EXPR_STMT@[37; 44) + FIELD_EXPR@[37; 43) + PATH_EXPR@[37; 38) + PATH@[37; 38) + PATH_SEGMENT@[37; 38) + NAME_REF@[37; 38) + IDENT@[37; 38) "x" + DOT@[38; 39) "." + err: `Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix` + INT_NUMBER@[39; 43) "0x01" + SEMI@[43; 44) ";" + WHITESPACE@[44; 45) "\n" + R_CURLY@[45; 46) "}" + WHITESPACE@[46; 47) "\n" diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.rs index 3e69538e5..b8da2ddc3 100644 --- a/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.rs +++ b/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.rs @@ -1,4 +1,5 @@ fn foo() { x.foo; x.0.bar; + x.0(); } diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.txt index a86702843..78054ec5a 100644 --- a/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.txt +++ b/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.txt @@ -1,5 +1,5 @@ -SOURCE_FILE@[0; 37) - FN_DEF@[0; 36) +SOURCE_FILE@[0; 48) + FN_DEF@[0; 47) FN_KW@[0; 2) "fn" WHITESPACE@[2; 3) " " NAME@[3; 6) @@ -8,7 +8,7 @@ SOURCE_FILE@[0; 37) L_PAREN@[6; 7) "(" R_PAREN@[7; 8) ")" WHITESPACE@[8; 9) " " - BLOCK@[9; 36) + BLOCK@[9; 47) L_CURLY@[9; 10) "{" WHITESPACE@[10; 15) "\n " EXPR_STMT@[15; 21) @@ -37,6 +37,21 @@ SOURCE_FILE@[0; 37) NAME_REF@[30; 33) IDENT@[30; 33) "bar" SEMI@[33; 34) ";" - WHITESPACE@[34; 35) "\n" - R_CURLY@[35; 36) "}" - WHITESPACE@[36; 37) "\n" + WHITESPACE@[34; 39) "\n " + EXPR_STMT@[39; 45) + CALL_EXPR@[39; 44) + FIELD_EXPR@[39; 42) + PATH_EXPR@[39; 40) + PATH@[39; 40) + PATH_SEGMENT@[39; 40) + NAME_REF@[39; 40) + IDENT@[39; 40) "x" + DOT@[40; 41) "." + INT_NUMBER@[41; 42) "0" + ARG_LIST@[42; 44) + L_PAREN@[42; 43) "(" + R_PAREN@[43; 44) ")" + SEMI@[44; 45) ";" + WHITESPACE@[45; 46) "\n" + R_CURLY@[46; 47) "}" + WHITESPACE@[47; 48) "\n" -- cgit v1.2.3 From 2caa690ef6feba3f78354e715deea37983b149ac Mon Sep 17 00:00:00 2001 From: robojumper Date: Fri, 5 Apr 2019 22:59:55 +0200 Subject: Rudimentarily autocomplete tuple struct field access --- crates/ra_ide_api/src/completion/complete_dot.rs | 22 ++++++++++++++++++++++ .../completion_item__tuple_field_inference.snap | 16 ++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 crates/ra_ide_api/src/completion/snapshots/completion_item__tuple_field_inference.snap (limited to 'crates') diff --git a/crates/ra_ide_api/src/completion/complete_dot.rs b/crates/ra_ide_api/src/completion/complete_dot.rs index f54a02d1d..18b2d68d5 100644 --- a/crates/ra_ide_api/src/completion/complete_dot.rs +++ b/crates/ra_ide_api/src/completion/complete_dot.rs @@ -184,4 +184,26 @@ mod tests { ", ); } + + #[test] + fn test_tuple_field_inference() { + check_ref_completion( + "tuple_field_inference", + r" + pub struct S; + impl S { + pub fn blah(&self) {} + } + + struct T(S); + + impl T { + fn foo(&self) { + // FIXME: This doesn't work without the trailing `a` as `0.` is a float + self.0.a<|> + } + } + ", + ); + } } diff --git a/crates/ra_ide_api/src/completion/snapshots/completion_item__tuple_field_inference.snap b/crates/ra_ide_api/src/completion/snapshots/completion_item__tuple_field_inference.snap new file mode 100644 index 000000000..72c8973b8 --- /dev/null +++ b/crates/ra_ide_api/src/completion/snapshots/completion_item__tuple_field_inference.snap @@ -0,0 +1,16 @@ +--- +created: "2019-04-05T23:00:18.283812700Z" +creator: insta@0.7.4 +source: crates/ra_ide_api/src/completion/completion_item.rs +expression: kind_completions +--- +[ + CompletionItem { + label: "blah", + source_range: [299; 300), + delete: [299; 300), + insert: "blah()$0", + kind: Method, + detail: "pub fn blah(&self)" + } +] -- cgit v1.2.3