jade-lang 0.9.0 → 0.10.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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +442 -12
  3. data/README.md +1 -0
  4. data/docs/interop.md +18 -1
  5. data/docs/json.md +34 -3
  6. data/docs/stdlib.md +3 -1
  7. data/docs/syntax.md +96 -0
  8. data/lib/jade/api.rb +9 -0
  9. data/lib/jade/ast.rb +37 -1
  10. data/lib/jade/cli/eject.rb +132 -0
  11. data/lib/jade/cli/init.rb +86 -0
  12. data/lib/jade/cli.rb +4 -0
  13. data/lib/jade/codegen/boundary/specialized/list.rb +11 -7
  14. data/lib/jade/codegen/boundary/specialized/maybe.rb +2 -2
  15. data/lib/jade/codegen/boundary/specialized/record.rb +19 -4
  16. data/lib/jade/codegen/boundary/specialized/scalar.rb +2 -2
  17. data/lib/jade/codegen/boundary/specialized.rb +13 -5
  18. data/lib/jade/codegen/boundary.rb +12 -2
  19. data/lib/jade/codegen/context.rb +17 -0
  20. data/lib/jade/codegen/error.rb +1 -0
  21. data/lib/jade/codegen/function_call.rb +38 -7
  22. data/lib/jade/codegen/function_declaration.rb +25 -13
  23. data/lib/jade/codegen/helpers.rb +27 -4
  24. data/lib/jade/codegen/implementation.rb +2 -2
  25. data/lib/jade/codegen/inline.rb +1 -1
  26. data/lib/jade/codegen/inlines.rb +6 -1
  27. data/lib/jade/codegen/names.rb +31 -0
  28. data/lib/jade/codegen/pattern/constructor.rb +2 -2
  29. data/lib/jade/codegen/transforms/fold_shape.rb +1 -1
  30. data/lib/jade/codegen.rb +76 -15
  31. data/lib/jade/compiler.rb +2 -0
  32. data/lib/jade/entry.rb +1 -1
  33. data/lib/jade/formatter/calls.rb +68 -11
  34. data/lib/jade/formatter/collections.rb +4 -4
  35. data/lib/jade/formatter/declarations.rb +13 -15
  36. data/lib/jade/formatter/helper.rb +20 -0
  37. data/lib/jade/formatter/infix_application.rb +19 -4
  38. data/lib/jade/formatter/lambda.rb +24 -12
  39. data/lib/jade/formatter/pattern.rb +3 -0
  40. data/lib/jade/formatter/type.rb +3 -5
  41. data/lib/jade/frontend/desugaring/placeholder.rb +40 -17
  42. data/lib/jade/frontend/desugaring.rb +29 -9
  43. data/lib/jade/frontend/fixity_fixer.rb +6 -4
  44. data/lib/jade/frontend/forward_declaration/error/alias_expansion.rb +23 -0
  45. data/lib/jade/frontend/forward_declaration/error/duplicate_constructor_name.rb +45 -0
  46. data/lib/jade/frontend/forward_declaration/error/duplicate_type_name.rb +42 -0
  47. data/lib/jade/frontend/forward_declaration/error.rb +3 -0
  48. data/lib/jade/frontend/forward_declaration/helper.rb +58 -0
  49. data/lib/jade/frontend/forward_declaration/import_declaration.rb +4 -0
  50. data/lib/jade/frontend/forward_declaration/interface_declaration.rb +6 -0
  51. data/lib/jade/frontend/forward_declaration/module.rb +4 -0
  52. data/lib/jade/frontend/forward_declaration/struct_declaration.rb +10 -1
  53. data/lib/jade/frontend/forward_declaration/type_alias_declaration.rb +37 -0
  54. data/lib/jade/frontend/forward_declaration/type_declaration.rb +10 -1
  55. data/lib/jade/frontend/forward_declaration.rb +2 -0
  56. data/lib/jade/frontend/pattern_analysis/exhaustiveness.rb +47 -43
  57. data/lib/jade/frontend/pattern_analysis/matrix.rb +28 -219
  58. data/lib/jade/frontend/pattern_analysis/pattern.rb +132 -0
  59. data/lib/jade/frontend/pattern_analysis/redundancy.rb +24 -0
  60. data/lib/jade/frontend/pattern_analysis/signature.rb +220 -0
  61. data/lib/jade/frontend/pattern_analysis/usefulness.rb +72 -0
  62. data/lib/jade/frontend/pattern_analysis/witnesses.rb +73 -0
  63. data/lib/jade/frontend/pattern_analysis.rb +5 -38
  64. data/lib/jade/frontend/semantic_analysis/error/implementation_on_alias.rb +24 -0
  65. data/lib/jade/frontend/semantic_analysis/error/no_base_case.rb +45 -0
  66. data/lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb +15 -3
  67. data/lib/jade/frontend/semantic_analysis/error/recursive_type_alias.rb +24 -0
  68. data/lib/jade/frontend/semantic_analysis/error.rb +3 -0
  69. data/lib/jade/frontend/semantic_analysis/helper.rb +31 -0
  70. data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -0
  71. data/lib/jade/frontend/semantic_analysis/inhabitedness.rb +90 -0
  72. data/lib/jade/frontend/semantic_analysis/interop_import_declaration.rb +28 -16
  73. data/lib/jade/frontend/semantic_analysis/keyed_call.rb +21 -5
  74. data/lib/jade/frontend/semantic_analysis/pattern_range.rb +16 -0
  75. data/lib/jade/frontend/semantic_analysis/struct_declaration.rb +1 -0
  76. data/lib/jade/frontend/semantic_analysis/type_alias_declaration/cycle_detection.rb +95 -0
  77. data/lib/jade/frontend/semantic_analysis/type_alias_declaration.rb +48 -0
  78. data/lib/jade/frontend/semantic_analysis/type_declaration.rb +1 -0
  79. data/lib/jade/frontend/semantic_analysis.rb +5 -0
  80. data/lib/jade/frontend/type_checking/canonicalize.rb +1 -0
  81. data/lib/jade/frontend/type_checking/cascade.rb +90 -0
  82. data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +10 -0
  83. data/lib/jade/frontend/type_checking/constraints.rb +18 -3
  84. data/lib/jade/frontend/type_checking/error/display.rb +49 -0
  85. data/lib/jade/frontend/type_checking/error/division_by_zero.rb +31 -0
  86. data/lib/jade/frontend/type_checking/error/empty_range_pattern.rb +25 -0
  87. data/lib/jade/frontend/type_checking/error/function_body_type_mismatch.rb +4 -3
  88. data/lib/jade/frontend/type_checking/error/function_call_type_mismatch.rb +108 -5
  89. data/lib/jade/frontend/type_checking/error/if_branch_type_mismatch.rb +1 -1
  90. data/lib/jade/frontend/type_checking/error/if_branches_type_mismatch.rb +1 -1
  91. data/lib/jade/frontend/type_checking/error/if_condition_type_mismatch.rb +1 -1
  92. data/lib/jade/frontend/type_checking/error/implementation_type_mismatch.rb +1 -1
  93. data/lib/jade/frontend/type_checking/error/list_item_type_mismatch.rb +1 -1
  94. data/lib/jade/frontend/type_checking/error/missing_patterns.rb +20 -4
  95. data/lib/jade/frontend/type_checking/error/pattern_type_mismatch.rb +7 -1
  96. data/lib/jade/frontend/type_checking/error/range_pattern_type.rb +29 -0
  97. data/lib/jade/frontend/type_checking/error/record_access_type_mismatch.rb +2 -2
  98. data/lib/jade/frontend/type_checking/error/record_update_type_mismatch.rb +21 -0
  99. data/lib/jade/frontend/type_checking/error/recursive_derivation.rb +25 -0
  100. data/lib/jade/frontend/type_checking/error/type_mismatch.rb +10 -2
  101. data/lib/jade/frontend/type_checking/error/unreachable_branch.rb +22 -0
  102. data/lib/jade/frontend/type_checking/error.rb +7 -0
  103. data/lib/jade/frontend/type_checking/inference/assign.rb +1 -1
  104. data/lib/jade/frontend/type_checking/inference/case_of.rb +21 -4
  105. data/lib/jade/frontend/type_checking/inference/division.rb +50 -0
  106. data/lib/jade/frontend/type_checking/inference/function_call.rb +26 -1
  107. data/lib/jade/frontend/type_checking/inference/lambda.rb +1 -1
  108. data/lib/jade/frontend/type_checking/inference/pattern.rb +35 -0
  109. data/lib/jade/frontend/type_checking/inference/record_update.rb +8 -1
  110. data/lib/jade/frontend/type_checking/inference/type_alias_declaration.rb +18 -0
  111. data/lib/jade/frontend/type_checking/inference.rb +2 -0
  112. data/lib/jade/frontend/type_checking/loader.rb +11 -0
  113. data/lib/jade/frontend/type_checking/port_resolution.rb +26 -8
  114. data/lib/jade/frontend/type_checking/substitution.rb +2 -2
  115. data/lib/jade/frontend/type_checking.rb +3 -1
  116. data/lib/jade/frontend/usage_analysis.rb +5 -1
  117. data/lib/jade/interop/boundary.rb +64 -24
  118. data/lib/jade/interop/error.rb +35 -7
  119. data/lib/jade/interop/lowering.rb +8 -2
  120. data/lib/jade/lsp/converters.rb +83 -2
  121. data/lib/jade/lsp/handlers.rb +122 -25
  122. data/lib/jade/lsp/snippets.rb +12 -0
  123. data/lib/jade/lsp/state.rb +11 -4
  124. data/lib/jade/module_loader/build.rb +36 -0
  125. data/lib/jade/module_loader.rb +1 -0
  126. data/lib/jade/parsing/combinators.rb +78 -2
  127. data/lib/jade/parsing/error.rb +43 -6
  128. data/lib/jade/parsing/type.rb +6 -4
  129. data/lib/jade/parsing.rb +140 -26
  130. data/lib/jade/runtime.rb +32 -13
  131. data/lib/jade/source.rb +6 -2
  132. data/lib/jade/stdlib/basics.rb +22 -3
  133. data/lib/jade/stdlib/char.rb +2 -0
  134. data/lib/jade/stdlib/clock.rb +6 -2
  135. data/lib/jade/stdlib/decimal.rb +51 -14
  136. data/lib/jade/stdlib/decode.rb +11 -0
  137. data/lib/jade/stdlib/intrinsics.rb +59 -6
  138. data/lib/jade/stdlib/list.rb +37 -1
  139. data/lib/jade/stdlib/number.rb +47 -0
  140. data/lib/jade/stdlib/range.rb +211 -0
  141. data/lib/jade/stdlib.rb +11 -4
  142. data/lib/jade/symbol/alias.rb +11 -0
  143. data/lib/jade/symbol/parser.rb +1 -1
  144. data/lib/jade/symbol/stdlib_function.rb +1 -1
  145. data/lib/jade/symbol.rb +13 -1
  146. data/lib/jade/task.rb +0 -2
  147. data/lib/jade/type/anonymous_record.rb +11 -2
  148. data/lib/jade/type/application.rb +11 -2
  149. data/lib/jade/type/base.rb +26 -0
  150. data/lib/jade/type/function.rb +11 -3
  151. data/lib/jade/type.rb +36 -10
  152. data/lib/jade/version.rb +1 -1
  153. metadata +35 -2
