method-ray 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,218 @@
1
+ //! Parameter Handlers - Processing Ruby method/block parameters
2
+ //!
3
+ //! This module is responsible for:
4
+ //! - Extracting parameter names from DefNode
5
+ //! - Creating vertices for parameters
6
+ //! - Registering parameters as local variables in method scope
7
+
8
+ use crate::env::{GlobalEnv, LocalEnv};
9
+ use crate::graph::{ChangeSet, VertexId};
10
+ use crate::types::Type;
11
+
12
+ /// Install a required parameter as a local variable
13
+ ///
14
+ /// Required parameters start with Bot (untyped) type since we don't know
15
+ /// what type will be passed at call sites.
16
+ ///
17
+ /// # Example
18
+ /// ```ruby
19
+ /// def greet(name) # 'name' is a required parameter
20
+ /// name.upcase
21
+ /// end
22
+ /// ```
23
+ pub fn install_required_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: String) -> VertexId {
24
+ // Create a vertex for the parameter (starts as Bot/untyped)
25
+ let param_vtx = genv.new_vertex();
26
+
27
+ // Register in LocalEnv for variable lookup
28
+ lenv.new_var(name, param_vtx);
29
+
30
+ param_vtx
31
+ }
32
+
33
+ /// Install an optional parameter with a default value
34
+ ///
35
+ /// The parameter's type is inferred from the default value expression.
36
+ ///
37
+ /// # Example
38
+ /// ```ruby
39
+ /// def greet(name = "World") # 'name' has type String from default
40
+ /// name.upcase
41
+ /// end
42
+ /// ```
43
+ pub fn install_optional_parameter(
44
+ genv: &mut GlobalEnv,
45
+ lenv: &mut LocalEnv,
46
+ _changes: &mut ChangeSet,
47
+ name: String,
48
+ default_value_vtx: VertexId,
49
+ ) -> VertexId {
50
+ // Create a vertex for the parameter
51
+ let param_vtx = genv.new_vertex();
52
+
53
+ // Connect default value to parameter vertex for type inference
54
+ // Use genv.add_edge directly so the type is immediately propagated
55
+ // before the method body is processed
56
+ genv.add_edge(default_value_vtx, param_vtx);
57
+
58
+ // Register in LocalEnv for variable lookup
59
+ lenv.new_var(name, param_vtx);
60
+
61
+ param_vtx
62
+ }
63
+
64
+ /// Install a rest parameter (*args) as a local variable with Array type
65
+ ///
66
+ /// Rest parameters collect all remaining arguments into an Array.
67
+ ///
68
+ /// # Example
69
+ /// ```ruby
70
+ /// def collect(*items) # 'items' has type Array
71
+ /// items.first
72
+ /// end
73
+ /// ```
74
+ pub fn install_rest_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: String) -> VertexId {
75
+ // Create a vertex for the parameter
76
+ let param_vtx = genv.new_vertex();
77
+
78
+ // Rest parameters are always Arrays
79
+ let array_src = genv.new_source(Type::array());
80
+ genv.add_edge(array_src, param_vtx);
81
+
82
+ // Register in LocalEnv for variable lookup
83
+ lenv.new_var(name, param_vtx);
84
+
85
+ param_vtx
86
+ }
87
+
88
+ /// Install a keyword rest parameter (**kwargs) as a local variable with Hash type
89
+ ///
90
+ /// Keyword rest parameters collect all remaining keyword arguments into a Hash.
91
+ ///
92
+ /// # Example
93
+ /// ```ruby
94
+ /// def configure(**options) # 'options' has type Hash
95
+ /// options[:debug]
96
+ /// end
97
+ /// ```
98
+ pub fn install_keyword_rest_parameter(
99
+ genv: &mut GlobalEnv,
100
+ lenv: &mut LocalEnv,
101
+ name: String,
102
+ ) -> VertexId {
103
+ // Create a vertex for the parameter
104
+ let param_vtx = genv.new_vertex();
105
+
106
+ // Keyword rest parameters are always Hashes
107
+ let hash_src = genv.new_source(Type::hash());
108
+ genv.add_edge(hash_src, param_vtx);
109
+
110
+ // Register in LocalEnv for variable lookup
111
+ lenv.new_var(name, param_vtx);
112
+
113
+ param_vtx
114
+ }
115
+
116
+ /// Install method parameters as local variables
117
+ ///
118
+ /// Returns a Vec of VertexId for required and optional parameters (positional),
119
+ /// which can be used for argument-to-parameter type propagation.
120
+ pub(crate) fn install_parameters(
121
+ genv: &mut GlobalEnv,
122
+ lenv: &mut LocalEnv,
123
+ changes: &mut ChangeSet,
124
+ source: &str,
125
+ params_node: &ruby_prism::ParametersNode,
126
+ ) -> Vec<VertexId> {
127
+ let mut param_vtxs = Vec::new();
128
+
129
+ // Required parameters: def foo(a, b)
130
+ for node in params_node.requireds().iter() {
131
+ if let Some(req_param) = node.as_required_parameter_node() {
132
+ let name = String::from_utf8_lossy(req_param.name().as_slice()).to_string();
133
+ let vtx = install_required_parameter(genv, lenv, name);
134
+ param_vtxs.push(vtx);
135
+ }
136
+ }
137
+
138
+ // Optional parameters: def foo(a = 1, b = "hello")
139
+ for node in params_node.optionals().iter() {
140
+ if let Some(opt_param) = node.as_optional_parameter_node() {
141
+ let name = String::from_utf8_lossy(opt_param.name().as_slice()).to_string();
142
+ let default_value = opt_param.value();
143
+
144
+ let vtx = if let Some(default_vtx) =
145
+ super::install::install_node(genv, lenv, changes, source, &default_value)
146
+ {
147
+ install_optional_parameter(genv, lenv, changes, name, default_vtx)
148
+ } else {
149
+ install_required_parameter(genv, lenv, name)
150
+ };
151
+ param_vtxs.push(vtx);
152
+ }
153
+ }
154
+
155
+ // Rest parameter: def foo(*args)
156
+ // Not included in param_vtxs (variadic args need special handling)
157
+ if let Some(rest_node) = params_node.rest() {
158
+ if let Some(rest_param) = rest_node.as_rest_parameter_node() {
159
+ if let Some(name_id) = rest_param.name() {
160
+ let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
161
+ install_rest_parameter(genv, lenv, name);
162
+ }
163
+ }
164
+ }
165
+
166
+ // Keyword rest parameter: def foo(**kwargs)
167
+ // Not included in param_vtxs (keyword args need special handling)
168
+ if let Some(kwrest_node) = params_node.keyword_rest() {
169
+ if let Some(kwrest_param) = kwrest_node.as_keyword_rest_parameter_node() {
170
+ if let Some(name_id) = kwrest_param.name() {
171
+ let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
172
+ install_keyword_rest_parameter(genv, lenv, name);
173
+ }
174
+ }
175
+ }
176
+
177
+ param_vtxs
178
+ }
179
+
180
+ #[cfg(test)]
181
+ mod tests {
182
+ use super::*;
183
+
184
+ #[test]
185
+ fn test_install_required_parameter() {
186
+ let mut genv = GlobalEnv::new();
187
+ let mut lenv = LocalEnv::new();
188
+
189
+ let vtx = install_required_parameter(&mut genv, &mut lenv, "name".to_string());
190
+
191
+ // Parameter should be registered in LocalEnv
192
+ assert_eq!(lenv.get_var("name"), Some(vtx));
193
+
194
+ // Vertex should exist in GlobalEnv (as untyped)
195
+ let vertex = genv.get_vertex(vtx);
196
+ assert!(vertex.is_some());
197
+ }
198
+
199
+ #[test]
200
+ fn test_install_multiple_parameters() {
201
+ let mut genv = GlobalEnv::new();
202
+ let mut lenv = LocalEnv::new();
203
+
204
+ let vtx_a = install_required_parameter(&mut genv, &mut lenv, "a".to_string());
205
+ let vtx_b = install_required_parameter(&mut genv, &mut lenv, "b".to_string());
206
+ let vtx_c = install_required_parameter(&mut genv, &mut lenv, "c".to_string());
207
+
208
+ // All parameters should be registered
209
+ assert_eq!(lenv.get_var("a"), Some(vtx_a));
210
+ assert_eq!(lenv.get_var("b"), Some(vtx_b));
211
+ assert_eq!(lenv.get_var("c"), Some(vtx_c));
212
+
213
+ // All vertices should be different
214
+ assert_ne!(vtx_a, vtx_b);
215
+ assert_ne!(vtx_b, vtx_c);
216
+ assert_ne!(vtx_a, vtx_c);
217
+ }
218
+ }
@@ -29,14 +29,23 @@ pub fn install_local_var_read(lenv: &LocalEnv, var_name: &str) -> Option<VertexI
29
29
  }
