Skip to content

Commit 990e74b

Browse files
bors[bot]robojumper
andcommitted
Merge #1117
1117: [WIP] Tuple struct index inference r=matklad a=robojumper The first commit adds a helper struct `ast::FieldKind` to facilitate inference. The second commit adds a slightly modified test from #1109 while mentioning that there is a problem with how we're handling tuple indexing / floats. cc #1109 Co-authored-by: robojumper <robojumper@gmail.com>
2 parents 9d39b7b + 2caa690 commit 990e74b

File tree

15 files changed

+248
-14
lines changed

15 files changed

+248
-14
lines changed

crates/ra_hir/src/expr.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,10 @@ impl ExprCollector {
671671
}
672672
ast::ExprKind::FieldExpr(e) => {
673673
let expr = self.collect_expr_opt(e.expr());
674-
let name = e.name_ref().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
674+
let name = match e.field_access() {
675+
Some(kind) => kind.as_name(),
676+
_ => Name::missing(),
677+
};
675678
self.alloc_expr(Expr::Field { expr, name }, syntax_ptr)
676679
}
677680
ast::ExprKind::TryExpr(e) => {

crates/ra_hir/src/name.rs

+9
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ impl AsName for ast::Name {
9090
}
9191
}
9292

93+
impl<'a> AsName for ast::FieldKind<'a> {
94+
fn as_name(&self) -> Name {
95+
match self {
96+
ast::FieldKind::Name(nr) => nr.as_name(),
97+
ast::FieldKind::Index(idx) => Name::new(idx.text().clone()),
98+
}
99+
}
100+
}
101+
93102
impl AsName for ra_db::Dependency {
94103
fn as_name(&self) -> Name {
95104
Name::new(self.name.clone())

crates/ra_hir/src/ty/tests.rs

+59
Original file line numberDiff line numberDiff line change
@@ -2242,6 +2242,65 @@ static B: u64 = { let x = 1; x };
22422242
);
22432243
}
22442244

