mt-lang 0.3.26 → 0.3.27

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,130 @@
1
+ # Self-Host Progress
2
+
3
+ Tracks implementation status against `docs/self-host-contract.md` §11.
4
+
5
+ Status: `[ ]` not started · `[~]` in progress · `[x]` done · `[-]` blocked
6
+
7
+ ---
8
+
9
+ ## Phase 0: Foundation
10
+
11
+ | Step | Status | Module | Notes |
12
+ |------|--------|--------|-------|
13
+ | 1 | [ ] | `mtc.lexer.token_kinds` | |
14
+ | 2 | [ ] | `mtc.lexer.token` | |
15
+ | 3 | [ ] | `mtc.lexer.keywords` | |
16
+ | 4 | [ ] | `mtc.ast` | |
17
+ | 5 | [ ] | `mtc.semantic.types` | |
18
+ | 6 | [ ] | `mtc.ir` | |
19
+ | 7 | [ ] | `mtc.sexpr_dumper` | |
20
+
21
+ ## Phase 1: Lexer
22
+
23
+ | Step | Status | Module | Notes |
24
+ |------|--------|--------|-------|
25
+ | 8 | [ ] | `mtc.lexer.lexer` | |
26
+
27
+ ## Phase 2: Parser
28
+
29
+ | Step | Status | Module | Notes |
30
+ |------|--------|--------|-------|
31
+ | 9 | [ ] | `mtc.parser.state` | |
32
+ | 10 | [ ] | `mtc.parser.parser` | |
33
+
34
+ ## Phase 3: Path Resolver
35
+
36
+ | Step | Status | Module | Notes |
37
+ |------|--------|--------|-------|
38
+ | 11 | [ ] | `mtc.loader.path_resolver` | |
39
+
40
+ ## Phase 4: Semantic Analyzer
41
+
42
+ | Step | Status | Module | Notes |
43
+ |------|--------|--------|-------|
44
+ | 12 | [ ] | `mtc.semantic.scope` | |
45
+ | 13 | [ ] | `mtc.semantic.intrinsics` | |
46
+ | 14 | [ ] | `mtc.semantic.compile_time` | |
47
+ | 15 | [ ] | `mtc.semantic.type_compatibility` | |
48
+ | 16 | [ ] | `mtc.semantic.expressions` | |
49
+ | 17 | [ ] | `mtc.semantic.diagnostics` | |
50
+ | 18 | [ ] | `mtc.semantic.analyzer` | |
51
+
52
+ ## Phase 4a: Control Flow
53
+
54
+ | Step | Status | Module | Notes |
55
+ |------|--------|--------|-------|
56
+ | 19 | [ ] | `mtc.semantic.control_flow.*` | |
57
+
58
+ ## Phase 4b: Module Binder
59
+
60
+ | Step | Status | Module | Notes |
61
+ |------|--------|--------|-------|
62
+ | 20 | [ ] | `mtc.loader.binder` | |
63
+
64
+ ## Phase 4c: Module Loader
65
+
66
+ | Step | Status | Module | Notes |
67
+ |------|--------|--------|-------|
68
+ | 21 | [ ] | `mtc.loader.module_loader` | |
69
+
70
+ ## Phase 5: Lowering
71
+
72
+ | Step | Status | Module | Notes |
73
+ |------|--------|--------|-------|
74
+ | 22 | [ ] | `mtc.lowering.utils` | |
75
+ | 23 | [ ] | `mtc.lowering.lowering` | |
76
+ | 24 | [ ] | `mtc.lowering.async` | |
77
+
78
+ ## Phase 6: C Backend
79
+
80
+ | Step | Status | Module | Notes |
81
+ |------|--------|--------|-------|
82
+ | 25 | [ ] | `mtc.c_backend` | |
83
+
84
+ ## Phase 6a: Pretty Printer
85
+
86
+ | Step | Status | Module | Notes |
87
+ |------|--------|--------|-------|
88
+ | 26 | [ ] | `mtc.pretty_printer` | |
89
+
90
+ ## Phase 7: Build & CLI
91
+
92
+ | Step | Status | Module | Notes |
93
+ |------|--------|--------|-------|
94
+ | 27 | [ ] | `mtc.build` | |
95
+ | 28 | [ ] | `mtc.build_cache` | |
96
+ | 29 | [ ] | `mtc.main` | |
97
+
98
+ ## Phase 8: Bootstrap
99
+
100
+ | Step | Status | Check | Notes |
101
+ |------|--------|-------|-------|
102
+ | 8a | [ ] | Stage2 builds | |
103
+ | 8b | [ ] | `diff stage2.c stage3.c` empty | |
104
+ | 8c | [ ] | Stage2 self-builds to working binary | |
105
+ | 8d | [ ] | Test suite passes under stage2 | |
106
+
107
+ ---
108
+
109
+ ## Summary
110
+
111
+ | Metric | Count |
112
+ |--------|------:|
113
+ | Total steps | 29 |
114
+ | Done | 0 |
115
+ | In progress | 0 |
116
+ | Blocked | 0 |
117
+ | Remaining | 29 |
118
+
119
+ ---
120
+
121
+ ## Post-Fixed-Point
122
+
123
+ | Subsystem | Status | Notes |
124
+ |-----------|--------|-------|
125
+ | LSP server | [ ] | Deferred |
126
+ | DAP server | [ ] | Deferred |
127
+ | Bindgen | [ ] | Deferred |
128
+ | Imported bindings | [ ] | Deferred |
129
+ | Linter | [ ] | Deferred |
130
+ | Packages | [ ] | Deferred |
data/lib/milk_tea/base.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "pathname"
4
4
 