30
30
 
31
31
  /// Install instance variable write: @name = value
32
+ ///
33
+ /// If @name already has a pre-allocated VertexId (e.g., from attr_reader),
34
+ /// an edge is added from value_vtx to the existing vertex so types propagate.
35
+ /// Otherwise, value_vtx is registered directly as the ivar's VertexId.
32
36
  pub fn install_ivar_write(
33
37
  genv: &mut GlobalEnv,
34
38
  ivar_name: String,
35
39
  value_vtx: VertexId,
36
40
  ) -> VertexId {
37
- genv.scope_manager
38
- .set_instance_var_in_class(ivar_name, value_vtx);
39
- value_vtx
41
+ if let Some(existing_vtx) = genv.scope_manager.lookup_instance_var(&ivar_name) {
42
+ genv.add_edge(value_vtx, existing_vtx);
43
+ existing_vtx
44
+ } else {
45
+ genv.scope_manager
46
+ .set_instance_var_in_class(ivar_name, value_vtx);
47
+ value_vtx
48
+ }
40
49
  }
41
50
 
42
51
  /// Install instance variable read: @name
@@ -45,13 +54,12 @@ pub fn install_ivar_read(genv: &GlobalEnv, ivar_name: &str) -> Option<VertexId>
45
54
  }
46
55
 