@@ -16,6 +16,9 @@ module Jade
16
16
  Inline.try_for(callee, args, dictionaries, registry)
17
17
  .then { return it if it }
18
18
 
19
+ constrained_constant(callee, dictionaries, registry)
20
+ .then { return it if it }
21
+
19
22
  return constructor_call(callee, args, registry) if constructor_callee?(callee, registry)
20
23
 
21
24
  [generate_many(args, registry), generate_dict_args(callee, dictionaries, registry)]
@@ -24,6 +27,18 @@ module Jade
24
27
  .then { "#{generate_callee(callee, args, registry, dictionaries)}#{invocation_op(callee, registry)}(#{it})" }
25
28
  end
26
29
 
30
+ # A constrained constant — `Decode.decoder` — has no parameters, so its
31
+ # dictionary slot holds the value itself rather than something to call.
32
+ # generate_callee already produced that value; invoking it would be
33
+ # calling a Decoder.
34
+ def constrained_constant(callee, dictionaries, registry)
35
+ symbol = resolve_callee_symbol(callee, registry)
36
+ return nil unless symbol.is_a?(Symbol::StdlibFunction)
37
+ return nil unless symbol.params.empty? && symbol.constraints.any?
38
+
39
+ generate_callee(callee, [], registry, dictionaries)
40
+ end
41
+
27
42
  def constructor_call(callee, args, registry)
