Repository: veryl-lang/veryl (Rust)

Issue:
variable assignment in function is not checked
```sv
module ModuleA {
    var v1: u32;
    var v2: u32;

    function FunctionA (
        a: output u32,
        b: ref    u32,
    ) {
        a = 100;
        b = 1000;
    }

    always_comb {
        FunctionA(v1, v2);
    }
}
```

```
]8;;https://doc.veryl-lang.org/book/07_appendix/02_semantic_error.html#unassign_variable\unassign_variable (link)]8;;\

  ⚠️ v1 is unassigned
   ╭─[:2:9]
 1 │ module ModuleA {
 2 │     var v1: u32;
   ·         ─┬
   ·          ╰── Error location
 3 │     var v2: u32;
   ╰────
  help: 
]8;;https://doc.veryl-lang.org/book/07_appendix/02_semantic_error.html#unassign_variable\unassign_variable (link)]8;;\

  ⚠️ v2 is unassigned
   ╭─[:3:9]
 2 │     var v1: u32;
 3 │     var v2: u32;
   ·         ─┬
   ·          ╰── Error location
 4 │ 
   ╰────
  help: 
```


Discussion:
I'm working on it.



Patch:
diff --git a/crates/analyzer/src/handlers/check_var_ref.rs b/crates/analyzer/src/handlers/check_var_ref.rs
index 596d3d060..bd4ff27c4 100644
--- a/crates/analyzer/src/handlers/check_var_ref.rs
+++ b/crates/analyzer/src/handlers/check_var_ref.rs
@@ -2,7 +2,7 @@ use crate::analyzer_error::AnalyzerError;
 use crate::attribute::Attribute as Attr;
 use crate::attribute::{AllowItem, CondTypeItem};
 use crate::attribute_table;
-use crate::symbol::{Direction, SymbolId, SymbolKind, TypeKind};
+use crate::symbol::{Direction, Symbol, SymbolId, SymbolKind, TypeKind};
 use crate::symbol_table;
 use crate::var_ref::{
     AssignDeclarationType, AssignPosition, AssignPositionType, AssignStatementBranchItemType,
@@ -15,14 +15,20 @@ use veryl_parser::veryl_token::Token;
 use veryl_parser::veryl_walker::{Handler, HandlerPoint};
 use veryl_parser::ParolError;
 
+struct FunctionCallContext {
+    pub token: Token,
+    pub port_directions: Vec<Direction>,
+}
+
 pub struct CheckVarRef<'a> {
     pub errors: Vec<AnalyzerError>,
     text: &'a str,
     point: HandlerPoint,
     affiliation: Vec<VarRefAffiliation>,
     assign_position: AssignPosition,
-    in_expression: Vec<()>,
+    in_expression: Vec<bool>,
     in_if_expression: Vec<()>,
+    function_call: Vec<Option<FunctionCallContext>>,
     branch_index: usize,
 }
 
@@ -36,6 +42,7 @@ impl<'a> CheckVarRef<'a> {
             assign_position: AssignPosition::default(),
             in_expression: Vec::new(),
             in_if_expression: Vec::new(),
+            function_call: Vec::new(),
             branch_index: 0,
         }
     }
@@ -62,6 +69,42 @@ impl<'a> CheckVarRef<'a> {
         };
         symbol_table::add_var_ref(&expression);
     }
+
+    fn push_function_call(&mut self, identifier: &ExpressionIdentifier) {
+        if let Ok(func) = symbol_table::resolve(identifier) {
+            let ports = match func.found.kind {
+                SymbolKind::Function(x) => x.ports,
+                SymbolKind::ModportFunctionMember(x) => symbol_table::get(x.function)
+                    .map(|x| {
+                        if let SymbolKind::Function(x) = x.kind {
+                            x.ports
+                        } else {
+                            unreachable!()
+                        }
+                    })
+                    .unwrap(),
+                _ => Vec::new(),
+            };
+            let port_directions: Vec<_> = ports
+                .iter()
+                .rev()
+                .map(|port| {
+                    if let SymbolKind::Port(port) = symbol_table::get(port.symbol).unwrap().kind {
+                        port.direction
+                    } else {
+                        unreachable!()
+                    }
+                })
+                .collect();
+            let context = FunctionCallContext {
+                token: func.found.token,
+                port_directions,
+            };
+            self.function_call.push(Some(context));
+        } else {
+            self.function_call.push(None);
+        }
+    }
 }
 
 impl Handler for CheckVarRef<'_> {
