Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tuple struct index inference #1117

Merged
merged 2 commits into from
Apr 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion crates/ra_hir/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
9 changes: 9 additions & 0 deletions crates/ra_hir/src/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
59 changes: 59 additions & 0 deletions crates/ra_hir/src/ty/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
22 changes: 22 additions & 0 deletions crates/ra_ide_api/src/completion/complete_dot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<|>
}
}
",
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
created: "2019-04-05T23:00:18.283812700Z"
creator: [email protected]
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)"
}
]
13 changes: 12 additions & 1 deletion crates/ra_parser/src/grammar/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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")
}
Expand Down
2 changes: 1 addition & 1 deletion crates/ra_syntax/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};

Expand Down
34 changes: 29 additions & 5 deletions crates/ra_syntax/src/ast/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<SyntaxToken> {
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<FieldKind> {
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)
Expand Down
4 changes: 4 additions & 0 deletions crates/ra_syntax/src/syntax_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ pub enum SyntaxErrorKind {
InvalidSuffix,
InvalidBlockAttr,
InvalidMatchInnerAttr,
InvalidTupleIndexFormat,
}

impl fmt::Display for SyntaxErrorKind {
Expand Down Expand Up @@ -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),
}
}
Expand Down
2 changes: 2 additions & 0 deletions crates/ra_syntax/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod byte_string;
mod char;
mod string;
mod block;
mod field_expr;

use crate::{
SourceFile, SyntaxError, AstNode, SyntaxNode,
Expand All @@ -17,6 +18,7 @@ pub(crate) fn validate(file: &SourceFile) -> Vec<SyntaxError> {
let _ = visitor_ctx(&mut errors)
.visit::<ast::Literal, _>(validate_literal)
.visit::<ast::Block, _>(block::validate_block_node)
.visit::<ast::FieldExpr, _>(field_expr::validate_field_expr_node)
.accept(node);
}
errors
Expand Down
12 changes: 12 additions & 0 deletions crates/ra_syntax/src/validation/field_expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::{ast::{self, FieldKind},
SyntaxError,
SyntaxErrorKind::*,
};

pub(crate) fn validate_field_expr_node(node: &ast::FieldExpr, errors: &mut Vec<SyntaxError>) {
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()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fn foo() {
x.0.;
x.1i32;
x.0x01;
}
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
fn foo() {
x.foo;
x.0.bar;
x.0();
}
27 changes: 21 additions & 6 deletions crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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"