28
43
  resolve_callee_symbol(callee, registry)
29
44
  .then { "::#{to_qualified(it.qualified_name)}" }
@@ -102,10 +117,25 @@ module Jade
102
117
  Codegen.dict_env[[interface, id]]
103
118
 
104
119
  in Symbol::Implementation
105
- Pretty.hash(generate_impl_dispatch(entry, registry))
120
+ hoisted(Pretty.hash(generate_impl_dispatch(entry, registry)))
106
121
  end
107
122
  end
108
123
 
124
+ # A dictionary built only from concrete implementations is the same
125
+ # object on every call, so it is worth building once. One that names
126
+ # a dict parameter is not: that slot is the caller's, and differs per
127
+ # call. Outside a module there is nowhere to put a constant, so the
128
+ # literal stands.
129
+ #
130
+ # Keyed by source, which dedupes the same dictionary across every
131
+ # call site in the module.
132
+ def hoisted(source)
133
+ table = Codegen.dict_consts or return source
134
+ return source if Codegen.dict_env.values.any? { source.include?(it) }
135
+
136
+ table[source] ||= "DICT_#{table.size}"
137
+ end
138
+
109
139
  # Polymorphic fn referenced as a value (not called). Wraps the fn with
110
140
  # its dispatched dictionaries so the result is a monomorphic callable
111
141
  # matching the type at the use site. Returns nil when the symbol
@@ -127,13 +157,13 @@ module Jade
127
157
  .each_with_index
128
158
  .filter_map { |c, i| dispatch_value(dictionaries[i], registry) if c.type.is_a?(Type::Var) }
129
159
  .then { (param_names + it).join(', ') }
130
- .then { "#{to_qualified(fn.module_name)}::Internal.#{fn_target_name(fn, registry)}(#{it})" }
160
+ .then { "#{internal(fn.module_name)}.#{fn_target_name(fn, registry)}(#{it})" }
131
161
  .then { Pretty.lambda(param_names.join(', '), it) }
132
162
 
133
163
  in Symbol::InterfaceFunction => fn
