1use std::any::TypeId;
8use std::borrow::Cow;
9
10use crate::op_table::{builtin_operand_types, cast_source_types};
11use crate::type_expr::{BuiltinTypeResolver, ResolvedType, TypeExpr, TypeResolver};
12use crate::{Expr, ExprSpan, Literal, OpLookup, ParseError, ResolvedLeafType};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
19pub enum Ty {
20 I8,
22 I16,
24 I32,
26 I64,
28 I128,
30 Isize,
32 U8,
34 U16,
36 U32,
38 U64,
40 U128,
42 Usize,
44 F32,
46 F64,
48 Bool,
50 String,
52 Array(Box<Ty>),
56 Tuple(Vec<Ty>),
58 Callable(Vec<Ty>, Box<Ty>),
60 Registered(ResolvedLeafType),
64 Any,
68}
69
70impl Ty {
71 pub fn from_leaf(leaf: &ResolvedLeafType) -> Self {
83 match Self::from_type_id(leaf.type_id()) {
84 Ty::Any | Ty::Array(_) => Ty::Registered(leaf.clone()),
85 primitive => primitive,
86 }
87 }
88
89 pub fn from_literal(lit: &Literal) -> Ty {
100 match lit {
101 Literal::I8(_) => Ty::I8,
102 Literal::I16(_) => Ty::I16,
103 Literal::I32(_) => Ty::I32,
104 Literal::I64(_) => Ty::I64,
105 Literal::I128(_) => Ty::I128,
106 Literal::Isize(_) => Ty::Isize,
107 Literal::U8(_) => Ty::U8,
108 Literal::U16(_) => Ty::U16,
109 Literal::U32(_) => Ty::U32,
110 Literal::U64(_) => Ty::U64,
111 Literal::U128(_) => Ty::U128,
112 Literal::Usize(_) => Ty::Usize,
113 Literal::F32(_) => Ty::F32,
114 Literal::F64(_) => Ty::F64,
115 Literal::Bool(_) => Ty::Bool,
116 Literal::Str(_) => Ty::String,
117 Literal::Char(_) => Self::known_leaf::<char>("char"),
118 Literal::ByteStr(_) => Self::known_leaf::<Vec<u8>>("byte string"),
119 Literal::CStr(_) => Self::known_leaf::<std::ffi::CString>("C string"),
120 Literal::Unit => Ty::Tuple(vec![]),
121 }
122 }
123
124 fn known_leaf<T: 'static>(name: &'static str) -> Ty {
128 Ty::Registered(ResolvedLeafType::new(
129 name,
130 cel_runtime::ValueType::leaf::<T>(),
131 ))
132 }
133
134 fn contains_any(&self) -> bool {
138 match self {
139 Ty::Any => true,
140 Ty::Array(element) => element.contains_any(),
141 Ty::Tuple(elements) => elements.iter().any(Self::contains_any),
142 Ty::Callable(parameters, result) => {
143 parameters.iter().any(Self::contains_any) || result.contains_any()
144 }
145 _ => false,
146 }
147 }
148
149 pub fn from_type_id(id: TypeId) -> Ty {
169 if id == TypeId::of::<i8>() {
170 Ty::I8
171 } else if id == TypeId::of::<i16>() {
172 Ty::I16
173 } else if id == TypeId::of::<i32>() {
174 Ty::I32
175 } else if id == TypeId::of::<i64>() {
176 Ty::I64
177 } else if id == TypeId::of::<i128>() {
178 Ty::I128
179 } else if id == TypeId::of::<isize>() {
180 Ty::Isize
181 } else if id == TypeId::of::<u8>() {
182 Ty::U8
183 } else if id == TypeId::of::<u16>() {
184 Ty::U16
185 } else if id == TypeId::of::<u32>() {
186 Ty::U32
187 } else if id == TypeId::of::<u64>() {
188 Ty::U64
189 } else if id == TypeId::of::<u128>() {
190 Ty::U128
191 } else if id == TypeId::of::<usize>() {
192 Ty::Usize
193 } else if id == TypeId::of::<f32>() {
194 Ty::F32
195 } else if id == TypeId::of::<f64>() {
196 Ty::F64
197 } else if id == TypeId::of::<bool>() {
198 Ty::Bool
199 } else if id == TypeId::of::<String>() {
200 Ty::String
201 } else {
202 Ty::Any
203 }
204 }
205
206 pub fn type_id(&self) -> Option<TypeId> {
223 Some(match self {
224 Ty::I8 => TypeId::of::<i8>(),
225 Ty::I16 => TypeId::of::<i16>(),
226 Ty::I32 => TypeId::of::<i32>(),
227 Ty::I64 => TypeId::of::<i64>(),
228 Ty::I128 => TypeId::of::<i128>(),
229 Ty::Isize => TypeId::of::<isize>(),
230 Ty::U8 => TypeId::of::<u8>(),
231 Ty::U16 => TypeId::of::<u16>(),
232 Ty::U32 => TypeId::of::<u32>(),
233 Ty::U64 => TypeId::of::<u64>(),
234 Ty::U128 => TypeId::of::<u128>(),
235 Ty::Usize => TypeId::of::<usize>(),
236 Ty::F32 => TypeId::of::<f32>(),
237 Ty::F64 => TypeId::of::<f64>(),
238 Ty::Bool => TypeId::of::<bool>(),
239 Ty::String => TypeId::of::<String>(),
240 Ty::Array(_) => return None,
241 Ty::Registered(leaf) => leaf.type_id(),
242 Ty::Tuple(elements) if elements.is_empty() => TypeId::of::<()>(),
243 Ty::Tuple(_) => return None,
244 Ty::Callable(..) => TypeId::of::<cel_runtime::DynClosure>(),
245 Ty::Any => return None,
246 })
247 }
248
249 pub fn name(&self) -> Cow<'_, str> {
264 Cow::Borrowed(match self {
265 Ty::I8 => "i8",
266 Ty::I16 => "i16",
267 Ty::I32 => "i32",
268 Ty::I64 => "i64",
269 Ty::I128 => "i128",
270 Ty::Isize => "isize",
271 Ty::U8 => "u8",
272 Ty::U16 => "u16",
273 Ty::U32 => "u32",
274 Ty::U64 => "u64",
275 Ty::U128 => "u128",
276 Ty::Usize => "usize",
277 Ty::F32 => "f32",
278 Ty::F64 => "f64",
279 Ty::Bool => "bool",
280 Ty::String => "String",
281 Ty::Array(_) | Ty::Tuple(_) | Ty::Callable(..) => {
282 let mut output = String::new();
283 self.write_name(&mut output);
284 return Cow::Owned(output);
285 }
286 Ty::Registered(leaf) => leaf.type_name(),
287 Ty::Any => "<any>",
288 })
289 }
290
291 fn write_name(&self, output: &mut String) {
295 match self {
296 Ty::Array(element) => {
297 output.push('[');
298 element.write_name(output);
299 output.push(']');
300 }
301 Ty::Tuple(elements) => Self::write_tuple_name(elements, output),
302 Ty::Callable(parameters, result) => {
303 output.push_str("Fn(");
304 Self::write_tuple_name(parameters, output);
305 output.push_str(", ");
306 result.write_name(output);
307 output.push(')');
308 }
309 leaf => output.push_str(&leaf.name()),
310 }
311 }
312
313 fn write_tuple_name(elements: &[Ty], output: &mut String) {
317 output.push('(');
318 for (index, element) in elements.iter().enumerate() {
320 if index != 0 {
321 output.push_str(", ");
322 }
323 element.write_name(output);
324 }
325 if elements.len() == 1 {
326 output.push(',');
327 }
328 output.push(')');
329 }
330
331 pub fn from_name(name: &str) -> Option<Ty> {
344 match name {
345 "i8" => Some(Ty::I8),
346 "i16" => Some(Ty::I16),
347 "i32" => Some(Ty::I32),
348 "i64" => Some(Ty::I64),
349 "i128" => Some(Ty::I128),
350 "isize" => Some(Ty::Isize),
351 "u8" => Some(Ty::U8),
352 "u16" => Some(Ty::U16),
353 "u32" => Some(Ty::U32),
354 "u64" => Some(Ty::U64),
355 "u128" => Some(Ty::U128),
356 "usize" => Some(Ty::Usize),
357 "f32" => Some(Ty::F32),
358 "f64" => Some(Ty::F64),
359 "bool" => Some(Ty::Bool),
360 "String" => Some(Ty::String),
361 _ => None,
362 }
363 }
364
365 pub fn unifies_with(&self, other: &Ty) -> bool {
383 match (self, other) {
384 (Ty::Any, _) | (_, Ty::Any) => true,
385 (Ty::Array(lhs), Ty::Array(rhs)) => lhs.unifies_with(rhs),
386 (Ty::Tuple(lhs), Ty::Tuple(rhs)) => {
387 lhs.len() == rhs.len() && lhs.iter().zip(rhs).all(|(a, b)| a.unifies_with(b))
388 }
389 (Ty::Callable(lhs, lr), Ty::Callable(rhs, rr)) => {
390 lhs.len() == rhs.len()
391 && lhs.iter().zip(rhs).all(|(a, b)| a.unifies_with(b))
392 && lr.unifies_with(rr)
393 }
394 (Ty::Registered(lhs), Ty::Registered(rhs)) => {
395 lhs.element_type().same_shape(rhs.element_type())
396 }
397 (lhs, rhs) => lhs == rhs,
398 }
399 }
400
401 pub fn unify(&self, other: &Ty) -> Option<Ty> {
422 match (self, other) {
423 (Ty::Any, other) => Some(other.clone()),
424 (this, Ty::Any) => Some(this.clone()),
425 (Ty::Array(lhs), Ty::Array(rhs)) => Some(Ty::Array(Box::new(lhs.unify(rhs)?))),
426 (Ty::Tuple(lhs), Ty::Tuple(rhs)) if lhs.len() == rhs.len() => Some(Ty::Tuple(
427 lhs.iter()
428 .zip(rhs)
429 .map(|(a, b)| a.unify(b))
430 .collect::<Option<_>>()?,
431 )),
432 (Ty::Callable(lhs, lr), Ty::Callable(rhs, rr)) if lhs.len() == rhs.len() => {
433 Some(Ty::Callable(
434 lhs.iter()
435 .zip(rhs)
436 .map(|(a, b)| a.unify(b))
437 .collect::<Option<_>>()?,
438 Box::new(lr.unify(rr)?),
439 ))
440 }
441 (Ty::Registered(lhs), Ty::Registered(rhs))
442 if lhs.element_type().same_shape(rhs.element_type()) =>
443 {
444 Some(self.clone())
445 }
446 (this, other) if this == other => Some(this.clone()),
447 _ => None,
448 }
449 }
450}
451
452pub fn check_expr(expr: &Expr, resolve_ident: &dyn Fn(&str) -> Ty) -> (Ty, Vec<ParseError>) {
488 check_expr_with_type_resolver(expr, resolve_ident, &BuiltinTypeResolver, &OpLookup::new())
489}
490
491pub fn check_expr_with_type_resolver(
523 expr: &Expr,
524 resolve_ident: &dyn Fn(&str) -> Ty,
525 resolve_type: &dyn TypeResolver,
526 lookup: &OpLookup,
527) -> (Ty, Vec<ParseError>) {
528 match expr {
529 Expr::Literal { value, .. } => (Ty::from_literal(value), Vec::new()),
530 Expr::Ident { name, .. } => {
531 let ty = resolve_ident(name);
532 let ty = if ty == Ty::Any {
533 lookup
534 .registered_function_type(name)
535 .map(Ty::from_leaf)
536 .or_else(|| matching_registered_type(name, &[], lookup))
537 .unwrap_or(ty)
538 } else {
539 ty
540 };
541 (ty, Vec::new())
542 }
543 Expr::Struct {
544 name,
545 name_span,
546 fields,
547 ..
548 } => {
549 let mut diagnostics = Vec::new();
550 let schema = lookup.record_schema(name);
551 let mut seen = std::collections::HashSet::new();
552 for field in fields {
553 let (actual, child_diags) = check_expr_with_type_resolver(
554 &field.value,
555 resolve_ident,
556 resolve_type,
557 lookup,
558 );
559 let valid = child_diags.is_empty();
560 diagnostics.extend(child_diags);
561 let Some(schema) = schema else { continue };
562 let error = if !seen.insert(field.name.as_str()) {
563 Some(format!("duplicate field `{}`", field.name))
564 } else if let Some(expected) = schema.fields.iter().find(|f| f.name == field.name) {
565 let expected = resolved_type_to_element_ty(&expected.ty);
566 (valid && !expected.unifies_with(&actual)).then(|| {
567 format!(
568 "field `{}` expects `{}`, found `{}`",
569 field.name,
570 expected.name(),
571 actual.name(),
572 )
573 })
574 } else {
575 Some(format!("unknown field `{}` for `{name}`", field.name))
576 };
577 if let Some(error) = error {
578 diagnostics.push(ParseError::new_range(
579 error,
580 field.name_span.start,
581 field.value.span().end,
582 ));
583 }
584 }
585 if let Some(schema) = schema {
586 for field in &schema.fields {
587 if !seen.contains(field.name) {
588 diagnostics.push(ParseError::new_range(
589 format!("missing field `{}` for `{name}`", field.name),
590 name_span.start,
591 name_span.end,
592 ));
593 }
594 }
595 let ty = if diagnostics.is_empty() {
596 Ty::from_leaf(&schema.ty)
597 } else {
598 Ty::Any
599 };
600 (ty, diagnostics)
601 } else {
602 diagnostics.push(ParseError::new_range(
603 format!("unknown record type `{name}`"),
604 name_span.start,
605 name_span.end,
606 ));
607 (Ty::Any, diagnostics)
608 }
609 }
610 Expr::Op {
611 name,
612 operands,
613 span,
614 } => check_op(name, operands, *span, resolve_ident, resolve_type, lookup),
615 Expr::Logical { lhs, rhs, span, .. } => {
616 check_logical(lhs, rhs, *span, resolve_ident, resolve_type, lookup)
617 }
618 Expr::Apply { callee, args, span } => {
619 let (callee_ty, mut diagnostics) =
620 check_expr_with_type_resolver(callee, resolve_ident, resolve_type, lookup);
621 let mut types = Vec::with_capacity(args.len());
622 for arg in args {
623 let (ty, errors) =
624 check_expr_with_type_resolver(arg, resolve_ident, resolve_type, lookup);
625 types.push(ty);
626 diagnostics.extend(errors);
627 }
628 if let Ty::Callable(parameters, result) = callee_ty {
629 if parameters.len() == types.len()
630 && parameters
631 .iter()
632 .zip(&types)
633 .all(|(a, b)| a.unifies_with(b))
634 {
635 return (*result, diagnostics);
636 }
637 diagnostics.push(ParseError::new_range(
638 "callable argument type mismatch",
639 span.start,
640 span.end,
641 ));
642 } else {
643 let function_name = callee_ty
644 .type_id()
645 .and_then(|id| lookup.registered_function_name(id))
646 .or_else(|| match (&callee_ty, callee.as_ref()) {
647 (Ty::Any, Expr::Ident { name, .. }) => Some(name.as_str()),
648 _ => None,
649 });
650 if let Some(name) = function_name
651 && let Some(ty) =
652 registered_operation_ty(name, &types, *span, lookup, &mut diagnostics)
653 {
654 return (ty, diagnostics);
655 }
656 if function_name.is_none() && callee_ty != Ty::Any {
657 diagnostics.push(ParseError::new_range(
658 "value is not callable",
659 span.start,
660 span.end,
661 ));
662 }
663 }
664 (Ty::Any, diagnostics)
665 }
666 Expr::Member {
667 receiver,
668 name,
669 args,
670 span,
671 ..
672 } => {
673 let mut diagnostics = Vec::new();
674 let operands = std::iter::once(receiver.as_ref()).chain(args.iter().flatten());
675 let types: Vec<_> = operands
676 .map(|expr| {
677 let (ty, child_diags) =
678 check_expr_with_type_resolver(expr, resolve_ident, resolve_type, lookup);
679 let valid = child_diags.is_empty();
680 diagnostics.extend(child_diags);
681 if valid { ty } else { Ty::Any }
682 })
683 .collect();
684 let ty = registered_operation_ty(name, &types, *span, lookup, &mut diagnostics)
685 .unwrap_or(Ty::Any);
686 (ty, diagnostics)
687 }
688 Expr::Tuple { elements, .. } => {
689 let mut diagnostics = Vec::new();
690 let types = elements
691 .iter()
692 .map(|element| {
693 let (ty, errors) =
694 check_expr_with_type_resolver(element, resolve_ident, resolve_type, lookup);
695 diagnostics.extend(errors);
696 ty
697 })
698 .collect();
699 (Ty::Tuple(types), diagnostics)
700 }
701 Expr::Array {
703 elements,
704 type_annotation,
705 annotation_span,
706 ..
707 } => check_array(
708 elements,
709 type_annotation.as_ref(),
710 *annotation_span,
711 resolve_ident,
712 resolve_type,
713 lookup,
714 ),
715 Expr::TupleIndex { base, index, span } => {
716 let (ty, mut diagnostics) =
717 check_expr_with_type_resolver(base, resolve_ident, resolve_type, lookup);
718 if let Ty::Tuple(elements) = ty {
719 if let Some(element) = elements.get(*index) {
720 return (element.clone(), diagnostics);
721 }
722 diagnostics.push(ParseError::new_range(
723 "tuple index out of bounds",
724 span.start,
725 span.end,
726 ));
727 } else if ty != Ty::Any {
728 diagnostics.push(ParseError::new_range(
729 "tuple index requires a tuple",
730 span.start,
731 span.end,
732 ));
733 }
734 (Ty::Any, diagnostics)
735 }
736 Expr::Cast {
737 expr,
738 type_name,
739 span,
740 } => check_cast(expr, type_name, *span, resolve_ident, resolve_type, lookup),
741 Expr::If {
742 cond,
743 then_branch,
744 else_branch,
745 span,
746 } => {
747 let (condition, mut diagnostics) =
748 check_expr_with_type_resolver(cond, resolve_ident, resolve_type, lookup);
749 if !condition.unifies_with(&Ty::Bool) {
750 diagnostics.push(ParseError::new_range(
751 "if condition must be bool",
752 cond.span().start,
753 cond.span().end,
754 ));
755 }
756 let (then_ty, errors) =
757 check_expr_with_type_resolver(then_branch, resolve_ident, resolve_type, lookup);
758 diagnostics.extend(errors);
759 let (else_ty, errors) = else_branch
760 .as_ref()
761 .map(|branch| {
762 check_expr_with_type_resolver(branch, resolve_ident, resolve_type, lookup)
763 })
764 .unwrap_or((Ty::Tuple(vec![]), vec![]));
765 diagnostics.extend(errors);
766 let ty = then_ty.unify(&else_ty).unwrap_or_else(|| {
767 diagnostics.push(ParseError::new_range(
768 "if branch type mismatch",
769 span.start,
770 span.end,
771 ));
772 Ty::Any
773 });
774 (ty, diagnostics)
775 }
776 Expr::Closure { params, body, .. } => {
777 let mut annotation_errors = crate::ast::closure_parameter_diagnostics(params);
778 let parameters: Vec<_> = params
779 .iter()
780 .map(|p| match p.type_expr.resolve(resolve_type) {
781 Ok(resolved) => resolved_type_to_element_ty(&resolved),
782 Err(error) => {
783 annotation_errors.push(error);
784 Ty::Any
785 }
786 })
787 .collect();
788 let resolve_with_params = |name: &str| -> Ty {
789 if let Some(index) = params.iter().position(|p| p.name == name) {
790 parameters[index].clone()
791 } else {
792 Ty::Any
794 }
795 };
796 let (result, mut diagnostics) =
797 check_expr_with_type_resolver(body, &resolve_with_params, resolve_type, lookup);
798 diagnostics.extend(annotation_errors);
799 (Ty::Callable(parameters, Box::new(result)), diagnostics)
800 }
801 }
802}
803
804fn check_array(
824 elements: &[Expr],
825 type_annotation: Option<&TypeExpr>,
826 annotation_span: Option<ExprSpan>,
827 resolve_ident: &dyn Fn(&str) -> Ty,
828 resolve_type: &dyn TypeResolver,
829 lookup: &OpLookup,
830) -> (Ty, Vec<ParseError>) {
831 let mut diagnostics = Vec::new();
832 let element_tys: Vec<Ty> = elements
833 .iter()
834 .map(|element| {
835 let (ty, element_diags) =
836 check_expr_with_type_resolver(element, resolve_ident, resolve_type, lookup);
837 diagnostics.extend(element_diags);
838 ty
839 })
840 .collect();
841 let Some(type_annotation) = type_annotation else {
842 let mut unified = Ty::Any;
843 for (element, ty) in elements.iter().zip(&element_tys) {
844 match unified.unify(ty) {
845 Some(merged) => unified = merged,
846 None => {
847 diagnostics.push(ParseError::new_range(
848 format!(
849 "array elements must share one type: expected `{}`, found `{}`",
850 unified.name(),
851 ty.name()
852 ),
853 element.span().start,
854 element.span().end,
855 ));
856 return (Ty::Array(Box::new(Ty::Any)), diagnostics);
857 }
858 }
859 }
860 return (Ty::Array(Box::new(unified)), diagnostics);
861 };
862
863 let (resolved_annotation, declared_ty, mut annotation_diags) =
864 check_array_annotation(type_annotation, annotation_span, resolve_type);
865 diagnostics.append(&mut annotation_diags);
866 let (Some(ResolvedType::Array { element: _ }), Ty::Array(declared_element_ty)) =
867 (&resolved_annotation, &declared_ty)
868 else {
869 return (Ty::Array(Box::new(Ty::Any)), diagnostics);
870 };
871
872 for (element, actual_ty) in elements.iter().zip(&element_tys) {
873 if !declared_element_ty.unifies_with(actual_ty) {
874 diagnostics.push(ParseError::new_range(
875 format!(
876 "array elements must match the annotation exactly: expected `{}`, found `{}`",
877 declared_element_ty.name(),
878 actual_ty.name()
879 ),
880 element.span().start,
881 element.span().end,
882 ));
883 break;
884 }
885 }
886 (declared_ty, diagnostics)
887}
888
889fn check_array_annotation(
898 type_annotation: &TypeExpr,
899 annotation_span: Option<ExprSpan>,
900 resolve_type: &dyn TypeResolver,
901) -> (Option<ResolvedType>, Ty, Vec<ParseError>) {
902 let resolved = match type_annotation.resolve(resolve_type) {
903 Ok(resolved) => resolved,
904 Err(err) => return (None, Ty::Array(Box::new(Ty::Any)), vec![err]),
905 };
906 match resolved_type_to_array_ty(
907 &resolved,
908 annotation_span.unwrap_or_else(|| type_annotation.span()),
909 ) {
910 Ok(array_ty) => (Some(resolved), array_ty, Vec::new()),
911 Err(err) => (Some(resolved), Ty::Array(Box::new(Ty::Any)), vec![err]),
912 }
913}
914
915fn resolved_type_to_array_ty(resolved: &ResolvedType, span: ExprSpan) -> crate::Result<Ty> {
922 match resolved {
923 ResolvedType::Array { element } => {
924 Ok(Ty::Array(Box::new(resolved_type_to_element_ty(element))))
925 }
926 other => Err(ParseError::new_range(
927 format!(
928 "array annotations must name a complete array type, found `{}`",
929 other.display_name()
930 ),
931 span.start,
932 span.end,
933 )),
934 }
935}
936
937fn resolved_type_to_element_ty(resolved: &ResolvedType) -> Ty {
942 match resolved {
943 ResolvedType::Scalar(leaf) => Ty::from_leaf(leaf),
944 ResolvedType::Array { element } => {
945 Ty::Array(Box::new(resolved_type_to_element_ty(element)))
946 }
947 ResolvedType::Tuple { elements } => {
948 Ty::Tuple(elements.iter().map(resolved_type_to_element_ty).collect())
949 }
950 ResolvedType::Callable { parameters, result } => Ty::Callable(
951 parameters.iter().map(resolved_type_to_element_ty).collect(),
952 Box::new(resolved_type_to_element_ty(result)),
953 ),
954 }
955}
956
957fn check_op(
967 name: &str,
968 operands: &[Expr],
969 span: ExprSpan,
970 resolve_ident: &dyn Fn(&str) -> Ty,
971 resolve_type: &dyn TypeResolver,
972 lookup: &OpLookup,
973) -> (Ty, Vec<ParseError>) {
974 let mut diagnostics = Vec::new();
975 let operand_tys: Vec<Ty> = operands
976 .iter()
977 .map(|operand| {
978 let (ty, operand_diags) =
979 check_expr_with_type_resolver(operand, resolve_ident, resolve_type, lookup);
980 diagnostics.extend(operand_diags);
981 ty
982 })
983 .collect();
984 if !diagnostics.is_empty() {
985 return (Ty::Any, diagnostics);
986 }
987 if let Some(ty) = matching_registered_type(name, &operand_tys, lookup) {
988 return (ty, diagnostics);
989 }
990 if name == "range_full" && operand_tys.is_empty() {
991 let builtin = crate::op_table::builtin_generic_type_0("RangeFull")
992 .expect("builtin full range descriptor");
993 return (
994 Ty::from_leaf(&ResolvedLeafType::new(
995 builtin.type_name,
996 (builtin.value_type)(),
997 )),
998 diagnostics,
999 );
1000 }
1001 let signatures = builtin_operand_types(name);
1002 let matched = signatures.iter().find(|sig| {
1003 sig.arity as usize == operand_tys.len()
1004 && (operand_tys[0] == Ty::Any || operand_tys[0].type_id() == Some(sig.lhs))
1005 && (operand_tys.len() < 2
1006 || operand_tys[1] == Ty::Any
1007 || operand_tys[1].type_id() == Some(sig.rhs))
1008 });
1009 if matched.is_some() {
1010 let ty = if operand_tys.iter().any(Ty::contains_any) {
1011 Ty::Any
1012 } else {
1013 result_ty_for_op(name, &operand_tys[0])
1014 };
1015 return (ty, diagnostics);
1016 }
1017 if let Some(ty) = registered_operation_ty(name, &operand_tys, span, lookup, &mut diagnostics) {
1018 return (ty, diagnostics);
1019 }
1020 if !signatures.is_empty() {
1021 let described = operand_tys
1022 .iter()
1023 .map(Ty::name)
1024 .collect::<Vec<_>>()
1025 .join(", ");
1026 diagnostics.push(ParseError::new_range(
1027 format!("`{name}` is not defined for operand type(s) `{described}`"),
1028 span.start,
1029 span.end,
1030 ));
1031 }
1032 (Ty::Any, diagnostics)
1033}
1034
1035fn registered_operation_ty(
1042 name: &str,
1043 operands: &[Ty],
1044 span: ExprSpan,
1045 lookup: &OpLookup,
1046 diagnostics: &mut Vec<ParseError>,
1047) -> Option<Ty> {
1048 lookup.operation_signatures(name).next()?;
1049 if let Some(ty) = matching_registered_type(name, operands, lookup) {
1050 return Some(ty);
1051 }
1052 let described = operands.iter().map(Ty::name).collect::<Vec<_>>().join(", ");
1053 diagnostics.push(ParseError::new_range(
1054 format!("`{name}` is not defined for operand type(s) `{described}`"),
1055 span.start,
1056 span.end,
1057 ));
1058 Some(Ty::Any)
1059}
1060
1061fn matching_registered_type(name: &str, operands: &[Ty], lookup: &OpLookup) -> Option<Ty> {
1067 let signature = lookup.operation_signatures(name).find(|signature| {
1068 signature.operands.len() == operands.len()
1069 && signature
1070 .operands
1071 .iter()
1072 .zip(operands)
1073 .all(|(expected, actual)| {
1074 resolved_type_to_element_ty(expected).unifies_with(actual)
1075 })
1076 })?;
1077 Some(if operands.iter().any(Ty::contains_any) {
1078 Ty::Any
1079 } else {
1080 resolved_type_to_element_ty(&signature.result)
1081 })
1082}
1083
1084fn result_ty_for_op(name: &str, operand_ty: &Ty) -> Ty {
1088 let range = match name {
1089 "range" => Some("Range"),
1090 "range_inclusive" => Some("RangeInclusive"),
1091 "range_from" => Some("RangeFrom"),
1092 "range_to" => Some("RangeTo"),
1093 "range_to_inclusive" => Some("RangeToInclusive"),
1094 _ => None,
1095 };
1096 if let Some(range) = range {
1097 let builtin = crate::op_table::builtin_generic_type(range, &operand_ty.name())
1098 .expect("matched builtin range endpoint has a concrete descriptor");
1099 return Ty::from_leaf(&ResolvedLeafType::new(
1100 builtin.type_name,
1101 (builtin.value_type)(),
1102 ));
1103 }
1104 match name {
1105 "==" | "!=" | "<" | "<=" | ">" | ">=" => Ty::Bool,
1106 _ => operand_ty.clone(),
1107 }
1108}
1109
1110fn check_cast(
1120 expr: &Expr,
1121 type_name: &str,
1122 span: ExprSpan,
1123 resolve_ident: &dyn Fn(&str) -> Ty,
1124 resolve_type: &dyn TypeResolver,
1125 lookup: &OpLookup,
1126) -> (Ty, Vec<ParseError>) {
1127 let (expr_ty, mut diagnostics) =
1128 check_expr_with_type_resolver(expr, resolve_ident, resolve_type, lookup);
1129 let Some(target_ty) = Ty::from_name(type_name) else {
1130 diagnostics.push(ParseError::new_range(
1131 format!("unknown type `{type_name}`"),
1132 span.start,
1133 span.end,
1134 ));
1135 return (Ty::Any, diagnostics);
1136 };
1137 if expr_ty != Ty::Any
1138 && !expr_ty.type_id().is_some_and(|source_type_id| {
1139 cast_source_types(type_name).any(|id| id == source_type_id)
1140 })
1141 {
1142 diagnostics.push(ParseError::new_range(
1143 format!("no cast from `{}` to `{type_name}`", expr_ty.name()),
1144 span.start,
1145 span.end,
1146 ));
1147 }
1148 (target_ty, diagnostics)
1149}
1150
1151fn check_logical(
1157 lhs: &Expr,
1158 rhs: &Expr,
1159 span: ExprSpan,
1160 resolve_ident: &dyn Fn(&str) -> Ty,
1161 resolve_type: &dyn TypeResolver,
1162 lookup: &OpLookup,
1163) -> (Ty, Vec<ParseError>) {
1164 let (lhs_ty, mut diagnostics) =
1165 check_expr_with_type_resolver(lhs, resolve_ident, resolve_type, lookup);
1166 let (rhs_ty, rhs_diags) =
1167 check_expr_with_type_resolver(rhs, resolve_ident, resolve_type, lookup);
1168 diagnostics.extend(rhs_diags);
1169 for (side, ty) in [("left", lhs_ty), ("right", rhs_ty)] {
1170 if !ty.unifies_with(&Ty::Bool) {
1171 diagnostics.push(ParseError::new_range(
1172 format!(
1173 "`&&`/`||` requires `bool`, found `{}` on the {side}",
1174 ty.name()
1175 ),
1176 span.start,
1177 span.end,
1178 ));
1179 }
1180 }
1181 (Ty::Bool, diagnostics)
1182}
1183
1184#[cfg(test)]
1185mod tests {
1186 use super::*;
1187
1188 #[test]
1190 fn diagnostic_names_borrow_leaves_and_preserve_nested_syntax() {
1191 assert!(matches!(Ty::I32.name(), Cow::Borrowed("i32")));
1192 let alias = Ty::Registered(crate::ResolvedLeafType::leaf::<i32>("Alias").unwrap());
1193 assert!(matches!(alias.name(), Cow::Borrowed("Alias")));
1194 let callable = Ty::Callable(
1195 vec![Ty::Array(Box::new(alias))],
1196 Box::new(Ty::Tuple(vec![Ty::I32])),
1197 );
1198 assert_eq!(callable.name(), "Fn(([Alias],), (i32,))");
1199 }
1200 use crate::LogicalOp;
1201
1202 #[test]
1203 fn from_literal_maps_every_concrete_variant() {
1204 assert_eq!(Ty::from_literal(&Literal::I8(1)), Ty::I8);
1205 assert_eq!(Ty::from_literal(&Literal::I16(1)), Ty::I16);
1206 assert_eq!(Ty::from_literal(&Literal::I32(1)), Ty::I32);
1207 assert_eq!(Ty::from_literal(&Literal::I64(1)), Ty::I64);
1208 assert_eq!(Ty::from_literal(&Literal::I128(1)), Ty::I128);
1209 assert_eq!(Ty::from_literal(&Literal::Isize(1)), Ty::Isize);
1210 assert_eq!(Ty::from_literal(&Literal::U8(1)), Ty::U8);
1211 assert_eq!(Ty::from_literal(&Literal::U16(1)), Ty::U16);
1212 assert_eq!(Ty::from_literal(&Literal::U32(1)), Ty::U32);
1213 assert_eq!(Ty::from_literal(&Literal::U64(1)), Ty::U64);
1214 assert_eq!(Ty::from_literal(&Literal::U128(1)), Ty::U128);
1215 assert_eq!(Ty::from_literal(&Literal::Usize(1)), Ty::Usize);
1216 assert_eq!(Ty::from_literal(&Literal::F32(1.0)), Ty::F32);
1217 assert_eq!(Ty::from_literal(&Literal::F64(1.0)), Ty::F64);
1218 assert_eq!(Ty::from_literal(&Literal::Bool(true)), Ty::Bool);
1219 assert_eq!(Ty::from_literal(&Literal::Str("s".to_string())), Ty::String);
1220 }
1221
1222 #[test]
1223 fn from_literal_preserves_nonprimitive_leaf_identity() {
1224 for (literal, id) in [
1225 (Literal::Char('a'), TypeId::of::<char>()),
1226 (Literal::ByteStr(vec![1]), TypeId::of::<Vec<u8>>()),
1227 (
1228 Literal::CStr(std::ffi::CString::new("a").unwrap()),
1229 TypeId::of::<std::ffi::CString>(),
1230 ),
1231 (Literal::Unit, TypeId::of::<()>()),
1232 ] {
1233 assert_eq!(Ty::from_literal(&literal).type_id(), Some(id));
1234 assert!(!Ty::from_literal(&literal).unifies_with(&Ty::F64));
1235 }
1236 }
1237
1238 #[test]
1239 fn type_id_round_trips_through_from_type_id_for_every_concrete_variant() {
1240 for ty in [
1241 Ty::I8,
1242 Ty::I16,
1243 Ty::I32,
1244 Ty::I64,
1245 Ty::I128,
1246 Ty::Isize,
1247 Ty::U8,
1248 Ty::U16,
1249 Ty::U32,
1250 Ty::U64,
1251 Ty::U128,
1252 Ty::Usize,
1253 Ty::F32,
1254 Ty::F64,
1255 Ty::Bool,
1256 Ty::String,
1257 ] {
1258 let id = ty.type_id().expect("concrete Ty has a TypeId");
1259 assert_eq!(Ty::from_type_id(id), ty);
1260 }
1261 }
1262
1263 #[test]
1264 fn any_has_no_type_id() {
1265 assert_eq!(Ty::Any.type_id(), None);
1266 }
1267
1268 #[test]
1269 fn from_type_id_maps_an_unregistered_type_id_to_any() {
1270 assert_eq!(Ty::from_type_id(TypeId::of::<Vec<u8>>()), Ty::Any);
1271 }
1272
1273 #[test]
1274 fn any_unifies_with_every_concrete_type_in_both_directions() {
1275 assert!(Ty::Any.unifies_with(&Ty::I32));
1276 assert!(Ty::I32.unifies_with(&Ty::Any));
1277 assert!(Ty::Any.unifies_with(&Ty::Any));
1278 }
1279
1280 #[test]
1281 fn identical_concrete_types_unify() {
1282 assert!(Ty::F64.unifies_with(&Ty::F64));
1283 }
1284
1285 #[test]
1286 fn distinct_concrete_types_do_not_unify() {
1287 assert!(!Ty::I32.unifies_with(&Ty::F64));
1288 assert!(!Ty::I32.unifies_with(&Ty::Bool));
1289 }
1290
1291 fn concrete_tys() -> [Ty; 16] {
1293 [
1294 Ty::I8,
1295 Ty::I16,
1296 Ty::I32,
1297 Ty::I64,
1298 Ty::I128,
1299 Ty::Isize,
1300 Ty::U8,
1301 Ty::U16,
1302 Ty::U32,
1303 Ty::U64,
1304 Ty::U128,
1305 Ty::Usize,
1306 Ty::F32,
1307 Ty::F64,
1308 Ty::Bool,
1309 Ty::String,
1310 ]
1311 }
1312
1313 #[test]
1314 fn every_builtin_scalar_table_agrees_on_the_same_names_and_types() {
1315 use crate::op_table::{BUILTIN_SCALAR_NAMES, builtin_scalar_type};
1316 use crate::type_expr::TypeResolver;
1317
1318 for ty in concrete_tys() {
1319 let name = ty.name();
1320 let expected_id = ty.type_id().expect("a concrete Ty has a TypeId");
1321 assert!(
1322 BUILTIN_SCALAR_NAMES.contains(&&*name),
1323 "`{name}` must be one of the built-in scalar names"
1324 );
1325 assert_eq!(
1326 Ty::from_name(&name),
1327 Some(ty.clone()),
1328 "`{name}` must round-trip through Ty::from_name"
1329 );
1330 let scalar = builtin_scalar_type(&name)
1331 .unwrap_or_else(|| panic!("`{name}` must resolve as a built-in scalar"));
1332 assert_eq!((scalar.value_type)().type_id(), expected_id);
1333 assert_eq!(scalar.type_name, name);
1334 let leaf = BuiltinTypeResolver
1335 .resolve_named_type(&name, &[])
1336 .unwrap_or_else(|| panic!("`{name}` must resolve as a CEL leaf type"));
1337 assert_eq!(leaf.type_id(), expected_id);
1338 assert_eq!(leaf.type_name(), name);
1339 assert_eq!(leaf.element_type().type_id(), expected_id);
1340 assert_eq!(
1341 leaf.element_type().type_name(),
1342 name,
1343 "the runtime element descriptor must carry the source-level name"
1344 );
1345 assert_eq!(leaf.element_type().size(), (scalar.value_type)().size());
1346 assert_eq!(leaf.element_type().align(), (scalar.value_type)().align());
1347 }
1348 }
1349
1350 #[test]
1351 fn every_builtin_scalar_name_has_a_concrete_ty() {
1352 use crate::op_table::BUILTIN_SCALAR_NAMES;
1353
1354 let types = concrete_tys();
1355 let ty_names: Vec<_> = types.iter().map(Ty::name).collect();
1356 for name in BUILTIN_SCALAR_NAMES {
1357 assert!(
1358 ty_names.iter().any(|ty_name| ty_name == name),
1359 "the built-in scalar `{name}` has no concrete Ty variant"
1360 );
1361 }
1362 }
1363
1364 #[test]
1365 fn name_is_distinct_per_type() {
1366 let types = [
1367 Ty::I32,
1368 Ty::F64,
1369 Ty::Bool,
1370 Ty::String,
1371 Ty::Array(Box::new(Ty::I32)),
1372 Ty::Array(Box::new(Ty::Array(Box::new(Ty::I32)))),
1373 Ty::Any,
1374 ];
1375 let names: Vec<_> = types.iter().map(Ty::name).collect();
1376 let mut unique = names.clone();
1377 unique.sort_unstable();
1378 unique.dedup();
1379 assert_eq!(
1380 names.len(),
1381 unique.len(),
1382 "every listed Ty has a distinct name"
1383 );
1384 }
1385
1386 fn any_resolver(_name: &str) -> Ty {
1387 Ty::Any
1388 }
1389
1390 fn point(span: proc_macro2::Span) -> ExprSpan {
1391 ExprSpan {
1392 start: span,
1393 end: span,
1394 }
1395 }
1396
1397 fn lit_i32(v: i32) -> Expr {
1398 Expr::Literal {
1399 value: Literal::I32(v),
1400 span: point(proc_macro2::Span::call_site()),
1401 }
1402 }
1403
1404 fn lit_f64(v: f64) -> Expr {
1405 Expr::Literal {
1406 value: Literal::F64(v),
1407 span: point(proc_macro2::Span::call_site()),
1408 }
1409 }
1410
1411 fn lit_bool(v: bool) -> Expr {
1412 Expr::Literal {
1413 value: Literal::Bool(v),
1414 span: point(proc_macro2::Span::call_site()),
1415 }
1416 }
1417
1418 fn lit_str(v: &str) -> Expr {
1419 Expr::Literal {
1420 value: Literal::Str(v.to_string()),
1421 span: point(proc_macro2::Span::call_site()),
1422 }
1423 }
1424
1425 fn op(name: &str, operands: Vec<Expr>) -> Expr {
1426 Expr::Op {
1427 name: name.to_string(),
1428 operands,
1429 span: point(proc_macro2::Span::call_site()),
1430 }
1431 }
1432
1433 fn array(elements: Vec<Expr>) -> Expr {
1434 Expr::Array {
1435 elements,
1436 type_annotation: None,
1437 annotation_span: None,
1438 span: point(proc_macro2::Span::call_site()),
1439 }
1440 }
1441
1442 fn call(callee: &str) -> Expr {
1443 Expr::Apply {
1444 callee: Box::new(Expr::Ident {
1445 name: callee.to_string(),
1446 span: point(proc_macro2::Span::call_site()),
1447 }),
1448 args: Vec::new(),
1449 span: point(proc_macro2::Span::call_site()),
1450 }
1451 }
1452
1453 #[test]
1454 fn literal_infers_its_own_type() {
1455 let (ty, diags) = check_expr(&lit_i32(1), &any_resolver);
1456 assert_eq!(ty, Ty::I32);
1457 assert!(diags.is_empty());
1458 }
1459
1460 #[test]
1461 fn ident_resolves_via_the_supplied_resolver() {
1462 let expr = Expr::Ident {
1463 name: "width".to_string(),
1464 span: point(proc_macro2::Span::call_site()),
1465 };
1466 let (ty, diags) = check_expr(&expr, &|name| {
1467 if name == "width" { Ty::F64 } else { Ty::Any }
1468 });
1469 assert_eq!(ty, Ty::F64);
1470 assert!(diags.is_empty());
1471 }
1472
1473 #[test]
1474 fn unknown_ident_is_any_and_not_a_diagnostic() {
1475 let expr = Expr::Ident {
1476 name: "mystery".to_string(),
1477 span: point(proc_macro2::Span::call_site()),
1478 };
1479 let (ty, diags) = check_expr(&expr, &any_resolver);
1480 assert_eq!(ty, Ty::Any);
1481 assert!(diags.is_empty());
1482 }
1483
1484 #[test]
1485 fn op_with_matching_signature_infers_the_operand_type() {
1486 let expr = op("+", vec![lit_i32(1), lit_i32(2)]);
1487 let (ty, diags) = check_expr(&expr, &any_resolver);
1488 assert_eq!(ty, Ty::I32);
1489 assert!(diags.is_empty());
1490 }
1491
1492 #[test]
1493 fn comparison_op_always_infers_bool() {
1494 let expr = op("==", vec![lit_i32(1), lit_i32(2)]);
1495 let (ty, diags) = check_expr(&expr, &any_resolver);
1496 assert_eq!(ty, Ty::Bool);
1497 assert!(diags.is_empty());
1498 }
1499
1500 #[test]
1501 fn unary_negation_preserves_the_operand_type() {
1502 let expr = op("-", vec![lit_i32(1)]);
1503 let (ty, diags) = check_expr(&expr, &any_resolver);
1504 assert_eq!(ty, Ty::I32);
1505 assert!(diags.is_empty());
1506 }
1507
1508 #[test]
1509 fn op_with_mismatched_operand_types_produces_one_diagnostic_and_infers_any() {
1510 let expr = op("+", vec![lit_i32(1), lit_str("s")]);
1511 let (ty, diags) = check_expr(&expr, &any_resolver);
1512 assert_eq!(ty, Ty::Any);
1513 assert_eq!(diags.len(), 1);
1514 }
1515
1516 #[test]
1517 fn op_with_an_any_operand_produces_no_diagnostic() {
1518 let expr = op(
1519 "+",
1520 vec![
1521 Expr::Ident {
1522 name: "mystery".to_string(),
1523 span: point(proc_macro2::Span::call_site()),
1524 },
1525 lit_i32(1),
1526 ],
1527 );
1528 let (ty, diags) = check_expr(&expr, &any_resolver);
1529 assert_eq!(ty, Ty::Any);
1530 assert!(diags.is_empty());
1531 }
1532
1533 #[test]
1534 fn unregistered_operator_name_is_any_and_not_a_diagnostic() {
1535 let expr = op("greet", vec![lit_i32(1)]);
1538 let (ty, diags) = check_expr(&expr, &any_resolver);
1539 assert_eq!(ty, Ty::Any);
1540 assert!(diags.is_empty());
1541 }
1542
1543 #[test]
1544 fn logical_and_with_bool_operands_infers_bool_with_no_diagnostic() {
1545 let expr = Expr::Logical {
1546 op: LogicalOp::And,
1547 lhs: Box::new(lit_bool(true)),
1548 rhs: Box::new(lit_bool(false)),
1549 span: point(proc_macro2::Span::call_site()),
1550 };
1551 let (ty, diags) = check_expr(&expr, &any_resolver);
1552 assert_eq!(ty, Ty::Bool);
1553 assert!(diags.is_empty());
1554 }
1555
1556 #[test]
1557 fn logical_or_with_a_non_bool_operand_produces_a_diagnostic_but_still_infers_bool() {
1558 let expr = Expr::Logical {
1559 op: LogicalOp::Or,
1560 lhs: Box::new(lit_i32(1)),
1561 rhs: Box::new(lit_bool(true)),
1562 span: point(proc_macro2::Span::call_site()),
1563 };
1564 let (ty, diags) = check_expr(&expr, &any_resolver);
1565 assert_eq!(ty, Ty::Bool);
1566 assert_eq!(diags.len(), 1);
1567 }
1568
1569 #[test]
1570 fn a_broken_op_nested_inside_a_tuple_still_surfaces_a_diagnostic() {
1571 let expr = Expr::Tuple {
1572 elements: vec![op("+", vec![lit_i32(1), lit_str("s")]), lit_i32(2)],
1573 span: point(proc_macro2::Span::call_site()),
1574 };
1575 let (ty, diags) = check_expr(&expr, &any_resolver);
1576 assert_eq!(ty, Ty::Tuple(vec![Ty::Any, Ty::I32]));
1577 assert_eq!(diags.len(), 1);
1578 }
1579
1580 #[test]
1581 fn a_broken_op_nested_inside_an_if_condition_still_surfaces_a_diagnostic() {
1582 let expr = Expr::If {
1583 cond: Box::new(op("+", vec![lit_i32(1), lit_str("s")])),
1584 then_branch: Box::new(lit_i32(1)),
1585 else_branch: Some(Box::new(lit_i32(2))),
1586 span: point(proc_macro2::Span::call_site()),
1587 };
1588 let (ty, diags) = check_expr(&expr, &any_resolver);
1589 assert_eq!(ty, Ty::I32);
1590 assert_eq!(diags.len(), 1);
1591 }
1592
1593 #[test]
1594 fn a_broken_op_nested_inside_a_call_argument_still_surfaces_a_diagnostic() {
1595 let expr = Expr::Apply {
1596 callee: Box::new(Expr::Ident {
1597 name: "f".to_string(),
1598 span: point(proc_macro2::Span::call_site()),
1599 }),
1600 args: vec![op("+", vec![lit_i32(1), lit_str("s")])],
1601 span: point(proc_macro2::Span::call_site()),
1602 };
1603 let (ty, diags) = check_expr(&expr, &any_resolver);
1604 assert_eq!(ty, Ty::Any, "Apply itself is not type-checked in v1");
1605 assert_eq!(diags.len(), 1);
1606 }
1607
1608 fn cast(inner: Expr, type_name: &str) -> Expr {
1609 Expr::Cast {
1610 expr: Box::new(inner),
1611 type_name: type_name.to_string(),
1612 span: point(proc_macro2::Span::call_site()),
1613 }
1614 }
1615
1616 #[test]
1617 fn cast_to_a_registered_numeric_target_infers_the_target_type_with_no_diagnostic() {
1618 let (ty, diags) = check_expr(&cast(lit_i32(1), "f64"), &any_resolver);
1619 assert_eq!(ty, Ty::F64);
1620 assert!(diags.is_empty());
1621 }
1622
1623 #[test]
1624 fn cast_from_bool_to_bool_produces_no_diagnostic() {
1625 let (ty, diags) = check_expr(&cast(lit_bool(true), "bool"), &any_resolver);
1629 assert_eq!(ty, Ty::Bool);
1630 assert!(diags.is_empty());
1631 }
1632
1633 #[test]
1634 fn cast_from_a_number_to_bool_is_a_diagnostic_not_an_unknown_type_error() {
1635 let (ty, diags) = check_expr(&cast(lit_i32(1), "bool"), &any_resolver);
1639 assert_eq!(ty, Ty::Bool);
1640 assert_eq!(diags.len(), 1);
1641 assert!(diags[0].message().contains("no cast from"));
1642 }
1643
1644 #[test]
1645 fn cast_to_an_unrecognized_type_name_is_a_diagnostic_and_infers_any() {
1646 let (ty, diags) = check_expr(&cast(lit_i32(1), "nonsense"), &any_resolver);
1647 assert_eq!(ty, Ty::Any);
1648 assert_eq!(diags.len(), 1);
1649 }
1650
1651 #[test]
1652 fn cast_of_an_any_typed_operand_produces_no_diagnostic() {
1653 let expr = Expr::Ident {
1654 name: "mystery".to_string(),
1655 span: point(proc_macro2::Span::call_site()),
1656 };
1657 let (ty, diags) = check_expr(&cast(expr, "i32"), &any_resolver);
1658 assert_eq!(ty, Ty::I32);
1659 assert!(diags.is_empty());
1660 }
1661
1662 fn closure_param(name: &str, type_name: &str) -> crate::ClosureParam {
1663 crate::ClosureParam {
1664 name: name.to_string(),
1665 name_span: point(proc_macro2::Span::call_site()),
1666 type_expr: crate::TypeExpr::Named {
1667 name: type_name.to_string(),
1668 args: Vec::new(),
1669 span: point(proc_macro2::Span::call_site()),
1670 },
1671 }
1672 }
1673
1674 fn closure(params: Vec<crate::ClosureParam>, body: Expr) -> Expr {
1675 Expr::Closure {
1676 params,
1677 body: Box::new(body),
1678 span: point(proc_macro2::Span::call_site()),
1679 }
1680 }
1681
1682 #[test]
1683 fn closure_literal_preserves_its_signature() {
1684 let expr = closure(
1685 vec![closure_param("x", "i32")],
1686 Expr::Ident {
1687 name: "x".to_string(),
1688 span: point(proc_macro2::Span::call_site()),
1689 },
1690 );
1691 let (ty, diags) = check_expr(&expr, &any_resolver);
1692 assert_eq!(ty, Ty::Callable(vec![Ty::I32], Box::new(Ty::I32)));
1693 assert!(diags.is_empty());
1694 }
1695
1696 #[test]
1697 fn closure_body_type_error_surfaces_through_the_closure() {
1698 let body = op(
1699 "+",
1700 vec![
1701 Expr::Ident {
1702 name: "x".to_string(),
1703 span: point(proc_macro2::Span::call_site()),
1704 },
1705 lit_str("s"),
1706 ],
1707 );
1708 let expr = closure(vec![closure_param("x", "i32")], body);
1709 let (ty, diags) = check_expr(&expr, &any_resolver);
1710 assert_eq!(ty, Ty::Callable(vec![Ty::I32], Box::new(Ty::Any)));
1711 assert_eq!(diags.len(), 1, "the body's i32 + String mismatch surfaces");
1712 }
1713
1714 #[test]
1715 fn closure_param_shadows_the_outer_resolver() {
1716 let outer_resolver = |_: &str| Ty::String;
1719 let body = op(
1720 "+",
1721 vec![
1722 Expr::Ident {
1723 name: "x".to_string(),
1724 span: point(proc_macro2::Span::call_site()),
1725 },
1726 lit_i32(1),
1727 ],
1728 );
1729 let expr = closure(vec![closure_param("x", "i32")], body);
1730 let (_, diags) = check_expr(&expr, &outer_resolver);
1731 assert!(diags.is_empty());
1732 }
1733
1734 #[test]
1735 fn array_type_id_requires_complete_shape_instead_of_a_flat_marker() {
1736 assert_eq!(Ty::Array(Box::new(Ty::I32)).type_id(), None);
1737 assert_eq!(Ty::Array(Box::new(Ty::Any)).type_id(), None);
1738 }
1739
1740 #[test]
1741 fn from_type_id_does_not_invent_structural_shape_from_an_owner() {
1742 assert_eq!(
1743 Ty::from_type_id(TypeId::of::<cel_runtime::OwnedValue>()),
1744 Ty::Any
1745 );
1746 }
1747
1748 #[test]
1750 fn registered_leaf_aliases_unify_independently_of_names_and_capabilities() {
1751 #[derive(Clone)]
1753 struct Foreign;
1754 let first = Ty::from_leaf(&ResolvedLeafType::new(
1755 "FirstAlias",
1756 cel_runtime::ValueType::leaf::<Foreign>(),
1757 ));
1758 let second = Ty::from_leaf(&ResolvedLeafType::new(
1759 "SecondAlias",
1760 cel_runtime::ValueType::cloneable_leaf::<Foreign>(),
1761 ));
1762 assert_eq!(first.unify(&second), Some(first.clone()));
1763 }
1764
1765 #[test]
1766 fn array_name_renders_bracket_syntax_recursively() {
1767 assert_eq!(Ty::Array(Box::new(Ty::I32)).name(), "[i32]");
1768 assert_eq!(
1769 Ty::Array(Box::new(Ty::Array(Box::new(Ty::I32)))).name(),
1770 "[[i32]]"
1771 );
1772 assert_eq!(Ty::Array(Box::new(Ty::Any)).name(), "[<any>]");
1773 }
1774
1775 #[test]
1776 fn arrays_unify_only_when_their_element_types_unify_recursively() {
1777 assert!(Ty::Array(Box::new(Ty::I32)).unifies_with(&Ty::Array(Box::new(Ty::I32))));
1778 assert!(Ty::Array(Box::new(Ty::Any)).unifies_with(&Ty::Array(Box::new(Ty::I32))));
1779 assert!(!Ty::Array(Box::new(Ty::I32)).unifies_with(&Ty::Array(Box::new(Ty::F64))));
1780 assert!(
1781 !Ty::Array(Box::new(Ty::Array(Box::new(Ty::I32))))
1782 .unifies_with(&Ty::Array(Box::new(Ty::I32))),
1783 "differing nesting depth does not unify"
1784 );
1785 assert!(!Ty::Array(Box::new(Ty::I32)).unifies_with(&Ty::I32));
1786 assert!(Ty::Any.unifies_with(&Ty::Array(Box::new(Ty::I32))));
1787 assert!(Ty::Array(Box::new(Ty::I32)).unifies_with(&Ty::Any));
1788 }
1789
1790 #[test]
1791 fn unify_keeps_the_more_concrete_side_at_every_nesting_level() {
1792 assert_eq!(Ty::Any.unify(&Ty::I32), Some(Ty::I32));
1793 assert_eq!(Ty::I32.unify(&Ty::Any), Some(Ty::I32));
1794 assert_eq!(
1795 Ty::Array(Box::new(Ty::Any)).unify(&Ty::Array(Box::new(Ty::Array(Box::new(Ty::I32))))),
1796 Some(Ty::Array(Box::new(Ty::Array(Box::new(Ty::I32)))))
1797 );
1798 }
1799
1800 #[test]
1801 fn unify_of_incompatible_types_is_none() {
1802 assert_eq!(Ty::I32.unify(&Ty::F64), None);
1803 assert_eq!(Ty::Array(Box::new(Ty::I32)).unify(&Ty::I32), None);
1804 assert_eq!(
1805 Ty::Array(Box::new(Ty::I32)).unify(&Ty::Array(Box::new(Ty::F64))),
1806 None
1807 );
1808 }
1809
1810 #[test]
1811 fn a_unified_type_unifies_with_both_of_its_sides() {
1812 let lhs = Ty::Array(Box::new(Ty::Any));
1813 let rhs = Ty::Array(Box::new(Ty::I32));
1814 let merged = lhs.unify(&rhs).expect("compatible types unify");
1815 assert!(merged.unifies_with(&lhs));
1816 assert!(merged.unifies_with(&rhs));
1817 }
1818
1819 #[test]
1820 fn homogeneous_array_infers_its_element_type() {
1821 let (ty, diags) = check_expr(&array(vec![lit_i32(1), lit_i32(2)]), &any_resolver);
1822 assert_eq!(ty, Ty::Array(Box::new(Ty::I32)));
1823 assert!(diags.is_empty());
1824 }
1825
1826 #[test]
1827 fn single_element_array_infers_that_element_type() {
1828 let (ty, diags) = check_expr(&array(vec![lit_str("s")]), &any_resolver);
1829 assert_eq!(ty, Ty::Array(Box::new(Ty::String)));
1830 assert!(diags.is_empty());
1831 }
1832
1833 #[test]
1834 fn nested_array_types_unify_recursively() {
1835 let expr = array(vec![array(vec![lit_i32(1)]), array(vec![lit_i32(2)])]);
1836 let (ty, diags) = check_expr(&expr, &any_resolver);
1837 assert_eq!(ty, Ty::Array(Box::new(Ty::Array(Box::new(Ty::I32)))));
1838 assert!(diags.is_empty());
1839 }
1840
1841 #[test]
1842 fn heterogeneous_array_reports_the_conflicting_element() {
1843 let (ty, diags) = check_expr(&array(vec![lit_i32(1), lit_f64(2.0)]), &any_resolver);
1844 assert_eq!(diags.len(), 1);
1845 assert!(
1846 diags[0].message().contains("expected `i32`, found `f64`"),
1847 "got: {}",
1848 diags[0].message()
1849 );
1850 assert_eq!(
1851 ty,
1852 Ty::Array(Box::new(Ty::Any)),
1853 "a conflicting literal is still an array, of an unknown element type"
1854 );
1855 }
1856
1857 #[test]
1858 fn heterogeneous_nested_array_reports_the_recursive_element_types() {
1859 let expr = array(vec![array(vec![lit_i32(1)]), array(vec![lit_f64(1.0)])]);
1860 let (ty, diags) = check_expr(&expr, &any_resolver);
1861 assert_eq!(diags.len(), 1);
1862 assert!(
1863 diags[0]
1864 .message()
1865 .contains("expected `[i32]`, found `[f64]`"),
1866 "got: {}",
1867 diags[0].message()
1868 );
1869 assert_eq!(ty, Ty::Array(Box::new(Ty::Any)));
1870 }
1871
1872 #[test]
1873 fn mismatched_nesting_depth_is_a_diagnostic() {
1874 let expr = array(vec![array(vec![lit_i32(1)]), lit_i32(2)]);
1875 let (_, diags) = check_expr(&expr, &any_resolver);
1876 assert_eq!(diags.len(), 1);
1877 assert!(
1878 diags[0].message().contains("expected `[i32]`, found `i32`"),
1879 "got: {}",
1880 diags[0].message()
1881 );
1882 }
1883
1884 #[test]
1885 fn array_of_unresolved_call_results_infers_an_array_of_any_with_no_diagnostic() {
1886 let expr = array(vec![call("f"), call("g")]);
1889 let (ty, diags) = check_expr(&expr, &any_resolver);
1890 assert_eq!(ty, Ty::Array(Box::new(Ty::Any)));
1891 assert!(diags.is_empty());
1892 }
1893
1894 #[test]
1895 fn an_any_element_keeps_the_concrete_element_type() {
1896 let expr = array(vec![call("f"), lit_i32(1)]);
1897 let (ty, diags) = check_expr(&expr, &any_resolver);
1898 assert_eq!(ty, Ty::Array(Box::new(Ty::I32)));
1899 assert!(diags.is_empty());
1900 }
1901
1902 #[test]
1903 fn an_any_nested_element_keeps_the_concrete_recursive_element_type() {
1904 let expr = array(vec![array(vec![call("f")]), array(vec![lit_i32(1)])]);
1905 let (ty, diags) = check_expr(&expr, &any_resolver);
1906 assert_eq!(ty, Ty::Array(Box::new(Ty::Array(Box::new(Ty::I32)))));
1907 assert!(diags.is_empty());
1908 }
1909
1910 #[test]
1911 fn every_elements_own_diagnostics_are_retained() {
1912 let expr = array(vec![
1913 op("+", vec![lit_i32(1), lit_str("s")]),
1914 op("+", vec![lit_i32(2), lit_str("t")]),
1915 ]);
1916 let (_, diags) = check_expr(&expr, &any_resolver);
1917 assert_eq!(diags.len(), 2, "both broken elements are reported");
1918 }
1919
1920 #[test]
1921 fn a_later_elements_own_diagnostic_survives_an_earlier_element_conflict() {
1922 let expr = array(vec![
1923 lit_i32(1),
1924 lit_f64(2.0),
1925 op("+", vec![lit_i32(3), lit_str("s")]),
1926 ]);
1927 let (ty, diags) = check_expr(&expr, &any_resolver);
1928 assert_eq!(
1929 diags.len(),
1930 2,
1931 "one element-conflict diagnostic plus the third element's own operator diagnostic"
1932 );
1933 assert_eq!(ty, Ty::Array(Box::new(Ty::Any)));
1934 }
1935
1936 #[test]
1937 fn only_the_first_conflicting_element_is_reported() {
1938 let expr = array(vec![lit_i32(1), lit_f64(2.0), lit_bool(true)]);
1939 let (_, diags) = check_expr(&expr, &any_resolver);
1940 assert_eq!(diags.len(), 1, "one diagnostic per array literal");
1941 assert!(diags[0].message().contains("found `f64`"), "the first one");
1942 }
1943
1944 #[test]
1945 fn a_conflicting_elements_diagnostic_is_anchored_at_that_element() {
1946 let source = "[\n 1i32,\n 2i32,\n 3.0f64,\n 4i32\n]";
1953 let mut parser = crate::Parser::<crate::AstContext>::new(crate::OpLookup::new());
1954 let expr = parser.parse_str_ast(source).expect("source parses");
1955 let (ty, diags) = check_expr(&expr, &any_resolver);
1956 assert_eq!(ty, Ty::Array(Box::new(Ty::Any)));
1957 assert_eq!(diags.len(), 1);
1958 assert!(
1959 diags[0].message().contains("expected `i32`, found `f64`"),
1960 "got: {}",
1961 diags[0].message()
1962 );
1963 let start = diags[0].span().start();
1964 let end = diags[0]
1965 .end_span()
1966 .expect("the conflict diagnostic spans a range")
1967 .end();
1968 assert_eq!(start.line, 4, "anchored at the conflicting element's line");
1969 assert_eq!(start.column, 4, "at the element, not the enclosing group");
1970 assert_eq!(end.line, 4);
1971 assert_eq!(end.column, 4 + "3.0f64".len());
1972 }
1973
1974 #[test]
1975 fn a_conflict_inside_a_nested_array_does_not_cascade_to_the_outer_literal() {
1976 let expr = array(vec![
1980 array(vec![lit_i32(1), lit_f64(2.0)]),
1981 array(vec![lit_i32(3), lit_i32(4)]),
1982 ]);
1983 let (ty, diags) = check_expr(&expr, &any_resolver);
1984 assert_eq!(diags.len(), 1, "only the inner conflict is reported");
1985 assert!(
1986 diags[0].message().contains("expected `i32`, found `f64`"),
1987 "got: {}",
1988 diags[0].message()
1989 );
1990 assert_eq!(ty, Ty::Array(Box::new(Ty::Array(Box::new(Ty::I32)))));
1991 }
1992
1993 #[test]
1994 fn typed_array_annotation_returns_the_declared_array_type() {
1995 let mut parser = crate::Parser::<crate::AstContext>::new(crate::OpLookup::new());
1996 let expr = parser
1997 .parse_str_ast("[0, 1]: [i32]")
1998 .expect("source parses");
1999 let (ty, diags) = check_expr(&expr, &any_resolver);
2000 assert_eq!(ty, Ty::Array(Box::new(Ty::I32)));
2001 assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
2002 }
2003
2004 #[test]
2005 fn typed_empty_array_annotation_returns_the_declared_nested_array_type() {
2006 let mut parser = crate::Parser::<crate::AstContext>::new(crate::OpLookup::new());
2007 let expr = parser.parse_str_ast("[]: [[i32]]").expect("source parses");
2008 let (ty, diags) = check_expr(&expr, &any_resolver);
2009 assert_eq!(ty, Ty::Array(Box::new(Ty::Array(Box::new(Ty::I32)))));
2010 assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
2011 }
2012
2013 #[test]
2014 fn typed_array_annotation_reports_exact_element_type_mismatches() {
2015 let mut parser = crate::Parser::<crate::AstContext>::new(crate::OpLookup::new());
2016 let expr = parser
2017 .parse_str_ast("[0, 1]: [f64]")
2018 .expect("source parses");
2019 let (ty, diags) = check_expr(&expr, &any_resolver);
2020 assert_eq!(ty, Ty::Array(Box::new(Ty::F64)));
2021 assert_eq!(diags.len(), 1);
2022 assert!(
2023 diags[0].message().contains("expected `f64`, found `i32`"),
2024 "got: {}",
2025 diags[0].message()
2026 );
2027 }
2028
2029 #[test]
2030 fn typed_array_annotation_reports_exact_custom_element_type_mismatches() {
2031 #[derive(Clone)]
2032 struct Custom;
2033
2034 let mut parser = crate::Parser::<crate::AstContext>::new(crate::OpLookup::new());
2035 let expr = parser
2036 .parse_str_ast("[0]: [Custom]")
2037 .expect("source parses");
2038 let (ty, diags) = check_expr_with_type_resolver(
2039 &expr,
2040 &any_resolver,
2041 &[(
2042 "Custom",
2043 crate::ResolvedLeafType::new("Custom", cel_runtime::ValueType::leaf::<Custom>()),
2044 )],
2045 &OpLookup::new(),
2046 );
2047 assert_eq!(
2048 ty,
2049 Ty::Array(Box::new(Ty::Registered(crate::ResolvedLeafType::new(
2050 "Custom",
2051 cel_runtime::ValueType::leaf::<Custom>()
2052 ))))
2053 );
2054 assert_eq!(diags.len(), 1);
2055 assert!(
2056 diags[0]
2057 .message()
2058 .contains("expected `Custom`, found `i32`"),
2059 "got: {}",
2060 diags[0].message()
2061 );
2062 }
2063
2064 #[test]
2067 fn nested_annotated_array_reports_a_custom_element_mismatch() {
2068 let mut parser = crate::Parser::<crate::AstContext>::new(crate::OpLookup::new());
2069 let expr = parser
2070 .parse_str_ast("[[]: [CustomA]]: [[CustomB]]")
2071 .expect("source parses");
2072 let (ty, diags) = check_expr_with_type_resolver(
2073 &expr,
2074 &any_resolver,
2075 &custom_ab_resolver(),
2076 &OpLookup::new(),
2077 );
2078
2079 assert_eq!(
2080 ty,
2081 Ty::Array(Box::new(Ty::Array(Box::new(Ty::Registered(
2082 custom_ab_resolver()[1].1.clone()
2083 )))))
2084 );
2085 assert_eq!(diags.len(), 1, "unexpected diagnostics: {diags:?}");
2086 assert!(
2087 diags[0]
2088 .message()
2089 .contains("expected `[CustomB]`, found `[CustomA]`"),
2090 "got: {}",
2091 diags[0].message()
2092 );
2093 }
2094
2095 #[test]
2097 fn nested_annotated_array_accepts_a_matching_custom_element_annotation() {
2098 let mut parser = crate::Parser::<crate::AstContext>::new(crate::OpLookup::new());
2099 let expr = parser
2100 .parse_str_ast("[[]: [CustomA]]: [[CustomA]]")
2101 .expect("source parses");
2102 let (ty, diags) = check_expr_with_type_resolver(
2103 &expr,
2104 &any_resolver,
2105 &custom_ab_resolver(),
2106 &OpLookup::new(),
2107 );
2108
2109 assert_eq!(
2110 ty,
2111 Ty::Array(Box::new(Ty::Array(Box::new(Ty::Registered(
2112 custom_ab_resolver()[0].1.clone()
2113 )))))
2114 );
2115 assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
2116 }
2117
2118 fn custom_ab_resolver() -> [(&'static str, crate::ResolvedLeafType); 2] {
2120 #[derive(Clone)]
2121 struct CustomA;
2122 #[derive(Clone)]
2123 struct CustomB;
2124
2125 [
2126 (
2127 "CustomA",
2128 crate::ResolvedLeafType::new("CustomA", cel_runtime::ValueType::leaf::<CustomA>()),
2129 ),
2130 (
2131 "CustomB",
2132 crate::ResolvedLeafType::new("CustomB", cel_runtime::ValueType::leaf::<CustomB>()),
2133 ),
2134 ]
2135 }
2136
2137 #[test]
2138 fn typed_array_annotation_diagnostics_are_anchored_at_the_annotation_span() {
2139 let source = "[0]: i32";
2140 let mut parser = crate::Parser::<crate::AstContext>::new(crate::OpLookup::new());
2141 let expr = parser.parse_str_ast(source).expect("source parses");
2142 let (_, diags) = check_expr(&expr, &any_resolver);
2143 assert_eq!(diags.len(), 1);
2144 assert!(
2145 diags[0]
2146 .message()
2147 .contains("array annotations must name a complete array type"),
2148 "got: {}",
2149 diags[0].message()
2150 );
2151 let start = diags[0].span().start();
2152 let end = diags[0]
2153 .end_span()
2154 .expect("annotation diagnostics span a range")
2155 .end();
2156 assert_eq!(&source[start.column..end.column], ": i32");
2157 }
2158
2159 #[test]
2160 fn typed_array_annotation_preserves_tuple_array_element_types() {
2161 let mut parser = crate::Parser::<crate::AstContext>::new(crate::OpLookup::new());
2162 let expr = parser
2163 .parse_str_ast("[]: [(i32, f64)]")
2164 .expect("source parses");
2165 let (ty, diags) = check_expr(&expr, &any_resolver);
2166 assert_eq!(ty, Ty::Array(Box::new(Ty::Tuple(vec![Ty::I32, Ty::F64]))));
2167 assert!(diags.is_empty());
2168 }
2169
2170 #[test]
2171 fn an_empty_array_expression_infers_an_array_of_any() {
2172 let (ty, diags) = check_expr(&array(Vec::new()), &any_resolver);
2175 assert_eq!(ty, Ty::Array(Box::new(Ty::Any)));
2176 assert!(diags.is_empty());
2177 }
2178}