@@ -70,6 +113,19 @@ impl Handler for CheckVarRef<'_> {
     }
 }
 
+fn is_assignable_symbol(symbol: &Symbol) -> bool {
+    match &symbol.kind {
+        SymbolKind::Variable(_) => true,
+        SymbolKind::Port(x) if x.direction == Direction::Output => true,
+        SymbolKind::Port(x) if x.direction == Direction::Ref => true,
+        SymbolKind::Port(x) if x.direction == Direction::Inout => true,
+        SymbolKind::ModportVariableMember(x) if x.direction == Direction::Output => true,
+        SymbolKind::ModportVariableMember(x) if x.direction == Direction::Ref => true,
+        SymbolKind::ModportVariableMember(x) if x.direction == Direction::Inout => true,
+        _ => false,
+    }
+}
+
 fn can_assign(full_path: &[SymbolId]) -> bool {
     if full_path.is_empty() {
         return false;
@@ -77,17 +133,7 @@ fn can_assign(full_path: &[SymbolId]) -> bool {
 
     for path in full_path {
         if let Some(symbol) = symbol_table::get(*path) {
-            let can_assign = match symbol.kind {
-                SymbolKind::Variable(_) => true,
-                SymbolKind::Port(x) if x.direction == Direction::Output => true,
-                SymbolKind::Port(x) if x.direction == Direction::Ref => true,
-                SymbolKind::Port(x) if x.direction == Direction::Inout => true,
-                SymbolKind::ModportVariableMember(x) if x.direction == Direction::Output => true,
-                SymbolKind::ModportVariableMember(x) if x.direction == Direction::Ref => true,
-                SymbolKind::ModportVariableMember(x) if x.direction == Direction::Inout => true,
-                _ => false,
-            };
-            if can_assign {
+            if is_assignable_symbol(&symbol) {
                 return true;
             }
         }
@@ -109,6 +155,85 @@ fn has_cond_type(token: &Token) -> bool {
     false
 }
 
+fn map_assignable_factor(arg: &Expression) -> Option<VarRefPath> {
+    if !arg.expression_list.is_empty() {
+        return None;
+    }
+
+    let exp = &*arg.expression01;
+    if !exp.expression01_list.is_empty() {
+        return None;
+    }
+
+    let exp = &*exp.expression02;
+    if !exp.expression02_list.is_empty() {
+        return None;
+    }
+
+    let exp = &*exp.expression03;
+    if !exp.expression03_list.is_empty() {
+        return None;
+    }
+
+    let exp = &*exp.expression04;
+    if !exp.expression04_list.is_empty() {
+        return None;
+    }
+
+    let exp = &*exp.expression05;
+    if !exp.expression05_list.is_empty() {
+        return None;
+    }
+
+    let exp = &*exp.expression06;
+    if !exp.expression06_list.is_empty() {
+        return None;
+    }
+
+    let exp = &*exp.expression07;
+    if !exp.expression07_list.is_empty() {
+        return None;
+    }
+
+    let exp = &*exp.expression08;
+    if !exp.expression08_list.is_empty() {
+        return None;
+    }
+
+    let exp = &*exp.expression09;
+    if !exp.expression09_list.is_empty() {
+        return None;
+    }
+
+    let exp = &*exp.expression10;
+    if !exp.expression10_list.is_empty() {
+        return None;
+    }
+
+    let exp = &*exp.expression11;
+    if exp.expression11_opt.is_some() {
+        return None;
+    }
+
+    let exp = &*exp.expression12;
+    if !exp.expression12_list.is_empty() {
+        return None;
+    }
+
+    if let Factor::ExpressionIdentifierFactorOpt(factor) = &*exp.factor {
+        if factor.factor_opt.is_none() {
+            if let Ok(symbol) = symbol_table::resolve(factor.expression_identifier.as_ref()) {
+                if is_assignable_symbol(&symbol.found) {
+                    let path = VarRefPath::try_from(factor.expression_identifier.as_ref()).unwrap();
+                    return Some(path);
+                }
+            }
+        }
+    }
+
+    None
+}
+
 impl VerylGrammarTrait for CheckVarRef<'_> {
     fn r#else(&mut self, arg: &Else) -> Result<(), ParolError> {
         if let HandlerPoint::Before = self.point {
@@ -149,7 +274,7 @@ impl VerylGrammarTrait for CheckVarRef<'_> {
     fn assignment(&mut self, _arg: &Assignment) -> Result<(), ParolError> {
         match self.point {
             HandlerPoint::Before => {
-                self.in_expression.push(());
+                self.in_expression.push(true);
             }
             HandlerPoint::After => {
                 self.in_expression.pop();
@@ -158,10 +283,48 @@ impl VerylGrammarTrait for CheckVarRef<'_> {
         Ok(())
     }
 
-    fn function_call(&mut self, _arg: &FunctionCall) -> Result<(), ParolError> {
+    fn factor(&mut self, arg: &Factor) -> Result<(), ParolError> {
+        if let Factor::ExpressionIdentifierFactorOpt(factor) = arg {
+            if factor.factor_opt.is_some() {
+                match self.point {
+                    HandlerPoint::Before => self.push_function_call(&factor.expression_identifier),
+                    HandlerPoint::After => {
+                        self.function_call.pop();
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn argument_item(&mut self, arg: &ArgumentItem) -> Result<(), ParolError> {
         match self.point {
             HandlerPoint::Before => {
-                self.in_expression.push(());
+                if let Some(function_call) = self.function_call.last_mut().unwrap() {
+                    let direction = function_call
+                        .port_directions
+                        .pop()
+                        .unwrap_or(Direction::Input);
+                    if !matches!(
+                        direction,
+                        Direction::Output | Direction::Inout | Direction::Ref
+                    ) {
+                        self.in_expression.push(true);
+                    } else if let Some(path) = map_assignable_factor(&arg.expression) {
+                        self.assign_position.push(AssignPositionType::Statement {
+                            token: function_call.token,
+                            resettable: false,
+                        });
+                        self.add_assign(&path);
+                        self.in_expression.push(false);
+                    } else {
+                        self.in_expression.push(true);
+                    }
+                } else {
+                    // unassignable expression is connected with an output param.
+                    // direction mismatch error should be raised.
+                    self.in_expression.push(true);
+                }
             }
             HandlerPoint::After => {
                 self.in_expression.pop();
@@ -172,7 +335,7 @@ impl VerylGrammarTrait for CheckVarRef<'_> {
 
     fn expression_identifier(&mut self, arg: &ExpressionIdentifier) -> Result<(), ParolError> {
         if let HandlerPoint::Before = self.point {
-            if !self.in_expression.is_empty() {
+            if *self.in_expression.last().unwrap_or(&false) {
                 if let Ok(path) = VarRefPath::try_from(arg) {
                     let full_path = path.full_path();
                     let symbol = symbol_table::get(*full_path.last().unwrap()).unwrap();
@@ -209,46 +372,61 @@ impl VerylGrammarTrait for CheckVarRef<'_> {
     }
 
     fn identifier_statement(&mut self, arg: &IdentifierStatement) -> Result<(), ParolError> {
-        if let HandlerPoint::After = self.point {
-            if let IdentifierStatementGroup::Assignment(x) = &*arg.identifier_statement_group {
-                let token = match x.assignment.assignment_group.as_ref() {
-                    AssignmentGroup::Equ(x) => x.equ.equ_token.token,
-                    AssignmentGroup::AssignmentOperator(x) => {
-                        x.assignment_operator.assignment_operator_token.token
-                    }
-                };
-                if let Ok(path) = VarRefPath::try_from(arg.expression_identifier.as_ref()) {
-                    let full_path = path.full_path();
-                    let symbol = symbol_table::get(*full_path.last().unwrap()).unwrap();
-
-                    if can_assign(&full_path) {
-                        self.assign_position.push(AssignPositionType::Statement {
-                            token,
-                            resettable: true,
-                        });
-                        self.add_assign(&path);
-                    } else {
-                        let token = arg.expression_identifier.identifier().token;
-                        self.errors.push(AnalyzerError::invalid_assignment(
-                            &token.to_string(),
-                            self.text,
-                            &symbol.kind.to_kind_name(),
-                            &arg.expression_identifier.as_ref().into(),
-                        ));
-                    }
+        match self.point {
+            HandlerPoint::Before => {
+                if matches!(
+                    &*arg.identifier_statement_group,
+                    IdentifierStatementGroup::FunctionCall(_)
+                ) {
+                    self.push_function_call(arg.expression_identifier.as_ref());
+                }
+            }
+            HandlerPoint::After => {
+                match &*arg.identifier_statement_group {
+                    IdentifierStatementGroup::Assignment(x) => {
+                        let token = match x.assignment.assignment_group.as_ref() {
+                            AssignmentGroup::Equ(x) => x.equ.equ_token.token,
+                            AssignmentGroup::AssignmentOperator(x) => {
+                                x.assignment_operator.assignment_operator_token.token
+                            }
+                        };
+                        if let Ok(path) = VarRefPath::try_from(arg.expression_identifier.as_ref()) {
+                            let full_path = path.full_path();
+                            let symbol = symbol_table::get(*full_path.last().unwrap()).unwrap();
+
+                            if can_assign(&full_path) {
+                                self.assign_position.push(AssignPositionType::Statement {
+                                    token,
+                                    resettable: true,
+                                });
+                                self.add_assign(&path);
+                            } else {
+                                let token = arg.expression_identifier.identifier().token;
+                                self.errors.push(AnalyzerError::invalid_assignment(
+                                    &token.to_string(),
+                                    self.text,
+                                    &symbol.kind.to_kind_name(),
+                                    &arg.expression_identifier.as_ref().into(),
+                                ));
+                            }
 
-                    // Check to confirm not assigning to constant
-                    if let SymbolKind::Variable(v) = symbol.kind.clone() {
-                        if v.r#type.is_const {
-                            let token = arg.expression_identifier.identifier().token;
-                            self.errors.push(AnalyzerError::invalid_assignment_to_const(
-                                &token.to_string(),
-                                self.text,
-                                &symbol.kind.to_kind_name(),
-                                &arg.expression_identifier.as_ref().into(),
-                            ));
+                            // Check to confirm not assigning to constant
+                            if let SymbolKind::Variable(v) = symbol.kind.clone() {
+                                if v.r#type.is_const {
+                                    let token = arg.expression_identifier.identifier().token;
+                                    self.errors.push(AnalyzerError::invalid_assignment_to_const(
+                                        &token.to_string(),
+                                        self.text,
+                                        &symbol.kind.to_kind_name(),
+                                        &arg.expression_identifier.as_ref().into(),
+                                    ));
+                                }
+                            }
                         }
                     }
+                    IdentifierStatementGroup::FunctionCall(_) => {
+                        self.function_call.pop();
+                    }
                 }
             }
         }


Test patch:
diff --git a/crates/analyzer/src/tests.rs b/crates/analyzer/src/tests.rs
index c3fd294d1..abd0b3003 100644
--- a/crates/analyzer/src/tests.rs
+++ b/crates/analyzer/src/tests.rs
@@ -2056,6 +2056,50 @@ fn unassign_variable() {
 
     let errors = analyze(code);
     assert!(errors.is_empty());
+
+    let code = r#"
+    interface InterfaceA {
+        function FuncA(
+            a: output logic,
+            b: ref    logic
+        ) {
+            a = 0;
+            b = 1;
+        }
+        modport mp {
+            FuncA: import,
+        }
+    }
+    module ModuleA (
+        if_a: modport InterfaceA::mp
+    ){
+        function FuncB(
+            a: output logic,
+            b: ref    logic,
+        ) -> logic {
+            a = 0;
+            b = 1;
+            return 0;
+        }
+
+        var _a: logic;
+        var _b: logic;
+        var _c: logic;
+        var _d: logic;
+        var _e: logic;
+
+        always_comb {
+            if_a.FuncA(_a, _b);
+        }
+
+        always_comb {
+            _c = FuncB(_d, _e);
+        }
+    }
+    "#;
+
+    let errors = analyze(code);
+    assert!(errors.is_empty());
 }
 
 #[test]
diff --git a/testcases/map/testcases/sv/06_function.sv.map b/testcases/map/testcases/sv/06_function.sv.map
index 654dfa7a1..1d3580977 100644
--- a/testcases/map/testcases/sv/06_function.sv.map
+++ b/testcases/map/testcases/sv/06_function.sv.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"06_function.sv.map","sources":["../../../veryl/06_function.veryl"],"names":["","module","Module06",";","localparam","int unsigned","ParamX","=","1","function","logic","[","]","FuncA","(","input","a",",","output","b","ref","c",")","d","+","/","return","2","endfunction","FuncC","e","always_comb","initial","begin","end","$clog2","endmodule"],"mappings":"AAAAA,AAAAC,sBAAOC,QAASC;IACZC,WAAcC,aAARC,OAAYC,EAAEC,CAACL;;;IAGrBM,mBAIKC,MAAKC,CAACL,UAAMM,EAJRC,KAAMC;QACRC,OAAOL,MAAKC,CAACL,UAAMM,EAAtBI,CAAuBC;QACpBC,OAAOR,MAAKC,CAACL,UAAMM,EAAtBO,CAAuBF;QACpBG,OAAOV,MAAKC,CAACL,UAAMM,EAAtBS,CAAuBrB;IAC3BsB,EAAEtB,CAAiBA;QACRK,aAAHkB;UAAOhB,EAAEC,CAACL;QACdgB,EAAEZ,EAAES,EAAEQ,EAAEhB,EAAEgB,EAAED,CAACpB;QACbkB,EAAEd,EAAES,EAAES,EAAEjB,CAACL;QACTuB,OAAOV,EAAEQ,EAAEG,CAACxB;IAChByB;;;IAGAnB,wBAASoB,KAAMf;QACRC,MAAML,MAAKC,CAACL,UAAMM,EAArBI,CAAsBC;QACnBG,MAAMV,MAAKC,CAACL,UAAMM,EAArBS,CAAsBrB;IAC1BsB,GAAEtB;QACEqB,EAAEd,EAAES,EAAES,EAAEjB,CAACL;IACbyB;;IAEOlB,MAAKC,CAACL,UAAMM,EAAfI;kBAAiBT,EAAEC,CAACL;IACjBO,MAAKC,CAACL,UAAMM,EAAfO;kBAAiBZ,EAAEC,CAACL;IACjBO,MAAKC,CAACL,UAAMM,EAAfS;kBAAiBd,EAAEC,CAACL;IACjBO,MAAKC,CAACL,UAAMM,EAAfW,CAAgBpB;IACbO,MAAKC,CAACL,UAAMM,EAAfkB,CAAgB3B;;;IAGpB4B,YAAOR,EAAEhB,EAAEM,KAAKC,CAACE,CAACC,EAAEE,CAACF,EAAEI,CAACC,CAACnB;;;IAGzB6B,QAAQC;QACJJ,KAAKf,CAACE,CAACC,EAAEI,CAACC,CAACnB;IACf+B;;;IAGAH,YAAOD,EAAEvB,EAAE4B,MAAMrB,CAACE,CAACM,CAACnB;AACxBiC"}
\ No newline at end of file
+{"version":3,"file":"06_function.sv.map","sources":["../../../veryl/06_function.veryl"],"names":["","module","Module06",";","localparam","int unsigned","ParamX","=","1","function","logic","[","]","FuncA","(","input","a",",","output","b","ref","c",")","d","+","/","return","2","endfunction","FuncC","e","f","always_comb","initial","begin","end","$clog2","endmodule"],"mappings":"AAAAA,AAAAC,sBAAOC,QAASC;IACZC,WAAcC,aAARC,OAAYC,EAAEC,CAACL;;;IAGrBM,mBAIKC,MAAKC,CAACL,UAAMM,EAJRC,KAAMC;QACRC,OAAOL,MAAKC,CAACL,UAAMM,EAAtBI,CAAuBC;QACpBC,OAAOR,MAAKC,CAACL,UAAMM,EAAtBO,CAAuBF;QACpBG,OAAOV,MAAKC,CAACL,UAAMM,EAAtBS,CAAuBrB;IAC3BsB,EAAEtB,CAAiBA;QACRK,aAAHkB;UAAOhB,EAAEC,CAACL;QACdgB,EAAEZ,EAAES,EAAEQ,EAAEhB,EAAEgB,EAAED,CAACpB;QACbkB,EAAEd,EAAES,EAAES,EAAEjB,CAACL;QACTuB,OAAOV,EAAEQ,EAAEG,CAACxB;IAChByB;;;IAGAnB,wBAASoB,KAAMf;QACRC,MAAML,MAAKC,CAACL,UAAMM,EAArBI,CAAsBC;QACnBG,MAAMV,MAAKC,CAACL,UAAMM,EAArBS,CAAsBrB;IAC1BsB,GAAEtB;QACEqB,EAAEd,EAAES,EAAES,EAAEjB,CAACL;IACbyB;;IAEOlB,MAAKC,CAACL,UAAMM,EAAfI;kBAAiBT,EAAEC,CAACL;IACjBO,MAAKC,CAACL,UAAMM,EAAfO,CAAgBhB;IACbO,MAAKC,CAACL,UAAMM,EAAfS,CAAgBlB;IACbO,MAAKC,CAACL,UAAMM,EAAfW,CAAgBpB;IACbO,MAAKC,CAACL,UAAMM,EAAfkB,CAAgB3B;IACbO,MAAKC,CAACL,UAAMM,EAAfmB,CAAgB5B;;;IAGpB6B,YAAOT,EAAEhB,EAAEM,KAAKC,CAACE,CAACC,EAAEE,CAACF,EAAEI,CAACC,CAACnB;;;IAGzB8B,QAAQC;QACJL,KAAKf,CAACE,CAACC,EAAEa,CAACR,CAACnB;IACfgC;;;IAGAH,YAAOD,EAAExB,EAAE6B,MAAMtB,CAACE,CAACM,CAACnB;AACxBkC"}
\ No newline at end of file
diff --git a/testcases/sv/06_function.sv b/testcases/sv/06_function.sv
index b02506114..580265037 100644
--- a/testcases/sv/06_function.sv
+++ b/testcases/sv/06_function.sv
@@ -25,21 +25,20 @@ module veryl_testcase_Module06;
     logic [ParamX-1:0] a;
     always_comb a = 1;
     logic [ParamX-1:0] b;
-    always_comb b = 1;
     logic [ParamX-1:0] c;
-    always_comb c = 1;
     logic [ParamX-1:0] d;
     logic [ParamX-1:0] e;
+    logic [ParamX-1:0] f;
 
     // function call
     always_comb d = FuncA(a, b, c);
 
     // void function call
     initial begin
-        FuncC(a, c);
+        FuncC(a, e);
     end
 
     // system function call
-    always_comb e = $clog2(a);
+    always_comb f = $clog2(a);
 endmodule
 //# sourceMappingURL=../map/testcases/sv/06_function.sv.map
diff --git a/testcases/veryl/06_function.veryl b/testcases/veryl/06_function.veryl
index 55a88c603..93cfccdec 100644
--- a/testcases/veryl/06_function.veryl
+++ b/testcases/veryl/06_function.veryl
@@ -22,19 +22,20 @@ module Module06 {
     }
 
     let a: logic<ParamX> = 1;
-    let b: logic<ParamX> = 1;
-    let c: logic<ParamX> = 1;
+    var b: logic<ParamX>;
+    var c: logic<ParamX>;
     var d: logic<ParamX>;
     var e: logic<ParamX>;
+    var f: logic<ParamX>;
 
     // function call
     assign d = FuncA(a, b, c);
 
     // void function call
     initial {
-        FuncC(a, c);
+        FuncC(a, e);
     }
 
     // system function call
-    assign e = $clog2(a);
+    assign f = $clog2(a);
 }