134
- dispatch_value(dictionaries.first, registry)
135
- &.then { "#{it}[#{fn.name.inspect}]" } ||
164
+ dispatch_lookup(dictionaries.first, fn.name, registry) {
136
165
  fail("no dict in scope to reference interface method `#{fn.qualified_name}` as a value")
166
+ }
137
167
 
138
168
  else
139
169
  nil
@@ -199,7 +229,7 @@ module Jade
199
229
  generate_node(callee, registry)
200
230
 
201
231
  in Symbol::Function => fn_sym
202
- to_qualified(fn_sym.module_name) + "::Internal." + fn_target_name(fn_sym, registry)
232
+ internal(fn_sym.module_name) + "." + fn_target_name(fn_sym, registry)
203
233
 
204
234
  in Symbol::StdlibImplementation => symbol
205
235
  dictionaries
@@ -323,10 +353,10 @@ module Jade
323
353
  # 0-arg fn: Ruby auto-invokes on bare reference. Result is the
324
354
  # dispatch-slot value (decoder, encoder template, ...) ready to use.
325
355
  in Symbol::Function => fn if fn.constant?
326
- "#{to_qualified(fn.module_name)}::Internal.#{fn.name}"
356
+ "#{internal(fn.module_name)}.#{fn.name}"
327
357
 
328
358
  in Symbol::Function => fn
329
- "#{to_qualified(fn.module_name)}::Internal.method(:#{fn.name})"
359
+ "#{internal(fn.module_name)}.method(:#{fn.name})"
330
360
  end
331
361
  end
332
362
 
@@ -334,6 +364,7 @@ module Jade
334
364
  dep_dispatches
335
365
  .map { it.is_a?(String) ? it : Pretty.hash(it) }
336
366
  .then { Pretty.array(it) }
367
+ .then { hoisted(it) }
337
368
  end
338
369
 
339
370
  # Stdlib intrinsics implementation language.
@@ -20,12 +20,12 @@ module Jade
20
20
  end
21
21
 
22
22
  args, return_type = Type.signature(fn_type)
23
- param_names = params.map(&:name)
23
+ param_names = params.map { Names.local(it.name) }
24
24
 
25
25
  if task_return?(return_type)
26
- task_wrapper_pair(name, args, param_names, return_type, registry)
26
+ task_wrapper_pair(name, args, param_names, return_type, registry, symbol)
27
27
  else
28
- eligible_wrapper(name, args, param_names, return_type, registry)
28
+ eligible_wrapper(name, args, param_names, return_type, registry, symbol)
29
29
  end
30
30
  end
31
31
 
@@ -58,8 +58,8 @@ module Jade
58
58
  end
59
59
  end
60
60
 
61
- def eligible_wrapper(name, args, param_names, return_type, registry)
62
- decoded_args(args, param_names, registry)
61
+ def eligible_wrapper(name, args, param_names, return_type, registry, symbol)
62
+ decoded_args(args, param_names, registry, name, symbol)
63
63
  .then { Pretty.call("Internal.#{name}", it) }
64
64
  .then { encode_return(return_type, it, registry) }
65
65
  .then { Pretty.block(boundary_def_header(name, param_names), it) }
@@ -69,14 +69,26 @@ module Jade
69
69
  "def self.#{name}(#{param_names.join(', ')})"
70
70
  end
71
71
 
72
- def decoded_args(args, param_names, registry)
73
- args.zip(param_names).map { |t, pname| decode_call(t, pname, registry) }
72
+ def decoded_args(args, param_names, registry, name, symbol)
73
+ args
74
+ .zip(param_names)
75
+ .map do |t, pname|
76
+ decode_call(t, pname, registry, "#{to_qualified(symbol.module_name)}.#{name}(#{pname})")
77
+ end
78
+ end
79
+
80
+ def named(expr, where)
81
+ "Jade::Interop::Boundary.arg(#{where.inspect}) { #{expr} }"
74
82
  end
75
83
 
76
- def decode_call(arg_type, pname, registry)
77
- Codegen::Boundary::Specialized.decode_expr(arg_type, pname, registry) ||
84
+ # A specialized decoder carries the argument name itself, in a
85
+ # constant it was going to pass anyway. Only the generic path needs a
86
+ # frame around it to say where the value came from.
87
+ def decode_call(arg_type, pname, registry, where)
88
+ Codegen::Boundary::Specialized.decode_expr(arg_type, pname, registry, where) ||
78
89
  Codegen::Boundary::Cache.decoder_for(arg_type, registry)
79
90
  .then { "Jade::Interop::Boundary.decode_or_raise(#{it}, #{pname})" }
91
+ .then { named(it, where) }
80
92
  end
81
93
 
82
94
  def encode_return(return_type, call_expr, registry)
@@ -92,17 +104,17 @@ module Jade
92
104
  end
93
105
  end
94
106
 
95
- def task_wrapper_pair(name, args, param_names, task_return, registry)
107
+ def task_wrapper_pair(name, args, param_names, task_return, registry, symbol)
96
108
  ok_enc, err_enc = Codegen::Boundary::Cache.task_arms(task_return, registry)
97
109
 
