gloss 0.0.6 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +41 -0
- data/.gloss.yml +2 -0
- data/Gemfile.lock +10 -10
- data/README.md +5 -7
- data/Rakefile +5 -3
- data/exe/gloss +10 -13
- data/ext/gloss/Makefile +6 -21
- data/ext/gloss/lib/cr_ruby.cr +5 -4
- data/ext/gloss/src/cr_ast.cr +61 -71
- data/ext/gloss/src/gloss.cr +7 -2
- data/ext/gloss/src/rb_ast.cr +37 -36
- data/gloss.gemspec +3 -3
- data/lib/gloss.rb +10 -7
- data/lib/gloss/cli.rb +61 -40
- data/lib/gloss/config.rb +14 -8
- data/lib/gloss/errors.rb +1 -1
- data/lib/gloss/logger.rb +6 -6
- data/lib/gloss/prog_loader.rb +144 -0
- data/lib/gloss/scope.rb +1 -1
- data/lib/gloss/source.rb +1 -1
- data/lib/gloss/type_checker.rb +60 -32
- data/lib/gloss/utils.rb +38 -0
- data/lib/gloss/version.rb +1 -1
- data/lib/gloss/{builder.rb → visitor.rb} +88 -53
- data/lib/gloss/watcher.rb +14 -7
- data/lib/gloss/writer.rb +21 -10
- data/logo.svg +6 -0
- data/sig/core.rbs +2 -0
- data/sig/fast_blank.rbs +4 -0
- data/sig/{gloss.rbs → gls.rbs} +0 -0
- data/sig/optparse.rbs +6 -0
- data/sig/rubygems.rbs +9 -0
- data/sig/yaml.rbs +3 -0
- data/src/exe/gloss +19 -0
- data/src/lib/gloss.gl +25 -0
- data/src/lib/gloss/cli.gl +41 -26
- data/src/lib/gloss/config.gl +13 -8
- data/src/lib/gloss/logger.gl +2 -5
- data/src/lib/gloss/prog_loader.gl +138 -0
- data/src/lib/gloss/scope.gl +0 -2
- data/src/lib/gloss/type_checker.gl +57 -28
- data/src/lib/gloss/utils.gl +35 -0
- data/src/lib/gloss/version.gl +1 -1
- data/src/lib/gloss/{builder.gl → visitor.gl} +82 -45
- data/src/lib/gloss/watcher.gl +13 -8
- data/src/lib/gloss/writer.gl +15 -13
- metadata +29 -18
- data/.github/workflows/crystal_specs.yml +0 -26
- data/.github/workflows/ruby_specs.yml +0 -33
data/src/lib/gloss/scope.gl
CHANGED
@@ -1,28 +1,45 @@
|
|
1
|
-
|
2
|
-
require "pry-byebug"
|
1
|
+
require "set"
|
3
2
|
|
4
3
|
module Gloss
|
5
4
|
class TypeChecker
|
6
|
-
|
5
|
+
attr_reader :steep_target, :top_level_decls, :env, :rbs_gem_dir
|
7
6
|
|
8
|
-
|
7
|
+
enum Strictness
|
8
|
+
Strict = "strict"
|
9
|
+
Lenient = "lenient"
|
10
|
+
Default = "default"
|
11
|
+
end
|
9
12
|
|
10
13
|
def initialize
|
14
|
+
options = Steep::Project::Options.new
|
15
|
+
case Config.type_checking_strictness
|
16
|
+
when Strictness::Strict
|
17
|
+
options.apply_strict_typing_options!
|
18
|
+
when Strictness::Lenient
|
19
|
+
options.apply_lenient_typing_options!
|
20
|
+
else
|
21
|
+
options.apply_default_typing_options!
|
22
|
+
end
|
11
23
|
@steep_target = Steep::Project::Target.new(
|
12
24
|
name: "gloss",
|
13
|
-
options:
|
14
|
-
|
15
|
-
end,
|
16
|
-
source_patterns: ["gloss.rb"],
|
25
|
+
options: options,
|
26
|
+
source_patterns: ["**/*.rb"],
|
17
27
|
ignore_patterns: Array.new,
|
18
28
|
signature_patterns: ["sig"]
|
19
29
|
)
|
20
|
-
@top_level_decls =
|
30
|
+
@top_level_decls = Set.new
|
31
|
+
@rbs_gem_dir = Utils.gem_path_for("rbs")
|
32
|
+
env_loader = RBS::EnvironmentLoader.new
|
33
|
+
@env = RBS::Environment.from_loader(env_loader)
|
34
|
+
project = Steep::Project.new(steepfile_path: Pathname.new(Config.src_dir).realpath)
|
35
|
+
project.targets << @steep_target
|
36
|
+
loader = Steep::Project::FileLoader.new(project: project)
|
37
|
+
#loader.load_signatures
|
21
38
|
end
|
22
39
|
|
23
|
-
def run(rb_str)
|
40
|
+
def run(filepath, rb_str)
|
24
41
|
begin
|
25
|
-
valid_types = check_types rb_str
|
42
|
+
valid_types = check_types filepath, rb_str
|
26
43
|
rescue ParseError => e
|
27
44
|
throw :error, ""
|
28
45
|
rescue => e
|
@@ -38,8 +55,8 @@ module Gloss
|
|
38
55
|
"Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
|
39
56
|
when Steep::Diagnostic::Ruby::IncompatibleArguments
|
40
57
|
<<-ERR
|
41
|
-
Invalid argmuents - method type: #{e.
|
42
|
-
method name: #{e.
|
58
|
+
Invalid argmuents - method type: #{e.method_types.first}
|
59
|
+
method name: #{e.method_name}
|
43
60
|
ERR
|
44
61
|
when Steep::Diagnostic::Ruby::ReturnTypeMismatch
|
45
62
|
"Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
|
@@ -48,7 +65,7 @@ module Gloss
|
|
48
65
|
when Steep::Diagnostic::Ruby::UnexpectedBlockGiven
|
49
66
|
"Unexpected block given"
|
50
67
|
else
|
51
|
-
e.
|
68
|
+
"#{e.header_line}\n#{e}"
|
52
69
|
end
|
53
70
|
}.join("\n")
|
54
71
|
throw :error, errors
|
@@ -57,31 +74,38 @@ module Gloss
|
|
57
74
|
true
|
58
75
|
end
|
59
76
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
loader = Steep::Project::FileLoader.new(project: project)
|
66
|
-
loader.load_signatures
|
77
|
+
def ready_for_checking!
|
78
|
+
@top_level_decls.each do |decl|
|
79
|
+
@env << decl
|
80
|
+
end
|
81
|
+
@env = @env.resolve_type_names
|
67
82
|
|
68
|
-
@steep_target.
|
83
|
+
@steep_target.instance_variable_set("@environment", @env)
|
84
|
+
end
|
69
85
|
|
70
|
-
|
71
|
-
|
72
|
-
end
|
73
|
-
env = env.resolve_type_names
|
86
|
+
def check_types(filepath, rb_str)
|
87
|
+
@steep_target.add_source(filepath, rb_str)
|
74
88
|
|
75
|
-
|
89
|
+
ready_for_checking!
|
76
90
|
|
77
91
|
@steep_target.type_check
|
78
92
|
|
79
93
|
if @steep_target.status.is_a? Steep::Project::Target::SignatureErrorStatus
|
80
94
|
throw :error, @steep_target.status.errors.map { |e|
|
95
|
+
msg = case e
|
96
|
+
when Steep::Diagnostic::Signature::UnknownTypeName
|
97
|
+
"Unknown type name: #{e.name.name} (#{e.location.source[/^.*$/]})"
|
98
|
+
when Steep::Diagnostic::Signature::InvalidTypeApplication
|
99
|
+
"Invalid type application: #{e.header_line}"
|
100
|
+
when Steep::Diagnostic::Signature::DuplicatedMethodDefinition
|
101
|
+
"Duplicated method: #{e.header_line}"
|
102
|
+
else
|
103
|
+
e.header_line
|
104
|
+
end
|
81
105
|
<<~MSG
|
82
106
|
SignatureSyntaxError:
|
83
107
|
Location: #{e.location}
|
84
|
-
Message: "#{
|
108
|
+
Message: "#{msg}"
|
85
109
|
MSG
|
86
110
|
}.join("\n")
|
87
111
|
end
|
@@ -97,5 +121,10 @@ module Gloss
|
|
97
121
|
@steep_target.no_error? &&
|
98
122
|
@steep_target.errors.empty?
|
99
123
|
end
|
124
|
+
|
125
|
+
def load_sig_path(path : String)
|
126
|
+
Gloss.logger.debug "Loading signature file for #{path}"
|
127
|
+
@steep_target.add_signature path, File.open(path).read
|
128
|
+
end
|
100
129
|
end
|
101
130
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
|
3
|
+
module Gloss
|
4
|
+
module Utils
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def absolute_path(path)
|
8
|
+
pn = Pathname.new(path)
|
9
|
+
if pn.absolute?
|
10
|
+
pn.to_s
|
11
|
+
else
|
12
|
+
ap = File.absolute_path path
|
13
|
+
if File.exist? ap
|
14
|
+
ap
|
15
|
+
else
|
16
|
+
throw :error, "File path #{path} does not exist (also looked for #{ap})"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def gem_path_for(gem_name)
|
22
|
+
spec = Gem::Specification.find_by_path(gem_name)
|
23
|
+
spec.full_require_paths.first if spec
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_file_header(str)
|
27
|
+
"#{Visitor::FILE_HEADER}\n\n#{str}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def src_path_to_output_path(src_path : String) : String
|
31
|
+
src_path.sub("#{Config.src_dir}/", "")
|
32
|
+
.sub(/\.gl$/, ".rb")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/src/lib/gloss/version.gl
CHANGED
@@ -1,35 +1,26 @@
|
|
1
1
|
module Gloss
|
2
|
-
|
3
|
-
module_function
|
4
|
-
|
5
|
-
def with_file_header(str)
|
6
|
-
"#{Builder::FILE_HEADER}\n\n#{str}"
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
class Builder
|
2
|
+
class Visitor
|
11
3
|
FILE_HEADER = <<~RUBY
|
12
4
|
#{"# frozen_string_literal: true\n" if Config.frozen_string_literals}
|
13
5
|
##### This file was generated by Gloss; any changes made here will be overwritten.
|
14
6
|
##### See #{Config.src_dir}/ to make changes
|
15
7
|
RUBY
|
16
8
|
|
17
|
-
include Utils
|
18
|
-
|
19
9
|
attr_reader :tree
|
20
10
|
|
21
|
-
def initialize(tree_hash, type_checker = nil)
|
11
|
+
def initialize(tree_hash, type_checker = nil, @on_new_file_referenced = nil)
|
22
12
|
@indent_level = 0
|
23
13
|
@inside_macro = false
|
24
14
|
@eval_vars = false
|
25
15
|
@current_scope = nil
|
26
16
|
@tree = tree_hash
|
27
17
|
@type_checker = type_checker
|
18
|
+
@after_module_function = false
|
28
19
|
end
|
29
20
|
|
30
21
|
def run
|
31
22
|
rb_output = visit_node(@tree)
|
32
|
-
with_file_header(rb_output)
|
23
|
+
Utils.with_file_header(rb_output)
|
33
24
|
end
|
34
25
|
|
35
26
|
# type node = Hash[Symbol, String | Array[String | node] | Hash[Symbol, node]] | true | false
|
@@ -41,18 +32,31 @@ module Gloss
|
|
41
32
|
class_name = visit_node(node[:name])
|
42
33
|
current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
|
43
34
|
superclass_type = nil
|
44
|
-
superclass_output =
|
35
|
+
superclass_output = ""
|
45
36
|
if node[:superclass]
|
46
37
|
@eval_vars = true
|
47
38
|
superclass_output = visit_node(node[:superclass])
|
48
39
|
@eval_vars = false
|
49
|
-
|
40
|
+
args = Array.new
|
50
41
|
if node.dig(:superclass, :type) == "Generic"
|
51
|
-
superclass_output = superclass_output[/^[^\[]+/]
|
42
|
+
superclass_output = superclass_output[/^[^\[]+/] || superclass_output
|
43
|
+
args = node.dig(:superclass, :args).map { |n| RBS::Parser.parse_type(visit_node(n)) }
|
52
44
|
end
|
45
|
+
|
46
|
+
class_name_index = superclass_output.index(/[^(?:::)]+\z/) || 0
|
47
|
+
namespace = superclass_output[0, class_name_index]
|
48
|
+
superclass_name = superclass_output[/[^(?:::)]+\z/] || superclass_output
|
49
|
+
superclass_type = RBS::AST::Declarations::Class::Super.new(
|
50
|
+
name: RBS::TypeName.new(
|
51
|
+
namespace: method(:Namespace).call(namespace),
|
52
|
+
name: superclass_name.to_sym,
|
53
|
+
),
|
54
|
+
args: args,
|
55
|
+
location: build_location(node),
|
56
|
+
)
|
53
57
|
end
|
54
58
|
|
55
|
-
src.write_ln "class #{class_name}#{" < #{superclass_output}"
|
59
|
+
src.write_ln "class #{class_name}#{" < #{superclass_output}" unless superclass_output.blank?}"
|
56
60
|
|
57
61
|
class_type = RBS::AST::Declarations::Class.new(
|
58
62
|
name: RBS::TypeName.new(
|
@@ -63,7 +67,7 @@ module Gloss
|
|
63
67
|
super_class: superclass_type,
|
64
68
|
members: Array.new, # TODO
|
65
69
|
annotations: Array.new, # TODO
|
66
|
-
location: node
|
70
|
+
location: build_location(node),
|
67
71
|
comment: node[:comment]
|
68
72
|
)
|
69
73
|
old_parent_scope = @current_scope
|
@@ -77,10 +81,12 @@ module Gloss
|
|
77
81
|
|
78
82
|
@current_scope.members << class_type if @current_scope
|
79
83
|
|
80
|
-
if @type_checker
|
81
|
-
@type_checker.top_level_decls
|
84
|
+
if @type_checker && !@current_scope
|
85
|
+
@type_checker.top_level_decls.add(class_type)
|
82
86
|
end
|
83
87
|
when "ModuleNode"
|
88
|
+
existing_module_function_state = @after_module_function.dup
|
89
|
+
@after_module_function = false
|
84
90
|
module_name = visit_node node[:name]
|
85
91
|
src.write_ln "module #{module_name}"
|
86
92
|
|
@@ -95,8 +101,8 @@ module Gloss
|
|
95
101
|
self_types: Array.new, # TODO
|
96
102
|
members: Array.new, # TODO
|
97
103
|
annotations: Array.new, # TODO
|
98
|
-
location: node
|
99
|
-
comment: node[:comment]
|
104
|
+
location: build_location(node),
|
105
|
+
comment: node[:comment],
|
100
106
|
)
|
101
107
|
old_parent_scope = @current_scope
|
102
108
|
@current_scope = module_type
|
@@ -107,10 +113,11 @@ module Gloss
|
|
107
113
|
|
108
114
|
@current_scope.members << module_type if @current_scope
|
109
115
|
|
110
|
-
if @type_checker
|
111
|
-
@type_checker.top_level_decls
|
116
|
+
if @type_checker && !@current_scope
|
117
|
+
@type_checker.top_level_decls.add(module_type)
|
112
118
|
end
|
113
119
|
src.write_ln "end"
|
120
|
+
@after_module_function = existing_module_function_state
|
114
121
|
when "DefNode"
|
115
122
|
args = render_args(node)
|
116
123
|
receiver = node[:receiver] ? visit_node(node[:receiver]) : nil
|
@@ -123,11 +130,11 @@ module Gloss
|
|
123
130
|
namespace: RBS::Namespace.root
|
124
131
|
),
|
125
132
|
args: EMPTY_ARRAY, # TODO
|
126
|
-
location: node
|
133
|
+
location: build_location(node)
|
127
134
|
)
|
128
135
|
else
|
129
136
|
RBS::Types::Bases::Any.new(
|
130
|
-
location: node
|
137
|
+
location: build_location(node)
|
131
138
|
)
|
132
139
|
end
|
133
140
|
|
@@ -154,19 +161,19 @@ module Gloss
|
|
154
161
|
required_keywords: Hash.new,
|
155
162
|
optional_keywords: Hash.new,
|
156
163
|
rest_keywords: nil,
|
157
|
-
return_type: RBS::Types::Bases::Any.new(location: node
|
164
|
+
return_type: RBS::Types::Bases::Any.new(location: build_location(node))
|
158
165
|
),
|
159
166
|
required: !!(node[:block_arg] || node[:yield_arg_count])
|
160
167
|
) : nil,
|
161
|
-
location: node
|
168
|
+
location: build_location(node)
|
162
169
|
)
|
163
170
|
]
|
164
171
|
method_definition = RBS::AST::Members::MethodDefinition.new(
|
165
172
|
name: node[:name].to_sym,
|
166
|
-
kind: receiver ? :
|
173
|
+
kind: @after_module_function ? :singleton_instance : receiver ? :singleton : :instance,
|
167
174
|
types: method_types,
|
168
175
|
annotations: EMPTY_ARRAY, # TODO
|
169
|
-
location: node
|
176
|
+
location: build_location(node),
|
170
177
|
comment: node[:comment],
|
171
178
|
overload: false
|
172
179
|
)
|
@@ -195,6 +202,7 @@ module Gloss
|
|
195
202
|
else
|
196
203
|
nil
|
197
204
|
end
|
205
|
+
name = node[:name]
|
198
206
|
block = node[:block] ? " #{visit_node(node[:block])}" : nil
|
199
207
|
has_parens = !!(node[:has_parentheses] || args || block)
|
200
208
|
opening_delimiter = if has_parens
|
@@ -202,7 +210,21 @@ module Gloss
|
|
202
210
|
else
|
203
211
|
nil
|
204
212
|
end
|
205
|
-
call = "#{obj}#{
|
213
|
+
call = "#{obj}#{name}#{opening_delimiter}#{args}#{")" if has_parens}#{block}"
|
214
|
+
case name
|
215
|
+
when "require_relative"
|
216
|
+
if @on_new_file_referenced
|
217
|
+
paths = arg_arr.map do |a|
|
218
|
+
unless a[:type] == "LiteralNode"
|
219
|
+
throw :error, "Dynamic file paths are not allowed in require_relative"
|
220
|
+
end
|
221
|
+
eval(visit_node(a, scope).strip)
|
222
|
+
end
|
223
|
+
@on_new_file_referenced.call(paths, true)
|
224
|
+
end
|
225
|
+
when "module_function"
|
226
|
+
@after_module_function = true
|
227
|
+
end
|
206
228
|
src.write_ln(call)
|
207
229
|
|
208
230
|
when "Block"
|
@@ -247,9 +269,10 @@ module Gloss
|
|
247
269
|
src.write node[:value]
|
248
270
|
|
249
271
|
when "Require"
|
272
|
+
path = node[:value]
|
273
|
+
src.write_ln %(require "#{path}")
|
250
274
|
|
251
|
-
|
252
|
-
|
275
|
+
@on_new_file_referenced.call([path], false) if @on_new_file_referenced
|
253
276
|
when "Assign", "OpAssign"
|
254
277
|
|
255
278
|
src.write_ln "#{visit_node(node[:target])} #{node[:op]}= #{visit_node(node[:value]).strip}"
|
@@ -388,7 +411,8 @@ module Gloss
|
|
388
411
|
src.write "return#{val}"
|
389
412
|
when "TypeDeclaration"
|
390
413
|
src.write_ln "# @type var #{visit_node(node[:var])}: #{visit_node(node[:declared_type])}"
|
391
|
-
|
414
|
+
value = node[:value] ? " = #{visit_node node[:value]}" : nil
|
415
|
+
src.write_ln "#{visit_node(node[:var])}#{value}"
|
392
416
|
when "ExceptionHandler"
|
393
417
|
src.write_ln "begin"
|
394
418
|
indented src do
|
@@ -422,7 +446,7 @@ module Gloss
|
|
422
446
|
name: method(:TypeName).call(name),
|
423
447
|
args: Array.new,
|
424
448
|
annotations: Array.new,
|
425
|
-
location: node
|
449
|
+
location: build_location(node),
|
426
450
|
comment: node[:comment]
|
427
451
|
)
|
428
452
|
if @current_scope
|
@@ -438,7 +462,7 @@ module Gloss
|
|
438
462
|
name: method(:TypeName).call(name),
|
439
463
|
args: Array.new,
|
440
464
|
annotations: Array.new,
|
441
|
-
location: node
|
465
|
+
location: build_location(node),
|
442
466
|
comment: node[:comment]
|
443
467
|
)
|
444
468
|
if @current_scope
|
@@ -510,37 +534,50 @@ module Gloss
|
|
510
534
|
rest_kw ? "**#{visit_node(rest_kw)}" : ""
|
511
535
|
].reject(&:empty?).flatten.join(", ")
|
512
536
|
representation = "(#{contents})"
|
513
|
-
rp.map
|
537
|
+
rp_args = rp.map do |a|
|
514
538
|
RBS::Types::Function::Param.new(
|
515
539
|
name: visit_node(a).to_sym,
|
516
540
|
type: RBS::Types::Bases::Any.new(
|
517
|
-
location: a
|
541
|
+
location: build_location(a)
|
518
542
|
)
|
519
543
|
)
|
520
544
|
end
|
521
|
-
op.map
|
545
|
+
op_args = op.map do |a|
|
522
546
|
RBS::Types::Function::Param.new(
|
523
547
|
name: visit_node(a).to_sym,
|
524
|
-
type: RBS::Types::Bases::Any.new(location: a
|
548
|
+
type: RBS::Types::Bases::Any.new(location: build_location(a))
|
525
549
|
)
|
526
550
|
end
|
527
|
-
|
551
|
+
rpa = rest_p ? RBS::Types::Function::Param.new(
|
552
|
+
name: rest_p.to_sym,
|
553
|
+
type: RBS::Types::Bases::Any.new(location: build_location(node))
|
554
|
+
) : nil
|
528
555
|
{
|
529
556
|
representation: representation,
|
530
557
|
types: {
|
531
|
-
required_positionals:
|
532
|
-
optional_positionals:
|
533
|
-
rest_positionals:
|
558
|
+
required_positionals: rp_args,
|
559
|
+
optional_positionals: op_args,
|
560
|
+
rest_positionals: rpa,
|
534
561
|
trailing_positionals: EMPTY_ARRAY, # TODO
|
535
562
|
required_keywords: node[:req_kw_args] || EMPTY_HASH,
|
536
563
|
optional_keywords: node[:opt_kw_args] || EMPTY_HASH,
|
537
564
|
rest_keywords: node[:rest_kw_args] ?
|
538
565
|
RBS::Types::Function::Param.new(
|
539
566
|
name: visit_node(node[:rest_kw_args]).to_sym,
|
540
|
-
type: RBS::Types::Bases::Any.new(location: node
|
567
|
+
type: RBS::Types::Bases::Any.new(location: build_location(node))
|
541
568
|
) : nil
|
542
569
|
}
|
543
570
|
}
|
544
571
|
end
|
572
|
+
|
573
|
+
def build_location(node)
|
574
|
+
return nil unless node[:location]
|
575
|
+
|
576
|
+
# RBS::Location.new(
|
577
|
+
# buffer: RBS::Buffer.new(@file_path, @file_content),
|
578
|
+
# start_pos: node[:location][:start],
|
579
|
+
# end_pos: node[:location][:end]
|
580
|
+
# )
|
581
|
+
end
|
545
582
|
end
|
546
583
|
end
|