mod check_alias;
mod check_augment;
mod check_clone;
mod check_declarations;
mod check_decorators;
mod check_directives;
mod check_enum;
mod check_expressions;
mod check_helpers;
mod check_interface;
mod check_literals;
pub(crate) mod check_model;
mod check_namespace;
mod check_namespace_helpers;
mod check_operation;
mod check_program_flow;
mod check_reference_resolution;
mod check_scalar;
mod check_spread;
mod check_stdlib;
mod check_template;
mod check_template_instantiation;
mod check_union;
mod check_visibility;
pub mod decorator_utils;
pub mod type_relation;
pub mod type_utils;
pub mod types;
pub use decorator_utils::*;
pub use type_relation::{Related, TypeRelationChecker, TypeRelationError, TypeRelationErrorCode};
pub use type_utils::*;
pub use types::*;
#[macro_export]
#[doc(hidden)]
macro_rules! require_ast_or {
($self:expr) => {{
match $self.require_ast() {
Some(ast) => ast,
None => return,
}
}};
($self:expr, $ret:expr) => {{
match $self.require_ast() {
Some(ast) => ast,
None => return $ret,
}
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! require_ast_node {
($self:expr, $node_id:expr, $variant:ident) => {{
let ast = match $self.require_ast() {
Some(ast) => ast,
None => return,
};
let node = match ast.id_to_node($node_id) {
Some($crate::parser::AstNode::$variant(decl)) => decl.clone(),
_ => return,
};
(ast, node)
}};
($self:expr, $node_id:expr, $variant:ident, $ret:expr) => {{
let ast = match $self.require_ast() {
Some(ast) => ast,
None => return $ret,
};
let node = match ast.id_to_node($node_id) {
Some($crate::parser::AstNode::$variant(decl)) => decl.clone(),
_ => return $ret,
};
(ast, node)
}};
}
pub(crate) use crate::require_ast_node;
pub(crate) use crate::require_ast_or;
use crate::ast::node::NodeId;
use crate::ast::types::SyntaxKind;
use crate::diagnostics::Diagnostic;
use crate::modifiers::{self, ModifierFlags};
use crate::parser::{AstBuilder, AstNode};
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
fn format_route_path(base: &str, path: &str) -> String {
let base = base.trim_end_matches('/');
let path = path.trim_start_matches('/');
if base.is_empty() {
format!("/{}", path)
} else if path.is_empty() {
base.to_string()
} else {
format!("{}/{}", base, path)
}
}
#[derive(Debug, Clone)]
pub struct CustomDecoratorDef {
pub name: String,
pub namespace: String,
pub target_type: String,
pub parameters: Vec<DecoratorParamDef>,
}
#[derive(Debug, Clone)]
pub struct DecoratorParamDef {
pub name: String,
pub type_name: String,
pub optional: bool,
pub rest: bool,
}
use std::sync::RwLock;
static GLOBAL_DECORATORS: RwLock<Vec<CustomDecoratorDef>> = RwLock::new(Vec::new());
pub fn register_global_decorator(name: &str, namespace: &str, target_type: &str) {
register_global_decorator_with_params(name, namespace, target_type, Vec::new());
}
pub fn register_global_decorator_with_params(
name: &str,
namespace: &str,
target_type: &str,
parameters: Vec<DecoratorParamDef>,
) {
if let Ok(mut registry) = GLOBAL_DECORATORS.write()
&& !registry.iter().any(|d| d.name == name && d.namespace == namespace)
{
registry.push(CustomDecoratorDef {
name: name.to_string(),
namespace: namespace.to_string(),
target_type: target_type.to_string(),
parameters,
});
}
}
pub fn register_global_decorators(decorators: Vec<(&str, &str, &str)>) {
if let Ok(mut registry) = GLOBAL_DECORATORS.write() {
for (name, namespace, target_type) in decorators {
if !registry.iter().any(|d| d.name == name && d.namespace == namespace) {
registry.push(CustomDecoratorDef {
name: name.to_string(),
namespace: namespace.to_string(),
target_type: target_type.to_string(),
parameters: Vec::new(),
});
}
}
}
}
fn get_global_decorators() -> Vec<CustomDecoratorDef> {
GLOBAL_DECORATORS
.read()
.map(|r| r.clone())
.unwrap_or_default()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CheckFlags {
None = 0,
InTemplateDeclaration = 1 << 0,
}
pub use crate::checker::types::TypeMapper;
#[derive(Debug, Clone)]
pub struct CheckContext {
pub mapper: Option<TypeMapper>,
pub flags: CheckFlags,
}
impl CheckContext {
pub fn new() -> Self {
Self {
mapper: None,
flags: CheckFlags::None,
}
}
pub fn with_mapper(mapper: Option<TypeMapper>) -> Self {
Self {
mapper,
flags: CheckFlags::None,
}
}
pub fn with_flags(&self, flags: CheckFlags) -> Self {
Self {
mapper: self.mapper.clone(),
flags,
}
}
pub fn has_flags(&self, flags: CheckFlags) -> bool {
(self.flags as u32 & flags as u32) == flags as u32
}
}
impl Default for CheckContext {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct SymbolLinks {
pub declared_type: Option<TypeId>,
pub type_id: Option<TypeId>,
pub instantiations: Option<HashMap<Vec<TypeId>, TypeId>>,
pub is_template_instantiation: bool,
}
pub struct Checker {
pub type_store: TypeStore,
pub value_store: ValueStore,
ast: Option<Rc<AstBuilder>>,
pub root_id: NodeId,
pub error_type: TypeId,
pub void_type: TypeId,
pub never_type: TypeId,
pub null_type: TypeId,
pub unknown_type: TypeId,
pub global_namespace_type: Option<TypeId>,
pub current_namespace: Option<TypeId>,
pub typespec_namespace_id: Option<TypeId>,
pub declared_types: HashMap<String, TypeId>,
pub declared_values: HashMap<String, ValueId>,
pub node_type_map: HashMap<NodeId, TypeId>,
pub node_value_map: HashMap<NodeId, ValueId>,
pub symbol_links: HashMap<NodeId, SymbolLinks>,
pub std_types: HashMap<String, TypeId>,
diagnostics_list: Vec<Diagnostic>,
pub pending_base_type_names: HashSet<String>,
pub pending_type_checks: HashSet<NodeId>,
pub pending_type_names: HashSet<String>,
pub pending_op_signature_names: HashSet<String>,
pub pending_template_constraint_names: HashSet<String>,
pub check_depth: u32,
pub type_relation: TypeRelationChecker,
pub deprecation_tracker: crate::deprecation::DeprecationTracker,
pub suppressed_diagnostics: HashMap<NodeId, Vec<String>>,
pub directives_processed: HashSet<NodeId>,
pub value_exact_types: HashMap<ValueId, TypeId>,
pub internal_declarations: HashSet<TypeId>,
pub template_param_scope: Vec<HashMap<String, NodeId>>,
pub using_declarations: Vec<(NodeId, String)>,
pub used_using_names: HashSet<String>,
pub string_literal_cache: HashMap<String, TypeId>,
pub numeric_literal_cache: HashMap<String, TypeId>,
pub boolean_literal_cache: HashMap<bool, TypeId>,
pub pending_spreads: HashMap<TypeId, Vec<TypeId>>,
pub spread_sources: HashMap<TypeId, Vec<TypeId>>,
pub pending_const_checks: HashSet<NodeId>,
pub state_accessors: crate::state_accessors::StateAccessors,
pub custom_decorators: Vec<CustomDecoratorDef>,
}
impl Checker {
pub fn new() -> Self {
let mut type_store = TypeStore::new();
let value_store = ValueStore::new();
let error_type = type_store.add(Type::Intrinsic(IntrinsicType {
id: type_store.next_type_id(),
name: IntrinsicTypeName::ErrorType,
node: None,
is_finished: true,
}));
let void_type = type_store.add(Type::Intrinsic(IntrinsicType {
id: type_store.next_type_id(),
name: IntrinsicTypeName::Void,
node: None,
is_finished: true,
}));
let never_type = type_store.add(Type::Intrinsic(IntrinsicType {
id: type_store.next_type_id(),
name: IntrinsicTypeName::Never,
node: None,
is_finished: true,
}));
let null_type = type_store.add(Type::Intrinsic(IntrinsicType {
id: type_store.next_type_id(),
name: IntrinsicTypeName::Null,
node: None,
is_finished: true,
}));
let unknown_type = type_store.add(Type::Intrinsic(IntrinsicType {
id: type_store.next_type_id(),
name: IntrinsicTypeName::Unknown,
node: None,
is_finished: true,
}));
Self {
type_store,
value_store,
ast: None,
root_id: 0,
error_type,
void_type,
never_type,
null_type,
unknown_type,
global_namespace_type: None,
current_namespace: None,
typespec_namespace_id: None,
declared_types: HashMap::new(),
declared_values: HashMap::new(),
node_type_map: HashMap::new(),
node_value_map: HashMap::new(),
symbol_links: HashMap::new(),
std_types: HashMap::new(),
diagnostics_list: Vec::new(),
pending_base_type_names: HashSet::new(),
pending_type_checks: HashSet::new(),
pending_type_names: HashSet::new(),
pending_op_signature_names: HashSet::new(),
pending_template_constraint_names: HashSet::new(),
check_depth: 0,
type_relation: TypeRelationChecker::new(),
deprecation_tracker: crate::deprecation::DeprecationTracker::new(),
suppressed_diagnostics: HashMap::new(),
directives_processed: HashSet::new(),
value_exact_types: HashMap::new(),
internal_declarations: HashSet::new(),
template_param_scope: Vec::new(),
using_declarations: Vec::new(),
used_using_names: HashSet::new(),
string_literal_cache: HashMap::new(),
numeric_literal_cache: HashMap::new(),
boolean_literal_cache: HashMap::new(),
pending_spreads: HashMap::new(),
spread_sources: HashMap::new(),
pending_const_checks: HashSet::new(),
state_accessors: crate::state_accessors::StateAccessors::new(),
custom_decorators: get_global_decorators(),
}
}
pub fn set_parse_result(&mut self, root_id: NodeId, builder: AstBuilder) {
self.root_id = root_id;
self.ast = Some(Rc::new(builder));
}
pub fn get_type(&self, id: TypeId) -> Option<&Type> {
self.type_store.get(id)
}
pub fn get_type_mut(&mut self, id: TypeId) -> Option<&mut Type> {
self.type_store.get_mut(id)
}
pub fn get_value(&self, id: ValueId) -> Option<&Value> {
self.value_store.get(id)
}
pub fn get_value_mut(&mut self, id: ValueId) -> Option<&mut Value> {
self.value_store.get_mut(id)
}
pub fn set_value_type(&mut self, value_id: ValueId, new_type: TypeId) {
if let Some(v) = self.value_store.get_mut(value_id) {
v.set_value_type(new_type);
}
}
pub fn next_type_id(&self) -> TypeId {
self.type_store.next_type_id()
}
pub fn next_value_id(&self) -> ValueId {
self.value_store.next_value_id()
}
pub fn create_type(&mut self, t: Type) -> TypeId {
self.type_store.add(t)
}
pub fn register_decorator(&mut self, name: &str, namespace: &str, target_type: &str) {
self.register_decorator_with_params(name, namespace, target_type, Vec::new());
}
pub fn register_decorator_with_params(
&mut self,
name: &str,
namespace: &str,
target_type: &str,
parameters: Vec<DecoratorParamDef>,
) {
self.custom_decorators.push(CustomDecoratorDef {
name: name.to_string(),
namespace: namespace.to_string(),
target_type: target_type.to_string(),
parameters,
});
}
pub fn register_decorators(&mut self, decorators: Vec<(&str, &str, &str)>) {
for (name, namespace, target_type) in decorators {
self.register_decorator(name, namespace, target_type);
}
}
pub fn create_value(&mut self, v: Value) -> ValueId {
self.value_store.add(v)
}
pub fn add_diagnostic(&mut self, diag: Diagnostic) {
self.diagnostics_list.push(diag);
}
pub(crate) fn error(&mut self, code: &str, msg: &str) {
self.add_diagnostic(Diagnostic::error(code, msg));
}
pub(crate) fn warning(&mut self, code: &str, msg: &str) {
self.add_diagnostic(Diagnostic::warning(code, msg));
}
pub(crate) fn type_to_string(&self, type_id: TypeId) -> String {
type_utils::type_to_string(&self.type_store, type_id)
}
pub(crate) fn error_unassignable(&mut self, code: &str, source: TypeId, target: TypeId) {
self.error(
code,
&format!(
"Type '{}' is not assignable to type '{}'",
self.type_to_string(source),
self.type_to_string(target),
),
);
}
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics_list
}
pub fn create_union(&mut self, options: Vec<TypeId>) -> TypeId {
if options.is_empty() {
let mut u = UnionType::new(0, String::new(), None, None, true);
u.is_finished = true;
return self.create_type(Type::Union(u));
}
for &opt in &options {
if opt == self.error_type {
return self.error_type;
}
}
let mut variant_ids = Vec::new();
let mut variant_names = Vec::new();
for (i, &opt) in options.iter().enumerate() {
let name = format!("v{}", i);
let variant_id = self.create_type(Type::UnionVariant(UnionVariantType {
id: 0, name: name.clone(),
node: None,
r#type: opt,
union: None, decorators: Vec::new(),
is_finished: true,
}));
variant_ids.push(variant_id);
variant_names.push(name);
}
let mut variant_map = HashMap::new();
for (i, &vid) in variant_ids.iter().enumerate() {
variant_map.insert(variant_names[i].clone(), vid);
}
let union_id = {
let mut u = UnionType::new(0, String::new(), None, None, true);
u.variants = variant_map;
u.variant_names = variant_names;
u.is_finished = true;
self.create_type(Type::Union(u))
};
for &vid in &variant_ids {
if let Some(Type::UnionVariant(v)) = self.get_type_mut(vid) {
v.union = Some(union_id);
}
}
union_id
}
pub fn create_literal_type_string(&mut self, value: String) -> TypeId {
if let Some(&cached) = self.string_literal_cache.get(&value) {
return cached;
}
let type_id = self.create_type(Type::String(StringType {
id: self.next_type_id(),
value: value.clone(),
node: None,
is_finished: true,
}));
self.string_literal_cache.insert(value, type_id);
type_id
}
pub fn create_literal_type_number(&mut self, value: f64, value_as_string: String) -> TypeId {
if let Some(&cached) = self.numeric_literal_cache.get(&value_as_string) {
return cached;
}
let type_id = self.create_type(Type::Number(NumericType {
id: self.next_type_id(),
value,
value_as_string: value_as_string.clone(),
node: None,
is_finished: true,
}));
self.numeric_literal_cache.insert(value_as_string, type_id);
type_id
}
pub fn create_literal_type_boolean(&mut self, value: bool) -> TypeId {
if let Some(&cached) = self.boolean_literal_cache.get(&value) {
return cached;
}
let type_id = self.create_type(Type::Boolean(BooleanType {
id: self.next_type_id(),
value,
node: None,
is_finished: true,
}));
self.boolean_literal_cache.insert(value, type_id);
type_id
}
pub(crate) fn require_ast(&self) -> Option<Rc<AstBuilder>> {
self.ast.clone()
}
pub fn check_node(&mut self, ctx: &CheckContext, node_id: NodeId) -> TypeId {
self.check_depth += 1;
if self.check_depth > 200 {
self.check_depth -= 1;
return self.error_type;
}
let result = self.check_node_impl(ctx, node_id);
if result != self.error_type && !self.directives_processed.contains(&node_id) {
self.process_directives(node_id, result);
self.directives_processed.insert(node_id);
}
self.check_depth -= 1;
self.node_type_map.insert(node_id, result);
result
}
pub(crate) fn check_modifiers_and_report(
&mut self,
node_id: NodeId,
kind: SyntaxKind,
modifiers: &[NodeId],
) {
let ast = match &self.ast {
Some(ast) => ast.clone(),
None => return,
};
let mut modifier_flags = ModifierFlags::None;
for &mod_id in modifiers {
if let Some(AstNode::Modifier(m)) = ast.id_to_node(mod_id) {
modifier_flags = modifier_flags | modifiers::modifier_to_flag(m.kind);
}
}
if modifier_flags.contains(ModifierFlags::Internal) {
self.warning(
"experimental-feature",
"The 'internal' modifier is an experimental feature.",
);
if let Some(&type_id) = self.node_type_map.get(&node_id) {
self.internal_declarations.insert(type_id);
}
}
let result = modifiers::check_modifiers(modifier_flags, kind);
for invalid in &result.invalid_modifiers {
self.error(
"invalid-modifier",
&format!(
"Modifier '{}' is not allowed on {}.",
invalid,
modifiers::get_declaration_kind_text(kind)
),
);
}
for missing in &result.missing_modifiers {
self.error(
"invalid-modifier",
&format!(
"Modifier '{}' is required on {}.",
missing,
modifiers::get_declaration_kind_text(kind)
),
);
}
}
pub(crate) fn check_node_impl(&mut self, ctx: &CheckContext, node_id: NodeId) -> TypeId {
let ast = require_ast_or!(self, self.error_type);
let node = match ast.id_to_node(node_id) {
Some(n) => n.clone(),
None => return self.error_type,
};
match &node {
AstNode::ModelDeclaration(decl) => {
self.check_modifiers_and_report(
node_id,
SyntaxKind::ModelStatement,
&decl.modifiers,
);
self.check_model(ctx, node_id)
}
AstNode::ModelExpression(_) => self.check_model(ctx, node_id),
AstNode::ScalarDeclaration(decl) => {
self.check_modifiers_and_report(
node_id,
SyntaxKind::ScalarStatement,
&decl.modifiers,
);
self.check_scalar(ctx, node_id)
}
AstNode::InterfaceDeclaration(decl) => {
self.check_modifiers_and_report(
node_id,
SyntaxKind::InterfaceStatement,
&decl.modifiers,
);
self.check_interface(ctx, node_id)
}
AstNode::EnumDeclaration(decl) => {
self.check_modifiers_and_report(
node_id,
SyntaxKind::EnumStatement,
&decl.modifiers,
);
self.check_enum(ctx, node_id)
}
AstNode::UnionDeclaration(decl) => {
self.check_modifiers_and_report(
node_id,
SyntaxKind::UnionStatement,
&decl.modifiers,
);
self.check_union(ctx, node_id)
}
AstNode::NamespaceDeclaration(decl) => {
self.check_modifiers_and_report(
node_id,
SyntaxKind::NamespaceStatement,
&decl.modifiers,
);
self.check_namespace(ctx, node_id)
}
AstNode::OperationDeclaration(decl) => {
self.check_modifiers_and_report(
node_id,
SyntaxKind::OperationStatement,
&decl.modifiers,
);
self.check_operation(ctx, node_id)
}
AstNode::AliasStatement(decl) => {
self.check_modifiers_and_report(
node_id,
SyntaxKind::AliasStatement,
&decl.modifiers,
);
self.check_alias(ctx, node_id)
}
AstNode::ConstStatement(decl) => {
self.check_modifiers_and_report(
node_id,
SyntaxKind::ConstStatement,
&decl.modifiers,
);
self.check_const(ctx, node_id);
self.error_type }
AstNode::DecoratorDeclaration(decl) => {
self.check_modifiers_and_report(
node_id,
SyntaxKind::DecoratorDeclarationStatement,
&decl.modifiers,
);
self.check_decorator_declaration(ctx, node_id)
}
AstNode::UsingDeclaration(_) => {
self.check_using(node_id);
self.void_type
}
AstNode::ImportStatement(decl) => {
let path_str = match ast.id_to_node(decl.path) {
Some(AstNode::StringLiteral(sl)) => sl.value.clone(),
_ => String::new(),
};
if !path_str.is_empty() {
self.error(
"import-not-found",
&format!("Cannot find import '{}'", path_str),
);
}
self.void_type
}
AstNode::AugmentDecoratorStatement(_) => {
self.check_augment_decorator(ctx, node_id);
self.void_type
}
AstNode::FunctionDeclaration(decl) => {
self.check_modifiers_and_report(
node_id,
SyntaxKind::FunctionDeclarationStatement,
&decl.modifiers,
);
self.check_function_declaration(ctx, node_id)
}
AstNode::StringLiteral(_) => self.check_string_literal(node_id),
AstNode::NumericLiteral(_) => self.check_numeric_literal(node_id),
AstNode::BooleanLiteral(_) => self.check_boolean_literal(node_id),
AstNode::TypeReference(_) => self.check_type_reference(ctx, node_id),
AstNode::ArrayExpression(_) => self.check_array_expression(ctx, node_id),
AstNode::TupleExpression(_) => self.check_tuple_expression(ctx, node_id),
AstNode::UnionExpression(_) => self.check_union_expression(ctx, node_id),
AstNode::IntersectionExpression(_) => self.check_intersection_expression(ctx, node_id),
AstNode::VoidKeyword(_) => self.void_type,
AstNode::NeverKeyword(_) => self.never_type,
AstNode::UnknownKeyword(_) => self.unknown_type,
AstNode::ValueOfExpression(_) => self.check_valueof(ctx, node_id),
AstNode::TypeOfExpression(_) => self.check_typeof(ctx, node_id),
AstNode::StringTemplateExpression(_) => self.check_string_template(node_id),
AstNode::CallExpression(_) => self.check_call_expression(ctx, node_id),
AstNode::ObjectLiteral(_) => self.check_object_literal(ctx, node_id),
AstNode::ArrayLiteral(_) => self.check_array_literal(ctx, node_id),
AstNode::MemberExpression(_) => self.check_member_expression(ctx, node_id),
AstNode::Identifier(_) => self.check_identifier(ctx, node_id),
AstNode::DecoratorExpression(_) => self.void_type,
AstNode::DirectiveExpression(_) => {
self.check_directive(node_id);
self.void_type
}
AstNode::Doc(_) | AstNode::DocText(_) => self.void_type,
AstNode::LineComment(_)
| AstNode::BlockComment(_)
| AstNode::EmptyStatement(_)
| AstNode::StringTemplateSpan(_) => self.void_type,
AstNode::TemplateArgument(tmpl_arg) => {
self.check_node(ctx, tmpl_arg.argument)
}
AstNode::StringTemplateHead(_)
| AstNode::StringTemplateMiddle(_)
| AstNode::StringTemplateTail(_) => self.check_string_template_part(node_id),
_ => self.error_type,
}
}
pub fn check_node_entity(&mut self, ctx: &CheckContext, node_id: NodeId) -> Entity {
let type_id = self.check_node(ctx, node_id);
Entity::Type(type_id)
}
pub fn entity_to_type_id(&self, entity: &Entity) -> TypeId {
match entity {
Entity::Type(id) => *id,
Entity::Value(id) => self
.get_value(*id)
.map(|v| v.value_type())
.unwrap_or(self.error_type),
Entity::Indeterminate(id) => *id,
Entity::MixedConstraint(mc) => mc.type_constraint.unwrap_or(self.error_type),
}
}
pub fn is_type_assignable_to(
&mut self,
source: TypeId,
target: TypeId,
_diagnostic_target: TypeId,
) -> (bool, Vec<TypeRelationError>) {
let result = self
.type_relation
.is_related_with_store(&self.type_store, source, target);
(result.is_true(), Vec::new())
}
pub fn get_effective_model_type(&self, type_id: TypeId) -> TypeId {
self.get_effective_model_type_with_filter(type_id, None)
}
pub fn get_effective_model_type_with_filter(
&self,
type_id: TypeId,
filter: Option<&dyn Fn(&Type) -> bool>,
) -> TypeId {
match self.get_type(type_id) {
Some(Type::Model(m)) => {
if !m.name.is_empty() {
return type_id;
}
if let Some(f) = filter {
let needs_filter = m.properties.values().any(|&prop_id| {
!f(self
.get_type(prop_id)
.unwrap_or(&Type::Intrinsic(IntrinsicType {
id: 0,
name: IntrinsicTypeName::ErrorType,
node: None,
is_finished: true,
})))
});
if needs_filter {
return type_id;
}
}
self.resolve_effective_model(type_id)
}
Some(Type::Union(u)) => {
let variant_ids: Vec<TypeId> = u
.variant_names
.iter()
.filter_map(|n| u.variants.get(n).copied())
.collect();
if variant_ids.iter().all(|&v| {
matches!(
self.get_type(self.resolve_alias_chain(v)),
Some(Type::Model(_))
)
}) {
type_id
} else {
self.error_type
}
}
_ => type_id,
}
}
pub fn get_effective_route(&self, op_id: TypeId) -> Option<String> {
let op = match self.get_type(op_id) {
Some(Type::Operation(op)) => op,
_ => return None,
};
let op_route = self.find_route_arg(&op.decorators);
let iface_route = op
.interface_
.and_then(|id| self.get_type(id))
.and_then(|t| match t {
Type::Interface(iface) => Some(self.find_route_arg(&iface.decorators)),
_ => None,
})
.flatten();
match (iface_route, op_route) {
(Some(base), Some(path)) => Some(format_route_path(&base, &path)),
(Some(base), None) => Some(base),
(None, Some(path)) => Some(path),
(None, None) => None,
}
}
pub fn get_effective_decorators(&self, op_id: TypeId) -> Vec<&DecoratorApplication> {
let op = match self.get_type(op_id) {
Some(Type::Operation(op)) => op,
_ => return Vec::new(),
};
let mut result = Vec::new();
if let Some(iface_id) = op.interface_
&& let Some(Type::Interface(iface)) = self.get_type(iface_id)
{
for dec in &iface.decorators {
result.push(dec);
}
}
for dec in &op.decorators {
result.push(dec);
}
result
}
fn find_route_arg(&self, decorators: &[DecoratorApplication]) -> Option<String> {
for dec in decorators {
let is_route = dec.definition.is_some_and(|def_id| {
self.get_type(def_id)
.is_some_and(|t| matches!(t, Type::Decorator(dt) if dt.name == "route"))
});
if is_route
&& let Some(arg) = dec.args.first()
&& let Some(DecoratorMarshalledValue::String(s)) = &arg.js_value
{
return Some(s.clone());
}
}
None
}
fn resolve_effective_model(&self, type_id: TypeId) -> TypeId {
let m = match self.get_type(type_id) {
Some(Type::Model(m)) => m,
_ => return type_id,
};
if m.base_model.is_some() {
return type_id;
}
if m.properties.is_empty() {
return type_id;
}
let mut candidates: Option<HashSet<TypeId>> = None;
for name in &m.property_names {
let &prop_id = match m.properties.get(name) {
Some(id) => id,
None => continue,
};
let sources = self.get_named_source_models(prop_id);
let Some(sources) = sources else {
return type_id;
};
match &mut candidates {
None => {
candidates = Some(sources);
}
Some(cands) => {
let mut sources_mut = sources;
Self::add_derived_models(&mut sources_mut, cands, self);
cands.retain(|c| sources_mut.contains(c));
}
}
}
if let Some(cands) = candidates {
let prop_count = m.properties.len();
for &candidate_id in &cands {
if let Some(count) = self.count_properties_inherited(candidate_id, None)
&& prop_count == count
{
return candidate_id;
}
}
}
type_id
}
fn get_named_source_models(&self, prop_id: TypeId) -> Option<HashSet<TypeId>> {
let prop = match self.get_type(prop_id) {
Some(Type::ModelProperty(p)) => p,
_ => return None,
};
prop.source_property?;
let mut set = HashSet::new();
let mut current = Some(prop_id);
while let Some(pid) = current {
if let Some(Type::ModelProperty(p)) = self.get_type(pid) {
if let Some(model_id) = p.model
&& let Some(Type::Model(m)) = self.get_type(model_id)
&& !m.name.is_empty()
{
set.insert(model_id);
}
current = p.source_property;
} else {
break;
}
}
if set.is_empty() { None } else { Some(set) }
}
fn add_derived_models(
sources: &mut HashSet<TypeId>,
possibly_derived: &HashSet<TypeId>,
checker: &Checker,
) {
for &element in possibly_derived {
if !sources.contains(&element) {
let mut current = if let Some(Type::Model(m)) = checker.get_type(element) {
m.base_model
} else {
None
};
while let Some(tid) = current {
if sources.contains(&tid) {
sources.insert(element);
break;
}
if let Some(Type::Model(m)) = checker.get_type(tid) {
current = m.base_model;
} else {
break;
}
}
}
}
}
fn count_properties_inherited(
&self,
type_id: TypeId,
filter: Option<&dyn Fn(&Type) -> bool>,
) -> Option<usize> {
let mut count = 0;
let mut current: Option<TypeId> = Some(type_id);
while let Some(mid) = current {
if let Some(Type::Model(m)) = self.get_type(mid) {
for &prop_id in m.properties.values() {
if let Some(f) = filter {
if let Some(prop_type) = self.get_type(prop_id)
&& f(prop_type)
{
count += 1;
}
} else {
count += 1;
}
}
current = m.base_model;
} else {
return None;
}
}
Some(count)
}
pub fn resolve_alias_chain(&self, type_id: TypeId) -> TypeId {
let mut current = type_id;
let mut seen = HashSet::new();
loop {
if !seen.insert(current) {
return current;
}
match self.get_type(current) {
Some(Type::Scalar(s)) if s.base_scalar.is_some() => {
let Some(base_id) = s.base_scalar else {
return current;
};
if matches!(self.get_type(base_id), Some(Type::Scalar(base)) if base.name == s.name)
{
return current;
}
if matches!(self.get_type(base_id), Some(Type::Scalar(_))) {
return current;
}
current = base_id;
}
_ => return current,
}
}
}
pub fn is_template_instance(&self, type_id: TypeId) -> bool {
match self.get_type(type_id) {
Some(t) => type_utils::is_template_instance(t),
None => false,
}
}
pub(crate) fn check_circular_ref(&self, node_id: NodeId) -> Option<TypeId> {
if self.pending_type_checks.contains(&node_id) {
Some(
self.node_type_map
.get(&node_id)
.copied()
.unwrap_or(self.error_type),
)
} else {
None
}
}
pub(crate) fn finalize_type_check(
&mut self,
ctx: &CheckContext,
type_id: TypeId,
node_id: NodeId,
template_params: &[NodeId],
decorators: &[NodeId],
mapper: Option<&TypeMapper>,
) {
self.check_and_store_decorators(ctx, type_id, decorators);
self.finish_template_or_type(type_id, node_id, template_params, decorators, mapper);
self.pending_type_checks.remove(&node_id);
}
pub fn infer_scalars_from_constraints(
&mut self,
value_id: ValueId,
constraint_type: TypeId,
) -> TypeId {
let value_type = self
.get_value(value_id)
.map(|v| v.value_type())
.unwrap_or(self.error_type);
if value_type == self.error_type {
return self.error_type;
}
if let Some(Type::Scalar(s)) = self.get_type(constraint_type)
&& s.base_scalar.is_some()
{
return constraint_type;
}
value_type
}
pub(crate) fn register_type(
&mut self,
node_id: NodeId,
type_id: TypeId,
name: &str,
mapper: Option<&TypeMapper>,
) {
let links = self.symbol_links.entry(node_id).or_default();
if let Some(mapper) = mapper {
if !mapper.partial {
let instantiations = links.instantiations.get_or_insert_with(HashMap::new);
instantiations.insert(mapper.args.clone(), type_id);
}
links.type_id = Some(type_id);
return;
}
self.declared_types.insert(name.to_string(), type_id);
links.declared_type = Some(type_id);
links.type_id = Some(type_id);
}
pub(crate) fn compute_template_node(
&mut self,
template_parameters: &[NodeId],
mapper: Option<&TypeMapper>,
node_id: NodeId,
) -> Option<NodeId> {
if !template_parameters.is_empty() && mapper.is_none() {
Some(node_id)
} else {
None
}
}
}
impl Default for Checker {
fn default() -> Self {
Self::new()
}
}
pub fn filter_model_properties(
checker: &mut Checker,
model_type_id: TypeId,
filter: &dyn Fn(TypeId) -> bool,
) -> TypeId {
let (props_to_keep, prop_names_to_keep) = {
let mut keep_props: Vec<(String, TypeId)> = Vec::new();
let mut keep_names: Vec<String> = Vec::new();
if let Some(Type::Model(m)) = checker.get_type(model_type_id) {
for name in &m.property_names {
if let Some(&prop_id) = m.properties.get(name)
&& filter(prop_id)
{
keep_props.push((name.clone(), prop_id));
keep_names.push(name.clone());
}
}
}
(keep_props, keep_names)
};
let original_count = match checker.get_type(model_type_id) {
Some(Type::Model(m)) => m.properties.len(),
_ => 0,
};
if props_to_keep.len() == original_count {
return model_type_id;
}
{
let mut m = ModelType::new(checker.next_type_id(), String::new(), None, None);
m.properties = props_to_keep.into_iter().collect();
m.property_names = prop_names_to_keep;
m.source_model = Some(model_type_id);
m.is_finished = true;
checker.create_type(Type::Model(m))
}
}
#[cfg(test)]
mod alias_tests;
#[cfg(test)]
mod augment_decorator_tests;
#[cfg(test)]
mod check_parse_errors_tests;
#[cfg(test)]
mod circular_ref_tests;
#[cfg(test)]
mod clone_type_tests;
#[cfg(test)]
mod decorators_tests;
#[cfg(test)]
mod deprecation_tests;
#[cfg(test)]
mod doc_comment_tests;
#[cfg(test)]
mod duplicate_ids_tests;
#[cfg(test)]
mod effective_type_tests;
#[cfg(test)]
mod enum_tests;
#[cfg(test)]
mod functions_tests;
#[cfg(test)]
mod global_ns_tests;
#[cfg(test)]
mod imports_tests;
#[cfg(test)]
mod internal_tests;
#[cfg(test)]
mod intersection_tests;
#[cfg(test)]
mod model_tests;
#[cfg(test)]
mod namespace_tests;
#[cfg(test)]
mod operation_tests;
#[cfg(test)]
mod references_tests;
#[cfg(test)]
mod relation_tests;
#[cfg(test)]
mod resolve_type_reference_tests;
#[cfg(test)]
mod scalar_tests;
#[cfg(test)]
mod spread_tests;
#[cfg(test)]
mod string_template_tests;
#[cfg(test)]
mod template_tests;
#[cfg(test)]
pub(crate) mod test_utils;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod type_utils_tests;
#[cfg(test)]
mod typeof_tests;
#[cfg(test)]
mod union_tests;
#[cfg(test)]
mod custom_decorator_tests;
#[cfg(test)]
mod integration_scenario_tests;
#[cfg(test)]
mod unused_template_parameter_tests;
#[cfg(test)]
mod unused_using_tests;
#[cfg(test)]
mod using_tests;
#[cfg(test)]
mod value_tests;
#[cfg(test)]
mod valueof_tests;