47
56
  /// Install self node
57
+ /// Uses the fully qualified name if available (e.g., Api::V1::User instead of just User)
48
58
  pub fn install_self(genv: &mut GlobalEnv) -> VertexId {
49
- if let Some(class_name) = genv.scope_manager.current_class_name() {
50
- genv.new_source(Type::Instance { class_name })
59
+ if let Some(qualified_name) = genv.scope_manager.current_qualified_name() {
60
+ genv.new_source(Type::instance(&qualified_name))
51
61
  } else {
52
- genv.new_source(Type::Instance {
53
- class_name: "Object".to_string(),
54
- })
62
+ genv.new_source(Type::instance("Object"))
55
63
  }
56
64
  }
57
65
 
@@ -26,14 +26,14 @@ pub struct SerializableMethodInfo {
26
26
  pub receiver_class: String,
27
27
  pub method_name: String,
28
28
  pub return_type_str: String, // Simplified: store as string
29
+ #[serde(default)]
30
+ pub block_param_types: Option<Vec<String>>,
29
31
  }
30
32
 
31
33
  impl SerializableMethodInfo {
32
34
  /// Parse return type string into Type (simple parser for cached data)
33
35
  pub fn return_type(&self) -> crate::types::Type {
34
- crate::types::Type::Instance {
35
- class_name: self.return_type_str.clone(),
36
- }
36
+ crate::types::Type::instance(&self.return_type_str)
37
37
  }
38
38
  }
39
39
 
@@ -79,7 +79,6 @@ impl RbsCache {
79
79
  if let Some(bundled_path) = Self::bundled_cache_path() {
80
80
  if let Ok(bytes) = fs::read(&bundled_path) {
81
81
  if let Ok(cache) = bincode::deserialize::<Self>(&bytes) {
82
- eprintln!("Loaded bundled cache from {}", bundled_path.display());
83
82
  return Ok(cache);
84
83
  }
85
84
  }
@@ -123,6 +122,7 @@ impl RbsCache {
123
122
  receiver_class: m.receiver_class.clone(),
124
123
  method_name: m.method_name.clone(),
125
124
  return_type: crate::rbs::converter::RbsTypeConverter::parse(&m.return_type_str),
125
+ block_param_types: m.block_param_types.clone(),
126
126
  })
127
127
  .collect()
128
128
  }
@@ -140,6 +140,7 @@ impl RbsCache {
140
140
  receiver_class: m.receiver_class,
141
141
  method_name: m.method_name,
142
142
  return_type_str: m.return_type.show(),
143
+ block_param_types: m.block_param_types,
143
144
  })
144
145
  .collect();
145
146
 
