1mod conversions;
33mod lookup;
34
35use crate::error::{HedlError, HedlResult};
36use crate::lex::{
37 is_tensor_literal, is_valid_id_token, parse_expression_token, parse_reference, parse_tensor,
38};
39use crate::types::{value_to_expected_type, ExpectedType};
40use crate::value::{Reference, Value};
41use std::collections::{BTreeMap, HashMap};
42
43use conversions::convert_lex_value_to_value;
44use lookup::{infer_expanded_alias, try_lookup_common, try_parse_number};
45
46pub use lookup::infer_quoted_value;
48
49pub struct InferenceContext<'a> {
53 pub aliases: &'a BTreeMap<String, String>,
55 alias_cache: HashMap<String, Value>,
58 pub is_matrix_cell: bool,
60 pub is_id_column: bool,
62 pub prev_row: Option<&'a [Value]>,
64 pub column_index: usize,
66 pub current_type: Option<&'a str>,
68 pub version: (u32, u32),
70 pub null_char: char,
72
73 pub expected_type: Option<ExpectedType>,
76 pub column_types: Option<&'a [ExpectedType]>,
78 pub strict_types: bool,
80 pub error_recovery: bool,
82}
83
84impl<'a> InferenceContext<'a> {
85 pub fn for_key_value(aliases: &'a BTreeMap<String, String>) -> Self {
87 Self {
88 aliases,
89 alias_cache: Self::build_alias_cache(aliases),
90 is_matrix_cell: false,
91 is_id_column: false,
92 prev_row: None,
93 column_index: 0,
94 current_type: None,
95 version: (1, 0), null_char: '~', expected_type: None,
98 column_types: None,
99 strict_types: false,
100 error_recovery: false,
101 }
102 }
103
104 pub fn for_matrix_cell(
106 aliases: &'a BTreeMap<String, String>,
107 column_index: usize,
108 prev_row: Option<&'a [Value]>,
109 current_type: &'a str,
110 ) -> Self {
111 Self {
112 aliases,
113 alias_cache: Self::build_alias_cache(aliases),
114 is_matrix_cell: true,
115 is_id_column: column_index == 0,
116 prev_row,
117 column_index,
118 current_type: Some(current_type),
119 version: (1, 0), null_char: '~', expected_type: None,
122 column_types: None,
123 strict_types: false,
124 error_recovery: false,
125 }
126 }
127
128 pub fn with_version(mut self, version: (u32, u32)) -> Self {
130 self.version = version;
131 self
132 }
133
134 pub fn with_expected_type(mut self, expected: ExpectedType) -> Self {
136 self.expected_type = Some(expected);
137 self
138 }
139
140 pub fn with_column_types(mut self, types: &'a [ExpectedType]) -> Self {
142 self.column_types = Some(types);
143 self
144 }
145
146 pub fn with_strict_types(mut self, strict: bool) -> Self {
148 self.strict_types = strict;
149 self
150 }
151
152 pub fn with_error_recovery(mut self, recovery: bool) -> Self {
154 self.error_recovery = recovery;
155 self
156 }
157
158 pub fn with_null_char(mut self, null_char: char) -> Self {
160 self.null_char = null_char;
161 self
162 }
163
164 fn build_alias_cache(aliases: &BTreeMap<String, String>) -> HashMap<String, Value> {
167 let mut cache = HashMap::with_capacity(aliases.len());
168 for (key, expanded) in aliases {
169 if let Ok(value) = infer_expanded_alias(expanded, 0) {
171 cache.insert(key.clone(), value);
172 }
173 }
175 cache
176 }
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum InferenceConfidence {
184 Certain,
186 Probable,
188 Ambiguous,
190}
191
192#[derive(Debug, Clone, PartialEq)]
212pub struct InferenceResult {
213 pub value: Value,
215 pub inferred_type: ExpectedType,
217 pub confidence: InferenceConfidence,
219}
220
221pub fn infer_value_with_type(
251 input: &str,
252 expected: &ExpectedType,
253 ctx: &InferenceContext<'_>,
254 line_num: usize,
255) -> HedlResult<InferenceResult> {
256 use crate::coercion::{coerce, CoercionMode};
257
258 let value = infer_value(input, ctx, line_num)?;
260 let inferred_type = value_to_expected_type(&value);
261
262 if expected.matches(&value) {
264 return Ok(InferenceResult {
265 value,
266 inferred_type,
267 confidence: InferenceConfidence::Certain,
268 });
269 }
270
271 let mode = if ctx.strict_types {
273 CoercionMode::Strict
274 } else {
275 CoercionMode::Lenient
276 };
277
278 match coerce(value.clone(), expected, mode) {
279 crate::coercion::CoercionResult::Matched(v) => Ok(InferenceResult {
280 value: v,
281 inferred_type: expected.clone(),
282 confidence: InferenceConfidence::Certain,
283 }),
284 crate::coercion::CoercionResult::Coerced(v) => Ok(InferenceResult {
285 value: v,
286 inferred_type: expected.clone(),
287 confidence: InferenceConfidence::Probable,
288 }),
289 crate::coercion::CoercionResult::Failed { reason, .. } => {
290 if ctx.error_recovery {
291 Ok(InferenceResult {
293 value,
294 inferred_type,
295 confidence: InferenceConfidence::Ambiguous,
296 })
297 } else {
298 Err(HedlError::type_mismatch(
300 format!(
301 "type mismatch: expected {}, got {} ({})",
302 expected.describe(),
303 crate::types::describe_value_type(&value),
304 reason
305 ),
306 line_num,
307 ))
308 }
309 }
310 }
311}
312
313pub fn infer_value_synthesize(
340 input: &str,
341 ctx: &InferenceContext<'_>,
342 line_num: usize,
343) -> HedlResult<InferenceResult> {
344 let value = infer_value(input, ctx, line_num)?;
345 let inferred_type = value_to_expected_type(&value);
346
347 Ok(InferenceResult {
348 value,
349 inferred_type,
350 confidence: InferenceConfidence::Certain,
351 })
352}
353
354pub fn infer_value(s: &str, ctx: &InferenceContext<'_>, line_num: usize) -> HedlResult<Value> {
373 let trimmed = s.trim();
374
375 if trimmed.len() == 1 && trimmed.starts_with(ctx.null_char) {
378 if ctx.is_id_column {
379 return Err(HedlError::semantic(
380 format!("null ({}) not permitted in ID column", ctx.null_char),
381 line_num,
382 ));
383 }
384 return Ok(Value::Null);
385 }
386
387 if let Some(value) = try_lookup_common(trimmed) {
391 return Ok(value);
392 }
393
394 let bytes = trimmed.as_bytes();
395
396 match bytes.first() {
398 Some(b'^') if bytes.len() == 1 => {
400 return infer_ditto(ctx, line_num);
401 }
402
403 Some(b'(') => {
405 use crate::lex::parse_list_literal;
406 match parse_list_literal(trimmed, 0) {
407 Ok((lex_value, _consumed)) => {
408 return convert_lex_value_to_value(lex_value);
410 }
411 Err(e) => {
412 return Err(HedlError::syntax(
413 format!("invalid list literal: {}", e),
414 line_num,
415 ));
416 }
417 }
418 }
419
420 Some(b'[') => {
422 if is_tensor_literal(trimmed) {
423 match parse_tensor(trimmed) {
424 Ok(tensor) => return Ok(Value::Tensor(Box::new(tensor))),
425 Err(e) => {
426 return Err(HedlError::syntax(
427 format!("invalid tensor literal: {}", e),
428 line_num,
429 ));
430 }
431 }
432 }
433 }
435
436 Some(b'@') => match parse_reference(trimmed) {
438 Ok(r) => {
439 return Ok(Value::Reference(Reference {
440 type_name: r.type_name.map(|t| t.into_boxed_str()),
441 id: r.id.into_boxed_str(),
442 }));
443 }
444 Err(e) => {
445 return Err(HedlError::syntax(
446 format!("invalid reference: {}", e),
447 line_num,
448 ));
449 }
450 },
451
452 Some(b'$') if bytes.get(1) == Some(&b'(') => match parse_expression_token(trimmed) {
454 Ok(expr) => return Ok(Value::Expression(Box::new(expr))),
455 Err(e) => {
456 return Err(HedlError::syntax(
457 format!("invalid expression: {}", e),
458 line_num,
459 ));
460 }
461 },
462
463 Some(b'%') => {
466 let key = &trimmed[1..];
467 if let Some(value) = ctx.alias_cache.get(key) {
468 return Ok(value.clone());
469 }
470 if ctx.aliases.contains_key(key) {
472 let expanded = &ctx.aliases[key];
474 return infer_expanded_alias(expanded, line_num);
475 }
476 return Err(HedlError::alias(
477 format!("undefined alias: %{}", key),
478 line_num,
479 ));
480 }
481
482 Some(b'-') | Some(b'0'..=b'9') => {
485 if let Some(value) = try_parse_number(trimmed) {
486 return Ok(value);
487 }
488 }
490
491 _ => {}
492 }
493
494 if ctx.is_id_column && !is_valid_id_token(trimmed) {
497 return Err(HedlError::semantic(
498 format!(
499 "invalid ID format '{}' - must start with letter or underscore",
500 trimmed
501 ),
502 line_num,
503 ));
504 }
505
506 Ok(Value::String(Box::from(trimmed)))
508}
509
510#[inline]
512fn infer_ditto(ctx: &InferenceContext<'_>, line_num: usize) -> HedlResult<Value> {
513 if !ctx.is_matrix_cell {
514 return Ok(Value::String("^".into()));
516 }
517
518 if ctx.version >= (2, 0) {
520 use crate::errors::messages;
521 return Err(messages::v20_ditto_not_allowed(line_num));
522 }
523
524 if ctx.is_id_column {
525 return Err(HedlError::semantic(
526 "ditto (^) not permitted in ID column",
527 line_num,
528 ));
529 }
530
531 match ctx.prev_row {
532 Some(prev) if ctx.column_index < prev.len() => Ok(prev[ctx.column_index].clone()),
533 Some(_) => Err(HedlError::semantic(
534 "ditto (^) column index out of range",
535 line_num,
536 )),
537 None => Err(HedlError::semantic(
538 "ditto (^) not allowed in first row of list",
539 line_num,
540 )),
541 }
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547
548 fn kv_ctx() -> InferenceContext<'static> {
549 static EMPTY: BTreeMap<String, String> = BTreeMap::new();
550 InferenceContext::for_key_value(&EMPTY)
551 }
552
553 fn ctx_with_aliases(aliases: &BTreeMap<String, String>) -> InferenceContext<'_> {
554 InferenceContext::for_key_value(aliases)
555 }
556
557 #[test]
560 fn test_infer_null() {
561 let v = infer_value("~", &kv_ctx(), 1).unwrap();
562 assert!(matches!(v, Value::Null));
563 }
564
565 #[test]
566 fn test_infer_null_with_whitespace() {
567 let v = infer_value(" ~ ", &kv_ctx(), 1).unwrap();
568 assert!(matches!(v, Value::Null));
569 }
570
571 #[test]
572 fn test_infer_tilde_as_part_of_string() {
573 let v = infer_value("~hello", &kv_ctx(), 1).unwrap();
574 assert!(matches!(v, Value::String(s) if s.as_ref() == "~hello"));
575 }
576
577 #[test]
578 fn test_null_in_id_column_error() {
579 let aliases = BTreeMap::new();
580 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, None, "User");
581 let result = infer_value("~", &ctx, 1);
582 assert!(result.is_err());
583 assert!(result.unwrap_err().message.contains("ID column"));
584 }
585
586 #[test]
589 fn test_infer_bool() {
590 assert!(matches!(
591 infer_value("true", &kv_ctx(), 1).unwrap(),
592 Value::Bool(true)
593 ));
594 assert!(matches!(
595 infer_value("false", &kv_ctx(), 1).unwrap(),
596 Value::Bool(false)
597 ));
598 }
599
600 #[test]
601 fn test_infer_bool_case_sensitive() {
602 assert!(matches!(
604 infer_value("True", &kv_ctx(), 1).unwrap(),
605 Value::String(_)
606 ));
607 assert!(matches!(
608 infer_value("FALSE", &kv_ctx(), 1).unwrap(),
609 Value::String(_)
610 ));
611 }
612
613 #[test]
614 fn test_infer_bool_with_whitespace() {
615 assert!(matches!(
616 infer_value(" true ", &kv_ctx(), 1).unwrap(),
617 Value::Bool(true)
618 ));
619 }
620
621 #[test]
624 fn test_infer_int() {
625 assert!(matches!(
626 infer_value("42", &kv_ctx(), 1).unwrap(),
627 Value::Int(42)
628 ));
629 assert!(matches!(
630 infer_value("-5", &kv_ctx(), 1).unwrap(),
631 Value::Int(-5)
632 ));
633 assert!(matches!(
634 infer_value("0", &kv_ctx(), 1).unwrap(),
635 Value::Int(0)
636 ));
637 }
638
639 #[test]
640 fn test_infer_int_large() {
641 let v = infer_value("9223372036854775807", &kv_ctx(), 1).unwrap();
642 assert!(matches!(v, Value::Int(i64::MAX)));
643 }
644
645 #[test]
646 fn test_infer_int_negative_large() {
647 let v = infer_value("-9223372036854775808", &kv_ctx(), 1).unwrap();
648 assert!(matches!(v, Value::Int(i64::MIN)));
649 }
650
651 #[test]
652 fn test_infer_int_with_whitespace() {
653 assert!(matches!(
654 infer_value(" 123 ", &kv_ctx(), 1).unwrap(),
655 Value::Int(123)
656 ));
657 }
658
659 #[test]
662 fn test_infer_float() {
663 match infer_value("3.25", &kv_ctx(), 1).unwrap() {
664 Value::Float(f) => assert!((f - 3.25).abs() < 0.001),
665 _ => panic!("expected float"),
666 }
667 match infer_value("42.0", &kv_ctx(), 1).unwrap() {
668 Value::Float(f) => assert!((f - 42.0).abs() < 0.001),
669 _ => panic!("expected float"),
670 }
671 }
672
673 #[test]
674 fn test_infer_float_negative() {
675 match infer_value("-3.5", &kv_ctx(), 1).unwrap() {
676 Value::Float(f) => assert!((f + 3.5).abs() < 0.001),
677 _ => panic!("expected float"),
678 }
679 }
680
681 #[test]
682 fn test_infer_float_small() {
683 match infer_value("0.001", &kv_ctx(), 1).unwrap() {
684 Value::Float(f) => assert!((f - 0.001).abs() < 0.0001),
685 _ => panic!("expected float"),
686 }
687 }
688
689 #[test]
692 fn test_infer_string() {
693 assert!(matches!(
694 infer_value("hello", &kv_ctx(), 1).unwrap(),
695 Value::String(s) if s.as_ref() == "hello"
696 ));
697 }
698
699 #[test]
700 fn test_infer_string_with_spaces() {
701 assert!(matches!(
703 infer_value(" hello ", &kv_ctx(), 1).unwrap(),
704 Value::String(s) if s.as_ref() == "hello"
705 ));
706 }
707
708 #[test]
709 fn test_infer_string_unicode() {
710 assert!(matches!(
711 infer_value("日本語", &kv_ctx(), 1).unwrap(),
712 Value::String(s) if s.as_ref() == "日本語"
713 ));
714 }
715
716 #[test]
717 fn test_infer_string_emoji() {
718 assert!(matches!(
719 infer_value("🎉", &kv_ctx(), 1).unwrap(),
720 Value::String(s) if s.as_ref() == "🎉"
721 ));
722 }
723
724 #[test]
727 fn test_infer_reference() {
728 let v = infer_value("@user_1", &kv_ctx(), 1).unwrap();
729 match v {
730 Value::Reference(r) => {
731 assert_eq!(r.type_name, None);
732 assert_eq!(r.id.as_ref(), "user_1");
733 }
734 _ => panic!("expected reference"),
735 }
736 }
737
738 #[test]
739 fn test_infer_qualified_reference() {
740 let v = infer_value("@User:user_1", &kv_ctx(), 1).unwrap();
741 match v {
742 Value::Reference(r) => {
743 assert_eq!(r.type_name.as_deref(), Some("User"));
744 assert_eq!(r.id.as_ref(), "user_1");
745 }
746 _ => panic!("expected reference"),
747 }
748 }
749
750 #[test]
751 fn test_infer_reference_with_whitespace() {
752 let v = infer_value(" @user_1 ", &kv_ctx(), 1).unwrap();
753 assert!(matches!(v, Value::Reference(_)));
754 }
755
756 #[test]
757 fn test_infer_reference_invalid_error() {
758 let result = infer_value("@User:123-invalid", &kv_ctx(), 1);
760 assert!(result.is_err());
761 assert!(result.unwrap_err().message.contains("invalid reference"));
762 }
763
764 #[test]
765 fn test_infer_reference_uppercase_valid() {
766 let v = infer_value("@User:ABC123", &kv_ctx(), 1).unwrap();
768 match v {
769 Value::Reference(r) => {
770 assert_eq!(r.type_name.as_deref(), Some("User"));
771 assert_eq!(r.id.as_ref(), "ABC123");
772 }
773 _ => panic!("Expected reference"),
774 }
775 }
776
777 #[test]
780 fn test_infer_expression() {
781 use crate::lex::Expression;
782 let v = infer_value("$(now())", &kv_ctx(), 1).unwrap();
783 match v {
784 Value::Expression(e) => {
785 assert!(
786 matches!(e.as_ref(), Expression::Call { name, args, .. } if name == "now" && args.is_empty())
787 );
788 }
789 _ => panic!("expected expression"),
790 }
791 }
792
793 #[test]
794 fn test_infer_expression_with_args() {
795 let v = infer_value("$(add(1, 2))", &kv_ctx(), 1).unwrap();
796 assert!(matches!(v, Value::Expression(_)));
797 }
798
799 #[test]
800 fn test_infer_expression_nested() {
801 let v = infer_value("$(outer(inner()))", &kv_ctx(), 1).unwrap();
802 assert!(matches!(v, Value::Expression(_)));
803 }
804
805 #[test]
806 fn test_infer_expression_identifier() {
807 let v = infer_value("$(x)", &kv_ctx(), 1).unwrap();
808 assert!(matches!(v, Value::Expression(_)));
809 }
810
811 #[test]
812 fn test_infer_expression_invalid_error() {
813 let result = infer_value("$(unclosed", &kv_ctx(), 1);
814 assert!(result.is_err());
815 }
816
817 #[test]
818 fn test_dollar_not_expression() {
819 let v = infer_value("$foo", &kv_ctx(), 1).unwrap();
821 assert!(matches!(v, Value::String(s) if s.as_ref() == "$foo"));
822 }
823
824 #[test]
827 fn test_infer_tensor() {
828 let v = infer_value("[1, 2, 3]", &kv_ctx(), 1).unwrap();
829 assert!(matches!(v, Value::Tensor(_)));
830 }
831
832 #[test]
833 fn test_infer_tensor_float() {
834 let v = infer_value("[1.5, 2.5, 3.5]", &kv_ctx(), 1).unwrap();
835 assert!(matches!(v, Value::Tensor(_)));
836 }
837
838 #[test]
839 fn test_infer_tensor_nested() {
840 let v = infer_value("[[1, 2], [3, 4]]", &kv_ctx(), 1).unwrap();
841 assert!(matches!(v, Value::Tensor(_)));
842 }
843
844 #[test]
845 fn test_infer_tensor_empty_error() {
846 let result = infer_value("[]", &kv_ctx(), 1);
848 assert!(result.is_err());
849 assert!(result.unwrap_err().message.contains("empty tensor"));
850 }
851
852 #[test]
853 fn test_infer_tensor_invalid_is_string() {
854 let v = infer_value("[not a tensor]", &kv_ctx(), 1).unwrap();
856 assert!(matches!(v, Value::String(_)));
857 }
858
859 #[test]
862 fn test_infer_alias_bool() {
863 let mut aliases = BTreeMap::new();
864 aliases.insert("active".to_string(), "true".to_string());
865 let ctx = ctx_with_aliases(&aliases);
866 let v = infer_value("%active", &ctx, 1).unwrap();
867 assert!(matches!(v, Value::Bool(true)));
868 }
869
870 #[test]
871 fn test_infer_alias_number() {
872 let mut aliases = BTreeMap::new();
873 aliases.insert("count".to_string(), "42".to_string());
874 let ctx = ctx_with_aliases(&aliases);
875 let v = infer_value("%count", &ctx, 1).unwrap();
876 assert!(matches!(v, Value::Int(42)));
877 }
878
879 #[test]
880 fn test_infer_alias_string() {
881 let mut aliases = BTreeMap::new();
882 aliases.insert("name".to_string(), "Alice".to_string());
883 let ctx = ctx_with_aliases(&aliases);
884 let v = infer_value("%name", &ctx, 1).unwrap();
885 assert!(matches!(v, Value::String(s) if s.as_ref() == "Alice"));
886 }
887
888 #[test]
889 fn test_infer_undefined_alias_error() {
890 let result = infer_value("%undefined", &kv_ctx(), 1);
891 assert!(result.is_err());
892 assert!(result.unwrap_err().message.contains("undefined alias"));
893 }
894
895 #[test]
898 fn test_ditto_in_kv_is_string() {
899 let v = infer_value("^", &kv_ctx(), 1).unwrap();
900 assert!(matches!(v, Value::String(s) if s.as_ref() == "^"));
901 }
902
903 #[test]
904 fn test_ditto_in_matrix_cell() {
905 let aliases = BTreeMap::new();
906 let prev_row = vec![Value::String("id".to_string().into()), Value::Int(42)];
907 let ctx = InferenceContext::for_matrix_cell(&aliases, 1, Some(&prev_row), "User");
908 let v = infer_value("^", &ctx, 1).unwrap();
909 assert!(matches!(v, Value::Int(42)));
910 }
911
912 #[test]
913 fn test_ditto_in_id_column_error() {
914 let aliases = BTreeMap::new();
915 let prev_row = vec![Value::String("id".to_string().into())];
916 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, Some(&prev_row), "User");
917 let result = infer_value("^", &ctx, 1);
918 assert!(result.is_err());
919 assert!(result.unwrap_err().message.contains("ID column"));
920 }
921
922 #[test]
923 fn test_ditto_first_row_error() {
924 let aliases = BTreeMap::new();
925 let ctx = InferenceContext::for_matrix_cell(&aliases, 1, None, "User");
926 let result = infer_value("^", &ctx, 1);
927 assert!(result.is_err());
928 assert!(result.unwrap_err().message.contains("first row"));
929 }
930
931 #[test]
932 fn test_ditto_column_out_of_range_error() {
933 let aliases = BTreeMap::new();
934 let prev_row = vec![Value::String("id".to_string().into())];
935 let ctx = InferenceContext::for_matrix_cell(&aliases, 5, Some(&prev_row), "User");
936 let result = infer_value("^", &ctx, 1);
937 assert!(result.is_err());
938 assert!(result.unwrap_err().message.contains("out of range"));
939 }
940
941 #[test]
944 fn test_number_edge_cases() {
945 assert!(matches!(
947 infer_value("1e10", &kv_ctx(), 1).unwrap(),
948 Value::String(_)
949 ));
950 assert!(matches!(
952 infer_value("1_000", &kv_ctx(), 1).unwrap(),
953 Value::String(_)
954 ));
955 assert!(matches!(
957 infer_value(".5", &kv_ctx(), 1).unwrap(),
958 Value::String(_)
959 ));
960 }
961
962 #[test]
963 fn test_number_trailing_decimal_is_string() {
964 assert!(matches!(
965 infer_value("123.", &kv_ctx(), 1).unwrap(),
966 Value::String(_)
967 ));
968 }
969
970 #[test]
971 fn test_number_plus_sign_is_string() {
972 assert!(matches!(
973 infer_value("+42", &kv_ctx(), 1).unwrap(),
974 Value::String(_)
975 ));
976 }
977
978 #[test]
979 fn test_number_leading_zeros_is_string() {
980 assert!(matches!(
982 infer_value("007", &kv_ctx(), 1).unwrap(),
983 Value::Int(7) ));
985 }
986
987 #[test]
988 fn test_number_hex_is_string() {
989 assert!(matches!(
990 infer_value("0xFF", &kv_ctx(), 1).unwrap(),
991 Value::String(_)
992 ));
993 }
994
995 #[test]
998 fn test_try_parse_number_empty() {
999 assert!(try_parse_number("").is_none());
1000 }
1001
1002 #[test]
1003 fn test_try_parse_number_whitespace() {
1004 assert!(try_parse_number(" ").is_none());
1005 }
1006
1007 #[test]
1008 fn test_try_parse_number_valid_int() {
1009 assert!(matches!(try_parse_number("123"), Some(Value::Int(123))));
1010 }
1011
1012 #[test]
1013 fn test_try_parse_number_valid_float() {
1014 match try_parse_number("3.5") {
1015 Some(Value::Float(f)) => assert!((f - 3.5).abs() < 0.001),
1016 _ => panic!("expected float"),
1017 }
1018 }
1019
1020 #[test]
1021 fn test_try_parse_number_negative() {
1022 assert!(matches!(try_parse_number("-42"), Some(Value::Int(-42))));
1023 }
1024
1025 #[test]
1026 fn test_try_parse_number_invalid() {
1027 assert!(try_parse_number("abc").is_none());
1028 assert!(try_parse_number("12abc").is_none());
1029 }
1030
1031 #[test]
1034 fn test_infer_quoted_value_simple() {
1035 let v = infer_quoted_value("hello");
1036 assert!(matches!(v, Value::String(s) if s.as_ref() == "hello"));
1037 }
1038
1039 #[test]
1040 fn test_infer_quoted_value_empty() {
1041 let v = infer_quoted_value("");
1042 assert!(matches!(v, Value::String(s) if s.is_empty()));
1043 }
1044
1045 #[test]
1046 fn test_infer_quoted_value_escaped_quotes() {
1047 let v = infer_quoted_value("say \"\"hello\"\"");
1048 assert!(matches!(v, Value::String(s) if s.as_ref() == "say \"hello\""));
1049 }
1050
1051 #[test]
1052 fn test_infer_quoted_value_multiple_escapes() {
1053 let v = infer_quoted_value("a\"\"b\"\"c");
1054 assert!(matches!(v, Value::String(s) if s.as_ref() == "a\"b\"c"));
1055 }
1056
1057 #[test]
1060 fn test_context_for_key_value() {
1061 let aliases = BTreeMap::new();
1062 let ctx = InferenceContext::for_key_value(&aliases);
1063 assert!(!ctx.is_matrix_cell);
1064 assert!(!ctx.is_id_column);
1065 assert!(ctx.prev_row.is_none());
1066 }
1067
1068 #[test]
1069 fn test_context_for_matrix_cell() {
1070 let aliases = BTreeMap::new();
1071 let ctx = InferenceContext::for_matrix_cell(&aliases, 2, None, "User");
1072 assert!(ctx.is_matrix_cell);
1073 assert!(!ctx.is_id_column); assert_eq!(ctx.column_index, 2);
1075 assert_eq!(ctx.current_type, Some("User"));
1076 }
1077
1078 #[test]
1079 fn test_context_id_column_detection() {
1080 let aliases = BTreeMap::new();
1081 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, None, "User");
1082 assert!(ctx.is_id_column); }
1084
1085 #[test]
1088 fn test_id_column_valid_id() {
1089 let aliases = BTreeMap::new();
1090 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, None, "User");
1091 let v = infer_value("user_123", &ctx, 1).unwrap();
1092 assert!(matches!(v, Value::String(s) if s.as_ref() == "user_123"));
1093 }
1094
1095 #[test]
1096 fn test_id_column_invalid_starts_digit_error() {
1097 let aliases = BTreeMap::new();
1099 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, None, "User");
1100 let result = infer_value("123User", &ctx, 1);
1101 assert!(result.is_err());
1102 assert!(result.unwrap_err().message.contains("invalid ID"));
1103 }
1104
1105 #[test]
1106 fn test_id_column_uppercase_valid() {
1107 let aliases = BTreeMap::new();
1109 let ctx = InferenceContext::for_matrix_cell(&aliases, 0, None, "User");
1110 let result = infer_value("SKU-4020", &ctx, 1);
1111 assert!(result.is_ok());
1112 }
1113
1114 #[test]
1117 fn test_lookup_table_bool_true() {
1118 let v = infer_value("true", &kv_ctx(), 1).unwrap();
1120 assert!(matches!(v, Value::Bool(true)));
1121 }
1122
1123 #[test]
1124 fn test_lookup_table_bool_false() {
1125 let v = infer_value("false", &kv_ctx(), 1).unwrap();
1127 assert!(matches!(v, Value::Bool(false)));
1128 }
1129
1130 #[test]
1131 fn test_lookup_table_null() {
1132 let v = infer_value("~", &kv_ctx(), 1).unwrap();
1134 assert!(matches!(v, Value::Null));
1135 }
1136
1137 #[test]
1138 fn test_lookup_table_collision_detection() {
1139 let v = infer_value("True", &kv_ctx(), 1).unwrap();
1142 assert!(matches!(v, Value::String(s) if s.as_ref() == "True"));
1143 }
1144
1145 #[test]
1146 fn test_lookup_table_multiple_calls() {
1147 for _ in 0..100 {
1149 let v = infer_value("true", &kv_ctx(), 1).unwrap();
1150 assert!(matches!(v, Value::Bool(true)));
1151 }
1152 }
1153
1154 #[test]
1157 fn test_inference_result_structure() {
1158 let result = infer_value_synthesize("42", &kv_ctx(), 1).unwrap();
1159 assert!(matches!(result.value, Value::Int(42)));
1160 assert_eq!(result.inferred_type, ExpectedType::Int);
1161 assert_eq!(result.confidence, InferenceConfidence::Certain);
1162 }
1163
1164 #[test]
1165 fn test_synthesize_int() {
1166 let result = infer_value_synthesize("42", &kv_ctx(), 1).unwrap();
1167 assert!(matches!(result.value, Value::Int(42)));
1168 assert_eq!(result.inferred_type, ExpectedType::Int);
1169 assert_eq!(result.confidence, InferenceConfidence::Certain);
1170 }
1171
1172 #[test]
1173 fn test_synthesize_float() {
1174 let result = infer_value_synthesize("3.25", &kv_ctx(), 1).unwrap();
1175 assert!(matches!(result.value, Value::Float(f) if (f - 3.25).abs() < 0.001));
1176 assert_eq!(result.inferred_type, ExpectedType::Float);
1177 assert_eq!(result.confidence, InferenceConfidence::Certain);
1178 }
1179
1180 #[test]
1181 fn test_synthesize_bool() {
1182 let result = infer_value_synthesize("true", &kv_ctx(), 1).unwrap();
1183 assert!(matches!(result.value, Value::Bool(true)));
1184 assert_eq!(result.inferred_type, ExpectedType::Bool);
1185 assert_eq!(result.confidence, InferenceConfidence::Certain);
1186 }
1187
1188 #[test]
1189 fn test_synthesize_string() {
1190 let result = infer_value_synthesize("hello", &kv_ctx(), 1).unwrap();
1191 assert!(matches!(result.value, Value::String(s) if s.as_ref() == "hello"));
1192 assert_eq!(result.inferred_type, ExpectedType::String);
1193 assert_eq!(result.confidence, InferenceConfidence::Certain);
1194 }
1195
1196 #[test]
1197 fn test_synthesize_null() {
1198 let result = infer_value_synthesize("~", &kv_ctx(), 1).unwrap();
1199 assert!(matches!(result.value, Value::Null));
1200 assert_eq!(result.inferred_type, ExpectedType::Null);
1201 assert_eq!(result.confidence, InferenceConfidence::Certain);
1202 }
1203
1204 #[test]
1205 fn test_checking_exact_match() {
1206 let result = infer_value_with_type("42", &ExpectedType::Int, &kv_ctx(), 1).unwrap();
1207 assert!(matches!(result.value, Value::Int(42)));
1208 assert_eq!(result.inferred_type, ExpectedType::Int);
1209 assert_eq!(result.confidence, InferenceConfidence::Certain);
1210 }
1211
1212 #[test]
1213 fn test_checking_int_to_float_coercion() {
1214 let result = infer_value_with_type("42", &ExpectedType::Float, &kv_ctx(), 1).unwrap();
1215 assert!(matches!(result.value, Value::Float(f) if (f - 42.0).abs() < 0.001));
1216 assert_eq!(result.inferred_type, ExpectedType::Float);
1217 assert_eq!(result.confidence, InferenceConfidence::Probable);
1218 }
1219
1220 #[test]
1221 fn test_checking_string_to_int_lenient() {
1222 let ctx = kv_ctx();
1223 let result = infer_value_with_type("42", &ExpectedType::Int, &ctx, 1).unwrap();
1224 assert!(matches!(result.value, Value::Int(42)));
1225 assert_eq!(result.confidence, InferenceConfidence::Certain);
1226 }
1227
1228 #[test]
1229 fn test_checking_with_strict_types() {
1230 let ctx = kv_ctx().with_strict_types(true);
1231 let result = infer_value_with_type("42", &ExpectedType::Float, &ctx, 1).unwrap();
1233 assert!(matches!(result.value, Value::Float(_)));
1234 }
1235
1236 #[test]
1237 fn test_checking_type_mismatch_error() {
1238 let ctx = kv_ctx().with_strict_types(true);
1239 let result = infer_value_with_type("true", &ExpectedType::Int, &ctx, 1);
1241 assert!(result.is_err());
1242 assert!(result.unwrap_err().message.contains("type mismatch"));
1243 }
1244
1245 #[test]
1246 fn test_checking_with_error_recovery() {
1247 let ctx = kv_ctx().with_strict_types(true).with_error_recovery(true);
1248 let result = infer_value_with_type("true", &ExpectedType::Int, &ctx, 1).unwrap();
1250 assert!(matches!(result.value, Value::Bool(true)));
1251 assert_eq!(result.confidence, InferenceConfidence::Ambiguous);
1252 }
1253
1254 #[test]
1255 fn test_checking_numeric_accepts_int() {
1256 let result = infer_value_with_type("42", &ExpectedType::Numeric, &kv_ctx(), 1).unwrap();
1257 assert!(matches!(result.value, Value::Int(42)));
1258 assert_eq!(result.confidence, InferenceConfidence::Certain);
1259 }
1260
1261 #[test]
1262 fn test_checking_numeric_accepts_float() {
1263 let result = infer_value_with_type("3.5", &ExpectedType::Numeric, &kv_ctx(), 1).unwrap();
1264 assert!(matches!(result.value, Value::Float(f) if (f - 3.5).abs() < 0.001));
1265 assert_eq!(result.confidence, InferenceConfidence::Certain);
1266 }
1267
1268 #[test]
1269 fn test_checking_any_accepts_all() {
1270 let result = infer_value_with_type("42", &ExpectedType::Any, &kv_ctx(), 1).unwrap();
1271 assert!(matches!(result.value, Value::Int(42)));
1272 assert_eq!(result.confidence, InferenceConfidence::Certain);
1273
1274 let result = infer_value_with_type("hello", &ExpectedType::Any, &kv_ctx(), 1).unwrap();
1275 assert!(matches!(result.value, Value::String(_)));
1276 assert_eq!(result.confidence, InferenceConfidence::Certain);
1277 }
1278
1279 #[test]
1280 fn test_checking_union_type() {
1281 use crate::types::ExpectedType;
1282 let union = ExpectedType::Union(vec![ExpectedType::Int, ExpectedType::String]);
1283 let result = infer_value_with_type("42", &union, &kv_ctx(), 1).unwrap();
1284 assert!(matches!(result.value, Value::Int(42)));
1285 assert_eq!(result.confidence, InferenceConfidence::Certain);
1286 }
1287
1288 #[test]
1289 fn test_checking_reference_qualified() {
1290 let expected = ExpectedType::Reference {
1291 target_type: Some("User".to_string()),
1292 };
1293 let result = infer_value_with_type("@User:user_1", &expected, &kv_ctx(), 1).unwrap();
1294 match result.value {
1295 Value::Reference(r) => {
1296 assert_eq!(r.type_name.as_deref(), Some("User"));
1297 assert_eq!(r.id.as_ref(), "user_1");
1298 }
1299 _ => panic!("Expected reference"),
1300 }
1301 assert_eq!(result.confidence, InferenceConfidence::Certain);
1302 }
1303
1304 #[test]
1305 fn test_context_with_expected_type() {
1306 let ctx = kv_ctx().with_expected_type(ExpectedType::Float);
1307 assert_eq!(ctx.expected_type, Some(ExpectedType::Float));
1308 }
1309
1310 #[test]
1311 fn test_context_with_column_types() {
1312 let types = vec![ExpectedType::String, ExpectedType::Int, ExpectedType::Float];
1313 let ctx = kv_ctx().with_column_types(&types);
1314 assert!(ctx.column_types.is_some());
1315 assert_eq!(ctx.column_types.unwrap().len(), 3);
1316 }
1317
1318 #[test]
1319 fn test_context_builder_pattern() {
1320 let types = vec![ExpectedType::Int];
1321 let ctx = kv_ctx()
1322 .with_expected_type(ExpectedType::Float)
1323 .with_column_types(&types)
1324 .with_strict_types(true)
1325 .with_error_recovery(true);
1326
1327 assert_eq!(ctx.expected_type, Some(ExpectedType::Float));
1328 assert!(ctx.column_types.is_some());
1329 assert!(ctx.strict_types);
1330 assert!(ctx.error_recovery);
1331 }
1332
1333 #[test]
1334 fn test_inference_confidence_levels() {
1335 let result = infer_value_with_type("42", &ExpectedType::Int, &kv_ctx(), 1).unwrap();
1337 assert_eq!(result.confidence, InferenceConfidence::Certain);
1338
1339 let result = infer_value_with_type("42", &ExpectedType::Float, &kv_ctx(), 1).unwrap();
1341 assert_eq!(result.confidence, InferenceConfidence::Probable);
1342
1343 let ctx = kv_ctx().with_strict_types(true).with_error_recovery(true);
1345 let result = infer_value_with_type("true", &ExpectedType::Int, &ctx, 1).unwrap();
1346 assert_eq!(result.confidence, InferenceConfidence::Ambiguous);
1347 }
1348
1349 #[test]
1350 fn test_synthesize_with_aliases() {
1351 let mut aliases = BTreeMap::new();
1352 aliases.insert("count".to_string(), "42".to_string());
1353 let ctx = ctx_with_aliases(&aliases);
1354 let result = infer_value_synthesize("%count", &ctx, 1).unwrap();
1355 assert!(matches!(result.value, Value::Int(42)));
1356 assert_eq!(result.inferred_type, ExpectedType::Int);
1357 }
1358
1359 #[test]
1360 fn test_checking_with_ditto() {
1361 let aliases = BTreeMap::new();
1362 let prev_row = vec![Value::String("id".to_string().into()), Value::Int(42)];
1363 let ctx = InferenceContext::for_matrix_cell(&aliases, 1, Some(&prev_row), "User");
1364 let result = infer_value_synthesize("^", &ctx, 1).unwrap();
1365 assert!(matches!(result.value, Value::Int(42)));
1366 }
1367
1368 #[test]
1369 fn test_checking_preserves_expression() {
1370 let result =
1371 infer_value_with_type("$(now())", &ExpectedType::Expression, &kv_ctx(), 1).unwrap();
1372 assert!(matches!(result.value, Value::Expression(_)));
1373 assert_eq!(result.inferred_type, ExpectedType::Expression);
1374 }
1375
1376 #[test]
1377 fn test_checking_preserves_tensor() {
1378 let expected = ExpectedType::Tensor {
1379 shape: None,
1380 dtype: None,
1381 };
1382 let result = infer_value_with_type("[1, 2, 3]", &expected, &kv_ctx(), 1).unwrap();
1383 assert!(matches!(result.value, Value::Tensor(_)));
1384 }
1385
1386 #[test]
1389 fn test_v20_rejects_ditto() {
1390 let aliases = BTreeMap::new();
1391 let prev_row = vec![Value::String("id".to_string().into()), Value::Int(42)];
1392 let ctx = InferenceContext::for_matrix_cell(&aliases, 1, Some(&prev_row), "User")
1393 .with_version((2, 0));
1394 let result = infer_value("^", &ctx, 1);
1395 assert!(result.is_err());
1396 let err = result.unwrap_err();
1397 assert!(err.message.contains("v2.0"));
1398 assert!(err.message.contains("not allowed"));
1399 }
1400
1401 #[test]
1402 fn test_pre_v20_allows_ditto() {
1403 let aliases = BTreeMap::new();
1404 let prev_row = vec![Value::String("id".to_string().into()), Value::Int(42)];
1405 let ctx = InferenceContext::for_matrix_cell(&aliases, 1, Some(&prev_row), "User")
1406 .with_version((1, 2));
1407 let result = infer_value("^", &ctx, 1);
1408 assert!(result.is_ok());
1409 assert!(matches!(result.unwrap(), Value::Int(42)));
1410 }
1411
1412 #[test]
1413 fn test_v10_allows_ditto() {
1414 let aliases = BTreeMap::new();
1415 let prev_row = vec![Value::String("id".to_string().into()), Value::Int(42)];
1416 let ctx = InferenceContext::for_matrix_cell(&aliases, 1, Some(&prev_row), "User")
1417 .with_version((1, 0));
1418 let result = infer_value("^", &ctx, 1);
1419 assert!(result.is_ok());
1420 assert!(matches!(result.unwrap(), Value::Int(42)));
1421 }
1422
1423 #[test]
1424 fn test_v20_context_builder() {
1425 let aliases = BTreeMap::new();
1426 let ctx = InferenceContext::for_key_value(&aliases).with_version((2, 0));
1427 assert_eq!(ctx.version, (2, 0));
1428 }
1429
1430 #[test]
1433 fn test_infer_empty_string() {
1434 let v = infer_value("", &kv_ctx(), 1).unwrap();
1435 assert!(matches!(v, Value::String(s) if s.is_empty()));
1436 }
1437
1438 #[test]
1439 fn test_infer_whitespace_only() {
1440 let v = infer_value(" ", &kv_ctx(), 1).unwrap();
1441 assert!(matches!(v, Value::String(s) if s.is_empty()));
1442 }
1443
1444 #[test]
1445 fn test_infer_mixed_content() {
1446 assert!(matches!(
1448 infer_value("true123", &kv_ctx(), 1).unwrap(),
1449 Value::String(_)
1450 ));
1451 assert!(matches!(
1452 infer_value("42abc", &kv_ctx(), 1).unwrap(),
1453 Value::String(_)
1454 ));
1455 assert!(matches!(
1456 infer_value("@invalid id", &kv_ctx(), 1).unwrap_err(),
1457 _
1458 ));
1459 }
1460}