method-ray 0.1.1

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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +21 -0
  4. data/README.md +39 -0
  5. data/exe/methodray +7 -0
  6. data/ext/Cargo.toml +24 -0
  7. data/ext/extconf.rb +40 -0
  8. data/ext/src/cli.rs +33 -0
  9. data/ext/src/lib.rs +79 -0
  10. data/lib/methodray/cli.rb +28 -0
  11. data/lib/methodray/commands.rb +78 -0
  12. data/lib/methodray/version.rb +5 -0
  13. data/lib/methodray.rb +9 -0
  14. data/rust/Cargo.toml +39 -0
  15. data/rust/src/analyzer/calls.rs +56 -0
  16. data/rust/src/analyzer/definitions.rs +70 -0
  17. data/rust/src/analyzer/dispatch.rs +134 -0
  18. data/rust/src/analyzer/install.rs +226 -0
  19. data/rust/src/analyzer/literals.rs +85 -0
  20. data/rust/src/analyzer/mod.rs +11 -0
  21. data/rust/src/analyzer/tests/integration_test.rs +136 -0
  22. data/rust/src/analyzer/tests/mod.rs +1 -0
  23. data/rust/src/analyzer/variables.rs +76 -0
  24. data/rust/src/cache/mod.rs +3 -0
  25. data/rust/src/cache/rbs_cache.rs +158 -0
  26. data/rust/src/checker.rs +139 -0
  27. data/rust/src/cli/args.rs +40 -0
  28. data/rust/src/cli/commands.rs +139 -0
  29. data/rust/src/cli/mod.rs +6 -0
  30. data/rust/src/diagnostics/diagnostic.rs +125 -0
  31. data/rust/src/diagnostics/formatter.rs +119 -0
  32. data/rust/src/diagnostics/mod.rs +5 -0
  33. data/rust/src/env/box_manager.rs +121 -0
  34. data/rust/src/env/global_env.rs +279 -0
  35. data/rust/src/env/local_env.rs +58 -0
  36. data/rust/src/env/method_registry.rs +63 -0
  37. data/rust/src/env/mod.rs +15 -0
  38. data/rust/src/env/scope.rs +330 -0
  39. data/rust/src/env/type_error.rs +23 -0
  40. data/rust/src/env/vertex_manager.rs +195 -0
  41. data/rust/src/graph/box.rs +157 -0
  42. data/rust/src/graph/change_set.rs +115 -0
  43. data/rust/src/graph/mod.rs +7 -0
  44. data/rust/src/graph/vertex.rs +167 -0
  45. data/rust/src/lib.rs +24 -0
  46. data/rust/src/lsp/diagnostics.rs +133 -0
  47. data/rust/src/lsp/main.rs +8 -0
  48. data/rust/src/lsp/mod.rs +4 -0
  49. data/rust/src/lsp/server.rs +138 -0
  50. data/rust/src/main.rs +46 -0
  51. data/rust/src/parser.rs +96 -0
  52. data/rust/src/rbs/converter.rs +82 -0
  53. data/rust/src/rbs/error.rs +37 -0
  54. data/rust/src/rbs/loader.rs +183 -0
  55. data/rust/src/rbs/mod.rs +15 -0
  56. data/rust/src/source_map.rs +102 -0
  57. data/rust/src/types.rs +75 -0
  58. metadata +119 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8b9351553b262a65f3b398bbb715a2e9600f893e1ff12703c1e903942798201a
4
+ data.tar.gz: 677baf919f242bc8e47a4b252f0ed929b64548bc6f873c58bb954b52515e0042
5
+ SHA512:
6
+ metadata.gz: b87a06709a8bb8dd57087f260a07a045c7f34f572d0595a4669d3d569d609d4076cfe7b2ae8897e67edb5e3d65c68a1259151e9ce40ce9b79f598430949c0450
7
+ data.tar.gz: 33703cf7ae417499620b85081e0e95f4bd5049e91cb40c8e3f9c8a5f4f4ed60d2cdaa57475022abd090f7ae3bc5314eb37d27a8275cda6f35769807305bdb704
data/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.1] - 2025-01-19
9
+
10
+ ### Added
11
+
12
+ - aarch64-linux (ARM64 Linux) support
13
+ - macOS support (arm64-darwin)
14
+
15
+ ## [0.1.0] - 2025-01-18
16
+
17
+ ### Added
18
+
19
+ - Initial release
20
+ - `methodray check` - Static type checking for Ruby files
21
+
22
+ [0.1.1]: https://github.com/dak2/method-ray/releases/tag/v0.1.1
23
+ [0.1.0]: https://github.com/dak2/method-ray/releases/tag/v0.1.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daichi Kamiyama
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Method-Ray
2
+
3
+ A fast static callable method checker for Ruby code.
4
+
5
+ ## Requirements
6
+
7
+ Method-Ray supports Ruby 3.4 or later.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ gem install methodray
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ### VSCode Extension
18
+
19
+ 1. Install the [Method-Ray VSCode extension](https://github.com/dak2/method-ray-vscode)
20
+ 2. Open a Ruby file in VSCode
21
+ 3. Errors will be highlighted automatically
22
+
23
+ ### CLI
24
+
25
+ ```bash
26
+ # Check a single file
27
+ bundle exec methodray check app/models/user.rb
28
+
29
+ # Watch mode - auto re-check on file changes
30
+ bundle exec methodray watch app/models/user.rb
31
+ ```
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at this repository!
36
+
37
+ ## License
38
+
39
+ MIT License. See [LICENSE](https://github.com/dak2/method-ray/blob/main/LICENSE) file for details.
data/exe/methodray ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/methodray'
5
+ require_relative '../lib/methodray/cli'
6
+
7
+ MethodRay::CLI.start(ARGV)
data/ext/Cargo.toml ADDED
@@ -0,0 +1,24 @@
1
+ [package]
2
+ name = "methodray"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ [lib]
7
+ crate-type = ["cdylib"]
8
+ name = "methodray"
9
+ path = "src/lib.rs"
10
+
11
+ [[bin]]
12
+ name = "methodray-cli"
13
+ path = "src/cli.rs"
14
+ required-features = ["cli"]
15
+
16
+ [features]
17
+ default = []
18
+ cli = ["methodray-core/cli", "dep:clap", "dep:anyhow"]
19
+
20
+ [dependencies]
21
+ methodray-core = { path = "../rust", features = ["ruby-ffi"] }
22
+ magnus = "0.8"
23
+ clap = { version = "4", features = ["derive"], optional = true }
24
+ anyhow = { version = "1", optional = true }
data/ext/extconf.rb ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mkmf'
4
+
5
+ # Check if precompiled binaries exist (platform gem)
6
+ lib_dir = File.expand_path('../lib/methodray', __dir__)
7
+ cli_binary = File.join(lib_dir, 'methodray-cli')
8
+ extension = Dir.glob(File.join(lib_dir, 'methodray.{bundle,so,dll}')).first
9
+
10
+ if File.exist?(cli_binary) && extension
11
+ # Precompiled gem - create dummy Makefile
12
+ File.write('Makefile', "install:\n\t@echo 'Using precompiled binaries'\n")
13
+ exit 0
14
+ end
15
+
16
+ # Source gem - build from Rust
17
+ require 'rb_sys/mkmf'
18
+
19
+ # Build the Ruby FFI extension
20
+ create_rust_makefile('methodray/methodray')
21
+
22
+ # Project root directory
23
+ project_root = File.expand_path('..', __dir__)
24
+ target_dir = File.join(project_root, 'target', 'release')
25
+
26
+ # Append CLI binary build to the generated Makefile
27
+ File.open('Makefile', 'a') do |f|
28
+ f.puts <<~MAKEFILE
29
+
30
+ # Build CLI binary after the extension
31
+ install: install-cli
32
+
33
+ install-cli:
34
+ \t@echo "Building CLI binary..."
35
+ \tcd #{__dir__} && cargo build --release --features cli --bin methodray-cli
36
+ \t@mkdir -p $(DESTDIR)$(sitearchdir)
37
+ \tcp #{target_dir}/methodray-cli $(DESTDIR)$(sitearchdir)/methodray-cli
38
+ \tchmod 755 $(DESTDIR)$(sitearchdir)/methodray-cli
39
+ MAKEFILE
40
+ end
data/ext/src/cli.rs ADDED
@@ -0,0 +1,33 @@
1
+ //! MethodRay CLI entry point for gem distribution
2
+
3
+ use anyhow::Result;
4
+ use clap::Parser;
5
+ use methodray_core::cli::{commands, Cli, Commands};
6
+
7
+ fn main() -> Result<()> {
8
+ let cli = Cli::parse();
9
+
10
+ match cli.command {
11
+ Commands::Check { file, verbose } => {
12
+ if let Some(file_path) = file {
13
+ let success = commands::check_single_file(&file_path, verbose)?;
14
+ if !success {
15
+ std::process::exit(1);
16
+ }
17
+ } else {
18
+ commands::check_project(verbose)?;
19
+ }
20
+ }
21
+ Commands::Watch { file } => {
22
+ commands::watch_file(&file)?;
23
+ }
24
+ Commands::Version => {
25
+ commands::print_version();
26
+ }
27
+ Commands::ClearCache => {
28
+ commands::clear_cache()?;
29
+ }
30
+ }
31
+
32
+ Ok(())
33
+ }
data/ext/src/lib.rs ADDED
@@ -0,0 +1,79 @@
1
+ //! Ruby FFI bindings for Method-Ray
2
+ //!
3
+ //! This module provides the Ruby gem interface using magnus.
4
+
5
+ use magnus::{function, method, prelude::*, Error, Ruby};
6
+ use methodray_core::{
7
+ analyzer::AstInstaller,
8
+ env::{GlobalEnv, LocalEnv},
9
+ parser, rbs,
10
+ };
11
+
12
+ #[magnus::wrap(class = "MethodRay::Analyzer")]
13
+ pub struct Analyzer {
14
+ #[allow(dead_code)]
15
+ path: String,
16
+ }
17
+
18
+ impl Analyzer {
19
+ fn new(path: String) -> Self {
20
+ Self { path }
21
+ }
22
+
23
+ fn version(&self) -> String {
24
+ "0.1.0".to_string()
25
+ }
26
+
27
+ /// Execute type inference
28
+ fn infer_types(&self, source: String) -> Result<String, Error> {
29
+ // Parse
30
+ let parse_result =
31
+ parser::parse_ruby_source(&source, "source.rb".to_string()).map_err(|e| {
32
+ let ruby = unsafe { Ruby::get_unchecked() };
33
+ Error::new(ruby.exception_runtime_error(), e.to_string())
34
+ })?;
35
+
36
+ // Build graph
37
+ let mut genv = GlobalEnv::new();
38
+
39
+ // Register built-in methods from RBS
40
+ let ruby = unsafe { Ruby::get_unchecked() };
41
+ rbs::register_rbs_methods(&mut genv, &ruby)?;
42
+
43
+ let mut lenv = LocalEnv::new();
44
+ let mut installer = AstInstaller::new(&mut genv, &mut lenv, &source);
45
+
46
+ // Process AST
47
+ let root = parse_result.node();
48
+ if let Some(program_node) = root.as_program_node() {
49
+ let statements = program_node.statements();
50
+ for stmt in &statements.body() {
51
+ installer.install_node(&stmt);
52
+ }
53
+ }
54
+
55
+ installer.finish();
56
+
57
+ // Return results as string
58
+ let mut results = Vec::new();
59
+ for (var_name, vtx_id) in lenv.all_vars() {
60
+ if let Some(vtx) = genv.get_vertex(*vtx_id) {
61
+ results.push(format!("{}: {}", var_name, vtx.show()));
62
+ }
63
+ }
64
+
65
+ Ok(results.join("\n"))
66
+ }
67
+ }
68
+
69
+ #[magnus::init]
70
+ fn init(ruby: &Ruby) -> Result<(), Error> {
71
+ let module = ruby.define_module("MethodRay")?;
72
+ let class = module.define_class("Analyzer", ruby.class_object())?;
73
+
74
+ class.define_singleton_method("new", function!(Analyzer::new, 1))?;
75
+ class.define_method("version", method!(Analyzer::version, 0))?;
76
+ class.define_method("infer_types", method!(Analyzer::infer_types, 1))?;
77
+
78
+ Ok(())
79
+ }
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'commands'
4
+
5
+ module MethodRay
6
+ class CLI
7
+ def self.start(args)
8
+ command = args.shift
9
+
10
+ case command
11
+ when 'help', '--help', '-h', nil
12
+ Commands.help
13
+ when 'version', '--version', '-v'
14
+ Commands.version
15
+ when 'check'
16
+ Commands.check(args)
17
+ when 'watch'
18
+ Commands.watch(args)
19
+ when 'clear-cache'
20
+ Commands.clear_cache(args)
21
+ else
22
+ puts "Unknown command: #{command}"
23
+ Commands.help
24
+ exit 1
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MethodRay
4
+ module Commands
5
+ COMMANDS_DIR = __dir__
6
+
7
+ class << self
8
+ def help
9
+ puts <<~HELP
10
+ MethodRay v#{MethodRay::VERSION} - A fast static analysis tool for Ruby methods.
11
+
12
+ Usage:
13
+ methodray help # Show this help
14
+ methodray version # Show version
15
+ methodray check [FILE] [OPTIONS] # Type check a Ruby file
16
+ methodray watch FILE # Watch file for changes and auto-check
17
+ methodray clear-cache # Clear RBS method cache
18
+
19
+ Examples:
20
+ methodray check app/models/user.rb
21
+ methodray watch app/models/user.rb
22
+ HELP
23
+ end
24
+
25
+ def version
26
+ puts "MethodRay v#{MethodRay::VERSION}"
27
+ end
28
+
29
+ def check(args)
30
+ exec_rust_cli('check', args)
31
+ end
32
+
33
+ def watch(args)
34
+ exec_rust_cli('watch', args)
35
+ end
36
+
37
+ def clear_cache(args)
38
+ exec_rust_cli('clear-cache', args)
39
+ end
40
+
41
+ private
42
+
43
+ def exec_rust_cli(command, args)
44
+ binary_path = find_rust_binary
45
+
46
+ unless binary_path
47
+ warn 'Error: CLI binary not found.'
48
+ warn ''
49
+ warn 'For development, build with:'
50
+ warn ' cd rust && cargo build --release --bin methodray --features cli'
51
+ warn ''
52
+ warn 'If installed via gem, this might be a platform compatibility issue.'
53
+ warn 'Please report at: https://github.com/dak2/method-ray/issues'
54
+ exit 1
55
+ end
56
+
57
+ exec(binary_path, command, *args)
58
+ end
59
+
60
+ def find_rust_binary
61
+ # Platform-specific binary name
62
+ cli_binary = Gem.win_platform? ? 'methodray-cli.exe' : 'methodray-cli'
63
+ legacy_binary = Gem.win_platform? ? 'methodray.exe' : 'methodray'
64
+
65
+ candidates = [
66
+ # CLI binary built during gem install (lib/methodray directory)
67
+ File.expand_path(cli_binary, COMMANDS_DIR),
68
+ # Development: target/release (project root)
69
+ File.expand_path("../../target/release/#{cli_binary}", COMMANDS_DIR),
70
+ # Development: rust/target/release (legacy standalone binary)
71
+ File.expand_path("../../rust/target/release/#{legacy_binary}", COMMANDS_DIR)
72
+ ]
73
+
74
+ candidates.find { |path| File.executable?(path) }
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MethodRay
4
+ VERSION = '0.1.1'
5
+ end
data/lib/methodray.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rbs'
4
+ require_relative 'methodray/version'
5
+ require_relative 'methodray/methodray' # ネイティブ拡張
6
+
7
+ module MethodRay
8
+ class Error < StandardError; end
9
+ end
data/rust/Cargo.toml ADDED
@@ -0,0 +1,39 @@
1
+ [package]
2
+ name = "methodray-core"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ [lib]
7
+ name = "methodray_core"
8
+ path = "src/lib.rs"
9
+
10
+ [[bin]]
11
+ name = "methodray"
12
+ path = "src/main.rs"
13
+ required-features = ["cli"]
14
+
15
+ [[bin]]
16
+ name = "methodray-lsp"
17
+ path = "src/lsp/main.rs"
18
+ required-features = ["lsp"]
19
+
20
+ [dependencies]
21
+ rayon = "1.10"
22
+ walkdir = "2.5"
23
+ anyhow = "1.0"
24
+ thiserror = "1.0"
25
+ ruby-prism = "1.7"
26
+ serde = { version = "1.0", features = ["derive"] }
27
+ bincode = "1.3"
28
+ dirs = "5.0"
29
+ clap = { version = "4.5", features = ["derive"], optional = true }
30
+ notify = { version = "6.1", optional = true }
31
+ tower-lsp = { version = "0.20", optional = true }
32
+ tokio = { version = "1.0", features = ["full"], optional = true }
33
+ tempfile = { version = "3.13", optional = true }
34
+ magnus = { version = "0.8", optional = true }
35
+
36
+ [features]
37
+ cli = ["clap", "notify"]
38
+ lsp = ["tower-lsp", "tokio", "tempfile"]
39
+ ruby-ffi = ["magnus"]
@@ -0,0 +1,56 @@
1
+ //! Method Call Handlers - Processing Ruby method calls
2
+ //!
3
+ //! This module is responsible for:
4
+ //! - Creating MethodCallBox for method invocations (x.upcase)
5
+ //! - Managing return value vertices
6
+ //! - Attaching source location for error reporting
7
+
8
+ use crate::env::GlobalEnv;
9
+ use crate::graph::{MethodCallBox, VertexId};
10
+ use crate::source_map::SourceLocation;
11
+
12
+ /// Install method call and return the return value's VertexId
13
+ pub fn install_method_call(
14
+ genv: &mut GlobalEnv,
15
+ recv_vtx: VertexId,
16
+ method_name: String,
17
+ location: Option<SourceLocation>,
18
+ ) -> VertexId {
19
+ // Create Vertex for return value
20
+ let ret_vtx = genv.new_vertex();
21
+
22
+ // Create MethodCallBox with location
23
+ let box_id = genv.alloc_box_id();
24
+ let call_box = MethodCallBox::new(box_id, recv_vtx, method_name, ret_vtx, location);
25
+ genv.register_box(box_id, Box::new(call_box));
26
+
27
+ ret_vtx
28
+ }
29
+
30
+ #[cfg(test)]
31
+ mod tests {
32
+ use super::*;
33
+ use crate::types::Type;
34
+
35
+ #[test]
36
+ fn test_install_method_call_creates_vertex() {
37
+ let mut genv = GlobalEnv::new();
38
+
39
+ let recv_vtx = genv.new_source(Type::string());
40
+ let ret_vtx = install_method_call(&mut genv, recv_vtx, "upcase".to_string(), None);
41
+
42
+ // Return vertex should exist
43
+ assert!(genv.get_vertex(ret_vtx).is_some());
44
+ }
45
+
46
+ #[test]
47
+ fn test_install_method_call_adds_box() {
48
+ let mut genv = GlobalEnv::new();
49
+
50
+ let recv_vtx = genv.new_source(Type::string());
51
+ let _ret_vtx = install_method_call(&mut genv, recv_vtx, "upcase".to_string(), None);
52
+
53
+ // Box should be added
54
+ assert_eq!(genv.box_count(), 1);
55
+ }
56
+ }
@@ -0,0 +1,70 @@
1
+ //! Definition Handlers - Processing Ruby class/method definitions
2
+ //!
3
+ //! This module is responsible for:
4
+ //! - Class definition scope management (class Foo ... end)
5
+ //! - Method definition scope management (def bar ... end)
6
+ //! - Extracting class names from AST nodes
7
+
8
+ use crate::env::GlobalEnv;
9
+
10
+ /// Install class definition
11
+ pub fn install_class(genv: &mut GlobalEnv, class_name: String) {
12
+ genv.enter_class(class_name);
13
+ }
14
+
15
+ /// Install method definition
16
+ pub fn install_method(genv: &mut GlobalEnv, method_name: String) {
17
+ genv.enter_method(method_name);
18
+ }
19
+
20
+ /// Exit current scope (class or method)
21
+ pub fn exit_scope(genv: &mut GlobalEnv) {
22
+ genv.exit_scope();
23
+ }
24
+
25
+ /// Extract class name from ClassNode
26
+ pub fn extract_class_name(class_node: &ruby_prism::ClassNode) -> String {
27
+ if let Some(constant_read) = class_node.constant_path().as_constant_read_node() {
28
+ String::from_utf8_lossy(constant_read.name().as_slice()).to_string()
29
+ } else {
30
+ "UnknownClass".to_string()
31
+ }
32
+ }
33
+
34
+ #[cfg(test)]
35
+ mod tests {
36
+ use super::*;
37
+
38
+ #[test]
39
+ fn test_enter_exit_class_scope() {
40
+ let mut genv = GlobalEnv::new();
41
+
42
+ install_class(&mut genv, "User".to_string());
43
+ assert_eq!(
44
+ genv.scope_manager.current_class_name(),
45
+ Some("User".to_string())
46
+ );
47
+
48
+ exit_scope(&mut genv);
49
+ assert_eq!(genv.scope_manager.current_class_name(), None);
50
+ }
51
+
52
+ #[test]
53
+ fn test_nested_method_scope() {
54
+ let mut genv = GlobalEnv::new();
55
+
56
+ install_class(&mut genv, "User".to_string());
57
+ install_method(&mut genv, "greet".to_string());
58
+
59
+ // Still in User class context
60
+ assert_eq!(
61
+ genv.scope_manager.current_class_name(),
62
+ Some("User".to_string())
63
+ );
64
+
65
+ exit_scope(&mut genv); // exit method
66
+ exit_scope(&mut genv); // exit class
67
+
68
+ assert_eq!(genv.scope_manager.current_class_name(), None);
69
+ }
70
+ }