@@ -166,6 +167,7 @@ mod tests {
166
167
  receiver_class: "String".to_string(),
167
168
  method_name: "upcase".to_string(),
168
169
  return_type_str: "String".to_string(),
170
+ block_param_types: None,
169
171
  }],
170
172
  timestamp: SystemTime::now(),
171
173
  };
@@ -197,6 +199,7 @@ mod tests {
197
199
  receiver_class: "String".to_string(),
198
200
  method_name: "upcase".to_string(),
199
201
  return_type_str: "String".to_string(),
202
+ block_param_types: None,
200
203
  };
201
204
 
202
205
  let return_type = method_info.return_type();
@@ -213,11 +216,13 @@ mod tests {
213
216
  receiver_class: "String".to_string(),
214
217
  method_name: "upcase".to_string(),
215
218
  return_type_str: "String".to_string(),
219
+ block_param_types: None,
216
220
  },
217
221
  SerializableMethodInfo {
218
222
  receiver_class: "Integer".to_string(),
219
223
  method_name: "to_s".to_string(),
220
224
  return_type_str: "String".to_string(),
225
+ block_param_types: None,
221
226
  },
222
227
  ],
223
228
  timestamp: SystemTime::now(),
@@ -244,11 +249,13 @@ mod tests {
244
249
  receiver_class: "String".to_string(),
245
250
  method_name: "upcase".to_string(),
246
251
  return_type_str: "String".to_string(),
252
+ block_param_types: None,
247
253
  },
248
254
  SerializableMethodInfo {
249
255
  receiver_class: "Array".to_string(),
250
256
  method_name: "first".to_string(),
251
257
  return_type_str: "Object".to_string(),
258
+ block_param_types: None,
252
259
  },
253
260
  ],
254
261
  timestamp: SystemTime::now(),
data/rust/src/checker.rs CHANGED
@@ -1,7 +1,7 @@
1
1
  use crate::analyzer::AstInstaller;
2
2
  use crate::diagnostics::Diagnostic;
3
3
  use crate::env::{GlobalEnv, LocalEnv};
4
- use crate::parser;
4
+ use crate::parser::ParseSession;
5
5
  use anyhow::{Context, Result};
6
6
  use std::path::Path;
7
7
 
@@ -30,8 +30,12 @@ impl FileChecker {
30
30
  let source = std::fs::read_to_string(file_path)
31
31
  .with_context(|| format!("Failed to read {}", file_path.display()))?;
32
32
 
33
+ // Create ParseSession (arena allocator) for this check
34
+ let session = ParseSession::new();
35
+
33
36
  // Parse file
34
- let parse_result = parser::parse_ruby_file(file_path)
37
+ let parse_result = session
38
+ .parse_source(&source, &file_path.to_string_lossy())
35
39
  .with_context(|| format!("Failed to parse {}", file_path.display()))?;
36
40
 
37
41
  // Create fresh GlobalEnv for this analysis
@@ -56,12 +60,13 @@ impl FileChecker {
56
60
  let diagnostics = collect_diagnostics(&genv, file_path);
57
61
 
58
62
  Ok(diagnostics)
59
- }
63
+ } // session drops here, freeing arena memory
60
64
  }
61
65
 
62
66
  /// Load RBS methods from cache (CLI mode without Ruby runtime)
63
67
  fn load_rbs_from_cache(genv: &mut GlobalEnv) -> Result<()> {
64
68
  use crate::cache::RbsCache;
69
+ use crate::rbs::converter::RbsTypeConverter;
65
70
  use crate::types::Type;
66
71
 
67
72
  let cache = RbsCache::load().context(
@@ -70,16 +75,23 @@ fn load_rbs_from_cache(genv: &mut GlobalEnv) -> Result<()> {
70
75
  )?;
71
76
 
72
77
  let methods = cache.methods();
73
- eprintln!("Loaded {} methods from cache", methods.len());
74
78
 
75
79
  for method_info in methods {
76
- let receiver_type = Type::Instance {
77
- class_name: method_info.receiver_class.clone(),
78
- };
79
- genv.register_builtin_method(
80
+ let receiver_type = Type::instance(&method_info.receiver_class);
81
+
82
+ // Convert block param type strings to Type enums
83
+ let block_param_types = method_info.block_param_types.as_ref().map(|types| {
84
+ types
85
+ .iter()
86
+ .map(|s| RbsTypeConverter::parse(s))
87
+ .collect()
88
+ });
89
+
90
+ genv.register_builtin_method_with_block(
80
91
  receiver_type,
81
92
  &method_info.method_name,
82
93
  method_info.return_type(),
94
+ block_param_types,
83
95
  );
84
96
  }
85
97
 
@@ -115,6 +115,11 @@ impl GlobalEnv {
115
115
  }
116
116
  }
117
117
  }
118
+
119
+ // Reschedule boxes that need to run again
120
+ for box_id in changes.take_reschedule_boxes() {
121
+ self.box_manager.add_run(box_id);
122
+ }
118
123
  }
