use crate::TypeDatabase;
use crate::TypeFormatter;
use crate::def::DefinitionStore;
use crate::types::{TypeId, Visibility};
use std::sync::Arc;
use tsz_binder::SymbolId;
use tsz_common::interner::Atom;
pub trait SubtypeTracer {
fn on_mismatch(&mut self, reason: impl FnOnce() -> SubtypeFailureReason) -> bool;
}
pub trait DynSubtypeTracer {
fn on_mismatch_dyn(&mut self, reason: SubtypeFailureReason) -> bool;
}
impl<T: SubtypeTracer> DynSubtypeTracer for T {
fn on_mismatch_dyn(&mut self, reason: SubtypeFailureReason) -> bool {
self.on_mismatch(|| reason)
}
}
#[cfg(test)]
#[derive(Clone, Copy, Debug)]
pub struct FastTracer;
#[cfg(test)]
impl SubtypeTracer for FastTracer {
#[inline(always)]
fn on_mismatch(&mut self, _reason: impl FnOnce() -> SubtypeFailureReason) -> bool {
false
}
}
#[cfg(test)]
#[derive(Debug)]
pub struct DiagnosticTracer {
failure: Option<SubtypeFailureReason>,
}
#[cfg(test)]
impl DiagnosticTracer {
pub fn new() -> Self {
Self { failure: None }
}
pub fn take_failure(&mut self) -> Option<SubtypeFailureReason> {
self.failure.take()
}
pub fn has_failure(&self) -> bool {
self.failure.is_some()
}
}
#[cfg(test)]
impl Default for DiagnosticTracer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
impl SubtypeTracer for DiagnosticTracer {
#[inline]
fn on_mismatch(&mut self, reason: impl FnOnce() -> SubtypeFailureReason) -> bool {
if self.failure.is_none() {
self.failure = Some(reason());
}
false
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum SubtypeFailureReason {
MissingProperty {
property_name: Atom,
source_type: TypeId,
target_type: TypeId,
},
MissingProperties {
property_names: Vec<Atom>,
source_type: TypeId,
target_type: TypeId,
},
PropertyTypeMismatch {
property_name: Atom,
source_property_type: TypeId,
target_property_type: TypeId,
nested_reason: Option<Box<Self>>,
},
OptionalPropertyRequired { property_name: Atom },
ReadonlyPropertyMismatch { property_name: Atom },
PropertyVisibilityMismatch {
property_name: Atom,
source_visibility: Visibility,
target_visibility: Visibility,
},
PropertyNominalMismatch { property_name: Atom },
ReturnTypeMismatch {
source_return: TypeId,
target_return: TypeId,
nested_reason: Option<Box<Self>>,
},
ParameterTypeMismatch {
param_index: usize,
source_param: TypeId,
target_param: TypeId,
},
TooManyParameters {
source_count: usize,
target_count: usize,
},
TupleElementMismatch {
source_count: usize,
target_count: usize,
},
TupleElementTypeMismatch {
index: usize,
source_element: TypeId,
target_element: TypeId,
},
ArrayElementMismatch {
source_element: TypeId,
target_element: TypeId,
},
IndexSignatureMismatch {
index_kind: &'static str, source_value_type: TypeId,
target_value_type: TypeId,
},
MissingIndexSignature { index_kind: &'static str },
NoUnionMemberMatches {
source_type: TypeId,
target_union_members: Vec<TypeId>,
},
NoIntersectionMemberMatches {
source_type: TypeId,
target_type: TypeId,
},
NoCommonProperties {
source_type: TypeId,
target_type: TypeId,
},
TypeMismatch {
source_type: TypeId,
target_type: TypeId,
},
IntrinsicTypeMismatch {
source_type: TypeId,
target_type: TypeId,
},
LiteralTypeMismatch {
source_type: TypeId,
target_type: TypeId,
},
ErrorType {
source_type: TypeId,
target_type: TypeId,
},
RecursionLimitExceeded,
ParameterCountMismatch {
source_count: usize,
target_count: usize,
},
ExcessProperty {
property_name: Atom,
target_type: TypeId,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DiagnosticSeverity {
Error,
Warning,
Suggestion,
Message,
}
#[derive(Clone, Debug)]
pub enum DiagnosticArg {
Type(TypeId),
Symbol(SymbolId),
Atom(Atom),
String(Arc<str>),
Number(usize),
}
macro_rules! impl_from_diagnostic_arg {
($($source:ty => $variant:ident),* $(,)?) => {
$(impl From<$source> for DiagnosticArg {
fn from(v: $source) -> Self { Self::$variant(v) }
})*
};
}
impl_from_diagnostic_arg! {
TypeId => Type,
SymbolId => Symbol,
Atom => Atom,
usize => Number,
}
impl From<&str> for DiagnosticArg {
fn from(s: &str) -> Self {
Self::String(s.into())
}
}
impl From<String> for DiagnosticArg {
fn from(s: String) -> Self {
Self::String(s.into())
}
}
#[derive(Clone, Debug)]
pub struct PendingDiagnostic {
pub code: u32,
pub args: Vec<DiagnosticArg>,
pub span: Option<SourceSpan>,
pub severity: DiagnosticSeverity,
pub related: Vec<Self>,
}
impl PendingDiagnostic {
pub const fn error(code: u32, args: Vec<DiagnosticArg>) -> Self {
Self {
code,
args,
span: None,
severity: DiagnosticSeverity::Error,
related: Vec::new(),
}
}
pub fn with_span(mut self, span: SourceSpan) -> Self {
self.span = Some(span);
self
}
pub fn with_related(mut self, related: Self) -> Self {
self.related.push(related);
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SourceSpan {
pub start: u32,
pub length: u32,
pub file: Arc<str>,
}
impl SourceSpan {
pub fn new(file: impl Into<Arc<str>>, start: u32, length: u32) -> Self {
Self {
start,
length,
file: file.into(),
}
}
}
#[derive(Clone, Debug)]
pub struct RelatedInformation {
pub span: SourceSpan,
pub message: String,
}
#[derive(Clone, Debug)]
pub struct TypeDiagnostic {
pub message: String,
pub code: u32,
pub severity: DiagnosticSeverity,
pub span: Option<SourceSpan>,
pub related: Vec<RelatedInformation>,
}
impl TypeDiagnostic {
pub fn error(message: impl Into<String>, code: u32) -> Self {
Self {
message: message.into(),
code,
severity: DiagnosticSeverity::Error,
span: None,
related: Vec::new(),
}
}
pub fn with_span(mut self, span: SourceSpan) -> Self {
self.span = Some(span);
self
}
pub fn with_related(mut self, span: SourceSpan, message: impl Into<String>) -> Self {
self.related.push(RelatedInformation {
span,
message: message.into(),
});
self
}
}
pub mod codes {
use tsz_common::diagnostics::diagnostic_codes as dc;
pub use dc::ARGUMENT_OF_TYPE_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE as ARG_NOT_ASSIGNABLE;
pub use dc::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_A_READ_ONLY_PROPERTY as READONLY_PROPERTY;
pub use dc::OBJECT_LITERAL_MAY_ONLY_SPECIFY_KNOWN_PROPERTIES_AND_DOES_NOT_EXIST_IN_TYPE as EXCESS_PROPERTY;
pub use dc::PROPERTY_IS_MISSING_IN_TYPE_BUT_REQUIRED_IN_TYPE as PROPERTY_MISSING;
pub use dc::PROPERTY_IS_PRIVATE_AND_ONLY_ACCESSIBLE_WITHIN_CLASS as PROPERTY_VISIBILITY_MISMATCH;
pub use dc::PROPERTY_IS_PROTECTED_AND_ONLY_ACCESSIBLE_THROUGH_AN_INSTANCE_OF_CLASS_THIS_IS_A as PROPERTY_NOMINAL_MISMATCH;
pub use dc::TYPE_HAS_NO_PROPERTIES_IN_COMMON_WITH_TYPE as NO_COMMON_PROPERTIES;
pub use dc::TYPE_IS_MISSING_THE_FOLLOWING_PROPERTIES_FROM_TYPE as MISSING_PROPERTIES;
pub use dc::TYPE_IS_NOT_ASSIGNABLE_TO_TYPE as TYPE_NOT_ASSIGNABLE;
pub use dc::INDEX_SIGNATURE_FOR_TYPE_IS_MISSING_IN_TYPE as MISSING_INDEX_SIGNATURE;
pub use dc::TYPES_OF_PROPERTY_ARE_INCOMPATIBLE as PROPERTY_TYPE_MISMATCH;
pub use dc::CANNOT_FIND_NAME;
pub use dc::CANNOT_FIND_NAME_DO_YOU_NEED_TO_CHANGE_YOUR_TARGET_LIBRARY_TRY_CHANGING_THE_LIB as CANNOT_FIND_NAME_TARGET_LIB;
pub use dc::CANNOT_FIND_NAME_DO_YOU_NEED_TO_CHANGE_YOUR_TARGET_LIBRARY_TRY_CHANGING_THE_LIB_2 as CANNOT_FIND_NAME_DOM;
pub use dc::CANNOT_FIND_NAME_DO_YOU_NEED_TO_INSTALL_TYPE_DEFINITIONS_FOR_A_TEST_RUNNER_TRY_N as CANNOT_FIND_NAME_TEST_RUNNER;
pub use dc::CANNOT_FIND_NAME_DO_YOU_NEED_TO_INSTALL_TYPE_DEFINITIONS_FOR_NODE_TRY_NPM_I_SAVE as CANNOT_FIND_NAME_NODE;
pub use dc::EXPECTED_ARGUMENTS_BUT_GOT as ARG_COUNT_MISMATCH;
pub use dc::PROPERTY_DOES_NOT_EXIST_ON_TYPE as PROPERTY_NOT_EXIST;
pub use dc::PROPERTY_DOES_NOT_EXIST_ON_TYPE_DID_YOU_MEAN as PROPERTY_NOT_EXIST_DID_YOU_MEAN;
pub use dc::THE_THIS_CONTEXT_OF_TYPE_IS_NOT_ASSIGNABLE_TO_METHODS_THIS_OF_TYPE as THIS_TYPE_MISMATCH;
pub use dc::THIS_EXPRESSION_IS_NOT_CALLABLE as NOT_CALLABLE;
pub use dc::FUNCTION_EXPRESSION_WHICH_LACKS_RETURN_TYPE_ANNOTATION_IMPLICITLY_HAS_AN_RETURN as IMPLICIT_ANY_RETURN_FUNCTION_EXPRESSION;
pub use dc::MEMBER_IMPLICITLY_HAS_AN_TYPE as IMPLICIT_ANY_MEMBER;
pub use dc::PARAMETER_IMPLICITLY_HAS_AN_TYPE as IMPLICIT_ANY_PARAMETER;
pub use dc::VARIABLE_IMPLICITLY_HAS_AN_TYPE as IMPLICIT_ANY;
pub use dc::WHICH_LACKS_RETURN_TYPE_ANNOTATION_IMPLICITLY_HAS_AN_RETURN_TYPE as IMPLICIT_ANY_RETURN;
}
fn cannot_find_name_code(name: &str) -> u32 {
match name {
"require" | "exports" | "module" | "process" | "Buffer" | "__filename" | "__dirname" => {
codes::CANNOT_FIND_NAME_NODE
}
"describe" | "suite" | "it" | "test" => codes::CANNOT_FIND_NAME_TEST_RUNNER,
"Promise" | "Symbol" | "Map" | "Set" | "Reflect" | "Iterator" | "AsyncIterator"
| "SharedArrayBuffer" => codes::CANNOT_FIND_NAME_TARGET_LIB,
"document" | "console" => codes::CANNOT_FIND_NAME_DOM,
_ => codes::CANNOT_FIND_NAME,
}
}
pub fn get_message_template(code: u32) -> &'static str {
tsz_common::diagnostics::get_message_template(code).unwrap_or("Unknown diagnostic")
}
pub struct DiagnosticBuilder<'a> {
formatter: TypeFormatter<'a>,
}
impl<'a> DiagnosticBuilder<'a> {
pub fn new(interner: &'a dyn TypeDatabase) -> Self {
DiagnosticBuilder {
formatter: TypeFormatter::new(interner),
}
}
pub fn with_symbols(
interner: &'a dyn TypeDatabase,
symbol_arena: &'a tsz_binder::SymbolArena,
) -> Self {
DiagnosticBuilder {
formatter: TypeFormatter::with_symbols(interner, symbol_arena),
}
}
pub fn with_def_store(mut self, def_store: &'a DefinitionStore) -> Self {
self.formatter = self.formatter.with_def_store(def_store);
self
}
pub fn type_not_assignable(&mut self, source: TypeId, target: TypeId) -> TypeDiagnostic {
let source_str = self.formatter.format(source);
let target_str = self.formatter.format(target);
TypeDiagnostic::error(
format!("Type '{source_str}' is not assignable to type '{target_str}'."),
codes::TYPE_NOT_ASSIGNABLE,
)
}
pub fn property_missing(
&mut self,
prop_name: &str,
source: TypeId,
target: TypeId,
) -> TypeDiagnostic {
let source_str = self.formatter.format(source);
let target_str = self.formatter.format(target);
TypeDiagnostic::error(
format!(
"Property '{prop_name}' is missing in type '{source_str}' but required in type '{target_str}'."
),
codes::PROPERTY_MISSING,
)
}
pub fn property_not_exist(&mut self, prop_name: &str, type_id: TypeId) -> TypeDiagnostic {
let type_str = self.formatter.format(type_id);
TypeDiagnostic::error(
format!("Property '{prop_name}' does not exist on type '{type_str}'."),
codes::PROPERTY_NOT_EXIST,
)
}
pub fn property_not_exist_did_you_mean(
&mut self,
prop_name: &str,
type_id: TypeId,
suggestion: &str,
) -> TypeDiagnostic {
let type_str = self.formatter.format(type_id);
TypeDiagnostic::error(
format!(
"Property '{prop_name}' does not exist on type '{type_str}'. Did you mean '{suggestion}'?"
),
codes::PROPERTY_NOT_EXIST_DID_YOU_MEAN,
)
}
pub fn argument_not_assignable(
&mut self,
arg_type: TypeId,
param_type: TypeId,
) -> TypeDiagnostic {
let arg_str = self.formatter.format(arg_type);
let param_str = self.formatter.format(param_type);
TypeDiagnostic::error(
format!(
"Argument of type '{arg_str}' is not assignable to parameter of type '{param_str}'."
),
codes::ARG_NOT_ASSIGNABLE,
)
}
pub fn cannot_find_name(&mut self, name: &str) -> TypeDiagnostic {
let is_obviously_invalid = name.len() == 1
&& matches!(
name.chars().next(),
Some(
',' | ';'
| ':'
| '('
| ')'
| '['
| ']'
| '{'
| '}'
| '+'
| '-'
| '*'
| '/'
| '%'
| '&'
| '|'
| '^'
| '!'
| '~'
| '<'
| '>'
| '='
| '.'
)
);
if is_obviously_invalid {
return TypeDiagnostic::error("", 0);
}
let code = cannot_find_name_code(name);
TypeDiagnostic::error(format!("Cannot find name '{name}'."), code)
}
pub fn not_callable(&mut self, type_id: TypeId) -> TypeDiagnostic {
let type_str = self.formatter.format(type_id);
TypeDiagnostic::error(
format!("Type '{type_str}' has no call signatures."),
codes::NOT_CALLABLE,
)
}
pub fn this_type_mismatch(
&mut self,
expected_this: TypeId,
actual_this: TypeId,
) -> TypeDiagnostic {
let expected_str = self.formatter.format(expected_this);
let actual_str = self.formatter.format(actual_this);
TypeDiagnostic::error(
format!(
"The 'this' context of type '{actual_str}' is not assignable to method's 'this' of type '{expected_str}'."
),
codes::THIS_TYPE_MISMATCH,
)
}
pub fn argument_count_mismatch(&mut self, expected: usize, got: usize) -> TypeDiagnostic {
TypeDiagnostic::error(
format!("Expected {expected} arguments, but got {got}."),
codes::ARG_COUNT_MISMATCH,
)
}
pub fn readonly_property(&mut self, prop_name: &str) -> TypeDiagnostic {
TypeDiagnostic::error(
format!("Cannot assign to '{prop_name}' because it is a read-only property."),
codes::READONLY_PROPERTY,
)
}
pub fn excess_property(&mut self, prop_name: &str, target: TypeId) -> TypeDiagnostic {
let target_str = self.formatter.format(target);
TypeDiagnostic::error(
format!(
"Object literal may only specify known properties, and '{prop_name}' does not exist in type '{target_str}'."
),
codes::EXCESS_PROPERTY,
)
}
pub fn implicit_any_parameter(&mut self, param_name: &str) -> TypeDiagnostic {
TypeDiagnostic::error(
format!("Parameter '{param_name}' implicitly has an 'any' type."),
codes::IMPLICIT_ANY_PARAMETER,
)
}
pub fn implicit_any_parameter_with_type(
&mut self,
param_name: &str,
implicit_type: TypeId,
) -> TypeDiagnostic {
let type_str = self.formatter.format(implicit_type);
TypeDiagnostic::error(
format!("Parameter '{param_name}' implicitly has an '{type_str}' type."),
codes::IMPLICIT_ANY_PARAMETER,
)
}
pub fn implicit_any_member(&mut self, member_name: &str) -> TypeDiagnostic {
TypeDiagnostic::error(
format!("Member '{member_name}' implicitly has an 'any' type."),
codes::IMPLICIT_ANY_MEMBER,
)
}
pub fn implicit_any_variable(&mut self, var_name: &str, var_type: TypeId) -> TypeDiagnostic {
let type_str = self.formatter.format(var_type);
TypeDiagnostic::error(
format!("Variable '{var_name}' implicitly has an '{type_str}' type."),
codes::IMPLICIT_ANY,
)
}
pub fn implicit_any_return(&mut self, func_name: &str, return_type: TypeId) -> TypeDiagnostic {
let type_str = self.formatter.format(return_type);
TypeDiagnostic::error(
format!(
"'{func_name}', which lacks return-type annotation, implicitly has an '{type_str}' return type."
),
codes::IMPLICIT_ANY_RETURN,
)
}
pub fn implicit_any_return_function_expression(
&mut self,
return_type: TypeId,
) -> TypeDiagnostic {
let type_str = self.formatter.format(return_type);
TypeDiagnostic::error(
format!(
"Function expression, which lacks return-type annotation, implicitly has an '{type_str}' return type."
),
codes::IMPLICIT_ANY_RETURN_FUNCTION_EXPRESSION,
)
}
}
pub struct PendingDiagnosticBuilder;
impl SubtypeFailureReason {
pub const fn diagnostic_code(&self) -> u32 {
match self {
Self::MissingProperty { .. } | Self::OptionalPropertyRequired { .. } => {
codes::PROPERTY_MISSING
}
Self::MissingProperties { .. } => codes::MISSING_PROPERTIES,
Self::PropertyTypeMismatch { .. } => codes::PROPERTY_TYPE_MISMATCH,
Self::ReadonlyPropertyMismatch { .. } => codes::READONLY_PROPERTY,
Self::PropertyVisibilityMismatch { .. } => codes::PROPERTY_VISIBILITY_MISMATCH,
Self::PropertyNominalMismatch { .. } => codes::PROPERTY_NOMINAL_MISMATCH,
Self::ReturnTypeMismatch { .. }
| Self::ParameterTypeMismatch { .. }
| Self::TupleElementMismatch { .. }
| Self::TupleElementTypeMismatch { .. }
| Self::ArrayElementMismatch { .. }
| Self::IndexSignatureMismatch { .. }
| Self::MissingIndexSignature { .. }
| Self::NoUnionMemberMatches { .. }
| Self::NoIntersectionMemberMatches { .. }
| Self::TypeMismatch { .. }
| Self::IntrinsicTypeMismatch { .. }
| Self::LiteralTypeMismatch { .. }
| Self::ErrorType { .. }
| Self::RecursionLimitExceeded
| Self::ParameterCountMismatch { .. } => codes::TYPE_NOT_ASSIGNABLE,
Self::TooManyParameters { .. } => codes::ARG_COUNT_MISMATCH,
Self::NoCommonProperties { .. } => codes::NO_COMMON_PROPERTIES,
Self::ExcessProperty { .. } => codes::EXCESS_PROPERTY,
}
}
pub fn to_diagnostic(&self, source: TypeId, target: TypeId) -> PendingDiagnostic {
match self {
Self::MissingProperty {
property_name,
source_type,
target_type,
} => PendingDiagnostic::error(
codes::PROPERTY_MISSING,
vec![
(*property_name).into(),
(*source_type).into(),
(*target_type).into(),
],
),
Self::MissingProperties {
property_names: _,
source_type,
target_type,
} => PendingDiagnostic::error(
codes::MISSING_PROPERTIES,
vec![(*source_type).into(), (*target_type).into()],
),
Self::PropertyTypeMismatch {
property_name,
source_property_type,
target_property_type,
nested_reason,
} => {
let mut diag = PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
);
let elaboration = PendingDiagnostic::error(
codes::PROPERTY_TYPE_MISMATCH,
vec![(*property_name).into()],
);
diag = diag.with_related(elaboration);
if let Some(nested) = nested_reason {
let nested_diag =
nested.to_diagnostic(*source_property_type, *target_property_type);
diag = diag.with_related(nested_diag);
}
diag
}
Self::OptionalPropertyRequired { property_name } => {
PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
.with_related(PendingDiagnostic::error(
codes::PROPERTY_MISSING, vec![(*property_name).into(), source.into(), target.into()],
))
}
Self::ReadonlyPropertyMismatch { property_name } => PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
.with_related(PendingDiagnostic::error(
codes::READONLY_PROPERTY,
vec![(*property_name).into()],
)),
Self::PropertyVisibilityMismatch {
property_name,
source_visibility,
target_visibility,
} => {
PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
.with_related(PendingDiagnostic::error(
codes::PROPERTY_VISIBILITY_MISMATCH,
vec![
(*property_name).into(),
format!("{source_visibility:?}").into(),
format!("{target_visibility:?}").into(),
],
))
}
Self::PropertyNominalMismatch { property_name } => {
PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
.with_related(PendingDiagnostic::error(
codes::PROPERTY_NOMINAL_MISMATCH,
vec![(*property_name).into()],
))
}
Self::ReturnTypeMismatch {
source_return,
target_return,
nested_reason,
} => {
let mut diag = PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
);
let return_diag = PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![(*source_return).into(), (*target_return).into()],
);
diag = diag.with_related(return_diag);
if let Some(nested) = nested_reason {
let nested_diag = nested.to_diagnostic(*source_return, *target_return);
diag = diag.with_related(nested_diag);
}
diag
}
Self::ParameterTypeMismatch {
param_index: _,
source_param,
target_param,
} => PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
.with_related(PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![(*source_param).into(), (*target_param).into()],
)),
Self::TooManyParameters {
source_count,
target_count,
} => PendingDiagnostic::error(
codes::ARG_COUNT_MISMATCH,
vec![(*target_count).into(), (*source_count).into()],
),
Self::TupleElementMismatch {
source_count,
target_count,
} => PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
.with_related(PendingDiagnostic::error(
codes::ARG_COUNT_MISMATCH,
vec![(*target_count).into(), (*source_count).into()],
)),
Self::TupleElementTypeMismatch {
index: _,
source_element,
target_element,
}
| Self::ArrayElementMismatch {
source_element,
target_element,
} => PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
.with_related(PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![(*source_element).into(), (*target_element).into()],
)),
Self::IndexSignatureMismatch {
index_kind: _,
source_value_type,
target_value_type,
} => PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
.with_related(PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![(*source_value_type).into(), (*target_value_type).into()],
)),
Self::MissingIndexSignature { index_kind } => PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
.with_related(PendingDiagnostic::error(
codes::MISSING_INDEX_SIGNATURE,
vec![index_kind.to_string().into(), source.into()],
)),
Self::NoUnionMemberMatches {
source_type,
target_union_members,
} => {
const UNION_MEMBER_DIAGNOSTIC_LIMIT: usize = 3;
let mut diag = PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![(*source_type).into(), target.into()],
);
for member in target_union_members
.iter()
.take(UNION_MEMBER_DIAGNOSTIC_LIMIT)
{
diag.related.push(PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![(*source_type).into(), (*member).into()],
));
}
diag
}
Self::NoIntersectionMemberMatches {
source_type,
target_type,
}
| Self::TypeMismatch {
source_type,
target_type,
}
| Self::IntrinsicTypeMismatch {
source_type,
target_type,
}
| Self::LiteralTypeMismatch {
source_type,
target_type,
}
| Self::ErrorType {
source_type,
target_type,
} => PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![(*source_type).into(), (*target_type).into()],
),
Self::NoCommonProperties {
source_type,
target_type,
} => PendingDiagnostic::error(
codes::NO_COMMON_PROPERTIES,
vec![(*source_type).into(), (*target_type).into()],
),
Self::RecursionLimitExceeded => {
PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
}
Self::ParameterCountMismatch {
source_count: _,
target_count: _,
} => {
PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
}
Self::ExcessProperty {
property_name,
target_type,
} => {
PendingDiagnostic::error(
codes::EXCESS_PROPERTY,
vec![(*property_name).into(), (*target_type).into()],
)
}
}
}
}
impl PendingDiagnosticBuilder {
pub fn argument_not_assignable(arg_type: TypeId, param_type: TypeId) -> PendingDiagnostic {
PendingDiagnostic::error(
codes::ARG_NOT_ASSIGNABLE,
vec![arg_type.into(), param_type.into()],
)
}
pub fn argument_count_mismatch(expected: usize, got: usize) -> PendingDiagnostic {
PendingDiagnostic::error(codes::ARG_COUNT_MISMATCH, vec![expected.into(), got.into()])
}
}
#[cfg(test)]
impl PendingDiagnosticBuilder {
pub fn type_not_assignable(source: TypeId, target: TypeId) -> PendingDiagnostic {
PendingDiagnostic::error(
codes::TYPE_NOT_ASSIGNABLE,
vec![source.into(), target.into()],
)
}
pub fn property_missing(prop_name: &str, source: TypeId, target: TypeId) -> PendingDiagnostic {
PendingDiagnostic::error(
codes::PROPERTY_MISSING,
vec![prop_name.into(), source.into(), target.into()],
)
}
pub fn property_not_exist(prop_name: &str, type_id: TypeId) -> PendingDiagnostic {
PendingDiagnostic::error(
codes::PROPERTY_NOT_EXIST,
vec![prop_name.into(), type_id.into()],
)
}
pub fn cannot_find_name(name: &str) -> PendingDiagnostic {
let code = cannot_find_name_code(name);
PendingDiagnostic::error(code, vec![name.into()])
}
pub fn not_callable(type_id: TypeId) -> PendingDiagnostic {
PendingDiagnostic::error(codes::NOT_CALLABLE, vec![type_id.into()])
}
pub fn this_type_mismatch(expected_this: TypeId, actual_this: TypeId) -> PendingDiagnostic {
PendingDiagnostic::error(
codes::THIS_TYPE_MISMATCH,
vec![actual_this.into(), expected_this.into()],
)
}
pub fn readonly_property(prop_name: &str) -> PendingDiagnostic {
PendingDiagnostic::error(codes::READONLY_PROPERTY, vec![prop_name.into()])
}
pub fn excess_property(prop_name: &str, target: TypeId) -> PendingDiagnostic {
PendingDiagnostic::error(
codes::EXCESS_PROPERTY,
vec![prop_name.into(), target.into()],
)
}
}
pub struct SpannedDiagnosticBuilder<'a> {
builder: DiagnosticBuilder<'a>,
file: Arc<str>,
}
impl<'a> SpannedDiagnosticBuilder<'a> {
pub fn new(interner: &'a dyn TypeDatabase, file: impl Into<Arc<str>>) -> Self {
SpannedDiagnosticBuilder {
builder: DiagnosticBuilder::new(interner),
file: file.into(),
}
}
pub fn with_symbols(
interner: &'a dyn TypeDatabase,
symbol_arena: &'a tsz_binder::SymbolArena,
file: impl Into<Arc<str>>,
) -> Self {
SpannedDiagnosticBuilder {
builder: DiagnosticBuilder::with_symbols(interner, symbol_arena),
file: file.into(),
}
}
pub fn with_def_store(mut self, def_store: &'a DefinitionStore) -> Self {
self.builder = self.builder.with_def_store(def_store);
self
}
pub fn span(&self, start: u32, length: u32) -> SourceSpan {
SourceSpan::new(std::sync::Arc::clone(&self.file), start, length)
}
pub fn type_not_assignable(
&mut self,
source: TypeId,
target: TypeId,
start: u32,
length: u32,
) -> TypeDiagnostic {
self.builder
.type_not_assignable(source, target)
.with_span(self.span(start, length))
}
pub fn property_missing(
&mut self,
prop_name: &str,
source: TypeId,
target: TypeId,
start: u32,
length: u32,
) -> TypeDiagnostic {
self.builder
.property_missing(prop_name, source, target)
.with_span(self.span(start, length))
}
pub fn property_not_exist(
&mut self,
prop_name: &str,
type_id: TypeId,
start: u32,
length: u32,
) -> TypeDiagnostic {
self.builder
.property_not_exist(prop_name, type_id)
.with_span(self.span(start, length))
}
pub fn property_not_exist_did_you_mean(
&mut self,
prop_name: &str,
type_id: TypeId,
suggestion: &str,
start: u32,
length: u32,
) -> TypeDiagnostic {
self.builder
.property_not_exist_did_you_mean(prop_name, type_id, suggestion)
.with_span(self.span(start, length))
}
pub fn argument_not_assignable(
&mut self,
arg_type: TypeId,
param_type: TypeId,
start: u32,
length: u32,
) -> TypeDiagnostic {
self.builder
.argument_not_assignable(arg_type, param_type)
.with_span(self.span(start, length))
}
pub fn cannot_find_name(&mut self, name: &str, start: u32, length: u32) -> TypeDiagnostic {
self.builder
.cannot_find_name(name)
.with_span(self.span(start, length))
}
pub fn argument_count_mismatch(
&mut self,
expected: usize,
got: usize,
start: u32,
length: u32,
) -> TypeDiagnostic {
self.builder
.argument_count_mismatch(expected, got)
.with_span(self.span(start, length))
}
pub fn not_callable(&mut self, type_id: TypeId, start: u32, length: u32) -> TypeDiagnostic {
self.builder
.not_callable(type_id)
.with_span(self.span(start, length))
}
pub fn this_type_mismatch(
&mut self,
expected_this: TypeId,
actual_this: TypeId,
start: u32,
length: u32,
) -> TypeDiagnostic {
self.builder
.this_type_mismatch(expected_this, actual_this)
.with_span(self.span(start, length))
}
pub fn excess_property(
&mut self,
prop_name: &str,
target: TypeId,
start: u32,
length: u32,
) -> TypeDiagnostic {
self.builder
.excess_property(prop_name, target)
.with_span(self.span(start, length))
}
pub fn readonly_property(
&mut self,
prop_name: &str,
start: u32,
length: u32,
) -> TypeDiagnostic {
self.builder
.readonly_property(prop_name)
.with_span(self.span(start, length))
}
pub fn add_related(
&self,
diag: TypeDiagnostic,
message: impl Into<String>,
start: u32,
length: u32,
) -> TypeDiagnostic {
diag.with_related(self.span(start, length), message)
}
}
impl TypeDiagnostic {
pub fn to_checker_diagnostic(&self, default_file: &str) -> tsz_common::diagnostics::Diagnostic {
use tsz_common::diagnostics::{
Diagnostic, DiagnosticCategory, DiagnosticRelatedInformation,
};
let (file, start, length) = if let Some(ref span) = self.span {
(span.file.to_string(), span.start, span.length)
} else {
(default_file.to_string(), 0, 0)
};
let category = match self.severity {
DiagnosticSeverity::Error => DiagnosticCategory::Error,
DiagnosticSeverity::Warning => DiagnosticCategory::Warning,
DiagnosticSeverity::Suggestion => DiagnosticCategory::Suggestion,
DiagnosticSeverity::Message => DiagnosticCategory::Message,
};
let related_information: Vec<DiagnosticRelatedInformation> = self
.related
.iter()
.map(|rel| DiagnosticRelatedInformation {
file: rel.span.file.to_string(),
start: rel.span.start,
length: rel.span.length,
message_text: rel.message.clone(),
category: DiagnosticCategory::Message,
code: 0,
})
.collect();
Diagnostic {
file,
start,
length,
message_text: self.message.clone(),
category,
code: self.code,
related_information,
}
}
}
#[derive(Clone)]
pub struct SourceLocation {
pub file: Arc<str>,
pub start: u32,
pub end: u32,
}
impl SourceLocation {
pub fn new(file: impl Into<Arc<str>>, start: u32, end: u32) -> Self {
Self {
file: file.into(),
start,
end,
}
}
pub const fn length(&self) -> u32 {
self.end.saturating_sub(self.start)
}
pub fn to_span(&self) -> SourceSpan {
SourceSpan::new(std::sync::Arc::clone(&self.file), self.start, self.length())
}
}
pub struct DiagnosticCollector<'a> {
interner: &'a dyn TypeDatabase,
file: Arc<str>,
diagnostics: Vec<TypeDiagnostic>,
}
impl<'a> DiagnosticCollector<'a> {
pub fn new(interner: &'a dyn TypeDatabase, file: impl Into<Arc<str>>) -> Self {
DiagnosticCollector {
interner,
file: file.into(),
diagnostics: Vec::new(),
}
}
pub fn diagnostics(&self) -> &[TypeDiagnostic] {
&self.diagnostics
}
pub fn take_diagnostics(&mut self) -> Vec<TypeDiagnostic> {
std::mem::take(&mut self.diagnostics)
}
pub fn type_not_assignable(&mut self, source: TypeId, target: TypeId, loc: &SourceLocation) {
let mut builder = DiagnosticBuilder::new(self.interner);
let diag = builder
.type_not_assignable(source, target)
.with_span(loc.to_span());
self.diagnostics.push(diag);
}
pub fn property_missing(
&mut self,
prop_name: &str,
source: TypeId,
target: TypeId,
loc: &SourceLocation,
) {
let mut builder = DiagnosticBuilder::new(self.interner);
let diag = builder
.property_missing(prop_name, source, target)
.with_span(loc.to_span());
self.diagnostics.push(diag);
}
pub fn property_not_exist(&mut self, prop_name: &str, type_id: TypeId, loc: &SourceLocation) {
let mut builder = DiagnosticBuilder::new(self.interner);
let diag = builder
.property_not_exist(prop_name, type_id)
.with_span(loc.to_span());
self.diagnostics.push(diag);
}
pub fn argument_not_assignable(
&mut self,
arg_type: TypeId,
param_type: TypeId,
loc: &SourceLocation,
) {
let mut builder = DiagnosticBuilder::new(self.interner);
let diag = builder
.argument_not_assignable(arg_type, param_type)
.with_span(loc.to_span());
self.diagnostics.push(diag);
}
pub fn cannot_find_name(&mut self, name: &str, loc: &SourceLocation) {
let mut builder = DiagnosticBuilder::new(self.interner);
let diag = builder.cannot_find_name(name).with_span(loc.to_span());
self.diagnostics.push(diag);
}
pub fn argument_count_mismatch(&mut self, expected: usize, got: usize, loc: &SourceLocation) {
let mut builder = DiagnosticBuilder::new(self.interner);
let diag = builder
.argument_count_mismatch(expected, got)
.with_span(loc.to_span());
self.diagnostics.push(diag);
}
pub fn to_checker_diagnostics(&self) -> Vec<tsz_common::diagnostics::Diagnostic> {
self.diagnostics
.iter()
.map(|d| d.to_checker_diagnostic(&self.file))
.collect()
}
}
#[cfg(test)]
use crate::types::*;
#[cfg(test)]
#[path = "../tests/diagnostics_tests.rs"]
mod tests;