5
5
  module MilkTea
6
- VERSION = "0.3.26"
6
+ VERSION = "0.3.27"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -164,7 +164,10 @@ module MilkTea
164
164
  end
165
165
  end
166
166
  check_redundant_type_annotation(statement)
167
- flag_redundant_widening_cast(statement.value) if statement.type && statement.value
167
+ if statement.type && statement.value
168
+ flag_redundant_widening_cast(statement.value)
169
+ flag_redundant_literal_cast(statement.value)
170
+ end
168
171
  record_ptr_candidate(statement)
169
172
  when AST::Assignment
170
173
  visit_expression(statement.value) # visit RHS first — reads in RHS count against dead-assignment
@@ -173,7 +176,10 @@ module MilkTea
173
176
  mark_mutated(statement.target)
174
177
  check_self_assignment(statement)
175
178
  check_noop_compound_assignment(statement)
176
- flag_redundant_widening_cast(statement.value) if statement.operator == "="
179
+ if statement.operator == "="
180
+ flag_redundant_widening_cast(statement.value)
181
+ flag_redundant_literal_cast(statement.value)
182
+ end
177
183
  when AST::IfStmt
178
184
  statement.branches.each do |branch|
179
185
  visit_expression(branch.condition)
@@ -223,7 +229,10 @@ module MilkTea
223
229
  @loop_depth -= 1
224
230
  when AST::ReturnStmt
225
231
  visit_expression(statement.value) if statement.value
226
- flag_redundant_widening_cast(statement.value) if statement.value
232
+ if statement.value
233
+ flag_redundant_widening_cast(statement.value)
234
+ flag_redundant_literal_cast(statement.value)
235
+ end
227
236
  when AST::DeferStmt
228
237
  with_scope { visit_statement_list(statement.body) }
229
238
  when AST::ExpressionStmt
@@ -278,6 +287,8 @@ module MilkTea
278
287
  visit_expression(expression.right)
279
288
  check_self_comparison(expression)
280
289
  check_redundant_bool_compare(expression)
290
+ check_redundant_binary_operand_cast(expression)
291
+ check_redundant_literal_operand_cast(expression)
281
292
  when AST::RangeExpr
282
293
  visit_expression(expression.start_expr)
283
294
  visit_expression(expression.end_expr)
@@ -1141,6 +1152,140 @@ module MilkTea
1141
1152
  emit_redundant_cast(value, target_name, "implicit widening makes this cast redundant")
1142
1153
  end
1143
1154
 
1155
+ INTEGER_TYPE_RANGES = {
1156
+ "byte" => (-128..127),
1157
+ "short" => (-32_768..32_767),
1158
+ "int" => (-(2**31)..2**31 - 1),
1159
+ "long" => (-(2**63)..2**63 - 1),
1160
+ "ubyte" => (0..255),
1161
+ "ushort" => (0..65_535),
1162
+ "uint" => (0..2**32 - 1),
1163
+ "ulong" => (0..2**64 - 1),
1164
+ "char" => (0..255),
1165
+ }.freeze
1166
+
1167
+ # Reports an integer-literal cast at a coercion slot as redundant when the
1168
+ # literal would implicitly coerce to the cast target: the compiler accepts
1169
+ # a fitting integer literal for any integer slot (`buf[0] = 1` for a
1170
+ # `ubyte` element), so `ubyte<-1` adds nothing. Out-of-range literals are
1171
+ # left alone because removing the cast would then be a type error.
1172
+ # Widening literal casts are already reported by flag_redundant_widening_cast.
1173
+ def flag_redundant_literal_cast(value)
1174
+ return unless value.is_a?(AST::PrefixCast)
1175
+ return unless @sema_facts
1176
+
1177
+ target_name = type_ref_name(value.target_type)
1178
+ return unless target_name
1179
+ range = INTEGER_TYPE_RANGES[target_name]
1180
+ return unless range
1181
+
1182
+ inner = value.expression
1183
+ return unless inner.is_a?(AST::IntegerLiteral)
1184
+ literal = inner.value
1185
+ return unless literal.is_a?(Integer)
1186
+ return unless range.cover?(literal)
1187
+
1188
+ inner_type = resolve_expr_type(inner)
1189
+ return unless inner_type.is_a?(Types::Primitive)
1190
+ return if inner_type.name == target_name # same-type handled elsewhere
1191
+ return if implicit_cast_allowed?(inner_type, target_name) # widening handled elsewhere
1192
+
1193
+ emit_redundant_cast(value, target_name, "integer literal coercion makes this cast redundant")
1194
+ end
1195
+
1196
+ BINARY_CAST_SAFE_OPS = %w[+ - * / % == != < <= > >=].freeze
1197
+
1198
+ # Reports a widening cast used directly as an operand of an arithmetic or
1199
+ # comparison operator when removing it provably leaves the operation's
1200
+ # result unchanged. An integer->float cast inside a float-family
1201
+ # expression promotes to the sibling's float type either way, so the cast
1202
+ # is redundant. Excluded:
1203
+ # * shifts (`<<`/`>>`) — the shifted operand's width is load-bearing;
1204
+ # * `double` in a `float` context — `float_y + double<-i` is `double`
1205
+ # with the cast but `float` without it;
1206
+ # * cast-chains such as `float<-x + float<-y` — each removal is only
1207
+ # safe if the sibling cast stays, so neither is independently
1208
+ # redundant.
1209
+ def check_redundant_binary_operand_cast(binary)
1210
+ return unless @sema_facts
1211
+ return unless BINARY_CAST_SAFE_OPS.include?(binary.operator)
1212
+
1213
+ check_binary_operand_cast(binary.left, binary.right)
1214
+ check_binary_operand_cast(binary.right, binary.left)
1215
+ end
1216
+
1217
+ def check_binary_operand_cast(operand, sibling)
1218
+ return unless operand.is_a?(AST::PrefixCast)
1219
+ return if sibling.is_a?(AST::PrefixCast)
1220
+
1221
+ target_type = resolve_expr_type(operand)
1222
+ return unless target_type.is_a?(Types::Primitive) && target_type.float?
1223
+
1224
+ inner_type = resolve_expr_type(operand.expression)
1225
+ return unless inner_type.is_a?(Types::Primitive) && inner_type.fixed_width_integer?
1226
+
1227
+ sibling_type = resolve_expr_type(sibling)
1228
+ return unless sibling_type.is_a?(Types::Primitive) && sibling_type.float?
1229
+ return if target_type.name == "double" && sibling_type.name == "float"
1230
+
1231
+ target_name = type_ref_name(operand.target_type)
1232
+ return unless target_name
1233
+
1234
+ emit_redundant_cast(operand, target_name, "implicit float widening makes this cast redundant")
1235
+ end
1236
+
1237
+ COMPARISON_CAST_OPS = %w[== != < <= > >=].freeze
1238
+
1239
+ # Reports an integer-literal cast used directly as a comparison operand
1240
+ # (e.g. `State.idle == ubyte<-0`). The literal coerces to the sibling's
1241
+ # type either way, so `D<-lit` adds nothing when the literal fits `D` and
1242
+ # the sibling is a float-family value, an integer the literal fits in, or
1243
+ # an enum (which compares against any integer literal). Out-of-range
1244
+ # literals are left alone: `ubyte<-300` wraps in `D`, which is not the
1245
+ # same value as the bare literal.
1246
+ def check_redundant_literal_operand_cast(binary)
1247
+ return unless @sema_facts
1248
+ return unless COMPARISON_CAST_OPS.include?(binary.operator)
1249
+
1250
+ check_comparison_literal_operand(binary.left, binary.right)
1251
+ check_comparison_literal_operand(binary.right, binary.left)
1252
+ end
1253
+
1254
+ def check_comparison_literal_operand(operand, sibling)
1255
+ return unless operand.is_a?(AST::PrefixCast)
1256
+
1257
+ target_name = type_ref_name(operand.target_type)
1258
+ return unless target_name
1259
+ target_range = INTEGER_TYPE_RANGES[target_name]
1260
+ return unless target_range
1261
+
1262
+ inner = operand.expression
1263
+ return unless inner.is_a?(AST::IntegerLiteral)
1264
+ literal = inner.value
1265
+ return unless literal.is_a?(Integer)
1266
+ return unless target_range.cover?(literal)
1267
+
1268
+ # Same-type casts are reported by check_redundant_cast.
1269
+ inner_type = resolve_expr_type(inner)
1270
+ return if inner_type.is_a?(Types::Primitive) && inner_type.name == target_name
1271
+
1272
+ sibling_type = resolve_expr_type(sibling)
1273
+ case sibling_type
1274
+ when Types::Primitive
1275
+ return unless sibling_type.float? || sibling_type.integer?
1276
+ if sibling_type.integer?
1277
+ sibling_range = INTEGER_TYPE_RANGES[sibling_type.name]
1278
+ return unless sibling_range && sibling_range.cover?(literal)
1279
+ end
1280
+ when Types::EnumBase
1281
+ nil
1282
+ else
1283
+ return
1284
+ end
1285
+
1286
+ emit_redundant_cast(operand, target_name, "integer literal coercion makes this cast redundant")
1287
+ end
1288
+
1144
1289
  def same_resolved_type?(left, right)
