@@ -88,9 +88,12 @@ impl<'a> StatementConverter<'a> {
8888 {
8989 Some ( ts_to_clar_type ( & boxed_type_annotation. type_annotation ) . unwrap ( ) )
9090 } else {
91- // type annotation is not always needed
92- // but this current approach probably isn't robust enough
93- None
91+ // Try to infer type from initializer expression
92+ if let Some ( init_expr) = & variable_declarator. init {
93+ self . infer_type_from_expression ( init_expr)
94+ } else {
95+ None
96+ }
9497 } ;
9598
9699 let binding_name = if let ast:: BindingPattern {
@@ -109,6 +112,45 @@ impl<'a> StatementConverter<'a> {
109112 type_annotation
110113 }
111114
115+ fn infer_type_from_expression ( & self , expr : & Expression < ' a > ) -> Option < TypeSignature > {
116+ match expr {
117+ // Handle method call chains like: counts.get(txSender).defaultTo(0)
118+ Expression :: CallExpression ( call_expr) => {
119+ if let Expression :: StaticMemberExpression ( member_expr) = & call_expr. callee {
120+ if member_expr. property . name . as_str ( ) == "defaultTo" {
121+ if let Some ( root_type) = self . find_root_data_map_type ( & member_expr. object ) {
122+ return Some ( root_type) ;
123+ }
124+ }
125+ }
126+ None
127+ }
128+ _ => None ,
129+ }
130+ }
131+
132+ fn find_root_data_map_type ( & self , expr : & Expression < ' a > ) -> Option < TypeSignature > {
133+ match expr {
134+ Expression :: StaticMemberExpression ( member_expr) => {
135+ self . find_root_data_map_type ( & member_expr. object )
136+ }
137+ Expression :: CallExpression ( call_expr) => {
138+ self . find_root_data_map_type ( & call_expr. callee )
139+ }
140+ Expression :: Identifier ( ident) => {
141+ let var_name = ident. name . as_str ( ) ;
142+ self . ir . data_maps . iter ( ) . find_map ( |data_map| {
143+ if data_map. name == var_name {
144+ Some ( data_map. value_type . clone ( ) )
145+ } else {
146+ None
147+ }
148+ } )
149+ }
150+ _ => None ,
151+ }
152+ }
153+
112154 fn get_parameter_type ( & self , param_name : & str ) -> Option < & TypeSignature > {
113155 self . function
114156 . parameters
@@ -387,6 +429,16 @@ impl<'a> Traverse<'a, ConverterState<'a>> for StatementConverter<'a> {
387429 }
388430 Expression :: StaticMemberExpression ( member_expr) => {
389431 let Expression :: Identifier ( ident) = & member_expr. object else {
432+ if member_expr. property . name . as_str ( ) == "defaultTo" {
433+ // For defaultTo, we need special handling to get correct argument order
434+ self . lists_stack
435+ . push ( PreSymbolicExpression :: list ( vec ! [ atom( "default-to" ) ] ) ) ;
436+ // Keep the current context type for proper type inference of the default value
437+ // The argument should have the same type as the optional's inner type
438+ self . current_context_type_stack
439+ . push ( self . current_context_type . clone ( ) ) ;
440+ return ;
441+ }
390442 return ;
391443 } ;
392444 let ident_name = ident. name . as_str ( ) ;
@@ -410,6 +462,27 @@ impl<'a> Traverse<'a, ConverterState<'a>> for StatementConverter<'a> {
410462 return ;
411463 }
412464
465+ // Handle data map access
466+ if let Some ( data_map) = self
467+ . ir
468+ . data_maps
469+ . iter ( )
470+ . find ( |data_map| data_map. name == ident_name)
471+ {
472+ self . current_context_type = Some ( data_map. value_type . clone ( ) ) ;
473+ let atom_name = match member_expr. property . name . as_str ( ) {
474+ "get" => "map-get?" ,
475+ "insert" => "map-insert" ,
476+ "set" => "map-set" ,
477+ "delete" => "map-delete" ,
478+ _ => return ,
479+ } ;
480+ self . lists_stack
481+ . push ( PreSymbolicExpression :: list ( vec ! [ atom( atom_name) ] ) ) ;
482+ ctx. state . ingest_call_expression = true ;
483+ return ;
484+ }
485+
413486 // Handle std namespace calls
414487 if self
415488 . ir
@@ -442,9 +515,24 @@ impl<'a> Traverse<'a, ConverterState<'a>> for StatementConverter<'a> {
442515
443516 fn exit_call_expression (
444517 & mut self ,
445- _call_expr : & mut ast:: CallExpression < ' a > ,
518+ call_expr : & mut ast:: CallExpression < ' a > ,
446519 ctx : & mut TraverseCtx < ' a > ,
447520 ) {
521+ if let Expression :: StaticMemberExpression ( member_expr) = & call_expr. callee {
522+ if member_expr. property . name . as_str ( ) == "defaultTo" {
523+ // For defaultTo, we need to reorder arguments: (default-to default_value optional_expr)
524+ if let Some ( current_list) = self . lists_stack . last_mut ( ) {
525+ if let PreSymbolicExpressionType :: List ( list) = & mut current_list. pre_expr {
526+ if list. len ( ) == 3 {
527+ list. swap ( 1 , 2 ) ;
528+ }
529+ }
530+ }
531+ self . ingest_last_stack_item ( ) ;
532+ return ;
533+ }
534+ }
535+
448536 if ctx. state . ingest_call_expression {
449537 // Don't ingest immediately if we're inside an object property
450538 // Let the object property handler take care of it
@@ -1336,4 +1424,58 @@ mod test {
13361424 let expected_clar_src = "{ list: (list (list true false)) }" ;
13371425 assert_body_eq ( ts_src, expected_clar_src) ;
13381426 }
1427+
1428+ #[ test]
1429+ fn test_data_map_get ( ) {
1430+ let ts_src = indoc ! {
1431+ r#"const counts = new DataMap<Principal, Uint>();
1432+ function getMyCount() {
1433+ const count = counts.get(txSender);
1434+ return count;
1435+ }"#
1436+ } ;
1437+ let expected_clar_src = "(let ((count (map-get? counts tx-sender))) count)" ;
1438+ assert_body_eq ( ts_src, expected_clar_src) ;
1439+ }
1440+
1441+ #[ test]
1442+ fn test_optional_default_to ( ) {
1443+ let ts_src = indoc ! {
1444+ r#"const counts = new DataMap<Principal, Uint>();
1445+ function getMyCount() {
1446+ return counts.get(txSender).defaultTo(0);
1447+ }"#
1448+ } ;
1449+ let expected_clar_src = "(default-to u0 (map-get? counts tx-sender))" ;
1450+ assert_body_eq ( ts_src, expected_clar_src) ;
1451+ }
1452+
1453+ #[ test]
1454+ fn test_optional_default_to_type_inference ( ) {
1455+ let ts_src = indoc ! {
1456+ r#"const counts = new DataMap<Principal, Uint>();
1457+ function getMyCountPlus1() {
1458+ return counts.get(txSender).defaultTo(0) + 1;
1459+ }"#
1460+ } ;
1461+ let expected_clar_src = "(+ (default-to u0 (map-get? counts tx-sender)) u1)" ;
1462+ assert_body_eq ( ts_src, expected_clar_src) ;
1463+ }
1464+
1465+ #[ test]
1466+ fn test_variable_type_inference ( ) {
1467+ let ts_src = indoc ! {
1468+ r#"const counts = new DataMap<Principal, Uint>();
1469+ function getMyCountPlus1() {
1470+ const currentCount = counts.get(txSender).defaultTo(0);
1471+ return currentCount + 1;
1472+ }"#
1473+ } ;
1474+ let expected_clar_src = indoc ! {
1475+ r#"(let ((current-count (default-to u0 (map-get? counts tx-sender))))
1476+ (+ current-count u1)
1477+ )"#
1478+ } ;
1479+ assert_body_eq ( ts_src, expected_clar_src) ;
1480+ }
13391481}
0 commit comments