Repository: veryl-lang/veryl (Rust)

Issue:
Not resolve modport default member recursvily
Veryl compiler does not resolve modport default member recursvily.
Therefore, Veryl compiler generates unexpected modport decalrations. For example:

Veryl

```systemverilog
interface IfA {
    var a: logic;
    var b: logic;
    var c: logic;

    modport mp_a {
        a: input,
    }

    modport mp_b {
        b: input,
        ..same(mp_a)
    }

    modport mp_c {
        c: input,
        ..same(mp_b)
    }
}
```

Expected SV

```systemverilog
interface project_IfA;
    logic a;
    logic b;
    logic c;

    modport mp_a (
        input a
    );

    modport mp_b (
        input b,
        input a
    );

    modport mp_c (
        input c,
        input b,
        input a
    );
endinterface
```

Actual SV

```systemverilog
interface project_IfA;
    logic a;
    logic b;
    logic c;

    modport mp_a (
        input a
    );

    modport mp_b (
        input b,
        input a
    );

    modport mp_c (
        input c,
        input b
    );
endinterface
```


Discussion:

Patch:
diff --git a/crates/analyzer/src/handlers/create_symbol_table.rs b/crates/analyzer/src/handlers/create_symbol_table.rs
index 5baaf05a6..5384bc423 100644
--- a/crates/analyzer/src/handlers/create_symbol_table.rs
+++ b/crates/analyzer/src/handlers/create_symbol_table.rs
@@ -468,20 +468,6 @@ impl CreateSymbolTable {
     }
 
     fn expand_modport_default_member(&mut self, interface_id: SymbolId) {
-        // collect all members in modport for default member
-        let mut directions = HashMap::new();
-        for id in &self.modport_ids {
-            let mp = symbol_table::get(*id).unwrap();
-            if let SymbolKind::Modport(ref x) = mp.kind {
-                for x in &x.members {
-                    let member = symbol_table::get(*x).unwrap();
-                    if let SymbolKind::ModportVariableMember(x) = &member.kind {
-                        directions.insert((mp.token.text, member.token.text), x.direction);
-                    }
-                }
-            }
-        }
-
         let interface_namesapce = symbol_table::get(interface_id)
             .map(|x| x.inner_namespace())
             .unwrap();
@@ -490,11 +476,7 @@ impl CreateSymbolTable {
             if let SymbolKind::Modport(ref x) = mp.kind {
                 // add default members
                 let mut members = x.members.clone();
-                members.append(&mut self.get_modport_default_members(
-                    &mp,
-                    &interface_namesapce,
-                    &directions,
-                ));
+                members.append(&mut self.get_modport_default_members(&mp, &interface_namesapce));
 
                 let property = ModportProperty {
                     interface: interface_id,
@@ -512,13 +494,16 @@ impl CreateSymbolTable {
         &mut self,
         mp: &Symbol,
         interface_namesapce: &Namespace,
-        directions: &HashMap<(StrId, StrId), SymDirection>,
     ) -> Vec<SymbolId> {
         let mut ret = Vec::new();
 
         if let SymbolKind::Modport(ref x) = mp.kind
             && let Some(ref default) = x.default
         {
+            let default_member_directions = Self::collect_modport_default_member_target_directions(
+                default,
+                interface_namesapce,
+            );
             let explicit_members: HashSet<_> = x
                 .members
                 .iter()
@@ -547,9 +532,9 @@ impl CreateSymbolTable {
                 let direction = match default {
                     SymModportDefault::Input => Some(SymDirection::Input),
                     SymModportDefault::Output => Some(SymDirection::Output),
-                    SymModportDefault::Same(tgt) => directions.get(&(tgt.text, *text)).copied(),
-                    SymModportDefault::Converse(tgt) => {
-                        directions.get(&(tgt.text, *text)).map(|x| x.converse())
+                    SymModportDefault::Same(_) => default_member_directions.get(text).copied(),
+                    SymModportDefault::Converse(_) => {
+                        default_member_directions.get(text).map(|x| x.converse())
                     }
                 };
 
@@ -576,6 +561,32 @@ impl CreateSymbolTable {
         ret
     }
 
+    fn collect_modport_default_member_target_directions(
+        default: &SymModportDefault,
+        namespace: &Namespace,
+    ) -> HashMap<StrId, SymDirection> {
+        let mut ret: HashMap<_, _> = HashMap::new();
+
+        match default {
+            SymModportDefault::Same(target) | SymModportDefault::Converse(target) => {
+                if let Ok(mp_symbol) = symbol_table::resolve((target, namespace))
+                    && let SymbolKind::Modport(modport) = mp_symbol.found.kind
+                {
+                    for member in &modport.members {
+                        if let Some(member_symbol) = symbol_table::get(*member)
+                            && let SymbolKind::ModportVariableMember(member) = member_symbol.kind
+                        {
+                            ret.insert(member_symbol.token.text, member.direction);
+                        }
+                    }
+                }
+            }
+            _ => {}
+        }
+
+        ret
+    }
+
     fn push_declaration_item(&mut self, id: SymbolId) {
         if matches!(
             self.affiliation.last(),


Test patch:
diff --git a/crates/analyzer/src/tests.rs b/crates/analyzer/src/tests.rs
index c73943545..5af491c05 100644
--- a/crates/analyzer/src/tests.rs
+++ b/crates/analyzer/src/tests.rs
@@ -4802,6 +4802,35 @@ fn unknown_member() {
 
     let errors = analyze(code);
     assert!(errors.is_empty());
+
+    let code = r#"
+    interface IfA {
+        var a: logic;
+        var b: logic;
+        var c: logic;
+        modport mp_a {
+            a: input,
+        }
+        modport mp_b {
+            b: input,
+            ..same(mp_a)
+        }
+        modport mp_c {
+            c: input,
+            ..same(mp_b)
+        }
+    }
+    module ModuleA (
+        if_a: modport IfA::mp_c,
+    ) {
+        let _a: logic = if_a.a;
+        let _b: logic = if_a.b;
+        let _c: logic = if_a.c;
+    }
+    "#;
+
+    let errors = analyze(code);
+    assert!(errors.is_empty());
 }
 
 #[test]
diff --git a/testcases/map/75_modport_default.sv.map b/testcases/map/75_modport_default.sv.map
index c35d0c5bc..1bb9f5d24 100644
--- a/testcases/map/75_modport_default.sv.map
+++ b/testcases/map/75_modport_default.sv.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"75_modport_default.sv.map","sources":["../veryl/75_modport_default.veryl"],"names":["","interface","veryl_testcase_Interface75",";","logic","a","b","c","d","function","Func75","(",")","e","=","0","return","endfunction","modport","master","input",",","output","slave","all_input","all_output","partial_converse","partial_input","master2","partial_same","endinterface"],"mappings":"AAAAA,AAAAC,UAAUC,0BAAYC;IACXC,MAAHC,CAAQF;IACLC,MAAHE,CAAQH;IACLC,MAAHG,CAAQJ;IACLC,MAAHI,CAAQL;;IAEZM,mBAAsBL,MAAbM,MAAOC,CAACC,EAAEZ,CAASA;QACjBI,MAAHS,CAAQV;QACZU,EAAEC,EAAEC,CAACZ;QACLa,OAAOH,CAACV;IACZc;;IAEAC,QAAQC,OAAOR;QACRS,OAAHf,CAASgB;QACND,OAAHd,CAASe;QACNC,OAAHf,CAASc;QACNC,OAAHd,CAASR;IACbY;;IAEAM,QAAQK,MAAMZ;QACVW,OAAAjB;QAAAiB,OAAAhB;QAAAc,OAAAb;QAAAa,OAAAZ;IACJI;;IAEAM,QAAQM,UAAUb;QACdS,MAAAf;QAAAe,MAAAd;QAAAc,MAAAb;QAAAa,MAAAZ;IACJI;;IAEAM,QAAQO,WAAWd;QACfS,MAAAf;QAAAe,MAAAd;QAAAc,MAAAb;QAAAa,MAAAZ;IACJI;;IAEAM,QAAQQ,iBAAiBf;QAClBS,OAAHf,CAAQL;QACRsB,OAAAhB;QAAAc,OAAAb;QAAAa,OAAAZ;IACJI;;IAEAM,QAAQS,cAAchB;QACfW,OAAHf,CAASP;QACToB,OAAAf;QAAAe,OAAAd;QAAAc,OAAAZ;IACJI;;IAEAM,QAAQU,QAAQjB;QACZS,OAAAf;QAAAe,OAAAd;QAAAgB,OAAAf;QAAAe,OAAAd;IACJI;;IAEAM,QAAQW,aAAalB;QACdW,OAAHjB,CAASL;QACToB,OAAAd;QAAAgB,OAAAf;QAAAe,OAAAd;IACJI;AACJkB"}
\ No newline at end of file
+{"version":3,"file":"75_modport_default.sv.map","sources":["../veryl/75_modport_default.veryl"],"names":["","interface","veryl_testcase_Interface75",";","logic","a","b","c","d","function","Func75","(",")","e","=","0","return","endfunction","modport","master","input",",","output","slave","all_input","all_output","partial_converse","partial_input","master2","slave2","partial_same","endinterface"],"mappings":"AAAAA,AAAAC,UAAUC,0BAAYC;IACXC,MAAHC,CAAQF;IACLC,MAAHE,CAAQH;IACLC,MAAHG,CAAQJ;IACLC,MAAHI,CAAQL;;IAEZM,mBAAsBL,MAAbM,MAAOC,CAACC,EAAEZ,CAASA;QACjBI,MAAHS,CAAQV;QACZU,EAAEC,EAAEC,CAACZ;QACLa,OAAOH,CAACV;IACZc;;IAEAC,QAAQC,OAAOR;QACRS,OAAHf,CAASgB;QACND,OAAHd,CAASe;QACNC,OAAHf,CAASc;QACNC,OAAHd,CAASR;IACbY;;IAEAM,QAAQK,MAAMZ;QACVW,OAAAjB;QAAAiB,OAAAhB;QAAAc,OAAAb;QAAAa,OAAAZ;IACJI;;IAEAM,QAAQM,UAAUb;QACdS,MAAAf;QAAAe,MAAAd;QAAAc,MAAAb;QAAAa,MAAAZ;IACJI;;IAEAM,QAAQO,WAAWd;QACfS,MAAAf;QAAAe,MAAAd;QAAAc,MAAAb;QAAAa,MAAAZ;IACJI;;IAEAM,QAAQQ,iBAAiBf;QAClBS,OAAHf,CAAQL;QACRsB,OAAAhB;QAAAc,OAAAb;QAAAa,OAAAZ;IACJI;;IAEAM,QAAQS,cAAchB;QACfW,OAAHf,CAASP;QACToB,OAAAf;QAAAe,OAAAd;QAAAc,OAAAZ;IACJI;;IAEAM,QAAQU,QAAQjB;QACZS,OAAAf;QAAAe,OAAAd;QAAAgB,OAAAf;QAAAe,OAAAd;IACJI;;IAEAM,QAAQW,OAAOlB;QACXW,OAAAjB;QAAAiB,OAAAhB;QAAAc,OAAAb;QAAAa,OAAAZ;IACJI;;IAEAM,QAAQY,aAAanB;QACdW,OAAHjB,CAASL;QACToB,OAAAd;QAAAgB,OAAAf;QAAAe,OAAAd;IACJI;AACJmB"}
\ No newline at end of file
diff --git a/testcases/sv/75_modport_default.sv b/testcases/sv/75_modport_default.sv
index ced7ed746..8bd4047d2 100644
--- a/testcases/sv/75_modport_default.sv
+++ b/testcases/sv/75_modport_default.sv
@@ -59,6 +59,13 @@ interface veryl_testcase_Interface75;
         output d
     );
 
+    modport slave2 (
+        output a,
+        output b,
+        input  c,
+        input  d
+    );
+
     modport partial_same (
         output a,
         input  b,
diff --git a/testcases/veryl/75_modport_default.veryl b/testcases/veryl/75_modport_default.veryl
index a926567f5..7dd9328ff 100644
--- a/testcases/veryl/75_modport_default.veryl
+++ b/testcases/veryl/75_modport_default.veryl
@@ -43,6 +43,10 @@ interface Interface75 {
         ..same(master)
     }
 
+    modport slave2 {
+        ..converse(master2)
+    }
+
     modport partial_same {
         a: output,
         ..same(master)