98
110
  [
99
- task_run_def(name, args, param_names, ok_enc, err_enc, registry),
111
+ task_run_def(name, args, param_names, ok_enc, err_enc, registry, symbol),
100
112
  task_bang_def(name, param_names),
101
113
  ].join(Pretty.newline(2))
102
114
  end
103
115
 
104
- def task_run_def(name, args, param_names, ok_enc, err_enc, registry)
105
- decoded_args(args, param_names, registry)
116
+ def task_run_def(name, args, param_names, ok_enc, err_enc, registry, symbol)
117
+ decoded_args(args, param_names, registry, name, symbol)
106
118
  .then { Pretty.call("Internal.#{name}", it) }
107
119
  .then { task_run_body(it, ok_enc, err_enc) }
108
120
  .then { Pretty.block(boundary_def_header(name, param_names), it) }
@@ -16,8 +16,19 @@ module Jade
16
16
  Stdlib.stdlib_name?(module_name) ? "Jade::#{base}" : base
17
17
  end
18
18
 
19
+ # Inside `module Plan`, a bare `Plan::` resolves to whatever `Plan`
20
+ # names there, which may be a type. `module X` is the only name that
21
+ # cannot be rooted.
22
+ def rooted(module_name)
23
+ "::#{to_qualified(module_name)}"
24
+ end
25
+
26
+ def internal(module_name)
27
+ "#{rooted(module_name)}::Internal"
28
+ end
29
+
19
30
  def data_define(fields)
20
- return "Data.define" if fields.empty?
31
+ return "Jade.nullary" if fields.empty?
21
32
 
22
33
  "Data.define(#{fields.map { ":#{it}" }.join(', ')})"
23
34
  end
@@ -44,8 +55,14 @@ module Jade
44
55
  end
45
56
 
46
57
  def impl_synthetic_name(interface, type_name, fn_name)
47
- sanitized = fn_name.gsub(/[^a-zA-Z0-9_]/) { |c| "x#{c.ord.to_s(16)}" }
48
- "__impl_#{interface}_#{type_name}_#{sanitized}__"
58
+ sanitized_name(fn_name)
59
+ .then { "__impl_#{interface}_#{type_name}_#{it}__" }
60
+ end
61
+
62
+ # `contains?` is a fine Jade name and not a fine Ruby one anywhere but
63
+ # the end of a method name, which is not where these put it.
64
+ def sanitized_name(name)
65
+ name.gsub(/[^a-zA-Z0-9_]/) { |c| "x#{c.ord.to_s(16)}" }
49
66
  end
50
67
 
51
68
  def dict_synthetic_name(index)
@@ -53,7 +70,8 @@ module Jade
53
70
  end
54
71
 
55
72
  def fn_impl_synthetic_name(name)
56
- "__#{name}__impl__"
73
+ sanitized_name(name)
74
+ .then { "__#{it}__impl__" }
57
75
  end
58
76
 
59
77
  def fn_constraints(fn_symbol, registry)
@@ -101,6 +119,11 @@ module Jade
101
119
 
102
120
  in Symbol::Interface
103
121
  []
122
+
123
+ in other
124
+ raise(UnrepresentableType, "`#{qname}` has no runtime form " \
125
+ "(#{other.class}); this is a jade bug, " \
126
+ 'please report it')
104
127
  end
105
128
  end
106
129
 
@@ -83,7 +83,7 @@ module Jade
83
83
  impl_sym
84
84
  .functions[fn_name]
85
85
  .then { it.is_a?(Symbol::ValueRef) ? it : nil }
86
- &.then { "::#{to_qualified(it.module_name)}::Internal.#{it.name}" }
86
+ &.then { "#{internal(it.module_name)}.#{it.name}" }
87
87
  &.then { Pretty.block("def #{ruby_method}(other)", "#{it}(self, other)") }
88
88
  end
89
89
  end
@@ -143,7 +143,7 @@ module Jade
143
143
  fn_map = symbol.functions.filter_map { |fn_name, ref|
144
144
  next unless ref.is_a?(Symbol::ValueRef)
145
145
 
146
- [fn_name, "->(*args) { ::#{to_qualified(ref.module_name)}::Internal.#{ref.name}(*args) }"]
146
+ [fn_name, "->(*args) { #{internal(ref.module_name)}.#{ref.name}(*args) }"]
147
147
  }.to_h
148
148
 
149
149
  return "" if fn_map.empty?
@@ -80,7 +80,7 @@ module Jade
80
80
 
81
81
  def lambda_param_name(pattern)
82
82
  case pattern
83
- in AST::Pattern::Binding(name:) then name
83
+ in AST::Pattern::Binding(name:) then Names.local(name)
84
84
  in AST::Pattern::Wildcard then '_'
85
85
  end
86
86
  end
@@ -5,7 +5,9 @@ module Jade
5
5
 
6
6
  # Ruby's native ordering on these types matches LT/EQ/GT semantics, so
7
7
  # the derived comparison ops can collapse to plain operators.