119
124
 
120
125
  /// Execute all Boxes
@@ -145,6 +150,30 @@ impl GlobalEnv {
145
150
  self.method_registry.register(recv_ty, method_name, ret_ty);
146
151
  }
147
152
 
153
+ /// Register built-in method with block parameter types
154
+ pub fn register_builtin_method_with_block(
155
+ &mut self,
156
+ recv_ty: Type,
157
+ method_name: &str,
158
+ ret_ty: Type,
159
+ block_param_types: Option<Vec<Type>>,
160
+ ) {
161
+ self.method_registry
162
+ .register_with_block(recv_ty, method_name, ret_ty, block_param_types);
163
+ }
164
+
165
+ /// Register a user-defined method (return type resolved via graph)
166
+ pub fn register_user_method(
167
+ &mut self,
168
+ recv_ty: Type,
169
+ method_name: &str,
170
+ return_vertex: VertexId,
171
+ param_vertices: Vec<VertexId>,
172
+ ) {
173
+ self.method_registry
174
+ .register_user_method(recv_ty, method_name, return_vertex, param_vertices);
175
+ }
176
+
148
177
  // ===== Type Errors =====
149
178
 
150
179
  /// Record a type error (undefined method)
@@ -170,12 +199,23 @@ impl GlobalEnv {
170
199
  scope_id
171
200
  }
172
201
 
202
+ /// Enter a module scope
203
+ pub fn enter_module(&mut self, name: String) -> ScopeId {
204
+ let scope_id = self.scope_manager.new_scope(ScopeKind::Module { name });
205
+ self.scope_manager.enter_scope(scope_id);
206
+ scope_id
207
+ }
208
+
173
209
  /// Enter a method scope
