1use crate::error::{HedlError, HedlResult};
33use crate::lex::{
34 is_tensor_literal, is_valid_id_token, parse_expression_token, parse_reference, parse_tensor,
35};
36use crate::types::{value_to_expected_type, ExpectedType};
37use crate::value::{Reference, Value};
38use std::collections::{BTreeMap, HashMap};
39
40#[allow(dead_code)]
44pub struct InferenceContext<'a> {
45 pub aliases: &'a BTreeMap<String, String>,
47 alias_cache: HashMap<String, Value>,
50 pub is_matrix_cell: bool,
52 pub is_id_column: bool,
54 pub prev_row: Option<&'a [Value]>,
56 pub column_index: usize,
58 pub current_type: Option<&'a str>,
60
61 pub expected_type: Option<ExpectedType>,
64 pub column_types: Option<&'a [ExpectedType]>,
66 pub strict_types: bool,
68 pub error_recovery: bool,
70}
71
72impl<'a> InferenceContext<'a> {
73 pub fn for_key_value(aliases: &'a BTreeMap<String, String>) -> Self {
75 Self {
76 aliases,
77 alias_cache: Self::build_alias_cache(aliases),
78 is_matrix_cell: false,
79 is_id_column: false,
80 prev_row: None,
81 column_index: 0,
82 current_type: None,
83 expected_type: None,
84 column_types: None,
85 strict_types: false,
86 error_recovery: false,
87 }
88 }
89
90 pub fn for_matrix_cell(
92 aliases: &'a BTreeMap<String, String>,
93 column_index: usize,
94 prev_row: Option<&'a [Value]>,
95 current_type: &'a str,
96 ) -> Self {
97 Self {
98 aliases,
99 alias_cache: Self::build_alias_cache(aliases),
100 is_matrix_cell: true,
101 is_id_column: column_index == 0,
102 prev_row,
103 column_index,
104 current_type: Some(current_type),
105 expected_type: None,
106 column_types: None,
107 strict_types: false,
108 error_recovery: false,
109 }
110 }
111
112 pub fn with_expected_type(mut self, expected: ExpectedType) -> Self {
114 self.expected_type = Some(expected);
115 self
116 }
117
118 pub fn with_column_types(mut self, types: &'a [ExpectedType]) -> Self {
120 self.column_types = Some(types);
121 self
122 }
123
124 pub fn with_strict_types(mut self, strict: bool) -> Self {
126 self.strict_types = strict;
127 self
128 }
129
130 pub fn with_error_recovery(mut self, recovery: bool) -> Self {
132 self.error_recovery = recovery;
133 self
134 }
135
136 fn build_alias_cache(aliases: &BTreeMap<String, String>) -> HashMap<String, Value> {
139 let mut cache = HashMap::with_capacity(aliases.len());
140 for (key, expanded) in aliases {
141 if let Ok(value) = infer_expanded_alias(expanded, 0) {
143 cache.insert(key.clone(), value);
144 }
145 }
147 cache
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
155pub enum InferenceConfidence {
156 Certain,
158 Probable,
160 Ambiguous,
162}
163
164#[derive(Debug, Clone, PartialEq)]
184pub struct InferenceResult {
185 pub value: Value,
187 pub inferred_type: ExpectedType,
189 pub confidence: InferenceConfidence,
191}
192
193pub fn infer_value_with_type(
223 input: &str,
224 expected: &ExpectedType,
225 ctx: &InferenceContext<'_>,
226 line_num: usize,
227) -> HedlResult<InferenceResult> {
228 use crate::coercion::{coerce, CoercionMode};
229
230 let value = infer_value(input, ctx, line_num)?;
232 let inferred_type = value_to_expected_type(&value);
233
234 if expected.matches(&value) {
236 return Ok(InferenceResult {
237 value,
238 inferred_type,
239 confidence: InferenceConfidence::Certain,
240 });
241 }
242
243 let mode = if ctx.strict_types {
245 CoercionMode::Strict
246 } else {
247 CoercionMode::Lenient
248 };
249
250 match coerce(value.clone(), expected, mode) {
251 crate::coercion::CoercionResult::Matched(v) => Ok(InferenceResult {
252 value: v,
253 inferred_type: expected.clone(),
254 confidence: InferenceConfidence::Certain,
255 }),
256 crate::coercion::CoercionResult::Coerced(v) => Ok(InferenceResult {
257 value: v,
258 inferred_type: expected.clone(),
259 confidence: InferenceConfidence::Probable,
260 }),
261 crate::coercion::CoercionResult::Failed { reason, .. } => {
262 if ctx.error_recovery {
263 Ok(InferenceResult {
265 value,
266 inferred_type,
267 confidence: InferenceConfidence::Ambiguous,
268 })
269 } else {
270 Err(HedlError::type_mismatch(
272 format!(
273 "type mismatch: expected {}, got {} ({})",
274 expected.describe(),
275 crate::types::describe_value_type(&value),
276 reason
277 ),
278 line_num,
279 ))
280 }
281 }
282 }
283}
284
285pub fn infer_value_synthesize(
312 input: &str,
313 ctx: &InferenceContext<'_>,
314 line_num: usize,
315) -> HedlResult<InferenceResult> {
316 let value = infer_value(input, ctx, line_num)?;
317 let inferred_type = value_to_expected_type(&value);
318
319 Ok(InferenceResult {
320 value,
321 inferred_type,
322 confidence: InferenceConfidence::Certain,
323 })
324}
325
326use std::sync::OnceLock;
342
343#[derive(Clone)]
345struct LookupEntry {
346 pattern: &'static str,
348 value: ValueTemplate,
350}
351
352#[derive(Clone)]
354enum ValueTemplate {
355 Null,
356 Bool(bool),
357}
358
359impl ValueTemplate {
360 #[inline(always)]
361 fn to_value(&self) -> Value {
362 match self {
363 ValueTemplate::Null => Value::Null,
364 ValueTemplate::Bool(b) => Value::Bool(*b),
365 }
366 }
367}
368
369static COMMON_VALUES: OnceLock<Vec<Option<LookupEntry>>> = OnceLock::new();
372
373fn init_common_values() -> Vec<Option<LookupEntry>> {
375 let mut table = vec![None; 256];
378
379 let entries = [
380 ("~", ValueTemplate::Null),
382 ("true", ValueTemplate::Bool(true)),
384 ("false", ValueTemplate::Bool(false)),
385 ];
386
387 for (pattern, value) in entries {
388 let hash = hash_string(pattern);
389 table[hash] = Some(LookupEntry { pattern, value });
390 }
391
392 table
393}
394
395#[inline(always)]
398fn hash_string(s: &str) -> usize {
399 let len = s.len();
400 let first = s.as_bytes().first().copied().unwrap_or(0);
401 (len ^ ((first as usize) << 3)) & 0xFF
403}
404
405#[inline]
413fn try_lookup_common(s: &str) -> Option<Value> {
414 let table = COMMON_VALUES.get_or_init(init_common_values);
416
417 let hash = hash_string(s);
418
419 if let Some(entry) = &table[hash] {
421 if entry.pattern == s {
423 return Some(entry.value.to_value());
424 }
425 }
426
427 None
428}
429
430pub fn infer_value(s: &str, ctx: &InferenceContext<'_>, line_num: usize) -> HedlResult<Value> {
449 let s = s.trim();
450
451 if let Some(value) = try_lookup_common(s) {
455 if value.is_null() && ctx.is_id_column {
457 return Err(HedlError::semantic(
458 "null (~) not permitted in ID column",
459 line_num,
460 ));
461 }
462 return Ok(value);
463 }
464
465 let bytes = s.as_bytes();
466
467 match bytes.first() {
469 Some(b'^') if bytes.len() == 1 => {
471 return infer_ditto(ctx, line_num);
472 }
473
474 Some(b'[') => {
476 if is_tensor_literal(s) {
477 match parse_tensor(s) {
478 Ok(tensor) => return Ok(Value::Tensor(Box::new(tensor))),
479 Err(e) => {
480 return Err(HedlError::syntax(
481 format!("invalid tensor literal: {}", e),
482 line_num,
483 ));
484 }
485 }
486 }
487 }
489
490 Some(b'@') => match parse_reference(s) {
492 Ok(r) => {
493 return Ok(Value::Reference(Reference {
494 type_name: r.type_name.map(|s| s.into_boxed_str()),
495 id: r.id.into_boxed_str(),
496 }));
497 }
498 Err(e) => {
499 return Err(HedlError::syntax(
500 format!("invalid reference: {}", e),
501 line_num,
502 ));
503 }
504 },
505
506 Some(b'$') if bytes.get(1) == Some(&b'(') => match parse_expression_token(s) {
508 Ok(expr) => return Ok(Value::Expression(Box::new(expr))),
509 Err(e) => {
510 return Err(HedlError::syntax(
511 format!("invalid expression: {}", e),
512 line_num,
513 ));
514 }
515 },
516
517 Some(b'%') => {
520 let key = &s[1..];
521 if let Some(value) = ctx.alias_cache.get(key) {
522 return Ok(value.clone());
523 }
524 if ctx.aliases.contains_key(key) {
526 let expanded = &ctx.aliases[key];
528 return infer_expanded_alias(expanded, line_num);
529 }
530 return Err(HedlError::alias(
531 format!("undefined alias: %{}", key),
532 line_num,
533 ));
534 }
535
536 Some(b'-') | Some(b'0'..=b'9') => {
539 if let Some(value) = try_parse_number(s) {
540 return Ok(value);
541 }
542 }
544
545 _ => {}
546 }
547
548 if ctx.is_id_column && !is_valid_id_token(s) {
551 return Err(HedlError::semantic(
552 format!(
553 "invalid ID format '{}' - must start with letter or underscore",
554 s
555 ),
556 line_num,
557 ));
558 }
559
560 Ok(Value::String(s.to_string().into_boxed_str()))
561}
562
563#[inline]
565fn infer_ditto(ctx: &InferenceContext<'_>, line_num: usize) -> HedlResult<Value> {
566 if !ctx.is_matrix_cell {
567 return Ok(Value::String("^".into()));
569 }
570
571 if ctx.is_id_column {
572 return Err(HedlError::semantic(
573 "ditto (^) not permitted in ID column",
574 line_num,
575 ));
576 }
577
578 match ctx.prev_row {
579 Some(prev) if ctx.column_index < prev.len() => Ok(prev[ctx.column_index].clone()),
580 Some(_) => Err(HedlError::semantic(
581 "ditto (^) column index out of range",
582 line_num,
583 )),
584 None => Err(HedlError::semantic(
585 "ditto (^) not allowed in first row of list",
586 line_num,
587 )),
588 }
589}
590
591fn infer_expanded_alias(s: &str, _line_num: usize) -> HedlResult<Value> {
593 if s == "true" {
595 return Ok(Value::Bool(true));
596 }
597 if s == "false" {
598 return Ok(Value::Bool(false));
599 }
600
601 if let Some(value) = try_parse_number(s) {
603 return Ok(value);
604 }
605
606 Ok(Value::String(s.to_string().into_boxed_str()))
608}
609
610fn try_parse_number(s: &str) -> Option<Value> {
613 let s = s.trim();
614 let bytes = s.as_bytes();
615
616 if bytes.is_empty() {
617 return None;
618 }
619
620 let first = bytes[0];
622 if first != b'-' && !first.is_ascii_digit() {
623 return None;
624 }
625
626 let has_decimal = memchr::memchr(b'.', bytes).is_some();
628
629 if has_decimal {
631 s.parse::<f64>().ok().and_then(|f| {
634 if f.is_finite() && !s.ends_with('.') {
636 Some(Value::Float(f))
637 } else {
638 None
639 }
640 })
641 } else {
642 s.parse::<i64>().ok().map(Value::Int)
643 }
644}
645
646pub fn infer_quoted_value(s: &str) -> Value {
648 let unescaped = s.replace("\"\"", "\"");
650 Value::String(unescaped.into_boxed_str())
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656
657 fn kv_ctx() -> InferenceContext<'static> {
658 static EMPTY: BTreeMap<String, String> = BTreeMap::new();
659 InferenceContext::for_key_value(&EMPTY)
660 }
661
662 fn ctx_with_aliases(aliases: &BTreeMap<String, String>) -> InferenceContext<'_> {
663 InferenceContext::for_key_value(aliases)
664 }
665
666 #[test]
669 fn test_infer_null() {
670 let v = infer_value("~", &kv_ctx(), 1).unwrap();
671 assert!(matches!(v, Value::Null));
672 }
673
674 #[test]
675 fn test_infer_null_with_whitespace() {
676 let v = infer_value(" ~ ", &kv_ctx(), 1).unwrap();
677 assert!(matches!(v, Value::Null));
678 }
679
680 #[test]
681 fn test_infer_tilde_as_part_of_string() {
682 let v = infer_value("~hello", &kv_ctx(), 1).unwrap();
683 assert!(matches!(v, Value::String(s) if s.as_ref() == "~hello"));
684 }
685
686 #[test]
687 fn test_null_in_id_column_error() {
688 let aliases = BTreeMap::new();
689 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, None, "User");
690 let result = infer_value("~", &ctx, 1);
691 assert!(result.is_err());
692 assert!(result.unwrap_err().message.contains("ID column"));
693 }
694
695 #[test]
698 fn test_infer_bool() {
699 assert!(matches!(
700 infer_value("true", &kv_ctx(), 1).unwrap(),
701 Value::Bool(true)
702 ));
703 assert!(matches!(
704 infer_value("false", &kv_ctx(), 1).unwrap(),
705 Value::Bool(false)
706 ));
707 }
708
709 #[test]
710 fn test_infer_bool_case_sensitive() {
711 assert!(matches!(
713 infer_value("True", &kv_ctx(), 1).unwrap(),
714 Value::String(_)
715 ));
716 assert!(matches!(
717 infer_value("FALSE", &kv_ctx(), 1).unwrap(),
718 Value::String(_)
719 ));
720 }
721
722 #[test]
723 fn test_infer_bool_with_whitespace() {
724 assert!(matches!(
725 infer_value(" true ", &kv_ctx(), 1).unwrap(),
726 Value::Bool(true)
727 ));
728 }
729
730 #[test]
733 fn test_infer_int() {
734 assert!(matches!(
735 infer_value("42", &kv_ctx(), 1).unwrap(),
736 Value::Int(42)
737 ));
738 assert!(matches!(
739 infer_value("-5", &kv_ctx(), 1).unwrap(),
740 Value::Int(-5)
741 ));
742 assert!(matches!(
743 infer_value("0", &kv_ctx(), 1).unwrap(),
744 Value::Int(0)
745 ));
746 }
747
748 #[test]
749 fn test_infer_int_large() {
750 let v = infer_value("9223372036854775807", &kv_ctx(), 1).unwrap();
751 assert!(matches!(v, Value::Int(i64::MAX)));
752 }
753
754 #[test]
755 fn test_infer_int_negative_large() {
756 let v = infer_value("-9223372036854775808", &kv_ctx(), 1).unwrap();
757 assert!(matches!(v, Value::Int(i64::MIN)));
758 }
759
760 #[test]
761 fn test_infer_int_with_whitespace() {
762 assert!(matches!(
763 infer_value(" 123 ", &kv_ctx(), 1).unwrap(),
764 Value::Int(123)
765 ));
766 }
767
768 #[test]
771 fn test_infer_float() {
772 match infer_value("3.25", &kv_ctx(), 1).unwrap() {
773 Value::Float(f) => assert!((f - 3.25).abs() < 0.001),
774 _ => panic!("expected float"),
775 }
776 match infer_value("42.0", &kv_ctx(), 1).unwrap() {
777 Value::Float(f) => assert!((f - 42.0).abs() < 0.001),
778 _ => panic!("expected float"),
779 }
780 }
781
782 #[test]
783 fn test_infer_float_negative() {
784 match infer_value("-3.5", &kv_ctx(), 1).unwrap() {
785 Value::Float(f) => assert!((f + 3.5).abs() < 0.001),
786 _ => panic!("expected float"),
787 }
788 }
789
790 #[test]
791 fn test_infer_float_small() {
792 match infer_value("0.001", &kv_ctx(), 1).unwrap() {
793 Value::Float(f) => assert!((f - 0.001).abs() < 0.0001),
794 _ => panic!("expected float"),
795 }
796 }
797
798 #[test]
801 fn test_infer_string() {
802 assert!(matches!(
803 infer_value("hello", &kv_ctx(), 1).unwrap(),
804 Value::String(s) if s.as_ref() == "hello"
805 ));
806 }
807
808 #[test]
809 fn test_infer_string_with_spaces() {
810 assert!(matches!(
812 infer_value(" hello ", &kv_ctx(), 1).unwrap(),
813 Value::String(s) if s.as_ref() == "hello"
814 ));
815 }
816
817 #[test]
818 fn test_infer_string_unicode() {
819 assert!(matches!(
820 infer_value("日本語", &kv_ctx(), 1).unwrap(),
821 Value::String(s) if s.as_ref() == "日本語"
822 ));
823 }
824
825 #[test]
826 fn test_infer_string_emoji() {
827 assert!(matches!(
828 infer_value("🎉", &kv_ctx(), 1).unwrap(),
829 Value::String(s) if s.as_ref() == "🎉"
830 ));
831 }
832
833 #[test]
836 fn test_infer_reference() {
837 let v = infer_value("@user_1", &kv_ctx(), 1).unwrap();
838 match v {
839 Value::Reference(r) => {
840 assert_eq!(r.type_name, None);
841 assert_eq!(r.id.as_ref(), "user_1");
842 }
843 _ => panic!("expected reference"),
844 }
845 }
846
847 #[test]
848 fn test_infer_qualified_reference() {
849 let v = infer_value("@User:user_1", &kv_ctx(), 1).unwrap();
850 match v {
851 Value::Reference(r) => {
852 assert_eq!(r.type_name.as_deref(), Some("User"));
853 assert_eq!(r.id.as_ref(), "user_1");
854 }
855 _ => panic!("expected reference"),
856 }
857 }
858
859 #[test]
860 fn test_infer_reference_with_whitespace() {
861 let v = infer_value(" @user_1 ", &kv_ctx(), 1).unwrap();
862 assert!(matches!(v, Value::Reference(_)));
863 }
864
865 #[test]
866 fn test_infer_reference_invalid_error() {
867 let result = infer_value("@User:123-invalid", &kv_ctx(), 1);
869 assert!(result.is_err());
870 assert!(result.unwrap_err().message.contains("invalid reference"));
871 }
872
873 #[test]
874 fn test_infer_reference_uppercase_valid() {
875 let v = infer_value("@User:ABC123", &kv_ctx(), 1).unwrap();
877 match v {
878 Value::Reference(r) => {
879 assert_eq!(r.type_name.as_deref(), Some("User"));
880 assert_eq!(r.id.as_ref(), "ABC123");
881 }
882 _ => panic!("Expected reference"),
883 }
884 }
885
886 #[test]
889 fn test_infer_expression() {
890 use crate::lex::Expression;
891 let v = infer_value("$(now())", &kv_ctx(), 1).unwrap();
892 match v {
893 Value::Expression(e) => {
894 assert!(
895 matches!(e.as_ref(), Expression::Call { name, args, .. } if name == "now" && args.is_empty())
896 );
897 }
898 _ => panic!("expected expression"),
899 }
900 }
901
902 #[test]
903 fn test_infer_expression_with_args() {
904 let v = infer_value("$(add(1, 2))", &kv_ctx(), 1).unwrap();
905 assert!(matches!(v, Value::Expression(_)));
906 }
907
908 #[test]
909 fn test_infer_expression_nested() {
910 let v = infer_value("$(outer(inner()))", &kv_ctx(), 1).unwrap();
911 assert!(matches!(v, Value::Expression(_)));
912 }
913
914 #[test]
915 fn test_infer_expression_identifier() {
916 let v = infer_value("$(x)", &kv_ctx(), 1).unwrap();
917 assert!(matches!(v, Value::Expression(_)));
918 }
919
920 #[test]
921 fn test_infer_expression_invalid_error() {
922 let result = infer_value("$(unclosed", &kv_ctx(), 1);
923 assert!(result.is_err());
924 }
925
926 #[test]
927 fn test_dollar_not_expression() {
928 let v = infer_value("$foo", &kv_ctx(), 1).unwrap();
930 assert!(matches!(v, Value::String(s) if s.as_ref() == "$foo"));
931 }
932
933 #[test]
936 fn test_infer_tensor() {
937 let v = infer_value("[1, 2, 3]", &kv_ctx(), 1).unwrap();
938 assert!(matches!(v, Value::Tensor(_)));
939 }
940
941 #[test]
942 fn test_infer_tensor_float() {
943 let v = infer_value("[1.5, 2.5, 3.5]", &kv_ctx(), 1).unwrap();
944 assert!(matches!(v, Value::Tensor(_)));
945 }
946
947 #[test]
948 fn test_infer_tensor_nested() {
949 let v = infer_value("[[1, 2], [3, 4]]", &kv_ctx(), 1).unwrap();
950 assert!(matches!(v, Value::Tensor(_)));
951 }
952
953 #[test]
954 fn test_infer_tensor_empty_error() {
955 let result = infer_value("[]", &kv_ctx(), 1);
957 assert!(result.is_err());
958 assert!(result.unwrap_err().message.contains("empty tensor"));
959 }
960
961 #[test]
962 fn test_infer_tensor_invalid_is_string() {
963 let v = infer_value("[not a tensor]", &kv_ctx(), 1).unwrap();
965 assert!(matches!(v, Value::String(_)));
966 }
967
968 #[test]
971 fn test_infer_alias_bool() {
972 let mut aliases = BTreeMap::new();
973 aliases.insert("active".to_string(), "true".to_string());
974 let ctx = ctx_with_aliases(&aliases);
975 let v = infer_value("%active", &ctx, 1).unwrap();
976 assert!(matches!(v, Value::Bool(true)));
977 }
978
979 #[test]
980 fn test_infer_alias_number() {
981 let mut aliases = BTreeMap::new();
982 aliases.insert("count".to_string(), "42".to_string());
983 let ctx = ctx_with_aliases(&aliases);
984 let v = infer_value("%count", &ctx, 1).unwrap();
985 assert!(matches!(v, Value::Int(42)));
986 }
987
988 #[test]
989 fn test_infer_alias_string() {
990 let mut aliases = BTreeMap::new();
991 aliases.insert("name".to_string(), "Alice".to_string());
992 let ctx = ctx_with_aliases(&aliases);
993 let v = infer_value("%name", &ctx, 1).unwrap();
994 assert!(matches!(v, Value::String(s) if s.as_ref() == "Alice"));
995 }
996
997 #[test]
998 fn test_infer_undefined_alias_error() {
999 let result = infer_value("%undefined", &kv_ctx(), 1);
1000 assert!(result.is_err());
1001 assert!(result.unwrap_err().message.contains("undefined alias"));
1002 }
1003
1004 #[test]
1007 fn test_ditto_in_kv_is_string() {
1008 let v = infer_value("^", &kv_ctx(), 1).unwrap();
1009 assert!(matches!(v, Value::String(s) if s.as_ref() == "^"));
1010 }
1011
1012 #[test]
1013 fn test_ditto_in_matrix_cell() {
1014 let aliases = BTreeMap::new();
1015 let prev_row = vec![Value::String("id".to_string().into()), Value::Int(42)];
1016 let ctx = InferenceContext::for_matrix_cell(&aliases, 1, Some(&prev_row), "User");
1017 let v = infer_value("^", &ctx, 1).unwrap();
1018 assert!(matches!(v, Value::Int(42)));
1019 }
1020
1021 #[test]
1022 fn test_ditto_in_id_column_error() {
1023 let aliases = BTreeMap::new();
1024 let prev_row = vec![Value::String("id".to_string().into())];
1025 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, Some(&prev_row), "User");
1026 let result = infer_value("^", &ctx, 1);
1027 assert!(result.is_err());
1028 assert!(result.unwrap_err().message.contains("ID column"));
1029 }
1030
1031 #[test]
1032 fn test_ditto_first_row_error() {
1033 let aliases = BTreeMap::new();
1034 let ctx = InferenceContext::for_matrix_cell(&aliases, 1, None, "User");
1035 let result = infer_value("^", &ctx, 1);
1036 assert!(result.is_err());
1037 assert!(result.unwrap_err().message.contains("first row"));
1038 }
1039
1040 #[test]
1041 fn test_ditto_column_out_of_range_error() {
1042 let aliases = BTreeMap::new();
1043 let prev_row = vec![Value::String("id".to_string().into())];
1044 let ctx = InferenceContext::for_matrix_cell(&aliases, 5, Some(&prev_row), "User");
1045 let result = infer_value("^", &ctx, 1);
1046 assert!(result.is_err());
1047 assert!(result.unwrap_err().message.contains("out of range"));
1048 }
1049
1050 #[test]
1053 fn test_number_edge_cases() {
1054 assert!(matches!(
1056 infer_value("1e10", &kv_ctx(), 1).unwrap(),
1057 Value::String(_)
1058 ));
1059 assert!(matches!(
1061 infer_value("1_000", &kv_ctx(), 1).unwrap(),
1062 Value::String(_)
1063 ));
1064 assert!(matches!(
1066 infer_value(".5", &kv_ctx(), 1).unwrap(),
1067 Value::String(_)
1068 ));
1069 }
1070
1071 #[test]
1072 fn test_number_trailing_decimal_is_string() {
1073 assert!(matches!(
1074 infer_value("123.", &kv_ctx(), 1).unwrap(),
1075 Value::String(_)
1076 ));
1077 }
1078
1079 #[test]
1080 fn test_number_plus_sign_is_string() {
1081 assert!(matches!(
1082 infer_value("+42", &kv_ctx(), 1).unwrap(),
1083 Value::String(_)
1084 ));
1085 }
1086
1087 #[test]
1088 fn test_number_leading_zeros_is_string() {
1089 assert!(matches!(
1091 infer_value("007", &kv_ctx(), 1).unwrap(),
1092 Value::Int(7) ));
1094 }
1095
1096 #[test]
1097 fn test_number_hex_is_string() {
1098 assert!(matches!(
1099 infer_value("0xFF", &kv_ctx(), 1).unwrap(),
1100 Value::String(_)
1101 ));
1102 }
1103
1104 #[test]
1107 fn test_try_parse_number_empty() {
1108 assert!(try_parse_number("").is_none());
1109 }
1110
1111 #[test]
1112 fn test_try_parse_number_whitespace() {
1113 assert!(try_parse_number(" ").is_none());
1114 }
1115
1116 #[test]
1117 fn test_try_parse_number_valid_int() {
1118 assert!(matches!(try_parse_number("123"), Some(Value::Int(123))));
1119 }
1120
1121 #[test]
1122 fn test_try_parse_number_valid_float() {
1123 match try_parse_number("3.5") {
1124 Some(Value::Float(f)) => assert!((f - 3.5).abs() < 0.001),
1125 _ => panic!("expected float"),
1126 }
1127 }
1128
1129 #[test]
1130 fn test_try_parse_number_negative() {
1131 assert!(matches!(try_parse_number("-42"), Some(Value::Int(-42))));
1132 }
1133
1134 #[test]
1135 fn test_try_parse_number_invalid() {
1136 assert!(try_parse_number("abc").is_none());
1137 assert!(try_parse_number("12abc").is_none());
1138 }
1139
1140 #[test]
1143 fn test_infer_quoted_value_simple() {
1144 let v = infer_quoted_value("hello");
1145 assert!(matches!(v, Value::String(s) if s.as_ref() == "hello"));
1146 }
1147
1148 #[test]
1149 fn test_infer_quoted_value_empty() {
1150 let v = infer_quoted_value("");
1151 assert!(matches!(v, Value::String(s) if s.is_empty()));
1152 }
1153
1154 #[test]
1155 fn test_infer_quoted_value_escaped_quotes() {
1156 let v = infer_quoted_value("say \"\"hello\"\"");
1157 assert!(matches!(v, Value::String(s) if s.as_ref() == "say \"hello\""));
1158 }
1159
1160 #[test]
1161 fn test_infer_quoted_value_multiple_escapes() {
1162 let v = infer_quoted_value("a\"\"b\"\"c");
1163 assert!(matches!(v, Value::String(s) if s.as_ref() == "a\"b\"c"));
1164 }
1165
1166 #[test]
1169 fn test_context_for_key_value() {
1170 let aliases = BTreeMap::new();
1171 let ctx = InferenceContext::for_key_value(&aliases);
1172 assert!(!ctx.is_matrix_cell);
1173 assert!(!ctx.is_id_column);
1174 assert!(ctx.prev_row.is_none());
1175 }
1176
1177 #[test]
1178 fn test_context_for_matrix_cell() {
1179 let aliases = BTreeMap::new();
1180 let ctx = InferenceContext::for_matrix_cell(&aliases, 2, None, "User");
1181 assert!(ctx.is_matrix_cell);
1182 assert!(!ctx.is_id_column); assert_eq!(ctx.column_index, 2);
1184 assert_eq!(ctx.current_type, Some("User"));
1185 }
1186
1187 #[test]
1188 fn test_context_id_column_detection() {
1189 let aliases = BTreeMap::new();
1190 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, None, "User");
1191 assert!(ctx.is_id_column); }
1193
1194 #[test]
1197 fn test_id_column_valid_id() {
1198 let aliases = BTreeMap::new();
1199 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, None, "User");
1200 let v = infer_value("user_123", &ctx, 1).unwrap();
1201 assert!(matches!(v, Value::String(s) if s.as_ref() == "user_123"));
1202 }
1203
1204 #[test]
1205 fn test_id_column_invalid_starts_digit_error() {
1206 let aliases = BTreeMap::new();
1208 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, None, "User");
1209 let result = infer_value("123User", &ctx, 1);
1210 assert!(result.is_err());
1211 assert!(result.unwrap_err().message.contains("invalid ID"));
1212 }
1213
1214 #[test]
1215 fn test_id_column_uppercase_valid() {
1216 let aliases = BTreeMap::new();
1218 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, None, "User");
1219 let result = infer_value("SKU-4020", &ctx, 1);
1220 assert!(result.is_ok());
1221 }
1222
1223 #[test]
1226 fn test_lookup_table_bool_true() {
1227 let v = infer_value("true", &kv_ctx(), 1).unwrap();
1229 assert!(matches!(v, Value::Bool(true)));
1230 }
1231
1232 #[test]
1233 fn test_lookup_table_bool_false() {
1234 let v = infer_value("false", &kv_ctx(), 1).unwrap();
1236 assert!(matches!(v, Value::Bool(false)));
1237 }
1238
1239 #[test]
1240 fn test_lookup_table_null() {
1241 let v = infer_value("~", &kv_ctx(), 1).unwrap();
1243 assert!(matches!(v, Value::Null));
1244 }
1245
1246 #[test]
1247 fn test_lookup_table_collision_detection() {
1248 let v = infer_value("True", &kv_ctx(), 1).unwrap();
1251 assert!(matches!(v, Value::String(s) if s.as_ref() == "True"));
1252 }
1253
1254 #[test]
1255 fn test_lookup_table_multiple_calls() {
1256 for _ in 0..100 {
1258 let v = infer_value("true", &kv_ctx(), 1).unwrap();
1259 assert!(matches!(v, Value::Bool(true)));
1260 }
1261 }
1262
1263 #[test]
1266 fn test_inference_result_structure() {
1267 let result = infer_value_synthesize("42", &kv_ctx(), 1).unwrap();
1268 assert!(matches!(result.value, Value::Int(42)));
1269 assert_eq!(result.inferred_type, ExpectedType::Int);
1270 assert_eq!(result.confidence, InferenceConfidence::Certain);
1271 }
1272
1273 #[test]
1274 fn test_synthesize_int() {
1275 let result = infer_value_synthesize("42", &kv_ctx(), 1).unwrap();
1276 assert!(matches!(result.value, Value::Int(42)));
1277 assert_eq!(result.inferred_type, ExpectedType::Int);
1278 assert_eq!(result.confidence, InferenceConfidence::Certain);
1279 }
1280
1281 #[test]
1282 fn test_synthesize_float() {
1283 let result = infer_value_synthesize("3.25", &kv_ctx(), 1).unwrap();
1284 assert!(matches!(result.value, Value::Float(f) if (f - 3.25).abs() < 0.001));
1285 assert_eq!(result.inferred_type, ExpectedType::Float);
1286 assert_eq!(result.confidence, InferenceConfidence::Certain);
1287 }
1288
1289 #[test]
1290 fn test_synthesize_bool() {
1291 let result = infer_value_synthesize("true", &kv_ctx(), 1).unwrap();
1292 assert!(matches!(result.value, Value::Bool(true)));
1293 assert_eq!(result.inferred_type, ExpectedType::Bool);
1294 assert_eq!(result.confidence, InferenceConfidence::Certain);
1295 }
1296
1297 #[test]
1298 fn test_synthesize_string() {
1299 let result = infer_value_synthesize("hello", &kv_ctx(), 1).unwrap();
1300 assert!(matches!(result.value, Value::String(s) if s.as_ref() == "hello"));
1301 assert_eq!(result.inferred_type, ExpectedType::String);
1302 assert_eq!(result.confidence, InferenceConfidence::Certain);
1303 }
1304
1305 #[test]
1306 fn test_synthesize_null() {
1307 let result = infer_value_synthesize("~", &kv_ctx(), 1).unwrap();
1308 assert!(matches!(result.value, Value::Null));
1309 assert_eq!(result.inferred_type, ExpectedType::Null);
1310 assert_eq!(result.confidence, InferenceConfidence::Certain);
1311 }
1312
1313 #[test]
1314 fn test_checking_exact_match() {
1315 let result = infer_value_with_type("42", &ExpectedType::Int, &kv_ctx(), 1).unwrap();
1316 assert!(matches!(result.value, Value::Int(42)));
1317 assert_eq!(result.inferred_type, ExpectedType::Int);
1318 assert_eq!(result.confidence, InferenceConfidence::Certain);
1319 }
1320
1321 #[test]
1322 fn test_checking_int_to_float_coercion() {
1323 let result = infer_value_with_type("42", &ExpectedType::Float, &kv_ctx(), 1).unwrap();
1324 assert!(matches!(result.value, Value::Float(f) if (f - 42.0).abs() < 0.001));
1325 assert_eq!(result.inferred_type, ExpectedType::Float);
1326 assert_eq!(result.confidence, InferenceConfidence::Probable);
1327 }
1328
1329 #[test]
1330 fn test_checking_string_to_int_lenient() {
1331 let ctx = kv_ctx();
1332 let result = infer_value_with_type("42", &ExpectedType::Int, &ctx, 1).unwrap();
1333 assert!(matches!(result.value, Value::Int(42)));
1334 assert_eq!(result.confidence, InferenceConfidence::Certain);
1335 }
1336
1337 #[test]
1338 fn test_checking_with_strict_types() {
1339 let ctx = kv_ctx().with_strict_types(true);
1340 let result = infer_value_with_type("42", &ExpectedType::Float, &ctx, 1).unwrap();
1342 assert!(matches!(result.value, Value::Float(_)));
1343 }
1344
1345 #[test]
1346 fn test_checking_type_mismatch_error() {
1347 let ctx = kv_ctx().with_strict_types(true);
1348 let result = infer_value_with_type("true", &ExpectedType::Int, &ctx, 1);
1350 assert!(result.is_err());
1351 assert!(result.unwrap_err().message.contains("type mismatch"));
1352 }
1353
1354 #[test]
1355 fn test_checking_with_error_recovery() {
1356 let ctx = kv_ctx().with_strict_types(true).with_error_recovery(true);
1357 let result = infer_value_with_type("true", &ExpectedType::Int, &ctx, 1).unwrap();
1359 assert!(matches!(result.value, Value::Bool(true)));
1360 assert_eq!(result.confidence, InferenceConfidence::Ambiguous);
1361 }
1362
1363 #[test]
1364 fn test_checking_numeric_accepts_int() {
1365 let result = infer_value_with_type("42", &ExpectedType::Numeric, &kv_ctx(), 1).unwrap();
1366 assert!(matches!(result.value, Value::Int(42)));
1367 assert_eq!(result.confidence, InferenceConfidence::Certain);
1368 }
1369
1370 #[test]
1371 fn test_checking_numeric_accepts_float() {
1372 let result = infer_value_with_type("3.5", &ExpectedType::Numeric, &kv_ctx(), 1).unwrap();
1373 assert!(matches!(result.value, Value::Float(f) if (f - 3.5).abs() < 0.001));
1374 assert_eq!(result.confidence, InferenceConfidence::Certain);
1375 }
1376
1377 #[test]
1378 fn test_checking_any_accepts_all() {
1379 let result = infer_value_with_type("42", &ExpectedType::Any, &kv_ctx(), 1).unwrap();
1380 assert!(matches!(result.value, Value::Int(42)));
1381 assert_eq!(result.confidence, InferenceConfidence::Certain);
1382
1383 let result = infer_value_with_type("hello", &ExpectedType::Any, &kv_ctx(), 1).unwrap();
1384 assert!(matches!(result.value, Value::String(_)));
1385 assert_eq!(result.confidence, InferenceConfidence::Certain);
1386 }
1387
1388 #[test]
1389 fn test_checking_union_type() {
1390 use crate::types::ExpectedType;
1391 let union = ExpectedType::Union(vec![ExpectedType::Int, ExpectedType::String]);
1392 let result = infer_value_with_type("42", &union, &kv_ctx(), 1).unwrap();
1393 assert!(matches!(result.value, Value::Int(42)));
1394 assert_eq!(result.confidence, InferenceConfidence::Certain);
1395 }
1396
1397 #[test]
1398 fn test_checking_reference_qualified() {
1399 let expected = ExpectedType::Reference {
1400 target_type: Some("User".to_string()),
1401 };
1402 let result = infer_value_with_type("@User:user_1", &expected, &kv_ctx(), 1).unwrap();
1403 match result.value {
1404 Value::Reference(r) => {
1405 assert_eq!(r.type_name.as_deref(), Some("User"));
1406 assert_eq!(r.id.as_ref(), "user_1");
1407 }
1408 _ => panic!("Expected reference"),
1409 }
1410 assert_eq!(result.confidence, InferenceConfidence::Certain);
1411 }
1412
1413 #[test]
1414 fn test_context_with_expected_type() {
1415 let ctx = kv_ctx().with_expected_type(ExpectedType::Float);
1416 assert_eq!(ctx.expected_type, Some(ExpectedType::Float));
1417 }
1418
1419 #[test]
1420 fn test_context_with_column_types() {
1421 let types = vec![ExpectedType::String, ExpectedType::Int, ExpectedType::Float];
1422 let ctx = kv_ctx().with_column_types(&types);
1423 assert!(ctx.column_types.is_some());
1424 assert_eq!(ctx.column_types.unwrap().len(), 3);
1425 }
1426
1427 #[test]
1428 fn test_context_builder_pattern() {
1429 let types = vec![ExpectedType::Int];
1430 let ctx = kv_ctx()
1431 .with_expected_type(ExpectedType::Float)
1432 .with_column_types(&types)
1433 .with_strict_types(true)
1434 .with_error_recovery(true);
1435
1436 assert_eq!(ctx.expected_type, Some(ExpectedType::Float));
1437 assert!(ctx.column_types.is_some());
1438 assert!(ctx.strict_types);
1439 assert!(ctx.error_recovery);
1440 }
1441
1442 #[test]
1443 fn test_inference_confidence_levels() {
1444 let result = infer_value_with_type("42", &ExpectedType::Int, &kv_ctx(), 1).unwrap();
1446 assert_eq!(result.confidence, InferenceConfidence::Certain);
1447
1448 let result = infer_value_with_type("42", &ExpectedType::Float, &kv_ctx(), 1).unwrap();
1450 assert_eq!(result.confidence, InferenceConfidence::Probable);
1451
1452 let ctx = kv_ctx().with_strict_types(true).with_error_recovery(true);
1454 let result = infer_value_with_type("true", &ExpectedType::Int, &ctx, 1).unwrap();
1455 assert_eq!(result.confidence, InferenceConfidence::Ambiguous);
1456 }
1457
1458 #[test]
1459 fn test_synthesize_with_aliases() {
1460 let mut aliases = BTreeMap::new();
1461 aliases.insert("count".to_string(), "42".to_string());
1462 let ctx = ctx_with_aliases(&aliases);
1463 let result = infer_value_synthesize("%count", &ctx, 1).unwrap();
1464 assert!(matches!(result.value, Value::Int(42)));
1465 assert_eq!(result.inferred_type, ExpectedType::Int);
1466 }
1467
1468 #[test]
1469 fn test_checking_with_ditto() {
1470 let aliases = BTreeMap::new();
1471 let prev_row = vec![Value::String("id".to_string().into()), Value::Int(42)];
1472 let ctx = InferenceContext::for_matrix_cell(&aliases, 1, Some(&prev_row), "User");
1473 let result = infer_value_synthesize("^", &ctx, 1).unwrap();
1474 assert!(matches!(result.value, Value::Int(42)));
1475 }
1476
1477 #[test]
1478 fn test_checking_preserves_expression() {
1479 let result =
1480 infer_value_with_type("$(now())", &ExpectedType::Expression, &kv_ctx(), 1).unwrap();
1481 assert!(matches!(result.value, Value::Expression(_)));
1482 assert_eq!(result.inferred_type, ExpectedType::Expression);
1483 }
1484
1485 #[test]
1486 fn test_checking_preserves_tensor() {
1487 let expected = ExpectedType::Tensor {
1488 shape: None,
1489 dtype: None,
1490 };
1491 let result = infer_value_with_type("[1, 2, 3]", &expected, &kv_ctx(), 1).unwrap();
1492 assert!(matches!(result.value, Value::Tensor(_)));
1493 }
1494
1495 #[test]
1498 fn test_infer_empty_string() {
1499 let v = infer_value("", &kv_ctx(), 1).unwrap();
1500 assert!(matches!(v, Value::String(s) if s.is_empty()));
1501 }
1502
1503 #[test]
1504 fn test_infer_whitespace_only() {
1505 let v = infer_value(" ", &kv_ctx(), 1).unwrap();
1506 assert!(matches!(v, Value::String(s) if s.is_empty()));
1507 }
1508
1509 #[test]
1510 fn test_infer_mixed_content() {
1511 assert!(matches!(
1513 infer_value("true123", &kv_ctx(), 1).unwrap(),
1514 Value::String(_)
1515 ));
1516 assert!(matches!(
1517 infer_value("42abc", &kv_ctx(), 1).unwrap(),
1518 Value::String(_)
1519 ));
1520 assert!(matches!(
1521 infer_value("@invalid id", &kv_ctx(), 1).unwrap_err(),
1522 _
1523 ));
1524 }
1525}