1145
1290
  return true if left.equal?(right)
1146
1291
 
@@ -1177,6 +1322,14 @@ module MilkTea
1177
1322
  target = find_primitive_type(target_name)
1178
1323
  return false unless target
1179
1324
  return false unless target.is_a?(Types::Primitive)
1325
+
1326
+ # Fixed-width integers widen implicitly to float/double (matching the
1327
+ # compiler's promotion, e.g. `float + int` or `return int` from a
1328
+ # `-> float` function). `char`/`bool` are not fixed-width integers and
1329
+ # have no implicit conversion to the float family; float->double is not
1330
+ # implicit either.
1331
+ return true if target.float? && inner_type.fixed_width_integer?
1332
+
1180
1333
  return false unless inner_type.fixed_width_integer? && target.fixed_width_integer?
1181
1334
 
1182
1335
  if inner_type.signed_integer? == target.signed_integer?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mt-lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.26
4
+ version: 0.3.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -149,6 +149,9 @@ files:
149
149
  - docs/index.html
150
150
  - docs/language-design.md
151
151
  - docs/language-manual.md
152
+ - docs/self-host-contract.md
153
+ - docs/self-host-plan.md
154
+ - docs/self-host-progress.md
152
155
  - lib/milk_tea.rb
153
156
  - lib/milk_tea/base.rb
154
157
  - lib/milk_tea/bindings.rb
@@ -624,7 +627,7 @@ metadata:
624
627
  homepage_uri: https://teefan.github.io/mt-lang/
625
628
  source_code_uri: https://github.com/teefan/mt-lang
626
629
  post_install_message: |
627
- Milk Tea 0.3.26 installed!
630
+ Milk Tea 0.3.27 installed!
628
631
 
629
632
  System requirements:
630
633
  - A C compiler (gcc or clang) must be available on PATH