mt-lang 0.2.0
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 +7 -0
- data/.ruby-version +1 -0
- data/AUTHORS +3 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +82 -0
- data/LICENSE +21 -0
- data/README.md +1208 -0
- data/Rakefile +332 -0
- data/bin/mtc +8 -0
- data/bin/profile-mtc-checks +133 -0
- data/bin/tracy-profiler +0 -0
- data/docs/build-guide.md +536 -0
- data/docs/index.html +2778 -0
- data/docs/language-design.md +1519 -0
- data/docs/language-manual.md +1534 -0
- data/lib/milk_tea/base.rb +49 -0
- data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
- data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
- data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
- data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
- data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
- data/lib/milk_tea/bindings/bindgen.rb +207 -0
- data/lib/milk_tea/bindings/cli.rb +121 -0
- data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
- data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
- data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
- data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
- data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
- data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
- data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
- data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
- data/lib/milk_tea/bindings/steamworks.rb +629 -0
- data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
- data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
- data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
- data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
- data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
- data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
- data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
- data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
- data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
- data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
- data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
- data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
- data/lib/milk_tea/bindings.rb +23 -0
- data/lib/milk_tea/core/ast.rb +311 -0
- data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
- data/lib/milk_tea/core/binding_types.rb +18 -0
- data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
- data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
- data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
- data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
- data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
- data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
- data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
- data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
- data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
- data/lib/milk_tea/core/c_backend/statements.rb +563 -0
- data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
- data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
- data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
- data/lib/milk_tea/core/c_backend.rb +287 -0
- data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
- data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
- data/lib/milk_tea/core/compile_time.rb +329 -0
- data/lib/milk_tea/core/control_flow/builder.rb +582 -0
- data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
- data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
- data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
- data/lib/milk_tea/core/control_flow/graph.rb +90 -0
- data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
- data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
- data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
- data/lib/milk_tea/core/control_flow/termination.rb +31 -0
- data/lib/milk_tea/core/control_flow.rb +34 -0
- data/lib/milk_tea/core/cst.rb +48 -0
- data/lib/milk_tea/core/cst_builder.rb +19 -0
- data/lib/milk_tea/core/ir.rb +85 -0
- data/lib/milk_tea/core/keywords.rb +97 -0
- data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
- data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
- data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
- data/lib/milk_tea/core/lexer/indentation.rb +62 -0
- data/lib/milk_tea/core/lexer/numbers.rb +96 -0
- data/lib/milk_tea/core/lexer/recovery.rb +32 -0
- data/lib/milk_tea/core/lexer/strings.rb +167 -0
- data/lib/milk_tea/core/lexer/symbols.rb +71 -0
- data/lib/milk_tea/core/lexer/trivia.rb +53 -0
- data/lib/milk_tea/core/lexer.rb +430 -0
- data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
- data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
- data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
- data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
- data/lib/milk_tea/core/lowering/async.rb +714 -0
- data/lib/milk_tea/core/lowering/block.rb +1052 -0
- data/lib/milk_tea/core/lowering/calls.rb +1565 -0
- data/lib/milk_tea/core/lowering/declarations.rb +214 -0
- data/lib/milk_tea/core/lowering/dyn.rb +206 -0
- data/lib/milk_tea/core/lowering/events.rb +1054 -0
- data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
- data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
- data/lib/milk_tea/core/lowering/functions.rb +242 -0
- data/lib/milk_tea/core/lowering/loops.rb +1087 -0
- data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
- data/lib/milk_tea/core/lowering/proc.rb +419 -0
- data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
- data/lib/milk_tea/core/lowering/scans.rb +220 -0
- data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
- data/lib/milk_tea/core/lowering/utils.rb +1453 -0
- data/lib/milk_tea/core/lowering.rb +378 -0
- data/lib/milk_tea/core/module_binder.rb +181 -0
- data/lib/milk_tea/core/module_loader/errors.rb +21 -0
- data/lib/milk_tea/core/module_loader.rb +479 -0
- data/lib/milk_tea/core/module_path_resolver.rb +153 -0
- data/lib/milk_tea/core/module_roots.rb +88 -0
- data/lib/milk_tea/core/parser/attributes.rb +71 -0
- data/lib/milk_tea/core/parser/blocks.rb +119 -0
- data/lib/milk_tea/core/parser/declarations.rb +749 -0
- data/lib/milk_tea/core/parser/expressions.rb +624 -0
- data/lib/milk_tea/core/parser/recovery.rb +131 -0
- data/lib/milk_tea/core/parser/statements.rb +756 -0
- data/lib/milk_tea/core/parser/types.rb +271 -0
- data/lib/milk_tea/core/parser.rb +400 -0
- data/lib/milk_tea/core/prelude_installer.rb +29 -0
- data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
- data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
- data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
- data/lib/milk_tea/core/pretty_printer.rb +17 -0
- data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
- data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
- data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
- data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
- data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
- data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
- data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
- data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
- data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
- data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
- data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
- data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
- data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
- data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
- data/lib/milk_tea/core/token.rb +26 -0
- data/lib/milk_tea/core/token_stream.rb +30 -0
- data/lib/milk_tea/core/types/layout.rb +243 -0
- data/lib/milk_tea/core/types/predicates.rb +609 -0
- data/lib/milk_tea/core/types/registry.rb +83 -0
- data/lib/milk_tea/core/types/types.rb +1696 -0
- data/lib/milk_tea/core/types/visitor.rb +442 -0
- data/lib/milk_tea/core.rb +25 -0
- data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
- data/lib/milk_tea/dap/protocol.rb +58 -0
- data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
- data/lib/milk_tea/dap/server/debug_map.rb +291 -0
- data/lib/milk_tea/dap/server/handlers.rb +374 -0
- data/lib/milk_tea/dap/server/launch.rb +261 -0
- data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
- data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
- data/lib/milk_tea/dap/server/utilities.rb +160 -0
- data/lib/milk_tea/dap/server/wire.rb +55 -0
- data/lib/milk_tea/dap/server.rb +131 -0
- data/lib/milk_tea/dap/session.rb +152 -0
- data/lib/milk_tea/dap.rb +6 -0
- data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
- data/lib/milk_tea/lsp/diagnostics.rb +611 -0
- data/lib/milk_tea/lsp/protocol.rb +104 -0
- data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
- data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
- data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
- data/lib/milk_tea/lsp/server/completion.rb +1099 -0
- data/lib/milk_tea/lsp/server/configuration.rb +167 -0
- data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
- data/lib/milk_tea/lsp/server/definition.rb +779 -0
- data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
- data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
- data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
- data/lib/milk_tea/lsp/server/formatting.rb +575 -0
- data/lib/milk_tea/lsp/server/hover.rb +1465 -0
- data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
- data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
- data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
- data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
- data/lib/milk_tea/lsp/server/progress.rb +47 -0
- data/lib/milk_tea/lsp/server/references.rb +433 -0
- data/lib/milk_tea/lsp/server/rename.rb +598 -0
- data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
- data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
- data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
- data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
- data/lib/milk_tea/lsp/server/utilities.rb +520 -0
- data/lib/milk_tea/lsp/server.rb +415 -0
- data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
- data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
- data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
- data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
- data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
- data/lib/milk_tea/lsp/workspace/store.rb +154 -0
- data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
- data/lib/milk_tea/lsp/workspace.rb +127 -0
- data/lib/milk_tea/lsp.rb +13 -0
- data/lib/milk_tea/packages/atomic_write.rb +63 -0
- data/lib/milk_tea/packages/dependency_solver.rb +194 -0
- data/lib/milk_tea/packages/graph.rb +139 -0
- data/lib/milk_tea/packages/lock.rb +421 -0
- data/lib/milk_tea/packages/manager_cli.rb +485 -0
- data/lib/milk_tea/packages/manifest.rb +356 -0
- data/lib/milk_tea/packages/manifest_editor.rb +134 -0
- data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
- data/lib/milk_tea/packages/registry_store.rb +420 -0
- data/lib/milk_tea/packages/services.rb +41 -0
- data/lib/milk_tea/packages/source_cache.rb +71 -0
- data/lib/milk_tea/packages/source_fetcher.rb +124 -0
- data/lib/milk_tea/packages/source_resolver.rb +389 -0
- data/lib/milk_tea/packages/version.rb +164 -0
- data/lib/milk_tea/packages.rb +16 -0
- data/lib/milk_tea/tooling/asset_pack.rb +141 -0
- data/lib/milk_tea/tooling/build.rb +1124 -0
- data/lib/milk_tea/tooling/build_cache.rb +240 -0
- data/lib/milk_tea/tooling/cli.rb +2688 -0
- data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
- data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
- data/lib/milk_tea/tooling/debug_map.rb +248 -0
- data/lib/milk_tea/tooling/docs_app.rb +369 -0
- data/lib/milk_tea/tooling/error_formatter.rb +86 -0
- data/lib/milk_tea/tooling/formatter.rb +787 -0
- data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
- data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
- data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
- data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
- data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
- data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
- data/lib/milk_tea/tooling/linter/rules.rb +593 -0
- data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
- data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
- data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
- data/lib/milk_tea/tooling/linter.rb +798 -0
- data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
- data/lib/milk_tea/tooling/public/css/docs.css +166 -0
- data/lib/milk_tea/tooling/public/js/docs.js +94 -0
- data/lib/milk_tea/tooling/run.rb +408 -0
- data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
- data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
- data/lib/milk_tea/tooling/views/404.erb +7 -0
- data/lib/milk_tea/tooling/views/index.erb +87 -0
- data/lib/milk_tea/tooling/views/layout.erb +69 -0
- data/lib/milk_tea/tooling/views/module.erb +97 -0
- data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
- data/lib/milk_tea/tooling.rb +20 -0
- data/lib/milk_tea.rb +8 -0
- metadata +426 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class Linter
|
|
5
|
+
module LinterRules
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
# ── missing-return ───────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
def check_missing_return(function)
|
|
11
|
+
return unless function.return_type # implicit void — no check
|
|
12
|
+
return if void_return_type?(function.return_type)
|
|
13
|
+
return if always_returns?(function.body)
|
|
14
|
+
|
|
15
|
+
@warnings << Warning.new(
|
|
16
|
+
path: @path,
|
|
17
|
+
line: function.line,
|
|
18
|
+
column: function.respond_to?(:column) ? function.column : nil,
|
|
19
|
+
length: function.name.length,
|
|
20
|
+
code: "missing-return",
|
|
21
|
+
message: "function '#{function.name}' does not always return a value",
|
|
22
|
+
severity: :error,
|
|
23
|
+
symbol_name: function.name
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def emit_redundant_return_warnings(function)
|
|
28
|
+
return unless function.return_type
|
|
29
|
+
return unless void_return_type?(function.return_type)
|
|
30
|
+
|
|
31
|
+
final_statement = function.body&.last
|
|
32
|
+
return unless final_statement.is_a?(AST::ReturnStmt)
|
|
33
|
+
return unless final_statement.value.nil?
|
|
34
|
+
|
|
35
|
+
@warnings << Warning.new(
|
|
36
|
+
path: @path,
|
|
37
|
+
line: final_statement.line,
|
|
38
|
+
column: final_statement.column,
|
|
39
|
+
length: final_statement.length || "return".length,
|
|
40
|
+
code: "redundant-return",
|
|
41
|
+
message: "final bare return in void function is redundant",
|
|
42
|
+
severity: :hint,
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def void_return_type?(return_type)
|
|
47
|
+
case return_type
|
|
48
|
+
when AST::TypeRef
|
|
49
|
+
name = return_type.name
|
|
50
|
+
name.is_a?(AST::QualifiedName) && name.parts == ["void"]
|
|
51
|
+
when Types::Primitive
|
|
52
|
+
return_type.name == "void"
|
|
53
|
+
else
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns true if every execution path through `stmts` ends with a
|
|
59
|
+
# guaranteed return. Conservative: only IfStmt with else + MatchStmt
|
|
60
|
+
# with arms are considered exhaustive.
|
|
61
|
+
def always_returns?(stmts)
|
|
62
|
+
stmts.any? do |stmt|
|
|
63
|
+
case stmt
|
|
64
|
+
when AST::ReturnStmt
|
|
65
|
+
true
|
|
66
|
+
when AST::ExpressionStmt
|
|
67
|
+
terminating_expression?(stmt.expression)
|
|
68
|
+
when AST::IfStmt
|
|
69
|
+
# Only exhaustive if there is an else branch AND every branch returns
|
|
70
|
+
stmt.else_body && !stmt.else_body.empty? &&
|
|
71
|
+
stmt.branches.all? { |b| always_returns?(b.body) } &&
|
|
72
|
+
always_returns?(stmt.else_body)
|
|
73
|
+
when AST::WhileStmt
|
|
74
|
+
infinite_while_without_break?(stmt)
|
|
75
|
+
when AST::MatchStmt
|
|
76
|
+
stmt.arms.any? && stmt.arms.all? { |arm| always_returns?(arm.body) }
|
|
77
|
+
when AST::WhenStmt
|
|
78
|
+
all_branches = stmt.branches.map(&:body)
|
|
79
|
+
all_branches << stmt.else_body if stmt.else_body && !stmt.else_body.empty?
|
|
80
|
+
all_branches.any? && all_branches.all? { |b| always_returns?(b) }
|
|
81
|
+
when AST::StaticAssert
|
|
82
|
+
stmt.condition.is_a?(AST::BooleanLiteral) && stmt.condition.value == false
|
|
83
|
+
when AST::UnsafeStmt
|
|
84
|
+
always_returns?(stmt.body)
|
|
85
|
+
else
|
|
86
|
+
false
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def infinite_while_without_break?(stmt)
|
|
92
|
+
stmt.condition.is_a?(AST::BooleanLiteral) &&
|
|
93
|
+
stmt.condition.value == true &&
|
|
94
|
+
!loop_body_can_break?(stmt.body)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def loop_body_can_break?(body)
|
|
98
|
+
return false if body.nil? || body.empty?
|
|
99
|
+
|
|
100
|
+
graph = ControlFlow::Builder.new.build_loop_body(body)
|
|
101
|
+
reachability = ControlFlow::Reachability.solve(graph)
|
|
102
|
+
graph.each_node.any? do |node|
|
|
103
|
+
node.kind == :break_exit && reachability.reachable_ids.include?(node.id)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def terminating_expression?(expression)
|
|
108
|
+
case expression
|
|
109
|
+
when AST::Call
|
|
110
|
+
terminating_callee?(expression.callee) || static_assert_false?(expression)
|
|
111
|
+
when AST::Specialization
|
|
112
|
+
terminating_callee?(expression.callee) || static_assert_false?(expression)
|
|
113
|
+
else
|
|
114
|
+
false
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def static_assert_false?(expression)
|
|
119
|
+
return false unless expression.callee.is_a?(AST::Identifier)
|
|
120
|
+
return false unless expression.callee.name == "static_assert"
|
|
121
|
+
|
|
122
|
+
first_arg = expression.arguments.first
|
|
123
|
+
first_arg.is_a?(AST::BooleanLiteral) && first_arg.value == false
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def terminating_callee?(callee)
|
|
127
|
+
case callee
|
|
128
|
+
when AST::Identifier
|
|
129
|
+
callee.name == "fatal"
|
|
130
|
+
when AST::Specialization
|
|
131
|
+
terminating_callee?(callee.callee)
|
|
132
|
+
else
|
|
133
|
+
false
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
# ── redundant-else ───────────────────────────────────────────────────
|
|
137
|
+
# Fire when every explicit if/else if branch always returns, making the else
|
|
138
|
+
# block an unnecessary level of indentation.
|
|
139
|
+
def check_redundant_else(stmt)
|
|
140
|
+
return unless stmt.else_body && !stmt.else_body.empty?
|
|
141
|
+
return unless stmt.branches.all? { |b| always_returns?(b.body) }
|
|
142
|
+
|
|
143
|
+
# Use the line of the first else-body statement as the diagnostic anchor.
|
|
144
|
+
else_line = stmt.else_body.first.respond_to?(:line) ? stmt.else_body.first.line : stmt.line
|
|
145
|
+
@warnings << Warning.new(
|
|
146
|
+
path: @path,
|
|
147
|
+
line: stmt.else_line || else_line,
|
|
148
|
+
column: stmt.else_column,
|
|
149
|
+
length: 4,
|
|
150
|
+
code: "redundant-else",
|
|
151
|
+
message: "else block is redundant because all preceding branches return"
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
# ── useless-expression ───────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
PURE_EXPRESSION_TYPES = [
|
|
157
|
+
AST::IntegerLiteral, AST::FloatLiteral, AST::StringLiteral, AST::FormatString,
|
|
158
|
+
AST::BooleanLiteral, AST::NullLiteral,
|
|
159
|
+
AST::BinaryOp, AST::UnaryOp,
|
|
160
|
+
AST::Identifier, AST::UnsafeExpr,
|
|
161
|
+
].freeze
|
|
162
|
+
|
|
163
|
+
SIDE_EFFECTING_EXPRESSION_TYPES = [
|
|
164
|
+
AST::Call, AST::AwaitExpr,
|
|
165
|
+
].freeze
|
|
166
|
+
|
|
167
|
+
def check_useless_expression(stmt)
|
|
168
|
+
expr = stmt.expression
|
|
169
|
+
return unless PURE_EXPRESSION_TYPES.any? { |t| expr.is_a?(t) }
|
|
170
|
+
return if expr.is_a?(AST::UnaryOp) && expr.operator == "?"
|
|
171
|
+
return if contains_side_effecting_expression?(expr)
|
|
172
|
+
|
|
173
|
+
line = expression_line(expr) || (stmt.respond_to?(:line) ? stmt.line : nil)
|
|
174
|
+
column = expression_column(expr)
|
|
175
|
+
length = expression_length(expr)
|
|
176
|
+
if line && (!column || !length || !expr.respond_to?(:column) || expr.is_a?(AST::UnsafeExpr))
|
|
177
|
+
fallback_span = source_statement_span(line)
|
|
178
|
+
column ||= fallback_span&.first
|
|
179
|
+
length = fallback_span&.last if !length || !expr.respond_to?(:column) || expr.is_a?(AST::UnsafeExpr)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
@warnings << Warning.new(
|
|
183
|
+
path: @path,
|
|
184
|
+
line:,
|
|
185
|
+
column:,
|
|
186
|
+
length:,
|
|
187
|
+
code: "useless-expression",
|
|
188
|
+
message: "expression result is unused and has no side effects",
|
|
189
|
+
severity: :warning
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def contains_side_effecting_expression?(expr)
|
|
194
|
+
return true if SIDE_EFFECTING_EXPRESSION_TYPES.any? { |t| expr.is_a?(t) }
|
|
195
|
+
return true if expr.is_a?(AST::UnaryOp) && expr.operator == "?"
|
|
196
|
+
|
|
197
|
+
case expr
|
|
198
|
+
when AST::BinaryOp
|
|
199
|
+
contains_side_effecting_expression?(expr.left) || contains_side_effecting_expression?(expr.right)
|
|
200
|
+
when AST::UnsafeExpr
|
|
201
|
+
contains_side_effecting_expression?(expr.expression)
|
|
202
|
+
when AST::FormatString
|
|
203
|
+
expr.parts.any? { |part| part.is_a?(AST::FormatExprPart) && contains_side_effecting_expression?(part.expression) }
|
|
204
|
+
else
|
|
205
|
+
false
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
# ── self-assignment ────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
def check_self_assignment(stmt)
|
|
211
|
+
return unless stmt.operator == "="
|
|
212
|
+
return unless stmt.target.is_a?(AST::Identifier) && stmt.value.is_a?(AST::Identifier)
|
|
213
|
+
return unless stmt.target.name == stmt.value.name
|
|
214
|
+
|
|
215
|
+
@warnings << Warning.new(
|
|
216
|
+
path: @path,
|
|
217
|
+
line: expression_line(stmt.target) || stmt.line,
|
|
218
|
+
column: expression_column(stmt.target),
|
|
219
|
+
length: expression_length(stmt.target),
|
|
220
|
+
code: "self-assignment",
|
|
221
|
+
message: "'#{stmt.target.name}' is assigned to itself",
|
|
222
|
+
severity: :warning,
|
|
223
|
+
symbol_name: stmt.target.name
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# ── self-comparison ────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
def check_self_comparison(expr)
|
|
230
|
+
return unless %w[== !=].include?(expr.operator)
|
|
231
|
+
return unless expr.left.is_a?(AST::Identifier) && expr.right.is_a?(AST::Identifier)
|
|
232
|
+
return unless expr.left.name == expr.right.name
|
|
233
|
+
|
|
234
|
+
line = expression_line(expr) || expr.left.line
|
|
235
|
+
always = expr.operator == "==" ? "always true" : "always false"
|
|
236
|
+
@warnings << Warning.new(
|
|
237
|
+
path: @path,
|
|
238
|
+
line:,
|
|
239
|
+
column: expression_column(expr),
|
|
240
|
+
length: expression_length(expr),
|
|
241
|
+
code: "self-comparison",
|
|
242
|
+
message: "'#{expr.left.name}' is compared to itself — #{always}",
|
|
243
|
+
severity: :warning,
|
|
244
|
+
symbol_name: expr.left.name
|
|
245
|
+
)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def check_redundant_bool_compare(expr)
|
|
249
|
+
return unless %w[== !=].include?(expr.operator)
|
|
250
|
+
|
|
251
|
+
left_bool = expr.left.is_a?(AST::BooleanLiteral)
|
|
252
|
+
right_bool = expr.right.is_a?(AST::BooleanLiteral)
|
|
253
|
+
return unless left_bool ^ right_bool
|
|
254
|
+
|
|
255
|
+
literal = left_bool ? expr.left : expr.right
|
|
256
|
+
compared = left_bool ? expr.right : expr.left
|
|
257
|
+
return if compared.is_a?(AST::BooleanLiteral)
|
|
258
|
+
|
|
259
|
+
suggestion = if expr.operator == "=="
|
|
260
|
+
literal.value ? "use the expression directly" : "invert the expression with 'not'"
|
|
261
|
+
else
|
|
262
|
+
literal.value ? "invert the expression with 'not'" : "use the expression directly"
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
@warnings << Warning.new(
|
|
266
|
+
path: @path,
|
|
267
|
+
line: expression_line(expr),
|
|
268
|
+
column: expression_column(expr),
|
|
269
|
+
length: redundant_bool_compare_length(expr),
|
|
270
|
+
code: "redundant-bool-compare",
|
|
271
|
+
message: "boolean comparison against literal is redundant; #{suggestion}",
|
|
272
|
+
severity: :hint,
|
|
273
|
+
)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def redundant_bool_compare_length(expr)
|
|
277
|
+
length = expression_length(expr)
|
|
278
|
+
return length if length
|
|
279
|
+
|
|
280
|
+
line = expression_line(expr)
|
|
281
|
+
column = expression_column(expr)
|
|
282
|
+
return nil unless line && column
|
|
283
|
+
|
|
284
|
+
text = source_code_line(line)
|
|
285
|
+
return nil unless text
|
|
286
|
+
|
|
287
|
+
snippet = text[(column - 1)..]
|
|
288
|
+
return nil unless snippet
|
|
289
|
+
|
|
290
|
+
right_literal = snippet.match(/\A\s*(.+?)\s*(==|!=)\s*(true|false)\b/)
|
|
291
|
+
return right_literal[0].rstrip.length if right_literal
|
|
292
|
+
|
|
293
|
+
left_literal = snippet.match(/\A\s*(true|false)\s*(==|!=)\s*(.+?)(?=\s*(?::|\)|,|and\b|or\b|$))/)
|
|
294
|
+
return left_literal[0].rstrip.length if left_literal
|
|
295
|
+
|
|
296
|
+
nil
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def check_duplicate_if_conditions(statement)
|
|
300
|
+
return unless statement.is_a?(AST::IfStmt)
|
|
301
|
+
|
|
302
|
+
seen_signatures = {}
|
|
303
|
+
statement.branches.each do |branch|
|
|
304
|
+
signature = expression_signature(branch.condition)
|
|
305
|
+
next unless signature
|
|
306
|
+
|
|
307
|
+
existing = seen_signatures[signature]
|
|
308
|
+
if existing
|
|
309
|
+
@warnings << Warning.new(
|
|
310
|
+
path: @path,
|
|
311
|
+
line: expression_line(branch.condition) || branch.line,
|
|
312
|
+
column: expression_column(branch.condition),
|
|
313
|
+
length: expression_length(branch.condition),
|
|
314
|
+
code: "duplicate-if-condition",
|
|
315
|
+
message: "duplicate condition matches an earlier if/else-if branch and is unreachable",
|
|
316
|
+
severity: :warning,
|
|
317
|
+
)
|
|
318
|
+
next
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
seen_signatures[signature] = branch
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def expression_signature(expression)
|
|
326
|
+
case expression
|
|
327
|
+
when AST::Identifier
|
|
328
|
+
"id:#{expression.name}"
|
|
329
|
+
when AST::BooleanLiteral
|
|
330
|
+
"bool:#{expression.value}"
|
|
331
|
+
when AST::IntegerLiteral, AST::FloatLiteral, AST::StringLiteral
|
|
332
|
+
"lit:#{expression.lexeme}"
|
|
333
|
+
when AST::NullLiteral
|
|
334
|
+
"null"
|
|
335
|
+
when AST::MemberAccess
|
|
336
|
+
receiver = expression_signature(expression.receiver)
|
|
337
|
+
receiver ? "member:(#{receiver}).#{expression.member}" : nil
|
|
338
|
+
when AST::UnaryOp
|
|
339
|
+
operand = expression_signature(expression.operand)
|
|
340
|
+
operand ? "unary:#{expression.operator}(#{operand})" : nil
|
|
341
|
+
when AST::BinaryOp
|
|
342
|
+
left = expression_signature(expression.left)
|
|
343
|
+
right = expression_signature(expression.right)
|
|
344
|
+
return nil unless left && right
|
|
345
|
+
|
|
346
|
+
"binary:(#{left})#{expression.operator}(#{right})"
|
|
347
|
+
else
|
|
348
|
+
nil
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def check_noop_compound_assignment(statement)
|
|
353
|
+
return unless statement.is_a?(AST::Assignment)
|
|
354
|
+
return unless %w[+= -= *= /= |= ^= <<= >>=].include?(statement.operator)
|
|
355
|
+
|
|
356
|
+
identity = noop_compound_identity_value(statement.operator, statement.value)
|
|
357
|
+
return unless identity
|
|
358
|
+
|
|
359
|
+
@warnings << Warning.new(
|
|
360
|
+
path: @path,
|
|
361
|
+
line: statement.line,
|
|
362
|
+
column: expression_column(statement.target),
|
|
363
|
+
length: statement.target.respond_to?(:name) ? statement.target.name.length : expression_length(statement.target),
|
|
364
|
+
code: "noop-compound-assignment",
|
|
365
|
+
message: "compound assignment with identity value has no effect",
|
|
366
|
+
severity: :hint,
|
|
367
|
+
)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def noop_compound_identity_value(operator, value)
|
|
371
|
+
case operator
|
|
372
|
+
when "+=", "-=", "|=", "^=", "<<=", ">>="
|
|
373
|
+
integer_literal_zero?(value)
|
|
374
|
+
when "*=", "/="
|
|
375
|
+
numeric_literal_one?(value)
|
|
376
|
+
else
|
|
377
|
+
false
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def integer_literal_zero?(value)
|
|
382
|
+
value.is_a?(AST::IntegerLiteral) && value.lexeme.gsub("_", "") == "0"
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def numeric_literal_one?(value)
|
|
386
|
+
return true if value.is_a?(AST::IntegerLiteral) && value.lexeme.gsub("_", "") == "1"
|
|
387
|
+
return true if value.is_a?(AST::FloatLiteral) && %w[1.0 1.].include?(value.lexeme.gsub("_", ""))
|
|
388
|
+
|
|
389
|
+
false
|
|
390
|
+
end
|
|
391
|
+
# ── prefer-let-else / prefer-var-else ──────────────────────────────
|
|
392
|
+
|
|
393
|
+
def emit_prefer_let_else_warnings(stmts)
|
|
394
|
+
emit_prefer_else_warnings(stmts, expected_kind: :let, code: "prefer-let-else")
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def emit_prefer_var_else_warnings(stmts)
|
|
398
|
+
emit_prefer_else_warnings(stmts, expected_kind: :var, code: "prefer-var-else")
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def emit_prefer_else_warnings(stmts, expected_kind:, code:)
|
|
402
|
+
return if stmts.nil? || stmts.empty?
|
|
403
|
+
|
|
404
|
+
walk_statement_lists(stmts) do |statement_list|
|
|
405
|
+
statement_list.each_with_index do |_statement, index|
|
|
406
|
+
candidate = prefer_else_candidate(statement_list, index, expected_kind:)
|
|
407
|
+
next unless candidate
|
|
408
|
+
|
|
409
|
+
branch = candidate[:branch]
|
|
410
|
+
line, column, length = condition_span(branch.condition, line: candidate[:if_stmt].line, keyword_pattern: "if")
|
|
411
|
+
|
|
412
|
+
@warnings << Warning.new(
|
|
413
|
+
path: @path,
|
|
414
|
+
line:,
|
|
415
|
+
column:,
|
|
416
|
+
length:,
|
|
417
|
+
code:,
|
|
418
|
+
message: "nullable guard for '#{candidate[:declaration].name}' can use #{expected_kind} ... else",
|
|
419
|
+
severity: :hint,
|
|
420
|
+
symbol_name: candidate[:declaration].name
|
|
421
|
+
)
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def prefer_else_candidate(stmts, index, expected_kind:)
|
|
427
|
+
declaration = stmts[index]
|
|
428
|
+
if_stmt = stmts[index + 1]
|
|
429
|
+
return unless declaration.is_a?(AST::LocalDecl)
|
|
430
|
+
return unless declaration.kind == expected_kind
|
|
431
|
+
return if declaration.type || declaration.else_binding || declaration.else_body || declaration.recovered_else
|
|
432
|
+
return unless declaration.value && declaration.name
|
|
433
|
+
return if ignored_binding_name?(declaration.name)
|
|
434
|
+
return unless if_stmt.is_a?(AST::IfStmt)
|
|
435
|
+
return unless if_stmt.else_body.nil? && if_stmt.branches.length == 1
|
|
436
|
+
|
|
437
|
+
branch = if_stmt.branches.first
|
|
438
|
+
identifier = null_equality_identifier(branch.condition)
|
|
439
|
+
return unless identifier&.name == declaration.name
|
|
440
|
+
return unless nullable_binding_declaration?(declaration)
|
|
441
|
+
return if expected_kind == :var && !prefer_var_else_binding_mutated?(declaration)
|
|
442
|
+
return unless ControlFlow::Termination.block_always_terminates?(branch.body, ignore_name: method(:ignored_binding_name?), binding_resolution: cfg_binding_resolution)
|
|
443
|
+
|
|
444
|
+
{ declaration:, if_stmt:, branch: }
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def prefer_var_else_binding_mutated?(declaration)
|
|
448
|
+
binding = @scopes.last[declaration.name]
|
|
449
|
+
binding&.mutated == true
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def null_equality_identifier(cond)
|
|
453
|
+
return nil unless cond.is_a?(AST::BinaryOp) && cond.operator == "=="
|
|
454
|
+
|
|
455
|
+
if cond.left.is_a?(AST::Identifier) && cond.right.is_a?(AST::NullLiteral)
|
|
456
|
+
cond.left
|
|
457
|
+
elsif cond.left.is_a?(AST::NullLiteral) && cond.right.is_a?(AST::Identifier)
|
|
458
|
+
cond.right
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def nullable_binding_declaration?(statement)
|
|
463
|
+
binding_type = binding_type_for_declaration(statement)
|
|
464
|
+
return binding_type.is_a?(Types::Nullable) if binding_type
|
|
465
|
+
|
|
466
|
+
nullable_initializer_without_binding_type?(statement.value)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def nullable_initializer_without_binding_type?(expression)
|
|
470
|
+
case expression
|
|
471
|
+
when AST::Identifier, AST::MemberAccess, AST::IndexAccess, AST::Call, AST::IfExpr, AST::MatchExpr
|
|
472
|
+
true
|
|
473
|
+
when AST::AwaitExpr, AST::UnsafeExpr
|
|
474
|
+
nullable_initializer_without_binding_type?(expression.expression)
|
|
475
|
+
else
|
|
476
|
+
false
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def binding_type_for_declaration(statement)
|
|
481
|
+
binding_resolution = @sema_facts&.binding_resolution
|
|
482
|
+
return nil unless binding_resolution&.binding_types
|
|
483
|
+
|
|
484
|
+
binding_id = binding_resolution.declaration_binding_ids[statement.object_id]
|
|
485
|
+
return nil unless binding_id
|
|
486
|
+
|
|
487
|
+
binding_resolution.binding_types[binding_id]
|
|
488
|
+
end
|
|
489
|
+
def pointer_like_cast_expression(expression)
|
|
490
|
+
return unless expression.is_a?(AST::PrefixCast)
|
|
491
|
+
|
|
492
|
+
target_type = expression.target_type
|
|
493
|
+
return unless target_type.is_a?(AST::TypeRef)
|
|
494
|
+
return unless pointer_like_type_ref?(target_type)
|
|
495
|
+
|
|
496
|
+
{ target_type:, source: expression.expression }
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def pointer_like_type_ref?(type_ref)
|
|
500
|
+
return false if type_ref.nullable
|
|
501
|
+
|
|
502
|
+
%w[ptr const_ptr ref].include?(type_ref.name.to_s)
|
|
503
|
+
end
|
|
504
|
+
# ── directional-ffi-arg ──────────────────────────────────────────────
|
|
505
|
+
|
|
506
|
+
def check_directional_ffi_call(expression)
|
|
507
|
+
call = resolve_directional_ffi_call(expression.callee)
|
|
508
|
+
return unless call
|
|
509
|
+
|
|
510
|
+
call[:params].zip(expression.arguments).each do |parameter, argument|
|
|
511
|
+
next unless parameter && argument
|
|
512
|
+
|
|
513
|
+
passing_mode = parameter_passing_mode(parameter)
|
|
514
|
+
next unless %i[in out inout].include?(passing_mode)
|
|
515
|
+
next unless legacy_directional_argument_expression?(argument.value)
|
|
516
|
+
|
|
517
|
+
@warnings << Warning.new(
|
|
518
|
+
path: @path,
|
|
519
|
+
line: expression_line(argument.value),
|
|
520
|
+
column: expression_column(argument.value),
|
|
521
|
+
length: expression_length(argument.value),
|
|
522
|
+
code: "directional-ffi-arg",
|
|
523
|
+
message: "pass the lvalue directly to '#{call[:name]}'; parameter '#{parameter_name(parameter)}' already declares #{passing_mode} passing",
|
|
524
|
+
severity: :hint,
|
|
525
|
+
symbol_name: parameter_name(parameter)
|
|
526
|
+
)
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def resolve_directional_ffi_call(callee)
|
|
531
|
+
case callee
|
|
532
|
+
when AST::Specialization
|
|
533
|
+
resolve_directional_ffi_call(callee.callee)
|
|
534
|
+
when AST::Identifier
|
|
535
|
+
if @sema_facts && (binding = @sema_facts.functions[callee.name]) && directional_ffi_binding?(binding)
|
|
536
|
+
return { name: binding.name, params: binding.type.params }
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
if (declaration = @declared_directional_functions[callee.name])
|
|
540
|
+
return { name: declaration.name, params: declaration.params }
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
nil
|
|
544
|
+
when AST::MemberAccess
|
|
545
|
+
return nil unless callee.receiver.is_a?(AST::Identifier)
|
|
546
|
+
return nil unless @sema_facts
|
|
547
|
+
|
|
548
|
+
imported_module = @sema_facts.imports[callee.receiver.name]
|
|
549
|
+
return nil unless imported_module
|
|
550
|
+
|
|
551
|
+
binding = imported_module.functions[callee.member]
|
|
552
|
+
return nil unless directional_ffi_binding?(binding)
|
|
553
|
+
|
|
554
|
+
{ name: binding.name, params: binding.type.params }
|
|
555
|
+
else
|
|
556
|
+
nil
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def directional_ffi_binding?(binding)
|
|
561
|
+
return false unless binding
|
|
562
|
+
return false unless binding.respond_to?(:ast) && (binding.ast.is_a?(AST::ExternFunctionDecl) || binding.ast.is_a?(AST::ForeignFunctionDecl))
|
|
563
|
+
|
|
564
|
+
binding.type.params.any? { |parameter| %i[in out inout].include?(parameter.passing_mode) }
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def parameter_passing_mode(parameter)
|
|
568
|
+
parameter.respond_to?(:passing_mode) ? parameter.passing_mode : parameter.mode
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
def parameter_name(parameter)
|
|
572
|
+
parameter.respond_to?(:name) ? parameter.name : "argument"
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def legacy_directional_argument_expression?(expression)
|
|
576
|
+
return true if expression.is_a?(AST::UnaryOp) && %w[in out inout].include?(expression.operator)
|
|
577
|
+
|
|
578
|
+
if expression.is_a?(AST::Call) && expression.callee.is_a?(AST::Identifier) && %w[ptr_of ref_of].include?(expression.callee.name)
|
|
579
|
+
return expression.arguments.length == 1
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
cast = pointer_like_cast_expression(expression)
|
|
583
|
+
return false unless cast
|
|
584
|
+
|
|
585
|
+
lvalue_expression?(cast[:source]) || legacy_directional_argument_expression?(cast[:source])
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def lvalue_expression?(expression)
|
|
589
|
+
expression.is_a?(AST::Identifier) || expression.is_a?(AST::MemberAccess) || expression.is_a?(AST::IndexAccess)
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
end
|