mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-08-22 19:34:16 +00:00
Parse and infer tuple indices
This commit is contained in:
parent
0372eca5b2
commit
ca40ca93a5
13 changed files with 210 additions and 14 deletions
|
@ -671,7 +671,10 @@ impl ExprCollector {
|
||||||
}
|
}
|
||||||
ast::ExprKind::FieldExpr(e) => {
|
ast::ExprKind::FieldExpr(e) => {
|
||||||
let expr = self.collect_expr_opt(e.expr());
|
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)
|
self.alloc_expr(Expr::Field { expr, name }, syntax_ptr)
|
||||||
}
|
}
|
||||||
ast::ExprKind::TryExpr(e) => {
|
ast::ExprKind::TryExpr(e) => {
|
||||||
|
|
|
@ -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 {
|
impl AsName for ra_db::Dependency {
|
||||||
fn as_name(&self) -> Name {
|
fn as_name(&self) -> Name {
|
||||||
Name::new(self.name.clone())
|
Name::new(self.name.clone())
|
||||||
|
|
|
@ -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 {
|
fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String {
|
||||||
let func = source_binder::function_from_position(db, pos).unwrap();
|
let func = source_binder::function_from_position(db, pos).unwrap();
|
||||||
let body_source_map = func.body_source_map(db);
|
let body_source_map = func.body_source_map(db);
|
||||||
|
|
|
@ -379,6 +379,14 @@ fn method_call_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker {
|
||||||
// fn foo() {
|
// fn foo() {
|
||||||
// x.foo;
|
// x.foo;
|
||||||
// x.0.bar;
|
// 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 {
|
fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker {
|
||||||
assert!(p.at(DOT));
|
assert!(p.at(DOT));
|
||||||
|
@ -387,7 +395,10 @@ fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker {
|
||||||
if p.at(IDENT) {
|
if p.at(IDENT) {
|
||||||
name_ref(p)
|
name_ref(p)
|
||||||
} else if p.at(INT_NUMBER) {
|
} 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 {
|
} else {
|
||||||
p.error("expected field name or number")
|
p.error("expected field name or number")
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub use self::{
|
||||||
generated::*,
|
generated::*,
|
||||||
traits::*,
|
traits::*,
|
||||||
tokens::*,
|
tokens::*,
|
||||||
extensions::{PathSegmentKind, StructKind, SelfParamKind},
|
extensions::{PathSegmentKind, StructKind, FieldKind, SelfParamKind},
|
||||||
expr_extensions::{ElseBranch, PrefixOp, BinOp, LiteralKind},
|
expr_extensions::{ElseBranch, PrefixOp, BinOp, LiteralKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,8 @@
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::{
|
use crate::{SmolStr, SyntaxToken, ast::{self, AstNode, children, child_opt}, SyntaxKind::*, SyntaxElement};
|
||||||
SmolStr, SyntaxToken,
|
use ra_parser::SyntaxKind;
|
||||||
ast::{self, AstNode, children, child_opt},
|
|
||||||
SyntaxKind::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl ast::Name {
|
impl ast::Name {
|
||||||
pub fn text(&self) -> &SmolStr {
|
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<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 {
|
impl ast::RefPat {
|
||||||
pub fn is_mut(&self) -> bool {
|
pub fn is_mut(&self) -> bool {
|
||||||
self.syntax().children_with_tokens().any(|n| n.kind() == MUT_KW)
|
self.syntax().children_with_tokens().any(|n| n.kind() == MUT_KW)
|
||||||
|
|
|
@ -95,6 +95,7 @@ pub enum SyntaxErrorKind {
|
||||||
InvalidSuffix,
|
InvalidSuffix,
|
||||||
InvalidBlockAttr,
|
InvalidBlockAttr,
|
||||||
InvalidMatchInnerAttr,
|
InvalidMatchInnerAttr,
|
||||||
|
InvalidTupleIndexFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SyntaxErrorKind {
|
impl fmt::Display for SyntaxErrorKind {
|
||||||
|
@ -139,6 +140,9 @@ impl fmt::Display for SyntaxErrorKind {
|
||||||
InvalidMatchInnerAttr => {
|
InvalidMatchInnerAttr => {
|
||||||
write!(f, "Inner attributes are only allowed directly after the opening brace of the match expression")
|
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),
|
ParseError(msg) => write!(f, "{}", msg.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ mod byte_string;
|
||||||
mod char;
|
mod char;
|
||||||
mod string;
|
mod string;
|
||||||
mod block;
|
mod block;
|
||||||
|
mod field_expr;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
SourceFile, SyntaxError, AstNode, SyntaxNode,
|
SourceFile, SyntaxError, AstNode, SyntaxNode,
|
||||||
|
@ -17,6 +18,7 @@ pub(crate) fn validate(file: &SourceFile) -> Vec<SyntaxError> {
|
||||||
let _ = visitor_ctx(&mut errors)
|
let _ = visitor_ctx(&mut errors)
|
||||||
.visit::<ast::Literal, _>(validate_literal)
|
.visit::<ast::Literal, _>(validate_literal)
|
||||||
.visit::<ast::Block, _>(block::validate_block_node)
|
.visit::<ast::Block, _>(block::validate_block_node)
|
||||||
|
.visit::<ast::FieldExpr, _>(field_expr::validate_field_expr_node)
|
||||||
.accept(node);
|
.accept(node);
|
||||||
}
|
}
|
||||||
errors
|
errors
|
||||||
|
|
12
crates/ra_syntax/src/validation/field_expr.rs
Normal file
12
crates/ra_syntax/src/validation/field_expr.rs
Normal file
|
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
fn foo() {
|
||||||
|
x.0.;
|
||||||
|
x.1i32;
|
||||||
|
x.0x01;
|
||||||
|
}
|
|
@ -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"
|
|
@ -1,4 +1,5 @@
|
||||||
fn foo() {
|
fn foo() {
|
||||||
x.foo;
|
x.foo;
|
||||||
x.0.bar;
|
x.0.bar;
|
||||||
|
x.0();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
SOURCE_FILE@[0; 37)
|
SOURCE_FILE@[0; 48)
|
||||||
FN_DEF@[0; 36)
|
FN_DEF@[0; 47)
|
||||||
FN_KW@[0; 2) "fn"
|
FN_KW@[0; 2) "fn"
|
||||||
WHITESPACE@[2; 3) " "
|
WHITESPACE@[2; 3) " "
|
||||||
NAME@[3; 6)
|
NAME@[3; 6)
|
||||||
|
@ -8,7 +8,7 @@ SOURCE_FILE@[0; 37)
|
||||||
L_PAREN@[6; 7) "("
|
L_PAREN@[6; 7) "("
|
||||||
R_PAREN@[7; 8) ")"
|
R_PAREN@[7; 8) ")"
|
||||||
WHITESPACE@[8; 9) " "
|
WHITESPACE@[8; 9) " "
|
||||||
BLOCK@[9; 36)
|
BLOCK@[9; 47)
|
||||||
L_CURLY@[9; 10) "{"
|
L_CURLY@[9; 10) "{"
|
||||||
WHITESPACE@[10; 15) "\n "
|
WHITESPACE@[10; 15) "\n "
|
||||||
EXPR_STMT@[15; 21)
|
EXPR_STMT@[15; 21)
|
||||||
|
@ -37,6 +37,21 @@ SOURCE_FILE@[0; 37)
|
||||||
NAME_REF@[30; 33)
|
NAME_REF@[30; 33)
|
||||||
IDENT@[30; 33) "bar"
|
IDENT@[30; 33) "bar"
|
||||||
SEMI@[33; 34) ";"
|
SEMI@[33; 34) ";"
|
||||||
WHITESPACE@[34; 35) "\n"
|
WHITESPACE@[34; 39) "\n "
|
||||||
R_CURLY@[35; 36) "}"
|
EXPR_STMT@[39; 45)
|
||||||
WHITESPACE@[36; 37) "\n"
|
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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue