Skip to content

Commit 77f9456

Browse files
committed
Add AST
1 parent aab64ed commit 77f9456

3 files changed

Lines changed: 282 additions & 1 deletion

File tree

lib/rbs/ast/ruby/declarations.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,55 @@ def name_location
129129
rbs_location(node.constant_path.location)
130130
end
131131
end
132+
133+
class ConstantDecl < Base
134+
attr_reader :leading_comment
135+
attr_reader :constant_name
136+
attr_reader :node
137+
attr_reader :type_annotation
138+
139+
def initialize(buffer, constant_name, node, leading_comment, type_annotation)
140+
super(buffer)
141+
@constant_name = constant_name
142+
@node = node
143+
@leading_comment = leading_comment
144+
@type_annotation = type_annotation
145+
end
146+
147+
def location
148+
rbs_location(node.location)
149+
end
150+
151+
def name_location
152+
case node
153+
when Prism::ConstantWriteNode
154+
rbs_location(node.name_loc)
155+
when Prism::ConstantPathWriteNode
156+
rbs_location(node.target.location)
157+
end
158+
end
159+
160+
def type
161+
return type_annotation.type if type_annotation
162+
163+
case node.value
164+
when Prism::IntegerNode
165+
BuiltinNames::Integer.instance_type
166+
when Prism::FloatNode
167+
BuiltinNames::Float.instance_type
168+
when Prism::StringNode
169+
BuiltinNames::String.instance_type
170+
when Prism::TrueNode, Prism::FalseNode
171+
Types::Bases::Bool.new(location: nil)
172+
when Prism::SymbolNode
173+
BuiltinNames::Symbol.instance_type
174+
when Prism::NilNode
175+
Types::Bases::Nil.new(location: nil)
176+
else
177+
Types::Bases::Any.new(location: nil)
178+
end
179+
end
180+
end
132181
end
133182
end
134183
end

sig/ast/ruby/declarations.rbs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module RBS
22
module AST
33
module Ruby
44
module Declarations
5-
type t = ClassDecl | ModuleDecl
5+
type t = ClassDecl | ModuleDecl | ConstantDecl
66

77
class Base
88
attr_reader buffer: Buffer
@@ -78,6 +78,31 @@ module RBS
7878

7979
def name_location: () -> Location
8080
end
81+
82+
class ConstantDecl < Base
83+
type node = Prism::ConstantWriteNode | Prism::ConstantPathWriteNode
84+
85+
attr_reader leading_comment: CommentBlock?
86+
87+
attr_reader constant_name: TypeName
88+
89+
attr_reader node: node
90+
91+
attr_reader type_annotation: Annotations::NodeTypeAssertion?
92+
93+
def initialize: (Buffer, TypeName, node, CommentBlock?, Annotations::NodeTypeAssertion?) -> void
94+
95+
def location: () -> Location
96+
97+
def name_location: () -> Location
98+
99+
# Returns the type of the constant
100+
#
101+
# - When type_anntoation is given, it returns the type from the annotation.
102+
# - When type_annotation is not given, it returns infered type from the right-hand-side of the constant assignment
103+
# - Or it returns `untyped` type
104+
def type: () -> Types::t
105+
end
81106
end
82107
end
83108
end
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
require "test_helper"
2+
3+
class RBS::AST::Ruby::DeclarationsTest < Test::Unit::TestCase
4+
include TestHelper
5+
6+
include RBS::AST::Ruby
7+
8+
def parse_ruby(source)
9+
buffer = RBS::Buffer.new(name: Pathname("a.rb"), content: source)
10+
[buffer, Prism.parse(source)]
11+
end
12+
13+
def test_constant_decl__literal_type_integer
14+
buffer, result = parse_ruby("FOO = 42")
15+
constant_node = result.value.statements.body[0]
16+
17+
decl = Declarations::ConstantDecl.new(
18+
buffer,
19+
RBS::TypeName.new(name: :FOO, namespace: RBS::Namespace.empty),
20+
constant_node,
21+
nil,
22+
nil
23+
)
24+
25+
assert_equal :FOO, decl.constant_name.name
26+
assert_equal "::Integer", decl.type.to_s
27+
assert_nil decl.type_annotation
28+
end
29+
30+
def test_constant_decl__literal_type_float
31+
buffer, result = parse_ruby("PI = 3.14")
32+
constant_node = result.value.statements.body[0]
33+
34+
decl = Declarations::ConstantDecl.new(
35+
buffer,
36+
RBS::TypeName.new(name: :PI, namespace: RBS::Namespace.empty),
37+
constant_node,
38+
nil,
39+
nil
40+
)
41+
42+
assert_equal :PI, decl.constant_name.name
43+
assert_equal "::Float", decl.type.to_s
44+
end
45+
46+
def test_constant_decl__literal_type_string
47+
buffer, result = parse_ruby('NAME = "test"')
48+
constant_node = result.value.statements.body[0]
49+
50+
decl = Declarations::ConstantDecl.new(
51+
buffer,
52+
RBS::TypeName.new(name: :NAME, namespace: RBS::Namespace.empty),
53+
constant_node,
54+
nil,
55+
nil
56+
)
57+
58+
assert_equal :NAME, decl.constant_name.name
59+
assert_equal "::String", decl.type.to_s
60+
end
61+
62+
def test_constant_decl__literal_type_boolean
63+
buffer, result = parse_ruby("ENABLED = true")
64+
constant_node = result.value.statements.body[0]
65+
66+
decl = Declarations::ConstantDecl.new(
67+
buffer,
68+
RBS::TypeName.new(name: :ENABLED, namespace: RBS::Namespace.empty),
69+
constant_node,
70+
nil,
71+
nil
72+
)
73+
74+
assert_equal :ENABLED, decl.constant_name.name
75+
assert_equal "bool", decl.type.to_s
76+
end
77+
78+
def test_constant_decl__literal_type_symbol
79+
buffer, result = parse_ruby("STATUS = :ready")
80+
constant_node = result.value.statements.body[0]
81+
82+
decl = Declarations::ConstantDecl.new(
83+
buffer,
84+
RBS::TypeName.new(name: :STATUS, namespace: RBS::Namespace.empty),
85+
constant_node,
86+
nil,
87+
nil
88+
)
89+
90+
assert_equal :STATUS, decl.constant_name.name
91+
assert_equal "::Symbol", decl.type.to_s
92+
end
93+
94+
def test_constant_decl__literal_type_nil
95+
buffer, result = parse_ruby("NOTHING = nil")
96+
constant_node = result.value.statements.body[0]
97+
98+
decl = Declarations::ConstantDecl.new(
99+
buffer,
100+
RBS::TypeName.new(name: :NOTHING, namespace: RBS::Namespace.empty),
101+
constant_node,
102+
nil,
103+
nil
104+
)
105+
106+
assert_equal :NOTHING, decl.constant_name.name
107+
assert_equal "nil", decl.type.to_s
108+
end
109+
110+
def test_constant_decl__complex_expression_fallback
111+
buffer, result = parse_ruby("COMPLEX = [1, 2, 3]")
112+
constant_node = result.value.statements.body[0]
113+
114+
decl = Declarations::ConstantDecl.new(
115+
buffer,
116+
RBS::TypeName.new(name: :COMPLEX, namespace: RBS::Namespace.empty),
117+
constant_node,
118+
nil,
119+
nil
120+
)
121+
122+
assert_equal :COMPLEX, decl.constant_name.name
123+
assert_equal "untyped", decl.type.to_s
124+
end
125+
126+
def test_constant_decl__with_explicit_type_annotation
127+
buffer, result = parse_ruby("ITEMS = []")
128+
constant_node = result.value.statements.body[0]
129+
130+
# Create explicit type annotation
131+
type = RBS::Types::ClassInstance.new(
132+
name: RBS::TypeName.new(name: :Array, namespace: RBS::Namespace.empty),
133+
args: [RBS::Types::ClassInstance.new(
134+
name: RBS::TypeName.new(name: :String, namespace: RBS::Namespace.empty),
135+
args: [],
136+
location: nil
137+
)],
138+
location: nil
139+
)
140+
annotation = Annotations::NodeTypeAssertion.new(
141+
location: RBS::Location.new(buffer, 0, 10),
142+
prefix_location: RBS::Location.new(buffer, 0, 4),
143+
type: type
144+
)
145+
146+
decl = Declarations::ConstantDecl.new(
147+
buffer,
148+
RBS::TypeName.new(name: :ITEMS, namespace: RBS::Namespace.empty),
149+
constant_node,
150+
nil,
151+
annotation
152+
)
153+
154+
assert_equal :ITEMS, decl.constant_name.name
155+
assert_equal "Array[String]", decl.type.to_s
156+
assert_not_nil decl.type_annotation
157+
end
158+
159+
def test_constant_decl__type_annotation_takes_precedence
160+
buffer, result = parse_ruby("COUNT = 42")
161+
constant_node = result.value.statements.body[0]
162+
163+
# Create explicit type annotation that overrides the inferred Integer type
164+
type = RBS::Types::ClassInstance.new(
165+
name: RBS::TypeName.new(name: :Float, namespace: RBS::Namespace.empty),
166+
args: [],
167+
location: nil
168+
)
169+
annotation = Annotations::NodeTypeAssertion.new(
170+
location: RBS::Location.new(buffer, 0, 10),
171+
prefix_location: RBS::Location.new(buffer, 0, 4),
172+
type: type
173+
)
174+
175+
decl = Declarations::ConstantDecl.new(
176+
buffer,
177+
RBS::TypeName.new(name: :COUNT, namespace: RBS::Namespace.empty),
178+
constant_node,
179+
nil,
180+
annotation
181+
)
182+
183+
assert_equal :COUNT, decl.constant_name.name
184+
assert_equal "Float", decl.type.to_s # Should use annotation, not infer Integer
185+
end
186+
187+
def test_constant_decl__with_leading_comment
188+
buffer, result = parse_ruby("FOO = 42")
189+
constant_node = result.value.statements.body[0]
190+
191+
# Create a leading comment
192+
leading_comment = CommentBlock.new(buffer, [])
193+
194+
decl = Declarations::ConstantDecl.new(
195+
buffer,
196+
RBS::TypeName.new(name: :FOO, namespace: RBS::Namespace.empty),
197+
constant_node,
198+
leading_comment,
199+
nil
200+
)
201+
202+
assert_equal :FOO, decl.constant_name.name
203+
assert_equal "::Integer", decl.type.to_s
204+
assert_equal leading_comment, decl.leading_comment
205+
assert_nil decl.type_annotation
206+
end
207+
end

0 commit comments

Comments
 (0)