//! Conversion from parser errors to user-facing diagnostics. //! //! This module maps [`ParseError`] values produced by the parser to structured //! [`Diagnostic`] values suitable for rendering. //! //! It owns the top-level dispatch by [`crate::parser::ParseErrorKind`] and //! keeps small shared utilities used by parse-error diagnostic constructors. //! //! Concrete diagnostic constructors are grouped into submodules that mirror //! parser areas or grammar families. use super::{Diagnostic, DiagnosticBuilder}; use crate::diagnostics::parse_error_diagnostics::block_items::{ diagnostic_block_expected_item, diagnostic_block_missing_closing_brace, diagnostic_block_missing_semicolon_after_expression, }; use crate::lexer::{TokenPosition, TokenSpan, TokenizedFile}; use crate::parser::{ParseError, ParseErrorKind}; mod block_items; mod control_flow_expressions; mod primary_expressions; #[derive(Clone, Copy)] enum FoundAt<'src> { Token(&'src str), EndOfFile, Unknown, } fn found_at<'src>(file: &TokenizedFile<'src>, position: TokenPosition) -> FoundAt<'src> { if let Some(token_text) = file.token_text(position) { FoundAt::Token(token_text) } else if file.is_eof(&position) { FoundAt::EndOfFile } else { FoundAt::Unknown } } fn collapse_span_to_end_on_same_line(file: &TokenizedFile<'_>, mut span: TokenSpan) -> TokenSpan { if file.same_line(span.start, span.end) { span.start = span.end; } span } fn should_show_context_label<'src>( file: &TokenizedFile<'src>, context_span: TokenSpan, blame_span: TokenSpan, ) -> bool { !file.same_line(context_span.start, blame_span.end) } fn primary_span_with_optional_multiline_context<'src>( file: &TokenizedFile<'src>, context_span: Option, blame_span: TokenSpan, ) -> TokenSpan { if let Some(context_span) = context_span && should_show_context_label(file, context_span, blame_span) { TokenSpan { start: context_span.start, end: blame_span.end, } } else { collapse_span_to_end_on_same_line(file, blame_span) } } pub(crate) fn diagnostic_from_parse_error<'src>( error: ParseError, file: &TokenizedFile<'src>, ) -> Diagnostic { use control_flow_expressions::*; use primary_expressions::*; match error.kind { // primary_expressions.rs ParseErrorKind::ParenthesizedExpressionInvalidStart => { diagnostic_parenthesized_expression_invalid_start(error, file) } ParseErrorKind::ExpressionExpected => diagnostic_expression_expected(error, file), ParseErrorKind::ParenthesizedExpressionMissingClosingParenthesis => { diagnostic_parenthesized_expression_missing_closing_parenthesis(error, file) } ParseErrorKind::ClassTypeMissingTypeArgument => { diagnostic_class_type_missing_type_argument(error, file) } ParseErrorKind::ClassTypeExpectedQualifiedTypeName => { diagnostic_class_type_expected_qualified_type_name(error, file) } ParseErrorKind::ClassTypeInvalidStart => diagnostic_class_type_invalid_start(error, file), ParseErrorKind::ClassTypeMissingClosingAngleBracket => { diagnostic_class_type_missing_closing_angle_bracket(error, file) } ParseErrorKind::NewMissingClassSpecifier => { diagnostic_new_missing_class_specifier(error, file) } ParseErrorKind::NewTooManyArguments => diagnostic_new_too_many_arguments(error, file), ParseErrorKind::NewMissingClosingParenthesis => { diagnostic_new_missing_closing_parenthesis(error, file) } ParseErrorKind::NewArgumentMissingComma => { diagnostic_new_argument_missing_comma(error, file) } // control_flow_expressions.rs ParseErrorKind::ConditionExpected => diagnostic_condition_expected(error, file), ParseErrorKind::ControlFlowBodyExpected => { diagnostic_control_flow_body_expected(error, file) } ParseErrorKind::DoMissingUntil => diagnostic_do_missing_until(error, file), ParseErrorKind::ForEachIteratorExpressionExpected => { diagnostic_for_each_iterator_expression_expected(error, file) } ParseErrorKind::ForLoopHeaderInitializerInvalidStart => { diagnostic_for_loop_header_initializer_invalid_start(error, file) } ParseErrorKind::ForLoopHeaderMissingSemicolonAfterInitializer => { diagnostic_for_loop_header_missing_semicolon_after_initializer(error, file) } ParseErrorKind::ForLoopHeaderConditionInvalidStart => { diagnostic_for_loop_header_condition_invalid_start(error, file) } ParseErrorKind::ForLoopHeaderMissingSemicolonAfterCondition => { diagnostic_for_loop_header_missing_semicolon_after_condition(error, file) } ParseErrorKind::ForLoopHeaderStepInvalidStart => { diagnostic_for_loop_header_step_invalid_start(error, file) } ParseErrorKind::ForLoopHeaderMissingClosingParenthesis => { diagnostic_for_loop_header_missing_closing_parenthesis(error, file) } ParseErrorKind::ReturnValueInvalidStart => { diagnostic_return_value_invalid_start(error, file) } ParseErrorKind::BreakValueInvalidStart => diagnostic_break_value_invalid_start(error, file), ParseErrorKind::GotoMissingLabel => diagnostic_goto_missing_label(error, file), // block_items.rs ParseErrorKind::BlockMissingSemicolonAfterExpression => { diagnostic_block_missing_semicolon_after_expression(error, file) } ParseErrorKind::BlockMissingClosingBrace => { diagnostic_block_missing_closing_brace(error, file) } ParseErrorKind::BlockExpectedItem => diagnostic_block_expected_item(error, file), _ => DiagnosticBuilder::error(format!("error {:?} while parsing", error.kind)) .primary_label(error.covered_span, "happened here") .build(), } }