174
210
  pub fn enter_method(&mut self, name: String) -> ScopeId {
175
- let class_name = self.scope_manager.current_class_name();
211
+ // Look for class or module context
212
+ let receiver_type = self
213
+ .scope_manager
214
+ .current_class_name()
215
+ .or_else(|| self.scope_manager.current_module_name());
176
216
  let scope_id = self.scope_manager.new_scope(ScopeKind::Method {
177
217
  name,
178
- receiver_type: class_name,
218
+ receiver_type,
179
219
  });
180
220
  self.scope_manager.enter_scope(scope_id);
181
221
  scope_id
@@ -1,5 +1,6 @@
1
1
  //! Method registration and resolution
2
2
 
3
+ use crate::graph::VertexId;
3
4
  use crate::types::Type;
4
5
  use std::collections::HashMap;
5
6
 
@@ -7,6 +8,9 @@ use std::collections::HashMap;
7
8
  #[derive(Debug, Clone)]
8
9
  pub struct MethodInfo {
9
10
  pub return_type: Type,
11
+ pub block_param_types: Option<Vec<Type>>,
12
+ pub return_vertex: Option<VertexId>,
13
+ pub param_vertices: Option<Vec<VertexId>>,
10
14
  }
11
15
 
12
16
  /// Registry for method definitions
@@ -25,18 +29,67 @@ impl MethodRegistry {
25
29
 
26
30
  /// Register a method for a receiver type
27
31
  pub fn register(&mut self, recv_ty: Type, method_name: &str, ret_ty: Type) {
32
+ self.register_with_block(recv_ty, method_name, ret_ty, None);
33
+ }
34
+
35
+ /// Register a method with block parameter types
36
+ pub fn register_with_block(
37
+ &mut self,
38
+ recv_ty: Type,
39
+ method_name: &str,
40
+ ret_ty: Type,
41
+ block_param_types: Option<Vec<Type>>,
42
+ ) {
28
43
  self.methods.insert(
29
44
  (recv_ty, method_name.to_string()),
30
45
  MethodInfo {
31
46
  return_type: ret_ty,
47
+ block_param_types,
48
+ return_vertex: None,
49
+ param_vertices: None,
50
+ },
51
+ );
52
+ }
53
+
54
+ /// Register a user-defined method (return type resolved via graph)
55
+ pub fn register_user_method(
56
+ &mut self,
57
+ recv_ty: Type,
58
+ method_name: &str,
59
+ return_vertex: VertexId,
60
+ param_vertices: Vec<VertexId>,
61
+ ) {
62
+ self.methods.insert(
63
+ (recv_ty, method_name.to_string()),
64
+ MethodInfo {
65
+ return_type: Type::Bot,
66
+ block_param_types: None,
67
+ return_vertex: Some(return_vertex),
68
+ param_vertices: Some(param_vertices),
32
69
  },
33
70
  );
34
71
  }
35
72
 
36
73
  /// Resolve a method for a receiver type
74
+ ///
75
+ /// For generic types like `Array[Integer]`, first tries exact match,
76
+ /// then falls back to base class match (`Array`).
37
77
  pub fn resolve(&self, recv_ty: &Type, method_name: &str) -> Option<&MethodInfo> {
38
- self.methods
78
+ // First, try exact match
79
+ if let Some(info) = self
80
+ .methods
39
81
  .get(&(recv_ty.clone(), method_name.to_string()))
82
+ {
83
+ return Some(info);
84
+ }
85
+
86
+ // For generic types, fall back to base class
87
+ if let Type::Generic { name, .. } = recv_ty {
88
+ let base_type = Type::Instance { name: name.clone() };
89
+ return self.methods.get(&(base_type, method_name.to_string()));
90
+ }
91
+
92
+ None
40
93
  }
41
94
  }
42
95
 
@@ -50,9 +103,7 @@ mod tests {
50
103
  registry.register(Type::string(), "length", Type::integer());
51
104
 
52
105
  let info = registry.resolve(&Type::string(), "length").unwrap();
53
- assert!(
54
- matches!(info.return_type, Type::Instance { ref class_name, .. } if class_name == "Integer")
55
- );
106
+ assert_eq!(info.return_type.base_class_name(), Some("Integer"));
56
107
  }
57
108
 
58
109
  #[test]
@@ -60,4 +111,35 @@ mod tests {
60
111
  let registry = MethodRegistry::new();
61
112
  assert!(registry.resolve(&Type::string(), "unknown").is_none());
62
113
  }
114
+
115
+ #[test]
116
+ fn test_register_user_method_and_resolve() {
117
+ let mut registry = MethodRegistry::new();
118
+ let return_vtx = VertexId(42);
119
+ registry.register_user_method(Type::instance("User"), "name", return_vtx, vec![]);
120
+
121
+ let info = registry.resolve(&Type::instance("User"), "name").unwrap();
122
+ assert_eq!(info.return_vertex, Some(VertexId(42)));
123
+ assert_eq!(info.return_type, Type::Bot);
124
+ }
125
+
126
+ #[test]
127
+ fn test_register_user_method_with_param_vertices() {
128
+ let mut registry = MethodRegistry::new();
129
+ let return_vtx = VertexId(10);
130
+ let param_vtxs = vec![VertexId(20), VertexId(21)];
131
+ registry.register_user_method(
132
+ Type::instance("Calc"),
133
+ "add",
134
+ return_vtx,
135
+ param_vtxs,
136
+ );
137
+
138
+ let info = registry.resolve(&Type::instance("Calc"), "add").unwrap();
139
+ assert_eq!(info.return_vertex, Some(VertexId(10)));
140
+ let pvs = info.param_vertices.as_ref().unwrap();
141
+ assert_eq!(pvs.len(), 2);
142
+ assert_eq!(pvs[0], VertexId(20));
143
+ assert_eq!(pvs[1], VertexId(21));
144
+ }
63
145
  }
data/rust/src/env/mod.rs CHANGED
@@ -13,3 +13,4 @@ pub mod vertex_manager;
13
13
 
14
14
  pub use global_env::GlobalEnv;
15
15
  pub use local_env::LocalEnv;
16
+ pub use scope::ScopeKind;