2245+
#[test]
2246+
fn tuple_struct_fields() {
2247+
assert_snapshot_matches!(
2248+
infer(r#"
2249+
struct S(i32, u64);
2250+
fn test() -> u64 {
2251+
let a = S(4, 6);
2252+
let b = a.0;
2253+
a.1
2254+
}
2255+
"#),
2256+
@r###"
2257+
[38; 87) '{ ... a.1 }': u64
2258+
[48; 49) 'a': S
2259+
[52; 53) 'S': S(i32, u64) -> S
2260+
[52; 59) 'S(4, 6)': S
2261+
[54; 55) '4': i32
2262+
[57; 58) '6': u64
2263+
[69; 70) 'b': i32
2264+
[73; 74) 'a': S
2265+
[73; 76) 'a.0': i32
2266+
[82; 83) 'a': S
2267+
[82; 85) 'a.1': u64"###
2268+
);
2269+
}
2270+
2271+
#[test]
2272+
fn tuple_struct_with_fn() {
2273+
assert_snapshot_matches!(
2274+
infer(r#"
2275+
struct S(fn(u32) -> u64);
2276+
fn test() -> u64 {
2277+
let a = S(|i| 2*i);
2278+
let b = a.0(4);
2279+
a.0(2)
2280+
}
2281+
"#),
2282+
@r###"
2283+
[44; 102) '{ ...0(2) }': u64
2284+
[54; 55) 'a': S
2285+
[58; 59) 'S': S(fn(u32) -> u64) -> S
2286+
[58; 68) 'S(|i| 2*i)': S
2287+
[60; 67) '|i| 2*i': fn(u32) -> u64
2288+
[61; 62) 'i': i32
2289+
[64; 65) '2': i32
2290+
[64; 67) '2*i': i32
2291+
[66; 67) 'i': i32
2292+
[78; 79) 'b': u64
2293+
[82; 83) 'a': S
2294+
[82; 85) 'a.0': fn(u32) -> u64
2295+
[82; 88) 'a.0(4)': u64
2296+
[86; 87) '4': u32
2297+
[94; 95) 'a': S
2298+
[94; 97) 'a.0': fn(u32) -> u64
2299+
[94; 100) 'a.0(2)': u64
2300+
[98; 99) '2': u32"###
2301+
);
2302+
}
2303+
22452304
fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String {
22462305
let func = source_binder::function_from_position(db, pos).unwrap();
22472306
let body_source_map = func.body_source_map(db);

crates/ra_ide_api/src/completion/complete_dot.rs

+22
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,26 @@ mod tests {
184184
",
185185
);
186186
}
187+
188+
#[test]
189+
fn test_tuple_field_inference() {
190+
check_ref_completion(
191+
"tuple_field_inference",
192+
r"
193+
pub struct S;
194+
impl S {
195+
pub fn blah(&self) {}
196+
}
197+
198+
struct T(S);
199+
200+
impl T {
201+
fn foo(&self) {
202+
// FIXME: This doesn't work without the trailing `a` as `0.` is a float
203+
self.0.a<|>
204+
}
205+
}
206+
",
207+
);
208+
}
187209
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
created: "2019-04-05T23:00:18.283812700Z"
3+
creator: insta@0.7.4
4+
source: crates/ra_ide_api/src/completion/completion_item.rs
5+
expression: kind_completions
6+
---
7+
[
8+
CompletionItem {
9+
label: "blah",
10+
source_range: [299; 300),
11+
delete: [299; 300),
12+
insert: "blah()$0",
13+
kind: Method,
14+
detail: "pub fn blah(&self)"
15+
}
16+
]

crates/ra_parser/src/grammar/expressions.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,14 @@ fn method_call_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker {
379379
// fn foo() {
380380
// x.foo;
381381
// x.0.bar;
382+
// x.0();
383+
// }
384+
385+
// test_err bad_tuple_index_expr
386+
// fn foo() {
387+
// x.0.;
388+
// x.1i32;
389+
// x.0x01;
382390
// }
383391
fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker {
384392
assert!(p.at(DOT));
@@ -387,7 +395,10 @@ fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker {
387395
if p.at(IDENT) {
388396
name_ref(p)
389397
} else if p.at(INT_NUMBER) {
390-
p.bump()
398+
p.bump();
399+
} else if p.at(FLOAT_NUMBER) {
400+
// FIXME: How to recover and instead parse INT + DOT?
401+
p.bump();
391402
} else {
392403
p.error("expected field name or number")
393404
}

crates/ra_syntax/src/ast.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub use self::{
1717
generated::*,
1818
traits::*,
1919
tokens::*,
20-
extensions::{PathSegmentKind, StructKind, SelfParamKind},
20+
extensions::{PathSegmentKind, StructKind, FieldKind, SelfParamKind},
2121
expr_extensions::{ElseBranch, PrefixOp, BinOp, LiteralKind},
2222
};
2323

crates/ra_syntax/src/ast/extensions.rs

+29-5
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
44
use itertools::Itertools;
55

6-
use crate::{
7-
SmolStr, SyntaxToken,
8-
ast::{self, AstNode, children, child_opt},
9-
SyntaxKind::*,
10-
};
6+
use crate::{SmolStr, SyntaxToken, ast::{self, AstNode, children, child_opt}, SyntaxKind::*, SyntaxElement};
7+
use ra_parser::SyntaxKind;
118

129
impl ast::Name {
1310
pub fn text(&self) -> &SmolStr {
@@ -217,6 +214,33 @@ impl ast::ExprStmt {
217214
}
218215
}
219216

217+
#[derive(Debug, Clone, PartialEq, Eq)]
218+
pub enum FieldKind<'a> {
219+
Name(&'a ast::NameRef),
220+
Index(SyntaxToken<'a>),
221+
}
222+
223+
impl ast::FieldExpr {
224+
pub fn index_token(&self) -> Option<SyntaxToken> {
225+
self.syntax
226+
.children_with_tokens()
227+
// FIXME: Accepting floats here to reject them in validation later
228+
.find(|c| c.kind() == SyntaxKind::INT_NUMBER || c.kind() == SyntaxKind::FLOAT_NUMBER)
229+
.as_ref()
230+
.and_then(SyntaxElement::as_token)
231+
}
232+
233+
pub fn field_access(&self) -> Option<FieldKind> {
234+
if let Some(nr) = self.name_ref() {
235+
Some(FieldKind::Name(nr))
236+
} else if let Some(tok) = self.index_token() {
237+
Some(FieldKind::Index(tok))
238+
} else {
239+
None
240+
}
241+
}
242+
}
243+
220244
impl ast::RefPat {
221245
pub fn is_mut(&self) -> bool {
222246
self.syntax().children_with_tokens().any(|n| n.kind() == MUT_KW)

crates/ra_syntax/src/syntax_error.rs

+4
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ pub enum SyntaxErrorKind {
9595
InvalidSuffix,
9696
InvalidBlockAttr,
9797
InvalidMatchInnerAttr,
98+
InvalidTupleIndexFormat,
9899
}
99100

100101
impl fmt::Display for SyntaxErrorKind {
@@ -139,6 +140,9 @@ impl fmt::Display for SyntaxErrorKind {
139140
InvalidMatchInnerAttr => {
140141
write!(f, "Inner attributes are only allowed directly after the opening brace of the match expression")
141142
}
143+
InvalidTupleIndexFormat => {
144+
write!(f, "Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix")
145+
}
142146
ParseError(msg) => write!(f, "{}", msg.0),
143147
}
144148
}

crates/ra_syntax/src/validation.rs

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod byte_string;
33
mod char;
44
mod string;
55
mod block;
6+
mod field_expr;
67

78
use crate::{
89
SourceFile, SyntaxError, AstNode, SyntaxNode,
@@ -17,6 +18,7 @@ pub(crate) fn validate(file: &SourceFile) -> Vec<SyntaxError> {
1718
let _ = visitor_ctx(&mut errors)
1819
.visit::<ast::Literal, _>(validate_literal)
1920
.visit::<ast::Block, _>(block::validate_block_node)
21+
.visit::<ast::FieldExpr, _>(field_expr::validate_field_expr_node)
2022
.accept(node);
2123
}
2224
errors
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use crate::{ast::{self, FieldKind},
2+
SyntaxError,
3+
SyntaxErrorKind::*,
4+
};
5+
6+
pub(crate) fn validate_field_expr_node(node: &ast::FieldExpr, errors: &mut Vec<SyntaxError>) {
7+
if let Some(FieldKind::Index(idx)) = node.field_access() {
8+
if idx.text().chars().any(|c| c < '0' || c > '9') {
9+
errors.push(SyntaxError::new(InvalidTupleIndexFormat, idx.range()));
10+
}
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fn foo() {
2+
x.0.;
3+
x.1i32;
4+
x.0x01;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
SOURCE_FILE@[0; 47)
2+
FN_DEF@[0; 46)
3+
FN_KW@[0; 2) "fn"
4+
WHITESPACE@[2; 3) " "
5+
NAME@[3; 6)
6+
IDENT@[3; 6) "foo"
7+
PARAM_LIST@[6; 8)
8+
L_PAREN@[6; 7) "("
9+
R_PAREN@[7; 8) ")"
10+
WHITESPACE@[8; 9) " "
11+
BLOCK@[9; 46)
12+
L_CURLY@[9; 10) "{"
13+
WHITESPACE@[10; 15) "\n "
14+
EXPR_STMT@[15; 20)
15+
FIELD_EXPR@[15; 19)
16+
PATH_EXPR@[15; 16)
17+
PATH@[15; 16)
18+
PATH_SEGMENT@[15; 16)
19+
NAME_REF@[15; 16)
20+
IDENT@[15; 16) "x"
21+
DOT@[16; 17) "."
22+
err: `Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix`
23+
FLOAT_NUMBER@[17; 19) "0."
24+
SEMI@[19; 20) ";"
25+
WHITESPACE@[20; 25) "\n "
26+
EXPR_STMT@[25; 32)
27+
FIELD_EXPR@[25; 31)
28+
PATH_EXPR@[25; 26)
29+
PATH@[25; 26)
30+
PATH_SEGMENT@[25; 26)
31+
NAME_REF@[25; 26)
32+
IDENT@[25; 26) "x"
33+
DOT@[26; 27) "."
34+
err: `Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix`
35+
INT_NUMBER@[27; 31) "1i32"
36+
SEMI@[31; 32) ";"
37+
WHITESPACE@[32; 37) "\n "
38+
EXPR_STMT@[37; 44)
39+
FIELD_EXPR@[37; 43)
40+
PATH_EXPR@[37; 38)
41+
PATH@[37; 38)
42+
PATH_SEGMENT@[37; 38)
43+
NAME_REF@[37; 38)
44+
IDENT@[37; 38) "x"
45+
DOT@[38; 39) "."
46+
err: `Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix`
47+
INT_NUMBER@[39; 43) "0x01"
48+
SEMI@[43; 44) ";"
49+
WHITESPACE@[44; 45) "\n"
50+
R_CURLY@[45; 46) "}"
51+
WHITESPACE@[46; 47) "\n"
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
fn foo() {
22
x.foo;
33
x.0.bar;
4+
x.0();
45
}

crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.txt

+21-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
SOURCE_FILE@[0; 37)
2-
FN_DEF@[0; 36)
1+
SOURCE_FILE@[0; 48)
2+
FN_DEF@[0; 47)
33
FN_KW@[0; 2) "fn"
44
WHITESPACE@[2; 3) " "
55
NAME@[3; 6)
@@ -8,7 +8,7 @@ SOURCE_FILE@[0; 37)
88
L_PAREN@[6; 7) "("
99
R_PAREN@[7; 8) ")"
1010
WHITESPACE@[8; 9) " "
11-
BLOCK@[9; 36)
11+
BLOCK@[9; 47)
1212
L_CURLY@[9; 10) "{"
1313
WHITESPACE@[10; 15) "\n "
1414
EXPR_STMT@[15; 21)
@@ -37,6 +37,21 @@ SOURCE_FILE@[0; 37)
3737
NAME_REF@[30; 33)
3838
IDENT@[30; 33) "bar"
3939
SEMI@[33; 34) ";"
40-
WHITESPACE@[34; 35) "\n"
41-
R_CURLY@[35; 36) "}"
42-
WHITESPACE@[36; 37) "\n"
40+
WHITESPACE@[34; 39) "\n "
41+
EXPR_STMT@[39; 45)
42+
CALL_EXPR@[39; 44)
43+
FIELD_EXPR@[39; 42)
44+
PATH_EXPR@[39; 40)
45+
PATH@[39; 40)
46+
PATH_SEGMENT@[39; 40)
47+
NAME_REF@[39; 40)
48+
IDENT@[39; 40) "x"
49+
DOT@[40; 41) "."
50+
INT_NUMBER@[41; 42) "0"
51+
ARG_LIST@[42; 44)
52+
L_PAREN@[42; 43) "("
53+
R_PAREN@[43; 44) ")"
54+
SEMI@[44; 45) ";"
55+
WHITESPACE@[45; 46) "\n"
56+
R_CURLY@[46; 47) "}"
57+
WHITESPACE@[47; 48) "\n"

0 commit comments

Comments
 (0)