mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
keyword in record field
This commit is contained in:
parent
860aa6d194
commit
0ccf17007e
6 changed files with 144 additions and 57 deletions
|
@ -1,7 +1,9 @@
|
||||||
use crate::ast::Attempting;
|
use crate::ast::Attempting;
|
||||||
use crate::keyword;
|
use crate::keyword;
|
||||||
use crate::parser::Progress::{self, *};
|
use crate::parser::Progress::{self, *};
|
||||||
use crate::parser::{peek_utf8_char, unexpected, ParseResult, Parser, State, SyntaxError};
|
use crate::parser::{
|
||||||
|
backtrackable, peek_utf8_char, unexpected, ParseResult, Parser, State, SyntaxError,
|
||||||
|
};
|
||||||
use bumpalo::collections::string::String;
|
use bumpalo::collections::string::String;
|
||||||
use bumpalo::collections::vec::Vec;
|
use bumpalo::collections::vec::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
@ -383,7 +385,7 @@ where
|
||||||
/// * A record field, e.g. "email" in `.email` or in `email:`
|
/// * A record field, e.g. "email" in `.email` or in `email:`
|
||||||
/// * A named pattern match, e.g. "foo" in `foo =` or `foo ->` or `\foo ->`
|
/// * A named pattern match, e.g. "foo" in `foo =` or `foo ->` or `\foo ->`
|
||||||
pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
|
pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
|
||||||
move |arena, state| {
|
move |arena, state: State<'a>| {
|
||||||
let (progress, ident, state) =
|
let (progress, ident, state) =
|
||||||
global_tag_or_ident(|first_char| first_char.is_lowercase()).parse(arena, state)?;
|
global_tag_or_ident(|first_char| first_char.is_lowercase()).parse(arena, state)?;
|
||||||
|
|
||||||
|
|
|
@ -336,7 +336,7 @@ pub enum BadInputError {
|
||||||
pub fn bad_input_to_syntax_error<'a>(
|
pub fn bad_input_to_syntax_error<'a>(
|
||||||
bad_input: BadInputError,
|
bad_input: BadInputError,
|
||||||
row: Row,
|
row: Row,
|
||||||
col: Col,
|
_col: Col,
|
||||||
) -> SyntaxError<'a> {
|
) -> SyntaxError<'a> {
|
||||||
use crate::parser::BadInputError::*;
|
use crate::parser::BadInputError::*;
|
||||||
match bad_input {
|
match bad_input {
|
||||||
|
@ -407,21 +407,6 @@ pub enum ContextStack<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ContextStack<'a> {
|
impl<'a> ContextStack<'a> {
|
||||||
fn into_vec(self) -> std::vec::Vec<ContextItem> {
|
|
||||||
let mut result = std::vec::Vec::new();
|
|
||||||
let mut next = &self;
|
|
||||||
|
|
||||||
while let ContextStack::Cons(item, rest) = next {
|
|
||||||
next = rest;
|
|
||||||
|
|
||||||
result.push(*item);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.reverse();
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uncons(&'a self) -> Option<(ContextItem, &'a Self)> {
|
pub fn uncons(&'a self) -> Option<(ContextItem, &'a Self)> {
|
||||||
match self {
|
match self {
|
||||||
ContextStack::Cons(item, rest) => Some((*item, rest)),
|
ContextStack::Cons(item, rest) => Some((*item, rest)),
|
||||||
|
@ -458,7 +443,7 @@ pub struct ParseProblem<'a, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fail<'a, T>() -> impl Parser<'a, T, SyntaxError<'a>> {
|
pub fn fail<'a, T>() -> impl Parser<'a, T, SyntaxError<'a>> {
|
||||||
move |arena, state: State<'a>| Err((NoProgress, SyntaxError::ConditionFailed, state))
|
move |_arena, state: State<'a>| Err((NoProgress, SyntaxError::ConditionFailed, state))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Parser<'a, Output, Error> {
|
pub trait Parser<'a, Output, Error> {
|
||||||
|
@ -505,7 +490,7 @@ where
|
||||||
let after_parse = state.clone();
|
let after_parse = state.clone();
|
||||||
|
|
||||||
match by.parse(arena, state) {
|
match by.parse(arena, state) {
|
||||||
Ok((_, _, state)) => {
|
Ok((_, _, _state)) => {
|
||||||
Err((NoProgress, SyntaxError::ConditionFailed, original_state))
|
Err((NoProgress, SyntaxError::ConditionFailed, original_state))
|
||||||
}
|
}
|
||||||
Err(_) => Ok((progress, answer, after_parse)),
|
Err(_) => Ok((progress, answer, after_parse)),
|
||||||
|
@ -659,7 +644,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_too_long_e<'a, TE, E>(
|
fn line_too_long_e<'a, TE, E>(
|
||||||
arena: &'a Bump,
|
_arena: &'a Bump,
|
||||||
state: State<'a>,
|
state: State<'a>,
|
||||||
to_error: TE,
|
to_error: TE,
|
||||||
) -> (Progress, E, State<'a>)
|
) -> (Progress, E, State<'a>)
|
||||||
|
@ -1307,6 +1292,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
fn in_context<'a, AddContext, P1, P2, Start, A, X, Y>(
|
fn in_context<'a, AddContext, P1, P2, Start, A, X, Y>(
|
||||||
add_context: AddContext,
|
add_context: AddContext,
|
||||||
parser_start: P1,
|
parser_start: P1,
|
||||||
|
|
|
@ -241,7 +241,9 @@ macro_rules! record_type_field {
|
||||||
use $crate::parser::Either::*;
|
use $crate::parser::Either::*;
|
||||||
|
|
||||||
// You must have a field name, e.g. "email"
|
// You must have a field name, e.g. "email"
|
||||||
let (progress, loc_label, state) = loc!(lowercase_ident()).parse(arena, state).map_err(|(p, _, s)| (p, TRecord::Field(s.line, s.column), s))?;
|
let row = state.line;
|
||||||
|
let col = state.column;
|
||||||
|
let (progress, loc_label, state) = loc!(lowercase_ident()).parse(arena, state).map_err(|(p, _, s)| (p, TRecord::Field(row, col), s))?;
|
||||||
debug_assert_eq!(progress, MadeProgress);
|
debug_assert_eq!(progress, MadeProgress);
|
||||||
|
|
||||||
let (_, spaces, state) =
|
let (_, spaces, state) =
|
||||||
|
@ -327,7 +329,6 @@ fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, Synta
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn record_type_internal<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TRecord<'a>> {
|
fn record_type_internal<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TRecord<'a>> {
|
||||||
use crate::type_annotation::TypeAnnotation::*;
|
use crate::type_annotation::TypeAnnotation::*;
|
||||||
type Fields<'a> = Vec<'a, Located<AssignedField<'a, TypeAnnotation<'a>>>>;
|
|
||||||
|
|
||||||
let field_term = move |a, s| match term(min_indent).parse(a, s) {
|
let field_term = move |a, s| match term(min_indent).parse(a, s) {
|
||||||
Ok(t) => Ok(t),
|
Ok(t) => Ok(t),
|
||||||
|
|
|
@ -1,34 +1,10 @@
|
||||||
use roc_parse::parser::{Col, ContextItem, ParseProblem, Row, SyntaxError};
|
use roc_parse::parser::{Col, ParseProblem, Row, SyntaxError};
|
||||||
use roc_region::all::Region;
|
use roc_region::all::Region;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::report::{Report, RocDocAllocator, RocDocBuilder};
|
use crate::report::{Report, RocDocAllocator};
|
||||||
use ven_pretty::DocAllocator;
|
use ven_pretty::DocAllocator;
|
||||||
|
|
||||||
fn context<'a>(
|
|
||||||
alloc: &'a RocDocAllocator<'a>,
|
|
||||||
context_stack: &[ContextItem],
|
|
||||||
default: &'a str,
|
|
||||||
) -> RocDocBuilder<'a> {
|
|
||||||
match context_stack.last() {
|
|
||||||
Some(context_item) => {
|
|
||||||
// assign string to `Attempting`
|
|
||||||
use roc_parse::ast::Attempting::*;
|
|
||||||
match context_item.context {
|
|
||||||
Def => alloc.text("while parsing a definition"),
|
|
||||||
_ => {
|
|
||||||
// use the default
|
|
||||||
alloc.text(default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// use the default
|
|
||||||
alloc.text(default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_problem<'a>(
|
pub fn parse_problem<'a>(
|
||||||
alloc: &'a RocDocAllocator<'a>,
|
alloc: &'a RocDocAllocator<'a>,
|
||||||
filename: PathBuf,
|
filename: PathBuf,
|
||||||
|
@ -115,7 +91,7 @@ fn to_type_report<'a>(
|
||||||
fn to_trecord_report<'a>(
|
fn to_trecord_report<'a>(
|
||||||
alloc: &'a RocDocAllocator<'a>,
|
alloc: &'a RocDocAllocator<'a>,
|
||||||
filename: PathBuf,
|
filename: PathBuf,
|
||||||
starting_line: u32,
|
_starting_line: u32,
|
||||||
parse_problem: roc_parse::parser::TRecord<'a>,
|
parse_problem: roc_parse::parser::TRecord<'a>,
|
||||||
start_row: Row,
|
start_row: Row,
|
||||||
start_col: Col,
|
start_col: Col,
|
||||||
|
@ -123,6 +99,48 @@ fn to_trecord_report<'a>(
|
||||||
use roc_parse::parser::TRecord;
|
use roc_parse::parser::TRecord;
|
||||||
|
|
||||||
match parse_problem {
|
match parse_problem {
|
||||||
|
TRecord::Open(row, col) => match what_is_next(alloc.src_lines, row, col) {
|
||||||
|
Next::Keyword(keyword) => {
|
||||||
|
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||||
|
let region = to_keyword_region(row, col, keyword);
|
||||||
|
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"),
|
||||||
|
alloc.region_with_subregion(surroundings, region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow(r"Looks like you are trying to use "),
|
||||||
|
alloc.keyword(keyword),
|
||||||
|
alloc.reflow(" as a field name, but that is a reserved word. Try using a different name!"),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename: filename.clone(),
|
||||||
|
doc,
|
||||||
|
title: "UNFINISHED RECORD TYPE".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||||
|
let region = Region::from_row_col(row, col);
|
||||||
|
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow(r"I just started parsing a record type, but I got stuck here:"),
|
||||||
|
alloc.region_with_subregion(surroundings, region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow(r"Record types look like "),
|
||||||
|
alloc.parser_suggestion("{ name : String, age : Int },"),
|
||||||
|
alloc.reflow(" so I was expecting to see a field name next."),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename: filename.clone(),
|
||||||
|
doc,
|
||||||
|
title: "UNFINISHED RECORD TYPE".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
TRecord::End(row, col) => {
|
TRecord::End(row, col) => {
|
||||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||||
let region = Region::from_row_col(row, col);
|
let region = Region::from_row_col(row, col);
|
||||||
|
@ -146,6 +164,53 @@ fn to_trecord_report<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TRecord::Field(row, col) => match what_is_next(alloc.src_lines, row, col) {
|
||||||
|
Next::Keyword(keyword) => {
|
||||||
|
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||||
|
let region = to_keyword_region(row, col, keyword);
|
||||||
|
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"),
|
||||||
|
alloc.region_with_subregion(surroundings, region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow(r"Looks like you are trying to use "),
|
||||||
|
alloc.keyword(keyword),
|
||||||
|
alloc.reflow(" as a field name, but that is a reserved word. Try using a different name!"),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename: filename.clone(),
|
||||||
|
doc,
|
||||||
|
title: "UNFINISHED RECORD TYPE".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Next::Other(Some(',')) => todo!(),
|
||||||
|
Next::Other(Some('}')) => unreachable!("or is it?"),
|
||||||
|
_ => {
|
||||||
|
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||||
|
let region = Region::from_row_col(row, col);
|
||||||
|
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow(r"I am partway through parsing a record type, but I got stuck here:"),
|
||||||
|
alloc.region_with_subregion(surroundings, region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow(r"I was expecting to see another record field defined next, so I am looking for a name like "),
|
||||||
|
alloc.parser_suggestion("userName"),
|
||||||
|
alloc.reflow(" or "),
|
||||||
|
alloc.parser_suggestion("plantHight"),
|
||||||
|
alloc.reflow("."),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename: filename.clone(),
|
||||||
|
doc,
|
||||||
|
title: "PROBLEM IN RECORD TYPE".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
TRecord::IndentEnd(row, col) => {
|
TRecord::IndentEnd(row, col) => {
|
||||||
match next_line_starts_with_close_curly(alloc.src_lines, row - 1) {
|
match next_line_starts_with_close_curly(alloc.src_lines, row - 1) {
|
||||||
Some((curly_row, curly_col)) => {
|
Some((curly_row, curly_col)) => {
|
||||||
|
@ -153,8 +218,6 @@ fn to_trecord_report<'a>(
|
||||||
Region::from_rows_cols(start_row, start_col, curly_row, curly_col);
|
Region::from_rows_cols(start_row, start_col, curly_row, curly_col);
|
||||||
let region = Region::from_row_col(curly_row, curly_col);
|
let region = Region::from_row_col(curly_row, curly_col);
|
||||||
|
|
||||||
dbg!(region);
|
|
||||||
|
|
||||||
let doc = alloc.stack(vec![
|
let doc = alloc.stack(vec![
|
||||||
alloc.reflow(
|
alloc.reflow(
|
||||||
"I am partway through parsing a record type, but I got stuck here:",
|
"I am partway through parsing a record type, but I got stuck here:",
|
||||||
|
@ -203,16 +266,18 @@ fn to_trecord_report<'a>(
|
||||||
|
|
||||||
enum Next<'a> {
|
enum Next<'a> {
|
||||||
Keyword(&'a str),
|
Keyword(&'a str),
|
||||||
Operator(&'a str),
|
// Operator(&'a str),
|
||||||
Close(&'a str, char),
|
Close(&'a str, char),
|
||||||
Other(Option<char>),
|
Other(Option<char>),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn what_is_next<'a>(source_lines: &'a [&'a str], row: Row, col: Col) -> Next<'a> {
|
fn what_is_next<'a>(source_lines: &'a [&'a str], row: Row, col: Col) -> Next<'a> {
|
||||||
match source_lines.get(row as usize) {
|
let row_index = row as usize;
|
||||||
|
let col_index = col as usize;
|
||||||
|
match source_lines.get(row_index) {
|
||||||
None => Next::Other(None),
|
None => Next::Other(None),
|
||||||
Some(line) => {
|
Some(line) => {
|
||||||
let chars = &line[col as usize..];
|
let chars = &line[col_index..];
|
||||||
|
|
||||||
match roc_parse::keyword::KEYWORDS
|
match roc_parse::keyword::KEYWORDS
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -238,7 +303,7 @@ fn starts_with_keyword(rest_of_line: &str, keyword: &str) -> bool {
|
||||||
if rest_of_line.starts_with(keyword) {
|
if rest_of_line.starts_with(keyword) {
|
||||||
match (&rest_of_line[keyword.len()..]).chars().nth(0) {
|
match (&rest_of_line[keyword.len()..]).chars().nth(0) {
|
||||||
None => true,
|
None => true,
|
||||||
Some(c) => c.is_alphanumeric() || c == '_',
|
Some(c) => !c.is_alphanumeric(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -258,3 +323,12 @@ fn next_line_starts_with_close_curly(source_lines: &[&str], row: Row) -> Option<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_keyword_region(row: Row, col: Col, keyword: &str) -> Region {
|
||||||
|
Region {
|
||||||
|
start_line: row,
|
||||||
|
start_col: col,
|
||||||
|
end_line: row,
|
||||||
|
end_col: col + keyword.len() as u16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ use inlinable_string::InlinableString;
|
||||||
use roc_module::ident::Ident;
|
use roc_module::ident::Ident;
|
||||||
use roc_module::ident::{Lowercase, TagName, Uppercase};
|
use roc_module::ident::{Lowercase, TagName, Uppercase};
|
||||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
use roc_parse::parser::{Col, Row};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated};
|
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated};
|
||||||
|
|
|
@ -4133,4 +4133,29 @@ mod test_reporting {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recort_type_keyword_field_name() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
f : { if : I64 }
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── UNFINISHED RECORD TYPE ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
I just started parsing a record type, but I got stuck on this field
|
||||||
|
name:
|
||||||
|
|
||||||
|
1│ f : { if : I64 }
|
||||||
|
^^
|
||||||
|
|
||||||
|
Looks like you are trying to use `if` as a field name, but that is a
|
||||||
|
reserved word. Try using a different name!
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue