mt-lang 0.2.7 → 0.2.10
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/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/core/ast.rb +2 -2
- data/lib/milk_tea/core/lowering/functions.rb +1 -1
- data/lib/milk_tea/core/lowering/resolve.rb +6 -0
- data/lib/milk_tea/core/parser/expressions.rb +1 -0
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +2 -2
- data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +39 -2
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +11 -4
- data/lib/milk_tea/core/semantic_analyzer.rb +1 -1
- data/lib/milk_tea/lsp/diagnostics.rb +20 -3
- data/lib/milk_tea/lsp/server/code_actions.rb +12 -1
- data/lib/milk_tea/lsp/server/completion.rb +18 -6
- data/lib/milk_tea/lsp/server/definition.rb +1 -1
- data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +10 -0
- data/lib/milk_tea/lsp/server/hover.rb +214 -12
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +1 -1
- data/lib/milk_tea/lsp/server.rb +15 -3
- data/lib/milk_tea/lsp/workspace/analysis.rb +3 -3
- data/lib/milk_tea/lsp/workspace/collection.rb +6 -0
- data/lib/milk_tea/tooling/cli.rb +9 -4
- data/lib/milk_tea/tooling/linter/visitors.rb +14 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9047d861f94f1e5b66f531712466e32779a0a86bd02db452e36cd8990980ee5c
|
|
4
|
+
data.tar.gz: 3e9d1c7f06e70e2a9e3b84b8df4e8ee64b92be6e34f6bc970ca0b2ef69683c8e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4144e09654664b0dd4eaf7d697fe077381c965902564297d32002807be4b9100861ca8c7c7e065b983e90b91dba4b86f22f52eaa4eae47fa1d68dee1cb1acc36
|
|
7
|
+
data.tar.gz: a712ee592463d961523bd2c8481be6993843f277c51bd3947f34bd8d3e32ae8d245ead8985f31d2621cefd8cc1ec705eba1749aca99324cdd3061adb5ef13ce2
|
data/lib/milk_tea/base.rb
CHANGED
data/lib/milk_tea/core/ast.rb
CHANGED
|
@@ -227,8 +227,8 @@ module MilkTea
|
|
|
227
227
|
RangeExpr = Data.define(:start_expr, :end_expr, :line, :column)
|
|
228
228
|
ExpressionList = Data.define(:elements, :line, :column)
|
|
229
229
|
IfExpr = Data.define(:condition, :then_expression, :else_expression)
|
|
230
|
-
MatchExpr = Data.define(:expression, :arms, :line, :column, :length) do
|
|
231
|
-
def initialize(expression:, arms:, line: nil, column: nil, length: nil) = super
|
|
230
|
+
MatchExpr = Data.define(:expression, :arms, :line, :column, :length, :desugared_from_is) do
|
|
231
|
+
def initialize(expression:, arms:, line: nil, column: nil, length: nil, desugared_from_is: false) = super
|
|
232
232
|
end
|
|
233
233
|
UnsafeExpr = Data.define(:expression, :line, :column, :length) do
|
|
234
234
|
def initialize(expression:, line: nil, column: nil, length: nil) = super
|
|
@@ -80,7 +80,7 @@ module MilkTea
|
|
|
80
80
|
def resolve_extending_receiver_type(analysis, type_name)
|
|
81
81
|
if type_name.is_a?(AST::TypeRef)
|
|
82
82
|
generic_type = resolve_named_generic_type_for_analysis(analysis, type_name.name.parts)
|
|
83
|
-
if generic_type.is_a?(Types::GenericStructDefinition)
|
|
83
|
+
if generic_type.is_a?(Types::GenericStructDefinition) || generic_type.is_a?(Types::GenericVariantDefinition)
|
|
84
84
|
validate_methods_receiver_type_arguments!(type_name, generic_type)
|
|
85
85
|
return generic_type
|
|
86
86
|
end
|
|
@@ -430,6 +430,8 @@ module MilkTea
|
|
|
430
430
|
[:get, nil, nil, nil]
|
|
431
431
|
elsif (type = @ctx.types[callee.name]).is_a?(Types::Struct) || type.is_a?(Types::StringView) || task_type?(type) || type.is_a?(Types::Vector) || type.is_a?(Types::Matrix) || type.is_a?(Types::Quaternion)
|
|
432
432
|
[ :struct_literal, nil, nil, type ]
|
|
433
|
+
elsif (type = @ctx.types[callee.name]).is_a?(Types::GenericStructDefinition) || type.is_a?(Types::GenericVariantDefinition)
|
|
434
|
+
raise LoweringError, "generic type #{callee.name} requires type arguments"
|
|
433
435
|
else
|
|
434
436
|
emit_fn = @artifacts.emitted_declarations.find { |d| d.is_a?(IR::Function) && d.name == callee.name }
|
|
435
437
|
if emit_fn
|
|
@@ -452,6 +454,10 @@ module MilkTea
|
|
|
452
454
|
return [:function, external_function_c_name(binding), nil, binding.type, binding]
|
|
453
455
|
end
|
|
454
456
|
imported_type = imported_module.types[callee.member]
|
|
457
|
+
if imported_type.is_a?(Types::GenericStructDefinition) || imported_type.is_a?(Types::GenericVariantDefinition)
|
|
458
|
+
raise LoweringError, "generic type #{callee.receiver.name}.#{callee.member} requires type arguments"
|
|
459
|
+
end
|
|
460
|
+
|
|
455
461
|
if imported_type.is_a?(Types::Struct) || imported_type.is_a?(Types::StringView) || task_type?(imported_type) || imported_type.is_a?(Types::Vector) || imported_type.is_a?(Types::Matrix) || imported_type.is_a?(Types::Quaternion)
|
|
456
462
|
return [:struct_literal, nil, nil, imported_module.types.fetch(callee.member)]
|
|
457
463
|
end
|
|
@@ -1005,7 +1005,7 @@ module MilkTea
|
|
|
1005
1005
|
)
|
|
1006
1006
|
|
|
1007
1007
|
check_function_call(callable, expression.arguments, scopes:)
|
|
1008
|
-
|
|
1008
|
+
validate_specialized_function_body(callable) unless callable.type_arguments.empty?
|
|
1009
1009
|
callable.type.return_type
|
|
1010
1010
|
when :method
|
|
1011
1011
|
callable = specialize_function_binding(
|
|
@@ -1018,7 +1018,7 @@ module MilkTea
|
|
|
1018
1018
|
raise_sema_error("cannot call editable method #{callable.name} on an immutable receiver") if callable.type.receiver_editable && !assignable_receiver?(receiver, scopes)
|
|
1019
1019
|
|
|
1020
1020
|
check_function_call(callable, expression.arguments, scopes:)
|
|
1021
|
-
|
|
1021
|
+
validate_specialized_function_body(callable) unless callable.type_arguments.empty?
|
|
1022
1022
|
callable.type.return_type
|
|
1023
1023
|
when :callable_value
|
|
1024
1024
|
check_callable_value_call(callable, expression.arguments, scopes:, callee_expression: expression.callee)
|
|
@@ -256,6 +256,22 @@ module MilkTea
|
|
|
256
256
|
end
|
|
257
257
|
end
|
|
258
258
|
|
|
259
|
+
# Validates the body of a specialized (instantiated) function or method
|
|
260
|
+
# binding. The owner checker may be in collecting-errors mode, which
|
|
261
|
+
# would silently swallow body errors into @structural_errors. We
|
|
262
|
+
# temporarily disable collect mode on the owner so the caller receives
|
|
263
|
+
# the SemanticError directly.
|
|
264
|
+
def validate_specialized_function_body(binding)
|
|
265
|
+
owner = binding.owner
|
|
266
|
+
prev_collecting = owner.instance_variable_get(:@collecting_errors)
|
|
267
|
+
owner.instance_variable_set(:@collecting_errors, false)
|
|
268
|
+
owner.send(:check_function, binding)
|
|
269
|
+
rescue SemanticError => e
|
|
270
|
+
raise unless e.message.include?("cannot assign through immutable")
|
|
271
|
+
ensure
|
|
272
|
+
owner.instance_variable_set(:@collecting_errors, prev_collecting) if owner
|
|
273
|
+
end
|
|
274
|
+
|
|
259
275
|
# Per-function error collection used by check_collecting_errors.
|
|
260
276
|
# Continues past individual function failures, accumulating SemanticErrors.
|
|
261
277
|
def check_functions_collecting(errors)
|
|
@@ -286,13 +302,29 @@ module MilkTea
|
|
|
286
302
|
end
|
|
287
303
|
end
|
|
288
304
|
|
|
305
|
+
# Scans generic method bodies for assignments to this through a
|
|
306
|
+
# non-editable receiver. Full body checking is deferred to call-site
|
|
307
|
+
# specialization, but immutable-this violations are type-independent.
|
|
308
|
+
def check_generic_method_immutable_this(binding, scopes)
|
|
309
|
+
this_binding = scopes.first&.values&.find { |v| v.name == "this" }
|
|
310
|
+
return unless this_binding
|
|
311
|
+
return if this_binding.mutable
|
|
312
|
+
|
|
313
|
+
binding.ast.body&.each do |statement|
|
|
314
|
+
next unless statement.is_a?(AST::Assignment) && statement.operator == "="
|
|
315
|
+
next unless statement.target.is_a?(AST::MemberAccess) && statement.target.receiver.is_a?(AST::Identifier) && statement.target.receiver.name == "this"
|
|
316
|
+
|
|
317
|
+
raise_sema_error("cannot assign through immutable this", statement.target)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
289
321
|
def check_function(binding)
|
|
290
322
|
@local_completion_frames = @local_completion_frames.dup if @local_completion_frames.frozen?
|
|
291
323
|
|
|
292
324
|
previous_type_substitutions = @current_type_substitutions
|
|
293
325
|
previous_specialization_owner = @current_specialization_owner
|
|
294
326
|
started_check = false
|
|
295
|
-
return if binding.external
|
|
327
|
+
return if binding.external
|
|
296
328
|
return if @checked_function_bindings[binding.object_id]
|
|
297
329
|
return if @checking_function_bindings[binding.object_id]
|
|
298
330
|
|
|
@@ -303,6 +335,11 @@ module MilkTea
|
|
|
303
335
|
with_error_node(binding.ast) do
|
|
304
336
|
with_scope(binding.body_params) do |scopes|
|
|
305
337
|
start_local_completion_frame(binding, scopes)
|
|
338
|
+
if binding.type_params.any?
|
|
339
|
+
check_generic_method_immutable_this(binding, scopes)
|
|
340
|
+
return
|
|
341
|
+
end
|
|
342
|
+
|
|
306
343
|
if binding.ast.is_a?(AST::ForeignFunctionDecl)
|
|
307
344
|
record_callable_value_expression_site(binding.ast.mapping) unless binding.ast.mapping.is_a?(AST::Call)
|
|
308
345
|
expression = foreign_mapping_expression(binding.ast)
|
|
@@ -337,10 +374,10 @@ module MilkTea
|
|
|
337
374
|
end
|
|
338
375
|
end
|
|
339
376
|
end
|
|
340
|
-
@checked_function_bindings[binding.object_id] = true
|
|
341
377
|
ensure
|
|
342
378
|
return unless started_check
|
|
343
379
|
|
|
380
|
+
@checked_function_bindings[binding.object_id] = true
|
|
344
381
|
finish_local_completion_frame(binding)
|
|
345
382
|
@preassigned_local_binding_ids = {}
|
|
346
383
|
@nullability_flow_result = nil
|
|
@@ -304,10 +304,17 @@ module MilkTea
|
|
|
304
304
|
end
|
|
305
305
|
display_name = type_name.is_a?(Array) ? type_name.join(".") : type_name
|
|
306
306
|
raise_sema_error("unknown type #{display_name} for struct destructure") unless struct_type
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
307
|
+
if struct_type.is_a?(Types::GenericStructDefinition)
|
|
308
|
+
if value_type.is_a?(Types::StructInstance) && value_type.definition == struct_type
|
|
309
|
+
fields = value_type.fields
|
|
310
|
+
else
|
|
311
|
+
raise_sema_error("generic type #{display_name} requires type arguments")
|
|
312
|
+
end
|
|
313
|
+
else
|
|
314
|
+
raise_sema_error("#{display_name} is not a struct") unless struct_type.is_a?(Types::Struct) || struct_type.is_a?(Types::Tuple)
|
|
315
|
+
ensure_assignable!(value_type, struct_type, "cannot destructure #{value_type} as #{display_name}")
|
|
316
|
+
fields = struct_type.fields
|
|
317
|
+
end
|
|
311
318
|
raise_sema_error("destructure pattern has #{statement.destructure_bindings.length} bindings but #{display_name} has #{fields.length} fields") unless statement.destructure_bindings.length == fields.length
|
|
312
319
|
|
|
313
320
|
current_scope = current_actual_scope(scopes)
|
|
@@ -87,8 +87,17 @@ module MilkTea
|
|
|
87
87
|
sema_start = total_start ? monotonic_time : nil
|
|
88
88
|
sema_snapshot ||= SemanticAnalyzer.tooling_snapshot(ast, imported_modules: imported_modules.fetch(:modules), path: path, allow_missing_imports: true)
|
|
89
89
|
sema_facts = sema_snapshot.facts
|
|
90
|
+
cross_by_uri = {}
|
|
91
|
+
target_path = path.is_a?(String) ? File.expand_path(path) : nil
|
|
90
92
|
sema_snapshot.diagnostics.reject { |diagnostic| redundant_unknown_import_diagnostic?(diagnostic, unresolved_import_paths) }
|
|
91
|
-
.each
|
|
93
|
+
.each do |diagnostic|
|
|
94
|
+
if target_path && diagnostic.path && File.expand_path(diagnostic.path) != target_path
|
|
95
|
+
target_uri = path_to_uri(diagnostic.path)
|
|
96
|
+
(cross_by_uri[target_uri] ||= []) << format_tooling_diagnostic(diagnostic, stage: 'sema')
|
|
97
|
+
else
|
|
98
|
+
diagnostics << format_tooling_diagnostic(diagnostic, stage: 'sema')
|
|
99
|
+
end
|
|
100
|
+
end
|
|
92
101
|
sema_ms = elapsed_ms(sema_start) if sema_start
|
|
93
102
|
rescue StandardError => e
|
|
94
103
|
log_diagnostics_warning("Error collecting diagnostics: #{e.message}")
|
|
@@ -142,7 +151,7 @@ module MilkTea
|
|
|
142
151
|
log_diagnostics_warning("Error collecting lint diagnostics: #{e.message}")
|
|
143
152
|
end
|
|
144
153
|
|
|
145
|
-
{ diagnostics: diagnostics, facts: sema_facts, sema_snapshot: sema_snapshot }
|
|
154
|
+
{ diagnostics: diagnostics, cross_file_diagnostics: cross_by_uri, facts: sema_facts, sema_snapshot: sema_snapshot }
|
|
146
155
|
ensure
|
|
147
156
|
if total_start
|
|
148
157
|
lint_breakdown = lint_profile&.summary(limit: 12)
|
|
@@ -196,7 +205,15 @@ module MilkTea
|
|
|
196
205
|
def self.redundant_unknown_import_diagnostic?(diagnostic, unresolved_import_paths)
|
|
197
206
|
return false unless diagnostic&.message.to_s.start_with?("unknown import ")
|
|
198
207
|
|
|
199
|
-
|
|
208
|
+
import_path = diagnostic.message.to_s.match(/unknown import (.+)$/)&.captures&.first
|
|
209
|
+
return false unless import_path
|
|
210
|
+
|
|
211
|
+
unresolved_import_paths.include?(import_path)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def self.path_to_uri(path)
|
|
215
|
+
escaped_path = path.split('/').map { |seg| CGI.escape(seg).gsub('+', '%20') }.join('/')
|
|
216
|
+
"file://#{escaped_path}"
|
|
200
217
|
end
|
|
201
218
|
|
|
202
219
|
def self.uri_to_path(uri)
|
|
@@ -366,7 +366,8 @@ module MilkTea
|
|
|
366
366
|
return { kind: 'full', items: [] } unless uri
|
|
367
367
|
|
|
368
368
|
content = @workspace.get_content(uri)
|
|
369
|
-
|
|
369
|
+
result = @workspace.collect_diagnostics(uri)
|
|
370
|
+
diagnostics = result
|
|
370
371
|
fingerprint = diagnostics_fingerprint(content, diagnostics)
|
|
371
372
|
previous_result_id = params['previousResultId']
|
|
372
373
|
cached = @diagnostic_report_cache[uri]
|
|
@@ -384,6 +385,16 @@ module MilkTea
|
|
|
384
385
|
fingerprint: fingerprint
|
|
385
386
|
}
|
|
386
387
|
|
|
388
|
+
cross = @workspace.cross_file_diagnostics
|
|
389
|
+
if cross&.any?
|
|
390
|
+
cross.each do |target_uri, items|
|
|
391
|
+
@protocol.write_notification('textDocument/publishDiagnostics', {
|
|
392
|
+
uri: target_uri,
|
|
393
|
+
diagnostics: items
|
|
394
|
+
})
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
387
398
|
{
|
|
388
399
|
kind: 'full',
|
|
389
400
|
resultId: result_id,
|
|
@@ -473,7 +473,7 @@ module MilkTea
|
|
|
473
473
|
|
|
474
474
|
current_type = first_type
|
|
475
475
|
chain_segments[1..].each do |seg|
|
|
476
|
-
field_owner = project_field_receiver_type_for_completion(current_type)
|
|
476
|
+
field_owner = project_field_receiver_type_for_completion(current_type, facts)
|
|
477
477
|
if field_owner.respond_to?(:field) && (field_type = field_owner.field(seg))
|
|
478
478
|
current_type = field_type
|
|
479
479
|
else
|
|
@@ -674,7 +674,7 @@ module MilkTea
|
|
|
674
674
|
def completion_items_for_value_receiver(facts, receiver_type, prefix)
|
|
675
675
|
items = []
|
|
676
676
|
|
|
677
|
-
field_receiver_type = project_field_receiver_type_for_completion(receiver_type)
|
|
677
|
+
field_receiver_type = project_field_receiver_type_for_completion(receiver_type, facts)
|
|
678
678
|
if field_receiver_type.respond_to?(:fields)
|
|
679
679
|
field_receiver_type.fields.each do |fname, ftype|
|
|
680
680
|
next unless prefix.empty? || fname.start_with?(prefix)
|
|
@@ -763,8 +763,20 @@ module MilkTea
|
|
|
763
763
|
dispatch_receiver_type == receiver_type ? receiver_type : dispatch_receiver_type
|
|
764
764
|
end
|
|
765
765
|
|
|
766
|
-
def project_field_receiver_type_for_completion(type)
|
|
767
|
-
project_receiver_type_for_completion(type)
|
|
766
|
+
def project_field_receiver_type_for_completion(type, facts = nil)
|
|
767
|
+
type = project_receiver_type_for_completion(type)
|
|
768
|
+
return type.definition if type.is_a?(Types::StructInstance)
|
|
769
|
+
return field_type_from_struct_definition(type, facts) if facts
|
|
770
|
+
|
|
771
|
+
type
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
def field_type_from_struct_definition(type, facts)
|
|
775
|
+
return type unless type.is_a?(Types::GenericInstance)
|
|
776
|
+
return type if ref_type_name?(type) || pointer_type_name?(type)
|
|
777
|
+
|
|
778
|
+
struct_def = facts.types[type.name]
|
|
779
|
+
struct_def if struct_def&.respond_to?(:fields)
|
|
768
780
|
end
|
|
769
781
|
|
|
770
782
|
def project_method_receiver_type_for_completion(type)
|
|
@@ -778,8 +790,8 @@ module MilkTea
|
|
|
778
790
|
type
|
|
779
791
|
end
|
|
780
792
|
|
|
781
|
-
def field_owner_type(receiver_type)
|
|
782
|
-
aggregate_type = project_field_receiver_type_for_completion(receiver_type)
|
|
793
|
+
def field_owner_type(receiver_type, facts = nil)
|
|
794
|
+
aggregate_type = project_field_receiver_type_for_completion(receiver_type, facts)
|
|
783
795
|
return aggregate_type.definition if aggregate_type.is_a?(Types::StructInstance)
|
|
784
796
|
|
|
785
797
|
aggregate_type
|
|
@@ -247,7 +247,7 @@ module MilkTea
|
|
|
247
247
|
return nil unless current_type
|
|
248
248
|
|
|
249
249
|
chain[:segments][1..hovered_segment[:position]].each do |segment|
|
|
250
|
-
field_receiver_type = project_field_receiver_type_for_completion(current_type)
|
|
250
|
+
field_receiver_type = project_field_receiver_type_for_completion(current_type, facts)
|
|
251
251
|
if field_receiver_type.respond_to?(:field) && (field_type = field_receiver_type.field(segment[:name]))
|
|
252
252
|
return field_definition_location(current_uri, field_receiver_type, segment[:name]) if segment[:token_index] == token_index
|
|
253
253
|
|
|
@@ -133,6 +133,16 @@ module MilkTea
|
|
|
133
133
|
diagnostics: diagnostics
|
|
134
134
|
})
|
|
135
135
|
notify_diagnostic_errors(uri, diagnostics)
|
|
136
|
+
|
|
137
|
+
cross = @workspace.cross_file_diagnostics
|
|
138
|
+
if cross&.any?
|
|
139
|
+
cross.each do |target_uri, items|
|
|
140
|
+
@protocol.write_notification('textDocument/publishDiagnostics', {
|
|
141
|
+
uri: target_uri,
|
|
142
|
+
diagnostics: items
|
|
143
|
+
})
|
|
144
|
+
end
|
|
145
|
+
end
|
|
136
146
|
end
|
|
137
147
|
elsif perf_logging?
|
|
138
148
|
@diagnostics_perf[:dropped_stale] += 1
|
|
@@ -8,19 +8,222 @@ module MilkTea
|
|
|
8
8
|
|
|
9
9
|
KEYWORD_HOVER_INFO = {
|
|
10
10
|
'size_of' => {
|
|
11
|
-
signature: 'size_of(
|
|
12
|
-
docs: '`size_of(
|
|
11
|
+
signature: 'size_of(Type) -> ptr_uint',
|
|
12
|
+
docs: '`size_of(Type)` evaluates the compile-time size (in bytes) of the type.',
|
|
13
13
|
},
|
|
14
14
|
'align_of' => {
|
|
15
|
-
signature: 'align_of(
|
|
16
|
-
docs: '`align_of(
|
|
15
|
+
signature: 'align_of(Type) -> ptr_uint',
|
|
16
|
+
docs: '`align_of(Type)` evaluates the compile-time alignment (in bytes) of the type.',
|
|
17
17
|
},
|
|
18
18
|
'offset_of' => {
|
|
19
|
-
signature: 'offset_of(Type, field) ->
|
|
19
|
+
signature: 'offset_of(Type, field) -> ptr_uint',
|
|
20
20
|
docs: '`offset_of(Type, field)` evaluates the compile-time byte offset of `field` within `Type`.',
|
|
21
21
|
},
|
|
22
22
|
}.freeze
|
|
23
23
|
|
|
24
|
+
LANGUAGE_KEYWORD_HOVER_INFO = {
|
|
25
|
+
'let' => {
|
|
26
|
+
signature: 'let',
|
|
27
|
+
docs: 'Immutable local variable declaration: `let name = value` or `let name: Type`. Supports `else` guard with `T?`, `Option[T]`, or `Result[T, E]`.',
|
|
28
|
+
},
|
|
29
|
+
'var' => {
|
|
30
|
+
signature: 'var',
|
|
31
|
+
docs: 'Mutable local or module-level variable: `var name: Type = initializer`. Supports `else` guard. Module `var` requires explicit type.',
|
|
32
|
+
},
|
|
33
|
+
'const' => {
|
|
34
|
+
signature: 'const',
|
|
35
|
+
docs: 'Compile-time constant, requires explicit type and initializer. Block-bodied form `const NAME -> TYPE:` evaluates at compile time.',
|
|
36
|
+
},
|
|
37
|
+
'function' => {
|
|
38
|
+
signature: 'function',
|
|
39
|
+
docs: 'Ordinary function declaration: `function name(params) -> ReturnType:`. Parameters are non-rebindable. Return type defaults to `void`.',
|
|
40
|
+
},
|
|
41
|
+
'async' => {
|
|
42
|
+
signature: 'async',
|
|
43
|
+
docs: 'Marks a function as async; the declared return type is lifted to `Task[T]`. Use `await` inside async functions.',
|
|
44
|
+
},
|
|
45
|
+
'await' => {
|
|
46
|
+
signature: 'await',
|
|
47
|
+
docs: 'Awaits a `Task[T]` inside an async function, yielding the unwrapped `T`. Only allowed in async function bodies.',
|
|
48
|
+
},
|
|
49
|
+
'struct' => {
|
|
50
|
+
signature: 'struct',
|
|
51
|
+
docs: 'Value-type struct with named, typed fields: `struct Name: field: Type`. Supports generics, nested structs, and `implements`.',
|
|
52
|
+
},
|
|
53
|
+
'enum' => {
|
|
54
|
+
signature: 'enum',
|
|
55
|
+
docs: 'Integer-backed enumeration: `enum Name: BackingType`. Backing type defaults to `int`. Values auto-increment from 0 or the last explicit value.',
|
|
56
|
+
},
|
|
57
|
+
'flags' => {
|
|
58
|
+
signature: 'flags',
|
|
59
|
+
docs: 'Bitmask type backed by an integer primitive: `flags Name: uint`. Members must be compile-time integer constants.',
|
|
60
|
+
},
|
|
61
|
+
'union' => {
|
|
62
|
+
signature: 'union',
|
|
63
|
+
docs: 'Overlapped storage union: `union Name: field1: Type1, field2: Type2`. All fields share the same memory.',
|
|
64
|
+
},
|
|
65
|
+
'variant' => {
|
|
66
|
+
signature: 'variant',
|
|
67
|
+
docs: 'Tagged union: `variant Name: arm1(field: Type), arm2`. Arms may carry named payload fields. No-payload arms are bare identifiers.',
|
|
68
|
+
},
|
|
69
|
+
'opaque' => {
|
|
70
|
+
signature: 'opaque',
|
|
71
|
+
docs: 'Externally-defined type with unknown layout: `opaque Name`. May declare `implements` for interface conformance.',
|
|
72
|
+
},
|
|
73
|
+
'type' => {
|
|
74
|
+
signature: 'type',
|
|
75
|
+
docs: 'Type alias: `type Name = ExistingType`. Generic type parameters are supported.',
|
|
76
|
+
},
|
|
77
|
+
'interface' => {
|
|
78
|
+
signature: 'interface',
|
|
79
|
+
docs: 'Declares a method contract: `interface Name: function method(params) -> T`. Implemented via `implements`; used at runtime via `dyn[Interface]`.',
|
|
80
|
+
},
|
|
81
|
+
'extending' => {
|
|
82
|
+
signature: 'extending',
|
|
83
|
+
docs: 'Extends an existing type with methods: `extending Type: function method():`. Supports `function`, `editable function`, and `static function`.',
|
|
84
|
+
},
|
|
85
|
+
'implements' => {
|
|
86
|
+
signature: 'implements',
|
|
87
|
+
docs: 'Nominal interface conformance on `struct` or `opaque`: `struct Foo implements Interface`. Must be declared on the type definition.',
|
|
88
|
+
},
|
|
89
|
+
'editable' => {
|
|
90
|
+
signature: 'editable',
|
|
91
|
+
docs: 'Method receiver modifier: `editable function`. Grants mutable access to `this`. Used in interfaces and extending blocks.',
|
|
92
|
+
},
|
|
93
|
+
'static' => {
|
|
94
|
+
signature: 'static',
|
|
95
|
+
docs: 'Static method modifier: `static function`. No `this` receiver. Used in interfaces and extending blocks for constructors and utilities.',
|
|
96
|
+
},
|
|
97
|
+
'import' => {
|
|
98
|
+
signature: 'import',
|
|
99
|
+
docs: 'Module import: `import module.path` or `import module.path as alias`. Module lookup resolves `a.b.c` to `a/b/c.mt`.',
|
|
100
|
+
},
|
|
101
|
+
'public' => {
|
|
102
|
+
signature: 'public',
|
|
103
|
+
docs: 'Export visibility modifier: `public function`, `public type`, etc. Rejected on `extending`, `external`, and `static_assert` declarations.',
|
|
104
|
+
},
|
|
105
|
+
'if' => {
|
|
106
|
+
signature: 'if',
|
|
107
|
+
docs: 'Conditional branch: `if condition:`. Condition must be `bool`. Supports `else if` and `else`. Inline form: `if cond: stmt else: stmt`.',
|
|
108
|
+
},
|
|
109
|
+
'else' => {
|
|
110
|
+
signature: 'else',
|
|
111
|
+
docs: 'Else branch for `if`. Chains as `else if condition:` for additional branches. Supports inline single-statement form `else: stmt`.',
|
|
112
|
+
},
|
|
113
|
+
'match' => {
|
|
114
|
+
signature: 'match',
|
|
115
|
+
docs: 'Pattern match on enum, variant, integer, `str`, or tuple. Expression form produces a value. Must be exhaustive without `_` wildcard.',
|
|
116
|
+
},
|
|
117
|
+
'for' => {
|
|
118
|
+
signature: 'for',
|
|
119
|
+
docs: 'Loop: `for i in 0..count:` (exclusive range) or `for item in iterable:`. Multi-iteration `for left, right in xs, ys:` iterates arrays/spans in lockstep.',
|
|
120
|
+
},
|
|
121
|
+
'while' => {
|
|
122
|
+
signature: 'while',
|
|
123
|
+
docs: 'Conditional loop: `while condition:`. Condition must be `bool`. Supports `break`, `continue`, and `defer` inside the body.',
|
|
124
|
+
},
|
|
125
|
+
'return' => {
|
|
126
|
+
signature: 'return',
|
|
127
|
+
docs: 'Returns a value from a function. Not allowed inside `defer` blocks.',
|
|
128
|
+
},
|
|
129
|
+
'break' => {
|
|
130
|
+
signature: 'break',
|
|
131
|
+
docs: 'Exits the nearest enclosing `for`, `while`, or `parallel for` loop.',
|
|
132
|
+
},
|
|
133
|
+
'continue' => {
|
|
134
|
+
signature: 'continue',
|
|
135
|
+
docs: 'Skips to the next iteration of the nearest enclosing `for` or `while` loop.',
|
|
136
|
+
},
|
|
137
|
+
'defer' => {
|
|
138
|
+
signature: 'defer',
|
|
139
|
+
docs: 'Defers an expression or block to function exit: `defer expr` or `defer:`. Multiple defers execute in LIFO order. `return` is not allowed inside.',
|
|
140
|
+
},
|
|
141
|
+
'unsafe' => {
|
|
142
|
+
signature: 'unsafe',
|
|
143
|
+
docs: 'Required for pointer indexing, raw pointer dereference, pointer arithmetic, pointer casts, and `reinterpret[...]`. Single-expr or block form.',
|
|
144
|
+
},
|
|
145
|
+
'external' => {
|
|
146
|
+
signature: 'external',
|
|
147
|
+
docs: 'Raw C ABI surface: `external function name(params) -> T`. No body, supports variadic `...`. Also marks external files with `external` header.',
|
|
148
|
+
},
|
|
149
|
+
'foreign' => {
|
|
150
|
+
signature: 'foreign',
|
|
151
|
+
docs: 'Foreign function bridging: `foreign function name(params) -> T = c.FuncName`. Supports `in`, `out`, `inout`, and `consuming` parameter modes.',
|
|
152
|
+
},
|
|
153
|
+
'consuming' => {
|
|
154
|
+
signature: 'consuming',
|
|
155
|
+
docs: 'Foreign function parameter mode: takes ownership of the argument. The caller\'s binding is consumed. Only on `foreign function` params.',
|
|
156
|
+
},
|
|
157
|
+
'when' => {
|
|
158
|
+
signature: 'when',
|
|
159
|
+
docs: 'Compile-time conditional: `when CONSTANT:`. Only the chosen branch is type-checked and emitted. Requires `else` unless exhaustive.',
|
|
160
|
+
},
|
|
161
|
+
'inline' => {
|
|
162
|
+
signature: 'inline',
|
|
163
|
+
docs: 'Compile-time unrolling modifier: `inline for` (loop unrolling), `inline while`, `inline match`, `inline if`. Only the active branch emits code.',
|
|
164
|
+
},
|
|
165
|
+
'emit' => {
|
|
166
|
+
signature: 'emit',
|
|
167
|
+
docs: 'Emits a declaration at compile time: `emit function ...`. Only allowed inside `const function` or `inline` bodies.',
|
|
168
|
+
},
|
|
169
|
+
'parallel' => {
|
|
170
|
+
signature: 'parallel',
|
|
171
|
+
docs: 'Concurrency construct: `parallel for i in 0..N:` (data-parallel loop) or `parallel:` block (concurrent statement dispatch). Uses OS threads via libuv.',
|
|
172
|
+
},
|
|
173
|
+
'detach' => {
|
|
174
|
+
signature: 'detach',
|
|
175
|
+
docs: 'Spawns work on a separate thread, returning a `Handle`: `let h = detach func()`. Use `gather` to wait. Supports global function calls only.',
|
|
176
|
+
},
|
|
177
|
+
'gather' => {
|
|
178
|
+
signature: 'gather',
|
|
179
|
+
docs: 'Blocks until one or more `detach` handles complete: `gather h1, h2`. Takes one or more `Handle` values.',
|
|
180
|
+
},
|
|
181
|
+
'event' => {
|
|
182
|
+
signature: 'event',
|
|
183
|
+
docs: 'Typed publisher/subscriber: `event name[capacity]` or `event name[capacity](PayloadType)`. Supports `subscribe`, `emit`, `unsubscribe`, `wait`.',
|
|
184
|
+
},
|
|
185
|
+
'attribute' => {
|
|
186
|
+
signature: 'attribute',
|
|
187
|
+
docs: 'Declares a reusable declaration attribute: `attribute[target, ...] name(params)`. Targets: struct, field, callable, const, event, enum, flags, union, variant.',
|
|
188
|
+
},
|
|
189
|
+
'proc' => {
|
|
190
|
+
signature: 'proc',
|
|
191
|
+
docs: 'Ref-counted closure: `proc(params...) -> T: body`. Captures values by value. Storable in structs, arrays, and tuples.',
|
|
192
|
+
},
|
|
193
|
+
'fn' => {
|
|
194
|
+
signature: 'fn',
|
|
195
|
+
docs: 'Function pointer type: `fn(params...) -> T`. Points to module-level functions. No captured state; capture-free.',
|
|
196
|
+
},
|
|
197
|
+
'dyn' => {
|
|
198
|
+
signature: 'dyn',
|
|
199
|
+
docs: 'Runtime interface value: `dyn[Interface]`. A fat pointer carrying a data pointer and vtable. Constructed via `adapt[Interface](value)`.',
|
|
200
|
+
},
|
|
201
|
+
'is' => {
|
|
202
|
+
signature: 'is',
|
|
203
|
+
docs: 'Variant arm membership test: `expr is Variant.arm`. Desugars to a `match` expression evaluating to `bool`. Supports `not` negation.',
|
|
204
|
+
},
|
|
205
|
+
'pass' => {
|
|
206
|
+
signature: 'pass',
|
|
207
|
+
docs: 'Explicit no-op statement for intentionally empty block bodies.',
|
|
208
|
+
},
|
|
209
|
+
'null' => {
|
|
210
|
+
signature: 'null',
|
|
211
|
+
docs: 'Null value for nullable types (`T?`). Use typed `null[T]` when context cannot determine the target type.',
|
|
212
|
+
},
|
|
213
|
+
'true' => {
|
|
214
|
+
signature: 'true',
|
|
215
|
+
docs: 'Boolean literal `true`. Type is `bool`.',
|
|
216
|
+
},
|
|
217
|
+
'false' => {
|
|
218
|
+
signature: 'false',
|
|
219
|
+
docs: 'Boolean literal `false`. Type is `bool`.',
|
|
220
|
+
},
|
|
221
|
+
'static_assert' => {
|
|
222
|
+
signature: 'static_assert',
|
|
223
|
+
docs: 'Compile-time assertion: `static_assert(condition, message)`. Fails compilation if the compile-time condition is false.',
|
|
224
|
+
},
|
|
225
|
+
}.freeze
|
|
226
|
+
|
|
24
227
|
def handle_hover(params)
|
|
25
228
|
stages = new_perf_stages
|
|
26
229
|
total_start = stages ? monotonic_time : nil
|
|
@@ -649,7 +852,7 @@ module MilkTea
|
|
|
649
852
|
return nil unless current_type
|
|
650
853
|
|
|
651
854
|
chain[:segments][1..hovered_segment[:position]].each do |segment|
|
|
652
|
-
field_receiver_type = project_field_receiver_type_for_completion(current_type)
|
|
855
|
+
field_receiver_type = project_field_receiver_type_for_completion(current_type, facts)
|
|
653
856
|
if field_receiver_type.respond_to?(:field) && (field_type = field_receiver_type.field(segment[:name]))
|
|
654
857
|
source_location = field_definition_location(current_uri, field_receiver_type, segment[:name])
|
|
655
858
|
|
|
@@ -787,7 +990,7 @@ module MilkTea
|
|
|
787
990
|
return nil unless receiver_info
|
|
788
991
|
|
|
789
992
|
field_name = tokens[token_index].lexeme
|
|
790
|
-
receiver_type = project_field_receiver_type_for_completion(receiver_info[:type])
|
|
993
|
+
receiver_type = project_field_receiver_type_for_completion(receiver_info[:type], facts)
|
|
791
994
|
return nil unless receiver_type.respond_to?(:field)
|
|
792
995
|
|
|
793
996
|
field_type = receiver_type.field(field_name)
|
|
@@ -809,7 +1012,7 @@ module MilkTea
|
|
|
809
1012
|
|
|
810
1013
|
field_name = tokens[token_index].lexeme
|
|
811
1014
|
if receiver_info
|
|
812
|
-
receiver_type = project_field_receiver_type_for_completion(receiver_info[:type])
|
|
1015
|
+
receiver_type = project_field_receiver_type_for_completion(receiver_info[:type], facts)
|
|
813
1016
|
if receiver_type.respond_to?(:field)
|
|
814
1017
|
field_type = receiver_type.field(field_name)
|
|
815
1018
|
if field_type
|
|
@@ -1260,10 +1463,9 @@ module MilkTea
|
|
|
1260
1463
|
return nil unless token
|
|
1261
1464
|
|
|
1262
1465
|
info = KEYWORD_HOVER_INFO[token.lexeme]
|
|
1263
|
-
return
|
|
1264
|
-
return nil unless [:size_of, :align_of, :offset_of].include?(token.type)
|
|
1466
|
+
return info if info && [:size_of, :align_of, :offset_of].include?(token.type)
|
|
1265
1467
|
|
|
1266
|
-
|
|
1468
|
+
LANGUAGE_KEYWORD_HOVER_INFO[token.lexeme]
|
|
1267
1469
|
end
|
|
1268
1470
|
|
|
1269
1471
|
def render_builtin_specialization(tokens)
|
|
@@ -1421,7 +1623,7 @@ module MilkTea
|
|
|
1421
1623
|
def_index = tokens.index(definition_token)
|
|
1422
1624
|
if def_index
|
|
1423
1625
|
prev = previous_non_trivia_token_index(tokens, def_index)
|
|
1424
|
-
if prev
|
|
1626
|
+
if prev
|
|
1425
1627
|
case tokens[prev].lexeme
|
|
1426
1628
|
when "let" then kind = :let
|
|
1427
1629
|
when "var" then kind = :var
|
|
@@ -961,7 +961,7 @@ module MilkTea
|
|
|
961
961
|
resolve_receiver_value_type(facts, receiver_tok).then do |receiver_type|
|
|
962
962
|
next false unless receiver_type
|
|
963
963
|
|
|
964
|
-
field_receiver_type = project_field_receiver_type_for_completion(receiver_type)
|
|
964
|
+
field_receiver_type = project_field_receiver_type_for_completion(receiver_type, facts)
|
|
965
965
|
next false unless field_receiver_type.respond_to?(:field)
|
|
966
966
|
|
|
967
967
|
callable_semantic_type?(field_receiver_type.field(name))
|
data/lib/milk_tea/lsp/server.rb
CHANGED
|
@@ -58,7 +58,7 @@ module MilkTea
|
|
|
58
58
|
KEYWORD_TOKEN_TYPES = Token::KEYWORDS.values.to_set.freeze
|
|
59
59
|
DEFAULT_LIBRARY_TYPE_NAMES = Types::BUILTIN_TYPE_NAMES.to_set.freeze
|
|
60
60
|
BUILTIN_FUNCTION_NAMES = %w[
|
|
61
|
-
ref_of const_ptr_of ptr_of read fatal reinterpret array span zero default adapt get
|
|
61
|
+
ref_of const_ptr_of ptr_of read fatal reinterpret array span zero default adapt get field_of callable_of has_attribute attribute_of attribute_arg fields_of members_of attributes_of
|
|
62
62
|
].to_set.freeze
|
|
63
63
|
BUILTIN_ASSOCIATED_HOOK_NAMES = %w[hash equal order].to_set.freeze
|
|
64
64
|
BUILTIN_CALL_HOVER_INFO = {
|
|
@@ -68,7 +68,7 @@ module MilkTea
|
|
|
68
68
|
},
|
|
69
69
|
'ref_of' => {
|
|
70
70
|
signature: 'builtin ref_of(value) -> ref[T]',
|
|
71
|
-
docs: '`ref_of(x)` borrows a
|
|
71
|
+
docs: '`ref_of(x)` borrows a safe lvalue as `ref[T]`.'
|
|
72
72
|
},
|
|
73
73
|
'const_ptr_of' => {
|
|
74
74
|
signature: 'builtin const_ptr_of(value) -> const_ptr[T]',
|
|
@@ -76,7 +76,7 @@ module MilkTea
|
|
|
76
76
|
},
|
|
77
77
|
'ptr_of' => {
|
|
78
78
|
signature: 'builtin ptr_of(value) -> ptr[T]',
|
|
79
|
-
docs: '`ptr_of(x)` takes the address of a
|
|
79
|
+
docs: '`ptr_of(x)` takes the address of a safe lvalue as `ptr[T]`.'
|
|
80
80
|
},
|
|
81
81
|
'read' => {
|
|
82
82
|
signature: 'builtin read(value) -> T',
|
|
@@ -130,6 +130,18 @@ module MilkTea
|
|
|
130
130
|
signature: 'builtin attribute_of(target, attribute_name) -> attribute_handle',
|
|
131
131
|
docs: '`attribute_of(target, attribute_name)` returns the applied attribute handle for the resolved target-and-attribute pair; use `has_attribute(...)` when absence is expected.'
|
|
132
132
|
},
|
|
133
|
+
'fields_of' => {
|
|
134
|
+
signature: 'builtin fields_of(Type) -> array[field_handle, N]',
|
|
135
|
+
docs: '`fields_of(Type)` returns all struct fields as a compile-time `array[field_handle, N]`, in declaration order. Use with `inline for` for reflective field iteration.'
|
|
136
|
+
},
|
|
137
|
+
'members_of' => {
|
|
138
|
+
signature: 'builtin members_of(Type) -> array[member_handle, N]',
|
|
139
|
+
docs: '`members_of(Type)` returns all members of an enum or flags type as a compile-time `array[member_handle, N]`. Each handle exposes `.name` and `.value`.'
|
|
140
|
+
},
|
|
141
|
+
'attributes_of' => {
|
|
142
|
+
signature: 'builtin attributes_of(target [, name]) -> array[attribute_handle, N]',
|
|
143
|
+
docs: '`attributes_of(target)` returns all attributes applied to a type, field, or callable as a compile-time array. `attributes_of(target, name)` filters by attribute kind.'
|
|
144
|
+
},
|
|
133
145
|
}.freeze
|
|
134
146
|
OPERATOR_TOKEN_TYPES = %i[
|
|
135
147
|
amp colon comma caret dot lparen rparen pipe lbracket rbracket question
|
|
@@ -158,9 +158,9 @@ module MilkTea
|
|
|
158
158
|
ast = get_ast(uri)
|
|
159
159
|
return @last_good_tooling_snapshot_cache[uri] if ast.nil?
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
result = MilkTea::SemanticAnalyzer.check_collecting_errors(ast, path:)
|
|
162
|
+
facts = result[:analysis]
|
|
163
|
+
MilkTea::SemanticAnalyzer::ToolingSnapshot.new(facts:, diagnostics: (Array(result[:errors]).map { |e| e.to_diagnostic(path:) } || []).freeze)
|
|
164
164
|
end
|
|
165
165
|
if snapshot&.facts
|
|
166
166
|
@last_good_tooling_snapshot_cache[uri] = snapshot
|
|
@@ -56,10 +56,12 @@ module MilkTea
|
|
|
56
56
|
)
|
|
57
57
|
collect_ms = elapsed_ms(collect_start) if collect_start
|
|
58
58
|
diagnostics = result[:diagnostics]
|
|
59
|
+
cross_by_uri = result[:cross_file_diagnostics] || {}
|
|
59
60
|
facts = result[:facts]
|
|
60
61
|
snapshot = result[:sema_snapshot]
|
|
61
62
|
@facts_cache_mutex.synchronize do
|
|
62
63
|
if @facts_generation[uri] == generation
|
|
64
|
+
@cross_file_diagnostics = cross_by_uri
|
|
63
65
|
@tooling_snapshot_cache[uri] = snapshot if snapshot
|
|
64
66
|
@last_good_tooling_snapshot_cache[uri] = snapshot if snapshot&.facts
|
|
65
67
|
@facts_cache[uri] = facts if facts
|
|
@@ -93,6 +95,10 @@ module MilkTea
|
|
|
93
95
|
end
|
|
94
96
|
end
|
|
95
97
|
end
|
|
98
|
+
|
|
99
|
+
def cross_file_diagnostics
|
|
100
|
+
@facts_cache_mutex.synchronize { @cross_file_diagnostics }
|
|
101
|
+
end
|
|
96
102
|
end
|
|
97
103
|
end
|
|
98
104
|
end
|
data/lib/milk_tea/tooling/cli.rb
CHANGED
|
@@ -684,12 +684,17 @@ module MilkTea
|
|
|
684
684
|
diagnostics = sort_by_location(diagnostics)
|
|
685
685
|
|
|
686
686
|
if diagnostics.any? || closure_errors.any?
|
|
687
|
-
|
|
687
|
+
main_source = read_source_file(path)
|
|
688
|
+
main_abs = File.expand_path(path)
|
|
688
689
|
diagnostics.each do |d|
|
|
690
|
+
same_file = !d.respond_to?(:path) || d.path.nil? || File.expand_path(d.path) == main_abs
|
|
691
|
+
source = same_file ? main_source : nil
|
|
689
692
|
@err.puts(ErrorFormatter.format(d, source:, color: error_color?(@err)))
|
|
690
693
|
end
|
|
691
694
|
closure_errors.each do |d|
|
|
692
|
-
|
|
695
|
+
same_file = !d.respond_to?(:path) || d.path.nil? || File.expand_path(d.path) == main_abs
|
|
696
|
+
source = same_file ? main_source : nil
|
|
697
|
+
@err.puts(ErrorFormatter.format(d, source:, color: error_color?(@err)))
|
|
693
698
|
end
|
|
694
699
|
all_diagnostics.concat(diagnostics)
|
|
695
700
|
all_diagnostics.concat(closure_errors)
|
|
@@ -711,9 +716,9 @@ module MilkTea
|
|
|
711
716
|
parts << "#{info_count} #{info_count == 1 ? 'note' : 'notes'}" if info_count > 0
|
|
712
717
|
body = parts.join("; ")
|
|
713
718
|
if error_count > 0
|
|
714
|
-
@err.puts("
|
|
719
|
+
@err.puts("#{body} found")
|
|
715
720
|
elsif warning_count > 0
|
|
716
|
-
@err.puts("
|
|
721
|
+
@err.puts("#{body}")
|
|
717
722
|
end
|
|
718
723
|
final_error_count = error_count + (resolution[:warnings_as_errors] ? warning_count : 0)
|
|
719
724
|
final_error_count > 0 ? 1 : 0
|
|
@@ -586,6 +586,8 @@ module MilkTea
|
|
|
586
586
|
return unless statement.type
|
|
587
587
|
return unless statement.value
|
|
588
588
|
|
|
589
|
+
return if statement.type.nullable
|
|
590
|
+
|
|
589
591
|
declared_name = type_ref_name(statement.type)
|
|
590
592
|
return unless declared_name
|
|
591
593
|
|
|
@@ -666,6 +668,7 @@ module MilkTea
|
|
|
666
668
|
# enum/int/str bool-matches are never misreported.
|
|
667
669
|
def check_prefer_is_variant(match_expr)
|
|
668
670
|
return unless @sema_facts
|
|
671
|
+
return if match_expr.desugared_from_is
|
|
669
672
|
|
|
670
673
|
arms = match_expr.arms
|
|
671
674
|
return unless arms.size == 2
|
|
@@ -771,6 +774,8 @@ module MilkTea
|
|
|
771
774
|
return if stmts.any?(&:nil?)
|
|
772
775
|
return unless stmts.all? { |s| inline_simple_statement?(s) }
|
|
773
776
|
|
|
777
|
+
return if visually_inline_if?(statement, stmts)
|
|
778
|
+
|
|
774
779
|
emit_conciseness_hint(
|
|
775
780
|
"prefer-inline-if",
|
|
776
781
|
line: statement.line,
|
|
@@ -779,6 +784,15 @@ module MilkTea
|
|
|
779
784
|
)
|
|
780
785
|
end
|
|
781
786
|
|
|
787
|
+
def visually_inline_if?(statement, stmts)
|
|
788
|
+
n = statement.branches.length
|
|
789
|
+
statement.branches.each_with_index.all? { |b, i|
|
|
790
|
+
stmts[i].respond_to?(:line) && stmts[i].line == b.line
|
|
791
|
+
} && (
|
|
792
|
+
stmts[n].respond_to?(:line) && statement.else_line && stmts[n].line == statement.else_line
|
|
793
|
+
)
|
|
794
|
+
end
|
|
795
|
+
|
|
782
796
|
# prefer-conditional-expression: if/match whose every branch either
|
|
783
797
|
# returns a value or assigns the same lvalue can become an expression form
|
|
784
798
|
# (`return if …: … else: …` or `x = match …`).
|
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.2.
|
|
4
|
+
version: 0.2.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Long (Teefan) Tran
|
|
@@ -583,7 +583,7 @@ metadata:
|
|
|
583
583
|
homepage_uri: https://teefan.github.io/mt-lang/
|
|
584
584
|
source_code_uri: https://github.com/teefan/mt-lang
|
|
585
585
|
post_install_message: |
|
|
586
|
-
Milk Tea 0.2.
|
|
586
|
+
Milk Tea 0.2.10 installed!
|
|
587
587
|
|
|
588
588
|
System requirements:
|
|
589
589
|
- A C compiler (gcc or clang) must be available on PATH
|