diff --git a/src/dreamchecker/lib.rs b/src/dreamchecker/lib.rs index 80d768d9..80a5f82f 100644 --- a/src/dreamchecker/lib.rs +++ b/src/dreamchecker/lib.rs @@ -1892,10 +1892,10 @@ impl<'o, 's> AnalyzeProc<'o, 's> { fn visit_follow(&mut self, location: Location, lhs: Analysis<'o>, rhs: &'o Follow, local_vars: &mut HashMap>) -> Analysis<'o> { match rhs { - Follow::Field(IndexKind::Colon, _) => Analysis::empty(), - Follow::Field(IndexKind::SafeColon, _) => Analysis::empty(), - Follow::Call(IndexKind::Colon, _, args) | - Follow::Call(IndexKind::SafeColon, _, args) => { + Follow::Field(PropertyAccessKind::Colon, _) => Analysis::empty(), + Follow::Field(PropertyAccessKind::SafeColon, _) => Analysis::empty(), + Follow::Call(PropertyAccessKind::Colon, _, args) | + Follow::Call(PropertyAccessKind::SafeColon, _, args) => { // No analysis yet, but be sure to visit the arguments for arg in args { let mut argument_value = arg; @@ -1914,7 +1914,7 @@ impl<'o, 's> AnalyzeProc<'o, 's> { Analysis::empty() }, - Follow::Index(expr) => { + Follow::Index(_, expr) => { self.visit_expression(location, expr, None, local_vars); // TODO: differentiate between L[1] and L[non_numeric_key] match lhs.static_ty { diff --git a/src/dreamchecker/tests/static_type_tests.rs b/src/dreamchecker/tests/static_type_tests.rs index eee40c61..f52886df 100644 --- a/src/dreamchecker/tests/static_type_tests.rs +++ b/src/dreamchecker/tests/static_type_tests.rs @@ -5,6 +5,7 @@ use dc::test_helpers::*; pub const FIELD_ACCESS_ERRORS: &[(u32, u16, &str)] = &[ (3, 9, "field access requires static type: \"name\""), + (4, 10, "field access requires static type: \"name\""), ]; #[test] @@ -13,12 +14,14 @@ fn field_access() { /proc/test() var/list/L = list() L[1].name + L?[1].name "##.trim(); check_errors_match(code, FIELD_ACCESS_ERRORS); } pub const PROC_CALL_ERRORS: &[(u32, u16, &str)] = &[ (3, 9, "proc call requires static type: \"foo\""), + (4, 10, "proc call requires static type: \"foo\""), ]; #[test] @@ -27,6 +30,7 @@ fn proc_call() { /proc/test() var/list/L = list() L[1].foo() + L?[1].foo() /mob/proc/foo() "##.trim(); check_errors_match(code, PROC_CALL_ERRORS); diff --git a/src/dreamchecker/type_expr.rs b/src/dreamchecker/type_expr.rs index 867f4aa4..9ef8bcfa 100644 --- a/src/dreamchecker/type_expr.rs +++ b/src/dreamchecker/type_expr.rs @@ -230,7 +230,7 @@ impl<'o> TypeExprCompiler<'o> { ) -> Result, DMError> { match rhs { // X[_] => static type of argument X with one /list stripped - Follow::Index(expr) => match expr.as_term() { + Follow::Index(_, expr) => match expr.as_term() { Some(Term::Ident(name)) if name == "_" => match lhs { TypeExpr::ParamTypepath { name, diff --git a/src/dreammaker/ast.rs b/src/dreammaker/ast.rs index 7cf059f2..fe080722 100644 --- a/src/dreammaker/ast.rs +++ b/src/dreammaker/ast.rs @@ -639,9 +639,18 @@ impl From for Term { } } +/// The possible kinds of access operators for lists +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ListAccessKind { + /// `[]` + Normal, + /// `?[]` + Safe, +} + /// The possible kinds of index operators, for both fields and methods. #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum IndexKind { +pub enum PropertyAccessKind { /// `a.b` Dot, /// `a:b` @@ -652,18 +661,18 @@ pub enum IndexKind { SafeColon, } -impl IndexKind { +impl PropertyAccessKind { pub fn name(self) -> &'static str { match self { - IndexKind::Dot => ".", - IndexKind::Colon => ":", - IndexKind::SafeDot => "?.", - IndexKind::SafeColon => "?:", + PropertyAccessKind::Dot => ".", + PropertyAccessKind::Colon => ":", + PropertyAccessKind::SafeDot => "?.", + PropertyAccessKind::SafeColon => "?:", } } } -impl fmt::Display for IndexKind { +impl fmt::Display for PropertyAccessKind { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str(self.name()) } @@ -673,17 +682,17 @@ impl fmt::Display for IndexKind { #[derive(Debug, Clone, PartialEq)] pub enum Follow { /// Index the value by an expression. - Index(Box), + Index(ListAccessKind, Box), /// Access a field of the value. - Field(IndexKind, Ident), + Field(PropertyAccessKind, Ident), /// Call a method of the value. - Call(IndexKind, Ident, Vec), + Call(PropertyAccessKind, Ident, Vec), } /// Like a `Follow` but only supports field accesses. #[derive(Debug, Clone, PartialEq)] pub struct Field { - pub kind: IndexKind, + pub kind: PropertyAccessKind, pub ident: Ident, } diff --git a/src/dreammaker/lexer.rs b/src/dreammaker/lexer.rs index 75582fa9..65fe4dcd 100644 --- a/src/dreammaker/lexer.rs +++ b/src/dreammaker/lexer.rs @@ -116,6 +116,7 @@ table! { "?", QuestionMark; "?.", SafeDot; "?:", SafeColon; + "?[", SafeLBracket; "[", LBracket; "]", RBracket; "^", BitXor; @@ -151,15 +152,15 @@ static SPEEDY_TABLE: [(usize, usize); 127] = [ (2, 3), (3, 5), (5, 6), (6, 8), (0, 0), (8, 10), (10, 14), (14, 15), (15, 16), (16, 17), (17, 20), (20, 23), (23, 24), (24, 27), (27, 30), (30, 34), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), - (0, 0), (0, 0), (34, 36), (36, 37), (37, 42), (42, 44), (44, 48), (48, 51), + (0, 0), (0, 0), (34, 36), (36, 37), (37, 42), (42, 44), (44, 48), (48, 52), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), - (0, 0), (0, 0), (0, 0), (51, 52), (0, 0), (52, 53), (53, 55), (0, 0), + (0, 0), (0, 0), (0, 0), (52, 53), (0, 0), (53, 54), (54, 56), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), - (0, 0), (0, 0), (0, 0), (55, 57), (57, 61), (61, 62), (62, 65)]; + (0, 0), (0, 0), (0, 0), (56, 58), (58, 62), (62, 63), (63, 66)]; #[test] fn make_speedy_table() { diff --git a/src/dreammaker/parser.rs b/src/dreammaker/parser.rs index c24bd43b..ece35082 100644 --- a/src/dreammaker/parser.rs +++ b/src/dreammaker/parser.rs @@ -2105,23 +2105,36 @@ impl<'ctx, 'an, 'inp> Parser<'ctx, 'an, 'inp> { success(Spanned::new(start, term)) } - fn follow(&mut self, belongs_to: &mut Vec, in_ternary: bool) -> Status> { + fn list_access(&mut self, belongs_to: &mut Vec) -> Status> { let first_location = self.updated_location(); + + // follow :: ('[' | '?[') expression ']' let kind = match self.next("field access")? { - // follow :: '[' expression ']' - Token::Punct(Punctuation::LBracket) => { - belongs_to.clear(); - let expr = require!(self.expression()); - require!(self.exact(Token::Punct(Punctuation::RBracket))); - return success(Spanned::new(first_location, Follow::Index(Box::new(expr)))) - } + Token::Punct(Punctuation::LBracket) => ListAccessKind::Normal, + Token::Punct(Punctuation::SafeLBracket) => ListAccessKind::Safe, + other => return self.try_another(other), + }; + + belongs_to.clear(); + let expr = require!(self.expression()); + require!(self.exact(Token::Punct(Punctuation::RBracket))); + success(Spanned::new(first_location, Follow::Index(kind, Box::new(expr)))) + } - // follow :: '.' ident arglist? + fn follow(&mut self, belongs_to: &mut Vec, in_ternary: bool) -> Status> { + let first_location = self.updated_location(); + + if let Some(follow) = self.list_access(belongs_to)? { + return success(follow); + } + + // follow :: '.' ident arglist? + let kind = match self.next("field access")? { // TODO: only apply these rules if there is no whitespace around the punctuation - Token::Punct(Punctuation::Dot) => IndexKind::Dot, - Token::Punct(Punctuation::CloseColon) if !belongs_to.is_empty() || !in_ternary => IndexKind::Colon, - Token::Punct(Punctuation::SafeDot) => IndexKind::SafeDot, - Token::Punct(Punctuation::SafeColon) => IndexKind::SafeColon, + Token::Punct(Punctuation::Dot) => PropertyAccessKind::Dot, + Token::Punct(Punctuation::CloseColon) if !belongs_to.is_empty() || !in_ternary => PropertyAccessKind::Colon, + Token::Punct(Punctuation::SafeDot) => PropertyAccessKind::SafeDot, + Token::Punct(Punctuation::SafeColon) => PropertyAccessKind::SafeColon, other => return self.try_another(other), }; @@ -2167,10 +2180,10 @@ impl<'ctx, 'an, 'inp> Parser<'ctx, 'an, 'inp> { let kind = match self.next("field access")? { // follow :: '.' ident // TODO: only apply these rules if there is no whitespace around the punctuation - Token::Punct(Punctuation::Dot) => IndexKind::Dot, - Token::Punct(Punctuation::CloseColon) if !belongs_to.is_empty() || !in_ternary => IndexKind::Colon, - Token::Punct(Punctuation::SafeDot) => IndexKind::SafeDot, - Token::Punct(Punctuation::SafeColon) => IndexKind::SafeColon, + Token::Punct(Punctuation::Dot) => PropertyAccessKind::Dot, + Token::Punct(Punctuation::CloseColon) if !belongs_to.is_empty() || !in_ternary => PropertyAccessKind::Colon, + Token::Punct(Punctuation::SafeDot) => PropertyAccessKind::SafeDot, + Token::Punct(Punctuation::SafeColon) => PropertyAccessKind::SafeColon, other => return self.try_another(other), }; diff --git a/src/langserver/find_references.rs b/src/langserver/find_references.rs index 0964f10c..926169c5 100644 --- a/src/langserver/find_references.rs +++ b/src/langserver/find_references.rs @@ -561,7 +561,7 @@ impl<'o> WalkProc<'o> { fn visit_follow(&mut self, location: Location, lhs: StaticType<'o>, rhs: &'o Follow) -> StaticType<'o> { match rhs { - Follow::Index(expr) => { + Follow::Index(_, expr) => { self.visit_expression(location, expr, None); // TODO: call operator[] or operator[]= // TODO: differentiate between L[1] and L[non_numeric_key]