use cairo_lang_defs::diagnostic_utils::StableLocation;
use cairo_lang_diagnostics::{
DiagnosticAdded, DiagnosticEntry, DiagnosticNote, DiagnosticsBuilder, Severity, error_code,
};
use cairo_lang_filesystem::ids::SpanInFile;
use cairo_lang_semantic as semantic;
use cairo_lang_semantic::corelib::LiteralError;
use cairo_lang_semantic::expr::inference::InferenceError;
use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
use salsa::Database;
use crate::Location;
pub type LoweringDiagnostics<'db> = DiagnosticsBuilder<'db, LoweringDiagnostic<'db>>;
pub trait LoweringDiagnosticsBuilder<'db> {
fn report(
&mut self,
stable_ptr: impl Into<SyntaxStablePtrId<'db>>,
kind: LoweringDiagnosticKind<'db>,
) -> DiagnosticAdded {
self.report_by_location(Location::new(StableLocation::new(stable_ptr.into())), kind)
}
fn report_by_location(
&mut self,
location: Location<'db>,
kind: LoweringDiagnosticKind<'db>,
) -> DiagnosticAdded;
}
impl<'db> LoweringDiagnosticsBuilder<'db> for LoweringDiagnostics<'db> {
fn report_by_location(
&mut self,
location: Location<'db>,
kind: LoweringDiagnosticKind<'db>,
) -> DiagnosticAdded {
self.add(LoweringDiagnostic { location, kind })
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
pub struct LoweringDiagnostic<'db> {
pub location: Location<'db>,
pub kind: LoweringDiagnosticKind<'db>,
}
impl<'db> DiagnosticEntry<'db> for LoweringDiagnostic<'db> {
fn format(&self, db: &'db dyn Database) -> String {
match &self.kind {
LoweringDiagnosticKind::Unreachable { .. } => "Unreachable code".into(),
LoweringDiagnosticKind::VariableMoved { .. } => "Variable was previously moved.".into(),
LoweringDiagnosticKind::VariableNotDropped { .. } => "Variable not dropped.".into(),
LoweringDiagnosticKind::DesnappingANonCopyableType { .. } => {
"Cannot desnap a non copyable type.".into()
}
LoweringDiagnosticKind::MatchError(match_err) => match_err.format(),
LoweringDiagnosticKind::CannotInlineFunctionThatMightCallItself => {
"Cannot inline a function that might call itself.".into()
}
LoweringDiagnosticKind::MemberPathLoop => {
"Currently, loops must change the entire variable.".into()
}
LoweringDiagnosticKind::UnexpectedError => {
"Unexpected error has occurred, Please submit a full bug report. \
See https://github.com/starkware-libs/cairo/issues/new/choose for instructions.\
"
.into()
}
LoweringDiagnosticKind::NoPanicFunctionCycle => {
"Call cycle of `nopanic` functions is not allowed.".into()
}
LoweringDiagnosticKind::LiteralError(literal_error) => literal_error.format(db),
LoweringDiagnosticKind::UnsupportedPattern => {
"Inner patterns are not allowed in this context.".into()
}
LoweringDiagnosticKind::Unsupported => "Unsupported feature.".into(),
LoweringDiagnosticKind::FixedSizeArrayNonCopyableType => {
"Fixed size array inner type must implement the `Copy` trait when the array size \
is greater than 1."
.into()
}
LoweringDiagnosticKind::EmptyRepeatedElementFixedSizeArray => {
"Fixed size array repeated element size must be greater than 0.".into()
}
}
}
fn severity(&self) -> Severity {
match self.kind {
LoweringDiagnosticKind::Unreachable { .. }
| LoweringDiagnosticKind::MatchError(MatchError {
kind: _,
error: MatchDiagnostic::UnreachableMatchArm,
}) => Severity::Warning,
_ => Severity::Error,
}
}
fn notes(&self, _db: &dyn Database) -> &[DiagnosticNote<'_>] {
&self.location.notes
}
fn location(&self, db: &'db dyn Database) -> SpanInFile<'db> {
if let LoweringDiagnosticKind::Unreachable { block_end_ptr } = &self.kind {
return self.location.stable_location.span_in_file_until(db, *block_end_ptr);
}
self.location.stable_location.span_in_file(db)
}
fn error_code(&self) -> Option<cairo_lang_diagnostics::ErrorCode> {
Some(match &self.kind {
LoweringDiagnosticKind::Unreachable { .. } => error_code!(E3000),
LoweringDiagnosticKind::VariableMoved { .. } => error_code!(E3001),
LoweringDiagnosticKind::VariableNotDropped { .. } => error_code!(E3002),
LoweringDiagnosticKind::DesnappingANonCopyableType { .. } => error_code!(E3003),
LoweringDiagnosticKind::MatchError(_) => error_code!(E3004),
LoweringDiagnosticKind::CannotInlineFunctionThatMightCallItself => error_code!(E3005),
LoweringDiagnosticKind::MemberPathLoop => error_code!(E3006),
LoweringDiagnosticKind::UnexpectedError => error_code!(E3007),
LoweringDiagnosticKind::NoPanicFunctionCycle => error_code!(E3008),
LoweringDiagnosticKind::LiteralError(_) => error_code!(E3009),
LoweringDiagnosticKind::UnsupportedPattern => error_code!(E3010),
LoweringDiagnosticKind::Unsupported => error_code!(E3011),
LoweringDiagnosticKind::FixedSizeArrayNonCopyableType => error_code!(E3012),
LoweringDiagnosticKind::EmptyRepeatedElementFixedSizeArray => error_code!(E3013),
})
}
fn is_same_kind(&self, other: &Self) -> bool {
other.kind == self.kind
}
}
impl<'db> MatchError<'db> {
fn format(&self) -> String {
match (&self.error, &self.kind) {
(MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::Match) => {
format!("Unsupported matched type. Type: `{matched_type}`.")
}
(MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::IfLet) => {
format!("Unsupported type in if-let. Type: `{matched_type}`.")
}
(MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::WhileLet(_, _)) => {
format!("Unsupported type in while-let. Type: `{matched_type}`.")
}
(MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::Match) => {
"Unsupported matched value. Currently, match on tuples only supports enums as \
tuple members."
.into()
}
(MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::IfLet) => {
"Unsupported value in if-let. Currently, if-let on tuples only supports enums as \
tuple members."
.into()
}
(MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::WhileLet(_, _)) => {
"Unsupported value in while-let. Currently, while-let on tuples only supports \
enums as tuple members."
.into()
}
(MatchDiagnostic::UnsupportedMatchArmNotAVariant, _) => {
"Unsupported pattern - not a variant.".into()
}
(MatchDiagnostic::UnsupportedMatchArmNotATuple, _) => {
"Unsupported pattern - not a tuple.".into()
}
(MatchDiagnostic::UnsupportedMatchArmNotALiteral, MatchKind::Match) => {
"Unsupported match arm - not a literal.".into()
}
(MatchDiagnostic::UnsupportedMatchArmNonSequential, MatchKind::Match) => {
"Unsupported match - numbers must be sequential starting from 0.".into()
}
(
MatchDiagnostic::UnsupportedMatchArmNotALiteral
| MatchDiagnostic::UnsupportedMatchArmNonSequential,
MatchKind::IfLet | MatchKind::WhileLet(_, _),
) => unreachable!("Numeric values are not supported in if/while-let conditions."),
(MatchDiagnostic::NonExhaustiveMatch(variant), MatchKind::Match) => {
format!("Match is non-exhaustive: `{variant}` not covered.")
}
(MatchDiagnostic::NonExhaustiveMatch(_), MatchKind::IfLet) => {
unreachable!("If-let is not required to be exhaustive.")
}
(MatchDiagnostic::NonExhaustiveMatch(_), MatchKind::WhileLet(_, _)) => {
unreachable!("While-let is not required to be exhaustive.")
}
(MatchDiagnostic::UnreachableMatchArm, MatchKind::Match) => {
"Unreachable pattern arm.".into()
}
(MatchDiagnostic::UnreachableMatchArm, MatchKind::IfLet) => {
"Unreachable clause.".into()
}
(MatchDiagnostic::UnreachableMatchArm, MatchKind::WhileLet(_, _)) => {
unreachable!("While-let does not have two arms.")
}
(MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::Match) => {
unreachable!("Numeric values are supported in match conditions.")
}
(MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::IfLet) => {
"Numeric values are not supported in if-let conditions.".into()
}
(MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::WhileLet(_, _)) => {
"Numeric values are not supported in while-let conditions.".into()
}
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
pub enum LoweringDiagnosticKind<'db> {
Unreachable { block_end_ptr: SyntaxStablePtrId<'db> },
VariableMoved { inference_error: InferenceError<'db> },
VariableNotDropped { drop_err: InferenceError<'db>, destruct_err: InferenceError<'db> },
MatchError(MatchError<'db>),
DesnappingANonCopyableType { inference_error: InferenceError<'db> },
UnexpectedError,
CannotInlineFunctionThatMightCallItself,
MemberPathLoop,
NoPanicFunctionCycle,
LiteralError(LiteralError<'db>),
FixedSizeArrayNonCopyableType,
EmptyRepeatedElementFixedSizeArray,
UnsupportedPattern,
Unsupported,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
pub struct MatchError<'db> {
pub kind: MatchKind<'db>,
pub error: MatchDiagnostic,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum MatchKind<'db> {
Match,
IfLet,
WhileLet(semantic::ExprId, SyntaxStablePtrId<'db>),
}
unsafe impl<'db> salsa::Update for MatchKind<'db> {
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
let old_value = unsafe { &mut *old_pointer };
match (old_value, &new_value) {
(MatchKind::Match, MatchKind::Match) | (MatchKind::IfLet, MatchKind::IfLet) => false,
(MatchKind::WhileLet(expr, end_ptr), MatchKind::WhileLet(new_expr, new_end_ptr)) => {
if unsafe { SyntaxStablePtrId::maybe_update(end_ptr, *new_end_ptr) } {
*expr = *new_expr;
true
} else if expr != new_expr {
*end_ptr = *new_end_ptr;
*expr = *new_expr;
true
} else {
false
}
}
(old_value, new_value) => {
*old_value = *new_value;
true
}
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
pub enum MatchDiagnostic {
UnsupportedMatchedType(String),
UnsupportedMatchedValueTuple,
UnsupportedMatchArmNotAVariant,
UnsupportedMatchArmNotATuple,
UnreachableMatchArm,
NonExhaustiveMatch(String),
UnsupportedMatchArmNotALiteral,
UnsupportedMatchArmNonSequential,
UnsupportedNumericInLetCondition,
}