8
- RUBY_NATIVE_COMPARES = %w[Basics.int_compare Basics.float_compare String.str_compare].to_set.freeze
8
+ RUBY_NATIVE_COMPARES = %w[
9
+ Basics.int_compare Basics.float_compare String.str_compare Char.char_compare
10
+ ].to_set.freeze
9
11
  RUBY_NATIVE_EQS = %w[Basics.int_eq Basics.float_eq Basics.bool_eq String.str_eq].to_set.freeze
10
12
 
11
13
  DERIVED_COMPARISONS = {
@@ -32,6 +34,7 @@ module Jade
32
34
  'Basics.int_mul' => ->(a, b) { "(#{a} * #{b})" },
33
35
  'Basics.int_div' => ->(a, b) { "(#{a} / #{b})" },
34
36
  'Basics.mod' => ->(a, b) { "(#{a} % #{b})" },
37
+ 'Number.unwrap' => ->(n) { n },
35
38
  'Basics.float_add' => ->(a, b) { "(#{a} + #{b})" },
36
39
  'Basics.float_sub' => ->(a, b) { "(#{a} - #{b})" },
37
40
  'Basics.float_mul' => ->(a, b) { "(#{a} * #{b})" },
@@ -113,6 +116,7 @@ module Jade
113
116
  'Char.upper?' => ->(c) { "#{c}.match?(/[A-Z]/)" },
114
117
  'Char.lower?' => ->(c) { "#{c}.match?(/[a-z]/)" },
115
118
  'Char.char_eq' => ->(a, b) { "(#{a} == #{b})" },
119
+ 'Char.char_compare' => ->(a, b) { "#{a}.compare(#{b})" },
116
120
 
117
121
  'Tuple.pair' => ->(a, b) { "Jade::Tuple::Tuple2[#{a}, #{b}]" },
118
122
  'Tuple.first' => ->(t) { "#{t}._1" },
@@ -173,6 +177,7 @@ module Jade
173
177
 
174
178
  # Bodies that don't fit a single Ruby expression.
175
179
  NO_INLINE = %w[
180
+ Number.checked
176
181
  List.sort_with
177
182
  List.sort_by_with
178
183
  List.filter_map
@@ -0,0 +1,31 @@
1
+ module Jade
2
+ module Codegen
3
+ # Ruby rejects its keywords as local variables and parameters, but accepts
4
+ # them as method names and Data members — so only bindings are rewritten
5
+ # here, and nothing a Ruby caller sees changes.
6
+ module Names
7
+ extend self
8
+
9
+ RESERVED = ::Set[
10
+ 'BEGIN', 'END', '__ENCODING__', '__FILE__', '__LINE__',
11
+ 'alias', 'and', 'begin', 'break', 'case', 'class', 'def',
12
+ 'defined?', 'do', 'else', 'elsif', 'end', 'ensure', 'false',
13
+ 'for', 'if', 'in', 'module', 'next', 'nil', 'not', 'or', 'redo',
14
+ 'rescue', 'retry', 'return', 'self', 'super', 'then', 'true',
15
+ 'undef', 'unless', 'until', 'when', 'while', 'yield',
16
+ ].freeze
17
+
18
+ # Injective: a name that is a reserved word under trailing underscores
19
+ # gains one more, so `begin` and `begin_` stay distinct.
20
+ def local(name)
21
+ reserved_stem?(name) ? "#{name}_" : name
22
+ end
23
+
24
+ private
25
+
26
+ def reserved_stem?(name)
27
+ RESERVED.include?(name.sub(/_+\z/, ''))
28
+ end
29
+ end
30
+ end
31
+ end
@@ -8,7 +8,7 @@ module Jade
8
8
  def generate(node, registry)
9
9
  node => AST::Pattern::Constructor(symbol:, patterns:)
10
10
  constructor = registry.lookup(symbol)
11
- qualified = to_qualified(constructor.qualified_name)
11
+ qualified = rooted(constructor.qualified_name)
12
12
 
13
13
  if keyed_variant?(constructor)
14
14
  generate_keyed(qualified, patterns.first, registry)
@@ -39,7 +39,7 @@ module Jade
39
39
  def generate_keyed(qualified, pattern, registry)
40
40
  case pattern
41
41
  in AST::Pattern::Binding(name:)
42
- "#{qualified} => #{name}"
42
+ "#{qualified} => #{Names.local(name)}"
43
43
 
44
44
  in AST::Pattern::Wildcard
45
45
  qualified
@@ -69,7 +69,7 @@ module Jade
69
69
  .then { generate_node(it, registry) }
70
70
 
71
71
  "#{list_arg}.reverse.reduce(#{base_code}) " \
72
- "{ |#{ACC_NAME}, #{head_name}| #{combine_code} }"
72
+ "{ |#{ACC_NAME}, #{Names.local(head_name)}| #{combine_code} }"
73
73
  end
74
74
 
75
75
  private
data/lib/jade/codegen.rb CHANGED
@@ -3,6 +3,7 @@ require 'jade/codegen/context'
3
3
  require 'jade/codegen/helpers'
4
4
  require 'jade/codegen/pretty'
5
5
  require 'jade/codegen/method_names'
6
+ require 'jade/codegen/names'
6
7
  require 'jade/codegen/inlines'
7
8
  require 'jade/codegen/inline'
8
9
  require 'jade/codegen/boundary'
@@ -83,10 +84,13 @@ module Jade
83
84
  def generate(node, registry, depth: 0)
84
85
  case node
85
86
  in AST::Module(name:, body:)
87
+ reopened = reopened_type(name, registry)
88
+
86
89
  preamble = [
87
90
  "require 'jade/runtime'",
88
91
  *Stdlib.requires(name),
89
92
  *namespace_setup_lines(name),
93
+ *reopening_require(reopened, name),
90
94
  ].reject(&:empty?).join(Pretty.newline)
91
95
 
92
96
  shape_consts = collect_record_shapes(body)
@@ -100,11 +104,15 @@ module Jade
100
104
  boundary_helpers = Boundary::Specialized.collect_helpers(body, registry)
101
105
  .then { Boundary::Specialized.emit_helpers(it, registry) }
102
106
 
107
+ dict_consts = {}
108
+
103
109
  outer, inner, wrappers =
104
- with_boundary_cache(boundary_cache) do
105
- with_dispatched_methods(collect_dispatched_methods(body, registry)) do
106
- with_hoisted_records do
107
- partition_module_body(body.expressions, registry, name.count('.'))
110
+ with_dict_consts(dict_consts) do
111
+ with_boundary_cache(boundary_cache) do
112
+ with_dispatched_methods(collect_dispatched_methods(body, registry)) do
113
+ with_hoisted_records do
114
+ partition_module_body(body.expressions, registry, name.count('.'))
115
+ end
108
116
  end
109
117
  end
110
118
  end
@@ -114,17 +122,18 @@ module Jade
114
122
  .then { Pretty.block("module Internal", it) }
115
123
 
116
124
  [
117
- "extend self",
125
+ (reopened ? "" : "extend self"),
118
126
  *shape_consts,
119
127
  *outer,
120
128
  inner_module,
129
+ *referenced_dicts(dict_consts, [*outer, *inner, *wrappers]),
121
130
  *boundary_consts,
122
131
  *boundary_helpers,
123
132
  *wrappers,
124
133
  ]
125
134
  .reject(&:empty?)
126
135
  .join(Pretty.newline(2))
127
- .then { Pretty.block("module #{to_qualified(name)}", it) }
136
+ .then { Pretty.block("#{reopened ? 'class' : 'module'} #{to_qualified(name)}", it) }
128
137
  .then { [preamble, it].reject(&:empty?).join(Pretty.newline(2)) }
129
138
 
130
139
  in AST::ImportDeclaration(module_name:)
@@ -138,7 +147,11 @@ module Jade
138
147
  end
139
148
 
140
149
  in AST::InteropImportDeclaration(module: mod)
141
- Pretty.block("begin", "require '#{mod.name.gsub('::', '/').downcase}'\nrescue LoadError")
150
+ Pretty.block(
151
+ "begin",
152
+ "require '#{mod.name.gsub('::', '/').downcase}'",
153
+ "rescue LoadError\nend",
154
+ )
142
155
 
143
156
  in AST::Implementation
144
157
  Implementation.generate(node, registry)
@@ -166,17 +179,17 @@ module Jade
166
179
  codegen
167
180
 
168
181
  in Symbol::Function => fn
169
- "#{to_qualified(fn.module_name)}::Internal.method(:#{fn.name})"
182
+ "#{internal(fn.module_name)}.method(:#{fn.name})"
170
183
 
171
184
  else
172
- name
185
+ Names.local(name)
173
186
  end
174
187
  end
175
188
 
176
189
  in AST::Assign(pattern:, expression:)
177
190
  case pattern
178
191
  in AST::Pattern::Binding(name:)
179
- "#{name} = #{generate(expression, registry)}"
192
+ "#{Names.local(name)} = #{generate(expression, registry)}"
180
193
  in AST::Pattern::Wildcard
181
194
  generate(expression, registry)
182
195
  else
@@ -193,7 +206,7 @@ module Jade
193
206
  FunctionDeclaration.generate(node, registry)
194
207
 
195
208
  in AST::FunctionDeclarationParam(name:)
196
- name
209
+ Names.local(name)
197
210
 
198
211
  in AST::FunctionCall
199
212
  FunctionCall.generate(node, registry)
@@ -209,6 +222,9 @@ module Jade
209
222
  in AST::StructDeclaration(name:, record_type:, symbol:)
210
223
  data_define_with_methods(name, record_type.fields.keys, "::#{to_qualified(symbol.qualified_name)}")
211
224
 
225
+ in AST::TypeAliasDeclaration
226
+ ""
227
+
212
228
  in AST::InterfaceDeclaration
213
229
  ""
214
230
 
@@ -221,7 +237,7 @@ module Jade
221
237
  codegen
222
238
 
223
239
  in Symbol::Function => fn
224
- "#{to_qualified(fn.module_name)}::Internal.method(:#{fn.name})"
240
+ "#{internal(fn.module_name)}.method(:#{fn.name})"
225
241
 
226
242
  in Symbol::Constructor => sym
227
243
  ConstructorReference.from_symbol(sym)
@@ -254,11 +270,14 @@ module Jade
254
270
  in AST::Pattern::Literal(literal:)
255
271
  generate(literal, registry)
256
272
 
273
+ in AST::Pattern::Range(from:, to:)
274
+ "#{from&.value}..#{to&.value}"
275
+
257
276
  in AST::Pattern::Wildcard
258
277
  "_"
259
278
 
260
279
  in AST::Pattern::Binding(name:)
261
- name
280
+ Names.local(name)
262
281
 
263
282
  in AST::Pattern::Record(fields:)
264
283
  generate_many(fields, registry)
@@ -270,7 +289,7 @@ module Jade
270
289
  in AST::Pattern::List(patterns:, rest:)
271
290
  rest_part =
272
291
  case rest
273
- in AST::Pattern::Binding(name:) then ["*#{name}"]
292
+ in AST::Pattern::Binding(name:) then ["*#{Names.local(name)}"]
274
293
  in AST::Pattern::Wildcard then ["*"]
275
294
  in nil then []
276
295
  end
@@ -325,7 +344,7 @@ module Jade
325
344
 
326
345
  def param_name(pattern, index = 0)
327
346
  case pattern
328
- in AST::Pattern::Binding(name:) then name
347
+ in AST::Pattern::Binding(name:) then Names.local(name)
329
348
  in AST::Pattern::Wildcard then '_'
330
349
  else
331
350
  param_synthetic_name(index)
@@ -340,6 +359,48 @@ module Jade
340
359
  "#{'../' * current_depth}#{import_path}"
341
360
  end
342
361
 
362
+ # Emission generates some expressions only to discard them (a decoder
363
+ # weighed against its specialized form, say), and a discarded one can
364
+ # still have claimed a constant. Keep the ones that survived, and the
365
+ # ones those refer to.
366
+ def referenced_dicts(table, body)
367
+ reachable_dicts(table, body.join(Pretty.newline))
368
+ .map { |source, const| "#{const} = #{source}.freeze" }
369
+ end
370
+
371
+ # A kept dictionary can name another one, so widen the search text
372
+ # with what was kept until nothing new turns up.
373
+ def reachable_dicts(table, code, kept = {})
374
+ table
375
+ .select { |_, const| code.match?(/\b#{const}\b/) }
376
+ .then do |found|
377
+ next found if found.size == kept.size
378
+
379
+ reachable_dicts(table, [code, *found.keys].join(Pretty.newline), found)
380
+ end
381
+ end
382
+
383
+ # A module whose name is already a type in its parent shares one Ruby
384
+ # constant with it, so it reopens that class rather than defining a
385
+ # module beside it and clobbering whichever loads second.
386
+ def reopened_type(name, registry)
387
+ *parent, last = name.split('.')
388
+ return nil if parent.empty?
389
+
390
+ registry
391
+ .get(parent.join('.'))
392
+ &.then { it.defined_types.key?(last) ? it : nil }
393
+ end
394
+
395
+ # Before the class body, not inside it: an import writes its require
396
+ # within the body, by which point `class` has already made a plain
397
+ # class for the constant and the type will clobber it on load.
398
+ def reopening_require(entry, name)
399
+ return [] if entry.nil? || Stdlib.is_stdlib?(entry)
400
+
401
+ ["require_relative '#{relative_require(entry.path, name.count('.'))}'"]
402
+ end
403
+
343
404
  def namespace_setup_lines(name)
344
405
  parts = name.split('.')
345
406
  (0...parts.length - 1)
data/lib/jade/compiler.rb CHANGED
@@ -7,6 +7,7 @@ module Jade
7
7
  end
8
8
 
9
9
  def require(path)
10
+ ModuleLoader::Build.discard_foreign(build_root)
10
11
  target = File.expand_path("#{build_root}/#{path}.rb", config.project_root)
11
12
 
12
13
  if needs_rebuild?(target)
@@ -14,6 +15,7 @@ module Jade
14
15
  .load(config.source_root, path + '.jd', cache_dir: cache_root)
15
16
  .tap { render_diagnostics(it) }
16
17
  .then { ModuleLoader.emit(it, path: build_root) }
18
+ .then { ModuleLoader::Build.stamp(build_root) }
17
19
  end
18
20
 
19
21
  Kernel.require(File.realpath(target))
data/lib/jade/entry.rb CHANGED
@@ -89,7 +89,7 @@ module Jade
89
89
 
90
90
  def define(symbol)
91
91
  case symbol
92
- in Symbol::Union | Symbol::Struct | Symbol::Interface
92
+ in Symbol::Union | Symbol::Struct | Symbol::Alias | Symbol::Interface
93
93
  add_defined_type(symbol)
94
94
 
95
95
  in Symbol::Implementation