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