Class: MilkTea::SemanticAnalyzer::Checker

Inherits:
Object
  • Object
show all
Includes:
CompatibilityHelpers
Defined in:
lib/milk_tea/core/semantic_analyzer.rb,
lib/milk_tea/core/semantic_analyzer/calls.rb,
lib/milk_tea/core/semantic_analyzer/generics.rb,
lib/milk_tea/core/semantic_analyzer/top_level.rb,
lib/milk_tea/core/semantic_analyzer/attributes.rb,
lib/milk_tea/core/semantic_analyzer/statements.rb,
lib/milk_tea/core/semantic_analyzer/expressions.rb,
lib/milk_tea/core/semantic_analyzer/nullability.rb,
lib/milk_tea/core/semantic_analyzer/flow_refinement.rb,
lib/milk_tea/core/semantic_analyzer/name_resolution.rb,
lib/milk_tea/core/semantic_analyzer/analysis_context.rb,
lib/milk_tea/core/semantic_analyzer/function_binding.rb,
lib/milk_tea/core/semantic_analyzer/type_declaration.rb,
lib/milk_tea/core/semantic_analyzer/foreign_functions.rb,
lib/milk_tea/core/semantic_analyzer/type_compatibility.rb,
lib/milk_tea/core/semantic_analyzer/interface_conformance.rb

Constant Summary collapse

INTEGER_SUFFIX_TYPES =
{
  "ub" => "ubyte",
  "b" => "byte",
  "us" => "ushort",
  "s" => "short",
  "u" => "uint",
  "i" => "int",
  "ul" => "ulong",
  "l" => "long",
  "z" => "ptr_uint",
  "iz" => "ptr_int",
}.freeze
BUILTIN_CALLABLE_NAMES =

Names recognised as builtin callable / type-constructor identifiers by resolve_callable and the compile-time evaluation path. We allow these so the generic-name check does not flag them as unknown before the full type checker has a chance to handle the surrounding Specialization / Call context.

%w[
  fatal ref_of const_ptr_of read ptr_of
  field_of fields_of callable_of attribute_of has_attribute
  members_of attributes_of get
  reinterpret array span zero default hash equal order
  adapt attribute_arg
  Task Option Result SoA str_buffer atomic
].to_set.freeze
PRELUDE_MODULE_PATHS =
%w[std.option std.result].freeze
PRELUDE_TYPE_DEFINING_MODULES =
{
  "Option" => "std.option",
  "Result" => "std.result",
}.freeze

Constants included from CompatibilityHelpers

CompatibilityHelpers::ATOMIC_METHOD_KINDS, CompatibilityHelpers::BUILTIN_CSTR, CompatibilityHelpers::EVENT_METHOD_KINDS, CompatibilityHelpers::EVENT_METHOD_NAMES, CompatibilityHelpers::SIMD_METHOD_KINDS, CompatibilityHelpers::STR_BUFFER_METHOD_KINDS, CompatibilityHelpers::STR_BUFFER_METHOD_NAMES

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from CompatibilityHelpers

#event_subscription_result_type, #event_wait_result_type, #range_expr?, #string_literal_cstr_compatibility?, #type_ref_from_specialization

Methods included from Types::Predicates

#arity_error_message, #call_arity_matches?, #callable_param_ref_supported?, #char_pointer_type?, #char_type?, #const_pointer_to, #const_pointer_type?, #contains_ref_type?, #contextual_int_to_float_compatibility?, #contextual_int_to_float_target?, #dyn_type?, #exact_integer_constant_value, #exactly_representable_float32?, #external_numeric_compatibility?, #external_typed_null_pointer_compatibility?, #flatten_field_types, #float_constant_fits_type?, #foreign_boundary_element_compatible?, #foreign_char_pointer_buffer_boundary_compatible?, #foreign_external_layout_compatible?, #foreign_external_layout_field_compatible?, #foreign_function_type_projection_compatible?, #foreign_identity_projection_cast_compatible?, #foreign_identity_projection_compatible?, #foreign_identity_projection_reinterpret_compatible?, #foreign_opaque_c_name, #foreign_span_boundary_compatible?, #function_type_matches_proc_type?, #integer_constant_fits_type?, #integer_like_char_source_type?, #integer_to_char_compatibility?, #lossless_external_integer_compatibility?, #lossless_integer_compatibility?, #method_dispatch_receiver_type, #mutable_pointer_type?, #mutable_to_const_pointer_compatibility?, #native_foreign_layout_compatible?, #null_assignable_to?, #numeric_constant_fits_type?, #opaque_type?, #own_to_raw_pointer_compatibility?, #own_type?, #owned_referent_type, #pointee_type, #pointer_type?, #proc_type?, #quat_vec4_layout_compatible?, #ref_lifetime, #ref_type?, #ref_type_without_lifetime?, #referenced_type, #same_external_opaque_c_name?, #same_external_opaque_handle_pointer_compatibility?, #string_builder_ref_type?, #string_builder_type?, #struct_with_target_type?, #task_root_proc_type?, #task_type?, #value_fits_integer_type?, #void_pointer_type?

Constructor Details

#initialize(ast, imported_modules: {}, allow_missing_imports: false, path: nil, global_import_index: {}) ⇒ Checker

Returns a new instance of Checker.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/milk_tea/core/semantic_analyzer.rb', line 140

def initialize(ast, imported_modules: {}, allow_missing_imports: false, path: nil, global_import_index: {})
  @path = path
  @allow_missing_imports = allow_missing_imports
  @ctx = ModuleContext.new(
    ast:,
    module_name: ast.module_name&.to_s,
    module_kind: ast.module_kind,
    imported_modules:,
    global_import_index:,
    const_declarations: ast.declarations.grep(AST::ConstDecl).each_with_object({}) { |decl, result| result[decl.name] = decl },
  )
  @null_type = Types::Null.new
  @error_type = Types::Error.new
  @loop_depth = 0
  @unsafe_depth = 0
  @compile_time_depth = 0
  @foreign_mapping_depth = 0
  @async_function_depth = 0
  @checked_function_bindings = {}
  @checking_function_bindings = {}
  @evaluating_const_values = []
  @evaluated_const_values = {}
  @error_node_stack = []
  @local_completion_frames = []
  @active_local_completion_stack = []
  @resolved_expr_types = {}
  @resolved_call_kinds = {}
  @const_values = {}
  @next_binding_id = 1
  @binding_name_by_id = {}
  @binding_type_by_id = {}
  @identifier_binding_ids = {}
  @declaration_binding_ids = {}
  @mutating_argument_identifier_ids = {}
  @mutable_lvalue_argument_identifier_ids = {}
  @editable_receiver_expression_ids = {}
  @preassigned_local_binding_ids = {}
  @nullability_flow_result = nil
  @unsafe_statement_lines = []
  @callable_value_identifier_sites = {}
  @callable_value_member_access_sites = {}
  @required_unsafe_lines = []
  @uses_parallel_for = false
  @current_specialization_owner = nil
  @return_context_stack = []
end

Instance Attribute Details

#ctxObject (readonly)

Returns the value of attribute ctx.



134
135
136
# File 'lib/milk_tea/core/semantic_analyzer.rb', line 134

def ctx
  @ctx
end

Instance Method Details

#addressable_storage_expression?(expression, scopes:) ⇒ Boolean

Returns:

  • (Boolean)


950
951
952
953
954
955
956
957
958
959
960
961
962
963
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 950

def addressable_storage_expression?(expression, scopes:)
  case expression
  when AST::Identifier
    true
  when AST::MemberAccess, AST::IndexAccess
    addressable_storage_expression?(expression.receiver, scopes:)
  when AST::Call
    return false unless expression.arguments.length == 1 && expression.arguments.first.name.nil?

    read_call?(expression) && ref_type?(infer_expression(expression.arguments.first.value, scopes:))
  else
    false
  end
end

#aggregate_display_name(type) ⇒ Object



831
832
833
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 831

def aggregate_display_name(type)
  type.is_a?(Types::StructInstance) ? type.to_s : type.name
end

#aggregate_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


701
702
703
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 701

def aggregate_type?(type)
  type.is_a?(Types::Struct) || type.is_a?(Types::Tuple) || span_type?(type) || string_view_type?(type) || task_type?(type) || vector_type?(type) || matrix_type?(type) || quaternion_type?(type)
end

#allocate_binding_idObject



1380
1381
1382
1383
1384
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1380

def allocate_binding_id
  id = @next_binding_id
  @next_binding_id += 1
  id
end

#apply_continuation_refinements!(scopes, refinements) ⇒ Object



14
15
16
17
18
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 14

def apply_continuation_refinements!(scopes, refinements)
  return if refinements.nil? || refinements.empty?

  scopes.replace(scopes_with_refinements(scopes, refinements))
end

#apply_nullability_continuation_refinements!(scopes, next_stmt) ⇒ Object

After processing a statement, apply ControlFlow-derived non-null refinements to the scopes so the next statement benefits from cross-branch narrowing.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/milk_tea/core/semantic_analyzer/nullability.rb', line 74

def apply_nullability_continuation_refinements!(scopes, next_stmt)
  return unless @nullability_flow_result

  nonnull_binding_ids = @nullability_flow_result.nonnull_before(next_stmt)
  return if nonnull_binding_ids.empty?

  refinements = {}
  nonnull_binding_ids.each do |binding_id|
    next unless binding_id.is_a?(Integer)

    name = @binding_name_by_id[binding_id]
    next unless name

    binding = lookup_value(name, scopes)
    next unless binding&.id == binding_id
    next unless binding&.storage_type.is_a?(Types::Nullable)

    refinements[name] = binding.storage_type.base
  end
  apply_continuation_refinements!(scopes, refinements) unless refinements.empty?
end

#argument_types_compatible?(actual_type, expected_type, external:, expression: nil, scopes: nil, contextual_int_to_float: false) ⇒ Boolean

Returns:

  • (Boolean)


65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 65

def argument_types_compatible?(actual_type, expected_type, external:, expression: nil, scopes: nil, contextual_int_to_float: false)
  return true if types_compatible?(actual_type, expected_type, expression:, scopes:, external_numeric: external, contextual_int_to_float:)
  return true if external && external_void_pointer_argument_compatibility?(actual_type, expected_type)
  return true if external && extern_enum_integer_argument_compatibility?(actual_type, expected_type)
  if external && foreign_mapping_context? && foreign_identity_projection_compatible?(actual_type, expected_type)
    return false if actual_type == @ctx.types.fetch("cstr") && char_pointer_type?(expected_type)

    return true
  end

  false
end

#array_element_type(type) ⇒ Object



730
731
732
733
734
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 730

def array_element_type(type)
  return unless array_type?(type)

  type.arguments.first
end

#array_length(type) ⇒ Object



736
737
738
739
740
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 736

def array_length(type)
  return unless array_type?(type)

  type.arguments[1].value
end

#array_to_span_call_argument_compatible?(actual_type, expected_type, expression:, scopes:) ⇒ Boolean

Returns:

  • (Boolean)


1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1223

def array_to_span_call_argument_compatible?(actual_type, expected_type, expression:, scopes:)
  return false unless expected_type.is_a?(Types::Span)

  if array_type?(actual_type)
    return false unless array_element_type(actual_type) == expected_type.element_type

    infer_addr_source_type(expression, scopes:)
    record_mutable_lvalue_argument_identifier(expression)
    return true
  end

  if str_buffer_type?(actual_type)
    return false unless expected_type.element_type == @ctx.types.fetch("char")

    infer_addr_source_type(expression, scopes:)
    record_mutable_lvalue_argument_identifier(expression)
    return true
  end

  false
rescue SemanticError
  false
end

#array_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


725
726
727
728
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 725

def array_type?(type)
  type.is_a?(Types::GenericInstance) && type.name == "array" && type.arguments.length == 2 &&
    !type.arguments.first.is_a?(Types::LiteralTypeArg) && type.arguments[1].is_a?(Types::LiteralTypeArg)
end

#assignable_receiver?(receiver_expression, scopes) ⇒ Boolean

Returns:

  • (Boolean)


1355
1356
1357
1358
1359
1360
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1355

def assignable_receiver?(receiver_expression, scopes)
  infer_lvalue_receiver(receiver_expression, scopes:, allow_ref_identifier: true, allow_pointer_identifier: true, require_mutable_pointer: true)
  true
rescue SemanticError
  false
end

#atomic_element_type(type) ⇒ Object



759
760
761
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 759

def atomic_element_type(type)
  type.arguments.first
end

#atomic_method_kind(receiver_type, name) ⇒ Object



654
655
656
657
658
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 654

def atomic_method_kind(receiver_type, name)
  return unless atomic_type?(receiver_type)

  ATOMIC_METHOD_KINDS[name]
end

#atomic_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


755
756
757
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 755

def atomic_type?(type)
  type.is_a?(Types::GenericInstance) && type.name == "atomic" && type.arguments.length == 1
end

#attribute_binding_for_handle_expression(expression) ⇒ Object



966
967
968
969
970
971
972
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 966

def attribute_binding_for_handle_expression(expression)
  return unless expression.is_a?(AST::Call)
  return unless expression.callee.is_a?(AST::Identifier) && expression.callee.name == "attribute_of"
  return unless expression.arguments.length == 2 && expression.arguments.none?(&:name)

  resolve_attribute_name_argument(expression.arguments[1].value)
end

#attribute_presence_guard_active?(scopes, key) ⇒ Boolean

Returns:

  • (Boolean)


43
44
45
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 43

def attribute_presence_guard_active?(scopes, key)
  scopes.any? { |scope| scope.key?(key) }
end

#attribute_presence_key_from_call(expression, scopes:) ⇒ Object



175
176
177
178
179
180
181
182
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 175

def attribute_presence_key_from_call(expression, scopes:)
  target = resolve_reflection_target_argument(expression.arguments.first.value, scopes:)
  binding = resolve_attribute_name_argument(expression.arguments[1].value)
  validate_attribute_target_compatibility!(target, binding)
  attribute_presence_refinement_key(target, binding)
rescue SemanticError
  nil
end

#attribute_presence_refinement_key(target, binding) ⇒ Object



39
40
41
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 39

def attribute_presence_refinement_key(target, binding)
  AttributePresenceKey.new(target, binding.module_name, binding.name)
end

#attribute_target_kind(target) ⇒ Object



1638
1639
1640
1641
1642
1643
1644
1645
1646
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1638

def attribute_target_kind(target)
  case target
  when Types::StructHandle then :struct
  when Types::FieldHandle then :field
  when Types::CallableHandle then :callable
  else
    raise_sema_error("unsupported attribute reflection target #{target}")
  end
end

#automatic_foreign_cstr_list_temp_needed?(parameter) ⇒ Boolean

Returns:

  • (Boolean)


1216
1217
1218
1219
1220
1221
1222
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1216

def automatic_foreign_cstr_list_temp_needed?(parameter)
  return false unless parameter.type.is_a?(Types::Span) && parameter.type.element_type == @ctx.types.fetch("str")
  return false unless parameter.boundary_type.is_a?(Types::Span)

  boundary_element_type = parameter.boundary_type.element_type
  boundary_element_type == @ctx.types.fetch("cstr") || char_pointer_type?(boundary_element_type)
end

#automatic_foreign_cstr_temp_needed?(parameter, expression, scopes:) ⇒ Boolean

Returns:

  • (Boolean)


1224
1225
1226
1227
1228
1229
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1224

def automatic_foreign_cstr_temp_needed?(parameter, expression, scopes:)
  return false unless parameter.boundary_type == @ctx.types.fetch("cstr") && parameter.type == @ctx.types.fetch("str")
  return false if expression.is_a?(AST::StringLiteral) && !expression.cstring

  infer_expression(expression, scopes:) != @ctx.types.fetch("cstr")
end

#binding_resolution_snapshotObject



1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1369

def binding_resolution_snapshot
  BindingResolution.new(
    identifier_binding_ids: @identifier_binding_ids.dup.freeze,
    declaration_binding_ids: @declaration_binding_ids.dup.freeze,
    mutating_argument_identifier_ids: @mutating_argument_identifier_ids.dup.freeze,
    editable_receiver_expression_ids: @editable_receiver_expression_ids.dup.freeze,
    mutable_lvalue_argument_identifier_ids: @mutable_lvalue_argument_identifier_ids.dup.freeze,
    binding_types: @binding_type_by_id.dup.freeze,
  )
end

#bitwise_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


414
415
416
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 414

def bitwise_type?(type)
  type.respond_to?(:bitwise?) && type.bitwise?
end

#build_analysisObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 6

def build_analysis
  Analysis.new(
    ast: @ctx.ast,
    module_name: @ctx.module_name,
    module_kind: @ctx.module_kind,
    directives: @ctx.ast.directives,
    imports: @ctx.imports,
    types: @ctx.types,
    interfaces: @ctx.interfaces,
    attributes: snapshot_attributes,
    attribute_applications: snapshot_attribute_applications,
    values: @ctx.top_level_values,
    functions: @ctx.top_level_functions,
    methods: snapshot_methods,
    implemented_interfaces: snapshot_implemented_interfaces,
    local_completion_frames: @local_completion_frames.dup.freeze,
    binding_resolution: binding_resolution_snapshot,
    callable_value_identifier_sites: @callable_value_identifier_sites.dup.freeze,
    callable_value_member_access_sites: @callable_value_member_access_sites.dup.freeze,
    required_unsafe_lines: @required_unsafe_lines.uniq.freeze,
    uses_parallel_for: @uses_parallel_for,
    resolved_expr_types: @resolved_expr_types.dup.freeze,
    resolved_call_kinds: @resolved_call_kinds.dup.freeze,
    const_values: @const_values.dup.freeze,
  )
end

#builtin_attribute_handle_typeObject



98
99
100
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 98

def builtin_attribute_handle_type
  Types::BUILTIN_ATTRIBUTE_HANDLE_TYPE
end

#builtin_callable_handle_typeObject



94
95
96
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 94

def builtin_callable_handle_type
  Types::BUILTIN_CALLABLE_HANDLE_TYPE
end

#builtin_event_error_typeObject



82
83
84
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 82

def builtin_event_error_type
  Types::Enum.new("EventError").define_members(@ctx.types.fetch("int"), ["full"]).define_member_values("full" => 0)
end

#builtin_field_handle_typeObject



90
91
92
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 90

def builtin_field_handle_type
  Types::BUILTIN_FIELD_HANDLE_TYPE
end

#builtin_member_handle_typeObject



102
103
104
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 102

def builtin_member_handle_type
  Types::BUILTIN_MEMBER_HANDLE_TYPE
end

#builtin_struct_handle_typeObject



86
87
88
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 86

def builtin_struct_handle_type
  Types::BUILTIN_STRUCT_HANDLE_TYPE
end

#builtin_subscription_typeObject



78
79
80
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 78

def builtin_subscription_type
  Types::Subscription.new
end

#builtin_type_meta_typeObject



106
107
108
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 106

def builtin_type_meta_type
  Types::BUILTIN_TYPE_META_TYPE
end

#c_natively_equality_comparable_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 847

def c_natively_equality_comparable_type?(type)
  return true if type.is_a?(Types::Primitive)
  return true if type.is_a?(Types::EnumBase)
  return true if type.is_a?(Types::Opaque)
  return true if type.is_a?(Types::Nullable)
  return true if type.is_a?(Types::Null)
  return true if type.is_a?(Types::Function)
  return true if type.is_a?(Types::Error)
  return true if type.is_a?(Types::StringView)
  return true if pointer_type?(type)
  return true if ref_type?(type)
  return true if type.is_a?(Types::Variant)
  return true if type.is_a?(Types::VariantArmPayload)

  false
end

#call_argument_compatible?(actual_type, expected_type, scopes:, external:, expression: nil) ⇒ Boolean

Returns:

  • (Boolean)


6
7
8
9
10
11
12
13
14
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 6

def call_argument_compatible?(actual_type, expected_type, scopes:, external:, expression: nil)
  return true if array_to_span_call_argument_compatible?(actual_type, expected_type, expression:, scopes:)
  return true if argument_types_compatible?(actual_type, expected_type, external:, expression:, scopes:, contextual_int_to_float: !external)
  return true if implicit_ref_argument_compatible?(actual_type, expected_type, expression, scopes)
  return true if direct_task_to_proc_argument_compatible?(actual_type, expected_type)
  return true if direct_function_to_proc_argument_compatible?(actual_type, expected_type, expression, scopes)

  false
end

#callable_receiver_type_for_specialization(callee, scopes:) ⇒ Object



92
93
94
95
96
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 92

def callable_receiver_type_for_specialization(callee, scopes:)
  return unless callee.is_a?(AST::MemberAccess)

  resolve_type_expression(callee.receiver)
end

#callable_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


418
419
420
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 418

def callable_type?(type)
  type.is_a?(Types::Function) || type.is_a?(Types::Proc)
end

#canonicalize_call_arguments(arguments, params, context_name, binding = nil) ⇒ Object



1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 1013

def canonicalize_call_arguments(arguments, params, context_name, binding = nil)
  return arguments unless arguments.any?(&:name)

  named_seen = false
  by_position = Array.new(params.length)
  param_names = params.map(&:name)

  arguments.each_with_index do |arg, _idx|
    if arg.name
      named_seen = true
      raise_sema_error("duplicate named argument '#{arg.name}' in call to #{context_name}") if by_position.any? { |slot| slot&.name == arg.name }
      param_idx = param_names.index(arg.name)
      raise_sema_error("unknown parameter '#{arg.name}' for #{context_name}") unless param_idx
      by_position[param_idx] = arg
    else
      raise_sema_error("positional argument after named argument in call to #{context_name}") if named_seen
      param_idx = _idx
      raise_sema_error("#{context_name} expects #{params.length} arguments, got #{arguments.length}") if param_idx >= params.length
      by_position[param_idx] = arg
    end
  end

  fill_parameter_defaults!(by_position, params, binding, context_name)

  by_position.reject(&:nil?)
end

#cast_numeric_type(type) ⇒ Object



191
192
193
194
195
196
197
198
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 191

def cast_numeric_type(type)
  return type if type.is_a?(Types::Primitive) && (type.numeric? || type.name == "bool")
  return type.backing_type if type.is_a?(Types::EnumBase) && type.backing_type.numeric?
  return type if char_type?(type)
  return type.backing_type if type.is_a?(Types::EnumBase) && char_type?(type.backing_type)

  nil
end

#castable_primitive?(type) ⇒ Boolean

Returns:

  • (Boolean)


477
478
479
480
481
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 477

def castable_primitive?(type)
  return castable_primitive?(type.base) if type.is_a?(Types::Nullable)

  type.is_a?(Types::Primitive) || type.is_a?(Types::Enum) || type.is_a?(Types::Flags)
end

#catch_structuralObject



323
324
325
326
327
# File 'lib/milk_tea/core/semantic_analyzer.rb', line 323

def catch_structural
  yield
rescue SemanticError => e
  @structural_errors << e
end

#cfg_block_always_terminates?(statements) ⇒ Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 55

def cfg_block_always_terminates?(statements)
  ControlFlow::Termination.block_always_terminates?(statements, ignore_name: ->(_name) { false })
end

#char_array_removed_text_method?(receiver_type, name) ⇒ Boolean

Returns:

  • (Boolean)


769
770
771
772
773
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 769

def char_array_removed_text_method?(receiver_type, name)
  return unless char_array_text_type?(receiver_type)

  name == "as_str" || name == "as_cstr"
end

#char_array_text_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


742
743
744
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 742

def char_array_text_type?(type)
  array_type?(type) && array_element_type(type) == @ctx.types.fetch("char")
end

#checkObject



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/milk_tea/core/semantic_analyzer.rb', line 187

def check
  install_builtin_types
  install_builtin_attributes
  install_imports
  install_prelude_types
  declare_named_types
  resolve_generic_type_param_constraints
  resolve_type_aliases
  declare_attributes
  resolve_aggregate_fields
  resolve_enum_members
  resolve_variant_arms
  collect_emit_declarations
  declare_top_level_values
  check_attribute_applications
  declare_functions
  check_interface_conformances
  check_top_level_values
  finalize_top_level_const_values
  check_top_level_static_asserts
  check_functions

  build_analysis
end

#check_adapt_call(interface, arguments, scopes:) ⇒ Object



974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 974

def check_adapt_call(interface, arguments, scopes:)
  raise_sema_error("adapt does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("adapt expects 1 argument, got #{arguments.length}") unless arguments.length == 1

  argument = arguments.first
  concrete_type = infer_expression(argument.value, scopes:)
  concrete_type = referenced_type(concrete_type) if ref_type?(concrete_type)
  unless concrete_type.is_a?(Types::Struct) || concrete_type.is_a?(Types::Opaque) || concrete_type.is_a?(Types::StructInstance)
    raise_sema_error("adapt requires a struct or opaque type, got #{concrete_type}")
  end

  unless type_implements_interface?(concrete_type, interface)
    raise_sema_error("#{concrete_type} does not implement #{interface.name}")
  end

  Types::Dyn.new(interface, interface.type_arguments || [])
end

#check_aggregate_construction(struct_type, arguments, scopes:) ⇒ Object



47
48
49
50
51
52
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 47

def check_aggregate_construction(struct_type, arguments, scopes:)
  require_unsafe!("str construction requires unsafe") if struct_type.is_a?(Types::StringView)

  check_aggregate_field_arguments(struct_type, arguments, scopes:, context: "aggregate construction")
  struct_type
end

#check_aggregate_field_arguments(struct_type, arguments, scopes:, context:) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 77

def check_aggregate_field_arguments(struct_type, arguments, scopes:, context:)
  display_name = aggregate_display_name(struct_type)

  raise_sema_error("#{context} for #{display_name} requires named arguments") unless arguments.all?(&:name)

  provided = {}
  arguments.each do |argument|
    field_type = struct_type.field(argument.name)
    raise_sema_error("unknown field #{display_name}.#{argument.name}") unless field_type
    raise_sema_error("duplicate field #{display_name}.#{argument.name}") if provided.key?(argument.name)

    actual_type = infer_expression(argument.value, scopes:, expected_type: field_type)
    ensure_assignable!(
      actual_type,
      field_type,
      "field #{display_name}.#{argument.name} expects #{field_type}, got #{actual_type}",
      expression: argument.value,
      external_numeric: struct_type.respond_to?(:external) && struct_type.external,
      external_pointer_null: struct_type.respond_to?(:external) && struct_type.external,
      contextual_int_to_float: contextual_int_to_float_target?(field_type),
    )
    provided[argument.name] = true
  end
end

#check_array_construction(array_type, arguments, scopes:) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 138

def check_array_construction(array_type, arguments, scopes:)
  raise_sema_error("array construction does not support named arguments") if arguments.any?(&:name)

  element_type = array_element_type(array_type)
  length = array_length(array_type)
  raise_sema_error("array expects at most #{length} elements, got #{arguments.length}") if arguments.length > length

  arguments.each do |argument|
    actual_type = infer_expression(argument.value, scopes:, expected_type: element_type)
    ensure_assignable!(
      actual_type,
      element_type,
      "array element expects #{element_type}, got #{actual_type}",
      expression: argument.value,
    )
  end

  array_type
end

#check_assignment(statement, scopes:) ⇒ Object



469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 469

def check_assignment(statement, scopes:)
  if statement.operator == "=" &&
     statement.target.is_a?(AST::IndexAccess) &&
     statement.target.index.is_a?(AST::RangeExpr)
    return check_range_index_assignment(statement, scopes:)
  end

  target_type = infer_lvalue(statement.target, scopes:)
  raise_sema_error("cannot assign to non-copyable event storage type #{target_type}") if noncopyable_event_storage_type?(target_type)

  validate_consuming_foreign_expression!(statement.value, scopes:, root_allowed: false)
  value_type = infer_expression(statement.value, scopes:, expected_type: target_type)

  case statement.operator
  when "="
    ensure_assignable!(
      value_type,
      target_type,
      "cannot assign #{value_type} to #{target_type}",
      expression: statement.value,
      external_numeric: external_numeric_assignment_target?(statement.target, scopes:),
      contextual_int_to_float: contextual_int_to_float_target?(target_type),
      line: statement.line, column: statement.column,
    )
  when "+=", "-=", "*=", "/="
    binary_op = statement.operator[0...-1]
    if (result_type = vector_arithmetic_result(binary_op, target_type, value_type))
      raise_sema_error("operator #{statement.operator} on #{target_type} and #{value_type} produces #{result_type}, expected #{target_type}") unless result_type == target_type
    else
      raise_sema_error("operator #{statement.operator} requires matching numeric types, got #{target_type} and #{value_type}") unless target_type.numeric? && value_type.numeric?

      ensure_assignable!(
        value_type,
        target_type,
        "operator #{statement.operator} requires matching numeric types, got #{target_type} and #{value_type}",
        expression: statement.value,
        contextual_int_to_float: contextual_int_to_float_target?(target_type),
        line: statement.line, column: statement.column,
      )
    end
  when "%="
    unless common_integer_type(target_type, value_type) == target_type || simd_mod_result(target_type, value_type)
      raise_sema_error("operator #{statement.operator} requires compatible integer types, got #{target_type} and #{value_type}")
    end
  when "&=", "|=", "^="
    unless (target_type == value_type && bitwise_type?(target_type)) || (simd_type?(target_type) && simd_type?(value_type) && target_type == value_type)
      raise_sema_error("operator #{statement.operator} requires matching integer or flags types, got #{target_type} and #{value_type}")
    end
  when "<<=", ">>="
    unless (target_type.is_a?(Types::Primitive) && target_type.integer? && value_type.is_a?(Types::Primitive) && value_type.integer?) || simd_shift_result(target_type, value_type)
      raise_sema_error("operator #{statement.operator} requires integer operands, got #{target_type} and #{value_type}")
    end
  else
    raise_sema_error("unsupported assignment operator #{statement.operator}")
  end
end

#check_atomic_method_call(kind, receiver_type, receiver, arguments, scopes:) ⇒ Object



666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 666

def check_atomic_method_call(kind, receiver_type, receiver, arguments, scopes:)
  elem_type = atomic_element_type(receiver_type)
  raise_sema_error("atomic methods do not support named arguments") if arguments.any?(&:name)

  case kind
  when :atomic_load
    raise_sema_error("load expects 0 arguments, got #{arguments.length}") unless arguments.empty?
    elem_type
  when :atomic_store
    raise_sema_error("store expects 1 argument, got #{arguments.length}") unless arguments.length == 1
    record_editable_receiver_expression(receiver)
    raise_sema_error("cannot call editable method store on an immutable receiver") unless assignable_receiver?(receiver, scopes)
    arg_type = infer_expression(arguments.first.value, scopes:, expected_type: elem_type)
    ensure_assignable!(arg_type, elem_type, "store expects #{elem_type}, got #{arg_type}")
    @ctx.types.fetch("void")
  when :atomic_add, :atomic_sub, :atomic_exchange
    raise_sema_error("#{kind.to_s.delete_prefix("atomic_")} expects 1 argument, got #{arguments.length}") unless arguments.length == 1
    record_editable_receiver_expression(receiver)
    raise_sema_error("cannot call editable method #{kind.to_s.delete_prefix("atomic_")} on an immutable receiver") unless assignable_receiver?(receiver, scopes)
    arg_type = infer_expression(arguments.first.value, scopes:, expected_type: elem_type)
    ensure_assignable!(arg_type, elem_type, "#{kind.to_s.delete_prefix("atomic_")} expects #{elem_type}, got #{arg_type}")
    elem_type
  when :atomic_compare_exchange
    raise_sema_error("compare_exchange expects 2 arguments, got #{arguments.length}") unless arguments.length == 2
    record_editable_receiver_expression(receiver)
    raise_sema_error("cannot call editable method compare_exchange on an immutable receiver") unless assignable_receiver?(receiver, scopes)
    exp_type = infer_expression(arguments[0].value, scopes:, expected_type: elem_type)
    des_type = infer_expression(arguments[1].value, scopes:, expected_type: elem_type)
    ensure_assignable!(exp_type, elem_type, "compare_exchange expected type #{elem_type}, got #{exp_type}")
    ensure_assignable!(des_type, elem_type, "compare_exchange desired type #{elem_type}, got #{des_type}")
    @ctx.types.fetch("bool")
  end
end

#check_attribute_applicationsObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/milk_tea/core/semantic_analyzer/attributes.rb', line 6

def check_attribute_applications
  expanded_declarations.each do |decl|
    with_error_node(decl) do
      case decl
      when AST::StructDecl
        packed, alignment = check_decl_attribute_applications!(decl.attributes, target_kind: :struct, target_label: "struct #{decl.name}", target_node: decl)
        @ctx.types.fetch(decl.name).set_layout(packed:, alignment:)

        decl.fields.each do |field|
          with_error_node(field) do
            if raw_module? && field.attributes.any?
              raise_sema_error("attributes are not allowed on fields in external files")
            end

            check_decl_attribute_applications!(field.attributes, target_kind: :field, target_label: "field #{decl.name}.#{field.name}", target_node: field)
          end
        end
      when AST::FunctionDef, AST::ExternFunctionDecl, AST::ForeignFunctionDecl
        check_decl_attribute_applications!(decl.attributes, target_kind: :callable, target_label: "callable #{decl.name}", target_node: decl)
      when AST::InterfaceDecl
        decl.methods.each do |method|
          with_error_node(method) do
            check_decl_attribute_applications!(method.attributes, target_kind: :callable, target_label: "callable #{decl.name}.#{method.name}", target_node: method)
          end
        end
      when AST::ExtendingBlock
        decl.methods.each do |method|
          with_error_node(method) do
            check_decl_attribute_applications!(method.attributes, target_kind: :callable, target_label: "callable #{decl.type_name}.#{method.name}", target_node: method)
          end
        end
      when AST::ConstDecl
        check_decl_attribute_applications!(decl.attributes, target_kind: :const, target_label: "const #{decl.name}", target_node: decl)
      when AST::EventDecl
        check_decl_attribute_applications!(decl.attributes, target_kind: :event, target_label: "event #{decl.name}", target_node: decl)
      when AST::UnionDecl
        check_decl_attribute_applications!(decl.attributes, target_kind: :union, target_label: "union #{decl.name}", target_node: decl)
      when AST::EnumDecl
        check_decl_attribute_applications!(decl.attributes, target_kind: :enum, target_label: "enum #{decl.name}", target_node: decl)
      when AST::FlagsDecl
        check_decl_attribute_applications!(decl.attributes, target_kind: :flags, target_label: "flags #{decl.name}", target_node: decl)
      when AST::VariantDecl
        check_decl_attribute_applications!(decl.attributes, target_kind: :variant, target_label: "variant #{decl.name}", target_node: decl)
      end
    end
  end
end

#check_attribute_arg_call(expected_type, arguments, scopes:) ⇒ Object



940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 940

def check_attribute_arg_call(expected_type, arguments, scopes:)
  raise_sema_error("attribute_arg does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("attribute_arg expects 2 arguments, got #{arguments.length}") unless arguments.length == 2

  handle_value = evaluate_compile_time_const_value(arguments.first.value, scopes:)
  parameter_source = if handle_value.is_a?(Types::AttributeHandle)
    [handle_value.attribute_name, handle_value.params]
  else
    handle_type = infer_expression(arguments.first.value, scopes:)
    raise_sema_error("attribute_arg expects an attribute handle") unless handle_type == builtin_attribute_handle_type

    binding = attribute_binding_for_handle_expression(arguments.first.value)
    raise_sema_error("attribute_arg expects an attribute handle") unless binding

    [binding.name, binding.params]
  end

  param_name = reflection_identifier_name(arguments[1].value, context: "attribute_arg")
  attribute_name, params = parameter_source
  parameter = params.find { |candidate| candidate.name == param_name }
  raise_sema_error("attribute #{attribute_name} has no parameter #{param_name}") unless parameter
  raise_sema_error("attribute_arg[#{expected_type}] does not match declared type #{parameter.type} for #{attribute_name}.#{param_name}") unless parameter.type == expected_type

  expected_type
end

#check_attribute_arguments!(binding, application) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/milk_tea/core/semantic_analyzer/attributes.rb', line 100

def check_attribute_arguments!(binding, application)
  params = binding.params
  arguments = application.arguments

  if params.empty?
    raise_sema_error("attribute #{binding.name} does not take arguments") if arguments.any?

    return {}
  end

  bound_arguments = {}
  next_position = 0

  arguments.each do |argument|
    param = if argument.name
      params.find { |candidate| candidate.name == argument.name }.tap do |candidate|
        raise_sema_error("unknown attribute argument #{binding.name}.#{argument.name}") unless candidate
      end
    else
      while next_position < params.length && bound_arguments.key?(params[next_position].name)
        next_position += 1
      end

      raise_sema_error("attribute #{binding.name} expects #{params.length} arguments, got #{arguments.length}") if next_position >= params.length

      param = params[next_position]
      next_position += 1
      param
    end

    raise_sema_error("duplicate attribute argument #{binding.name}.#{param.name}") if bound_arguments.key?(param.name)

    bound_arguments[param.name] = argument.value
  end

  missing = params.reject { |param| bound_arguments.key?(param.name) }
  unless missing.empty?
    names = missing.map(&:name).join(", ")
    raise_sema_error("attribute #{binding.name} is missing required arguments: #{names}")
  end

  params.each_with_object({}) do |param, values|
    argument_expression = bound_arguments.fetch(param.name)
    actual_type = infer_expression(argument_expression, scopes: [], expected_type: param.type)
    ensure_assignable!(
      actual_type,
      param.type,
      "attribute #{binding.name} argument #{param.name} expects #{param.type}, got #{actual_type}",
      expression: argument_expression,
    )

    const_value = evaluate_compile_time_const_value(argument_expression)
    raise_sema_error("attribute #{binding.name} argument #{param.name} must be a compile-time constant") if const_value.nil?

    values[param.name] = const_value
  end
end

#check_attribute_of_call(arguments, scopes:) ⇒ Object



925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 925

def check_attribute_of_call(arguments, scopes:)
  raise_sema_error("attribute_of does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("attribute_of expects 2 arguments, got #{arguments.length}") unless arguments.length == 2

  target = resolve_reflection_target_argument(arguments.first.value, scopes:)
  binding = resolve_attribute_name_argument(arguments[1].value)
  validate_attribute_target_compatibility!(target, binding)

  application = find_attribute_application(target, binding)
  unless application || attribute_presence_guard_active?(scopes, attribute_presence_refinement_key(target, binding))
    raise_sema_error("attribute #{qualified_attribute_name(binding)} is not applied to #{target}")
  end

  builtin_attribute_handle_type
end

#check_block(statements, scopes:, return_type:, allow_return: true) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 6

def check_block(statements, scopes:, return_type:, allow_return: true)
  statements ||= []
  with_return_context(return_type, allow_return:) do
    with_nested_scope(scopes) do |nested_scopes|
      with_type_resolution_scopes(nested_scopes) do
        statements.each_with_index do |statement, idx|
          begin
            record_local_completion_snapshot(
              statement.line,
              statement.column || 0,
              nested_scopes,
            )
            refinements = check_statement(statement, scopes: nested_scopes, return_type:, allow_return:)
            apply_continuation_refinements!(nested_scopes, refinements)
            if @nullability_flow_result && idx + 1 < statements.length
              apply_nullability_continuation_refinements!(nested_scopes, statements[idx + 1])
            end
            end_line = statement_end_line(statement)
            # When the statement spans multiple lines (e.g. let … else:),
            # record an extra snapshot at the declaration line so that
            # hover and completion lookups on the declaration line itself
            # can still find the new binding.
            if end_line && statement.line && end_line > statement.line
              record_local_completion_snapshot(
                statement.line,
                statement.column || 0,
                nested_scopes,
              )
            end
            record_local_completion_snapshot(end_line, 1_000_000, nested_scopes)
          rescue SemanticError => e
            if @collecting_errors
              @structural_errors << e
              next
            end

            raise e unless e.line.nil?

            stmt_line = statement.line
            raise e if stmt_line.nil?

            raise_sema_error(e.message, statement)
          end
        end
      end
    end
  end
end

#check_block_body_const(decl) ⇒ Object



60
61
62
63
64
65
66
67
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 60

def check_block_body_const(decl)
  return unless decl.block_body

  last_stmt = decl.block_body.last
  unless last_stmt.is_a?(AST::ReturnStmt) && last_stmt.value
    raise_sema_error("block-bodied const must end with a return statement")
  end
end

#check_callable_of_call(arguments, scopes:) ⇒ Object



908
909
910
911
912
913
914
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 908

def check_callable_of_call(arguments, scopes:)
  raise_sema_error("callable_of does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("callable_of expects 1 argument, got #{arguments.length}") unless arguments.length == 1

  evaluate_callable_of_call(arguments, scopes:)
  builtin_callable_handle_type
end

#check_callable_value_call(function_type, arguments, scopes:, callee_expression:) ⇒ Object



823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 823

def check_callable_value_call(function_type, arguments, scopes:, callee_expression:)
  arguments = canonicalize_call_arguments(arguments, function_type.params, describe_expression(callee_expression))

  unless call_arity_matches?(function_type, arguments.length)
    raise_sema_error(arity_error_message(function_type, describe_expression(callee_expression), arguments.length))
  end

  function_type.params.each_with_index do |parameter, index|
    argument = arguments.fetch(index)
    actual_type = infer_expression(argument.value, scopes:, expected_type: parameter.type)
    unless call_argument_compatible?(actual_type, parameter.type, scopes:, external: false, expression: argument.value)
      suggestion = explicit_cast_suggestion(actual_type, parameter.type)
      raise_sema_error("argument #{parameter.name || index} to #{describe_expression(callee_expression)} expects #{parameter.type}, got #{actual_type}", argument, suggestion:)
    end
  end

  arguments.drop(function_type.params.length).each do |argument|
    infer_expression(argument.value, scopes:)
  end
end

#check_cast_call(target_type, arguments, scopes:) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 158

def check_cast_call(target_type, arguments, scopes:)
  raise_sema_error("cast requires exactly one argument") unless arguments.length == 1
  raise_sema_error("cast does not support named arguments") if arguments.first.name

  source_type = infer_expression(arguments.first.value, scopes:)
  if source_type == target_type
    return target_type
  end

  if pointer_cast?(source_type, target_type)
    expression = arguments.first.value
    require_unsafe!("pointer cast requires unsafe", line: source_line(expression), column: source_column(expression))

    return target_type
  end

  if ref_to_pointer_cast?(source_type, target_type)
    expression = arguments.first.value
    require_unsafe!("ref to pointer cast requires unsafe", line: source_line(expression), column: source_column(expression))

    return target_type
  end

  source_numeric_type = cast_numeric_type(source_type)
  target_numeric_type = cast_numeric_type(target_type)

  unless source_numeric_type && target_numeric_type
    raise_sema_error("cast requires compatible types, got #{source_type} -> #{target_type}")
  end

  target_type
end

#check_collecting_errorsObject

Like check, but collects per-function errors instead of raising at first. Structural phases (imports, type resolution, declaration) collect errors per declaration so that the maximum number of diagnostics are surfaced. Returns { analysis: Analysis, errors: [SemanticError] }.



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/milk_tea/core/semantic_analyzer.rb', line 274

def check_collecting_errors
  @collecting_errors = true
  @structural_errors = []

  catch_structural { install_builtin_types }
  catch_structural { install_builtin_attributes }
  catch_structural { install_imports }
  catch_structural { install_prelude_types }
  catch_structural { declare_named_types }
  catch_structural { resolve_generic_type_param_constraints }
  catch_structural { resolve_type_aliases }
  catch_structural { declare_attributes }
  catch_structural { resolve_aggregate_fields }
  catch_structural { resolve_enum_members }
  catch_structural { resolve_variant_arms }
  catch_structural { collect_emit_declarations }
  catch_structural { declare_top_level_values }
  catch_structural { check_attribute_applications }
  catch_structural { declare_functions }
  catch_structural { check_interface_conformances }

  errors = @structural_errors.dup

  begin
    check_top_level_values
  rescue SemanticError => e
    errors << e
  end
  errors.concat(@structural_errors.drop(errors.length))

  begin
    finalize_top_level_const_values
  rescue SemanticError => e
    errors << e
  end

  begin
    check_top_level_static_asserts
  rescue SemanticError => e
    errors << e
  end

  check_functions_collecting(errors)

  analysis = build_analysis

  { analysis: analysis, errors: errors.uniq { |e| [e.message, e.line, e.column, e.length] } }
end

#check_const_ptr_of_call(arguments, scopes:) ⇒ Object



879
880
881
882
883
884
885
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 879

def check_const_ptr_of_call(arguments, scopes:)
  raise_sema_error("const_ptr_of does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("const_ptr_of expects 1 argument, got #{arguments.length}") unless arguments.length == 1

  source_type = infer_ro_addr_source_type(arguments.first.value, scopes:)
  const_pointer_to(source_type)
end

#check_decl_attribute_applications!(applications, target_kind:, target_label:, target_node:) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/milk_tea/core/semantic_analyzer/attributes.rb', line 54

def check_decl_attribute_applications!(applications, target_kind:, target_label:, target_node:)
  seen = {}
  packed = false
  alignment = nil
  resolved_applications = []

  applications.each do |application|
    with_error_node(application) do
      binding = resolve_attribute_binding(application.name)

      if raw_module?
        raise_sema_error("only built-in struct attributes are allowed in external files") unless binding.builtin && target_kind == :struct
      end

      unless binding.targets.include?(target_kind)
        raise_sema_error("attribute #{binding.name} cannot target #{target_kind}")
      end

      binding_key = [binding.module_name, binding.name]
      raise_sema_error("duplicate attribute #{binding.name} on #{target_label}") if seen.key?(binding_key)

      seen[binding_key] = true
      argument_values = check_attribute_arguments!(binding, application)
      argument_values = argument_values.freeze
      @ctx.attribute_application_bindings[application.object_id] = binding
      @ctx.validated_attribute_arguments[application.object_id] = argument_values
      resolved_applications << ResolvedAttributeApplication.new(binding:, argument_values:)

      case binding.name
      when "packed"
        packed = true
      when "align"
        bytes = argument_values.fetch("bytes")
        raise_sema_error("align(...) requires a positive alignment") unless bytes.is_a?(Integer) && bytes.positive?
        raise_sema_error("align(...) requires a power-of-two alignment, got #{bytes}") unless power_of_two?(bytes)

        alignment = bytes
      end
    end
  end

  @ctx.resolved_attribute_applications[target_node.object_id] = resolved_applications.freeze

  [packed, alignment]
end

#check_definite_assignment(binding) ⇒ Object

Raises:



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/milk_tea/core/semantic_analyzer/nullability.rb', line 6

def check_definite_assignment(binding)
  return unless binding.ast.respond_to?(:body)

  resolution = binding_resolution_snapshot
  graph = ControlFlow::Builder.new(
    binding_resolution: ControlFlow::BindingResolution.new(
      identifier_binding_ids: resolution.identifier_binding_ids,
      declaration_binding_ids: resolution.declaration_binding_ids,
      mutating_argument_identifier_ids: resolution.mutating_argument_identifier_ids,
    ),
    strict_binding_ids: true,
    local_decl_without_initializer_writes: true,
  ).build(binding.ast.body)

  local_declared_ids = Set.new
  graph.each_node do |node|
    node.writes_info.each do |write|
      origin = write[:origin]
      next unless %i[declaration for_binding match_binding].include?(origin)

      local_declared_ids << write[:binding_key]
    end
  end

  initially_assigned = binding.body_params.each_with_object(Set.new) do |param, set|
    set << param.id if param.id
  end
  # Any binding read but never defined in this function body is treated as
  # preassigned (for example module-level const/var bindings).
  initially_assigned.merge(graph.read_bindings - local_declared_ids)

  result = ControlFlow::DefiniteAssignment.solve(graph, initially_assigned:)
  first_issue = result.read_before_assignment.min_by do |issue|
    [issue.line || Float::INFINITY, issue.column || Float::INFINITY, issue.node_id]
  end
  return unless first_issue

  name = @binding_name_by_id[first_issue.binding_key] || first_issue.binding_key
  raise SemanticError.new(
    "read of '#{name}' before definite assignment",
    line: first_issue.line,
    column: first_issue.column,
    length: first_issue.length || name.to_s.length,
    path: @path,
  )
end

#check_dyn_method_call(method_binding, _receiver, arguments, scopes:) ⇒ Object



992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 992

def check_dyn_method_call(method_binding, _receiver, arguments, scopes:)
  raise_sema_error("#{method_binding.name} expects #{method_binding.params.length} arguments, got #{arguments.length}") unless arguments.length == method_binding.params.length

  method_binding.params.each_with_index do |param, index|
    actual_type = infer_expression(arguments[index].value, scopes:, expected_type: param.type)
    ensure_assignable!(
      actual_type,
      param.type,
      "argument #{index + 1} of #{method_binding.name} expects #{param.type}, got #{actual_type}",
      expression: arguments[index].value,
    )
  end
end

#check_emit_stmt(statement) ⇒ Object



1214
1215
1216
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1214

def check_emit_stmt(statement)
  raise_sema_error("emit is only allowed inside const function or inline blocks") unless @compile_time_depth.positive?
end

#check_enum_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:) ⇒ Object



584
585
586
587
588
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 584

def check_enum_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
  each_enum_match_arm(statement, scrutinee_type, scopes:) do |arm, arm_scopes|
    check_block(arm.body, scopes: arm_scopes, return_type:, allow_return:)
  end
end

#check_equal_call(resolution, arguments, scopes:) ⇒ Object



434
435
436
437
438
439
440
441
442
443
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 434

def check_equal_call(resolution, arguments, scopes:)
  raise_sema_error("equal does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("equal expects 2 arguments, got #{arguments.length}") unless arguments.length == 2

  arguments.each do |argument|
    validate_hash_operation_argument!(argument.value, resolution.target_type, scopes:, operation: "equal")
  end

  @ctx.types.fetch("bool")
end

#check_event_method_call(kind, receiver, arguments, scopes:) ⇒ Object



517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 517

def check_event_method_call(kind, receiver, arguments, scopes:)
  method_name = event_method_name(kind)
  receiver_type = infer_expression(receiver, scopes:)
  raise_sema_error("unknown method #{receiver_type}.#{method_name}") unless event_type?(receiver_type)

  raise_sema_error("#{method_name} does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("cannot call editable method #{receiver_type}.#{method_name} on an immutable receiver") unless event_receiver_mutable?(receiver, scopes:)
  if kind == :event_emit
    raise_sema_error("#{receiver_type}.emit is only available inside module #{receiver_type.module_name}") unless receiver_type.module_name == @ctx.module_name
  end

  case kind
  when :event_subscribe, :event_subscribe_once
    if arguments.length == 2 && arguments.none?(&:name)
      state_type = infer_expression(arguments[0].value, scopes:)
      unless pointer_type?(state_type)
        suggestion = if arguments[0].value.is_a?(AST::Identifier)
                       "use ptr_of(#{arguments[0].value.name})"
                     end
        raise_sema_error("first argument to #{receiver_type}.#{method_name} stateful overload must be a non-null pointer, got #{state_type}", arguments[0].value, suggestion:)
      end

      state_pointed_type = state_type.arguments.first
      expected_listener_type = event_stateful_listener_type(receiver_type, state_pointed_type)
      actual_listener_type = infer_expression(arguments[1].value, scopes:, expected_type: expected_listener_type)
      ensure_argument_assignable!(
        actual_listener_type,
        expected_listener_type,
        external: false,
        message: "listener argument to #{receiver_type}.#{method_name} expects #{expected_listener_type}, got #{actual_listener_type}",
        expression: arguments[1].value,
      )
    elsif arguments.length == 1
      listener_type = event_listener_type(receiver_type)
      actual_type = infer_expression(arguments.first.value, scopes:, expected_type: listener_type)
      ensure_argument_assignable!(
        actual_type,
        listener_type,
        external: false,
        message: "argument listener to #{receiver_type}.#{method_name} expects #{listener_type}, got #{actual_type}",
        expression: arguments.first.value,
      )
    else
      raise_sema_error("#{method_name} expects 1 or 2 arguments, got #{arguments.length}")
    end

    event_subscription_result_type
  when :event_unsubscribe
    raise_sema_error("unsubscribe expects 1 argument, got #{arguments.length}") unless arguments.length == 1

    actual_type = infer_expression(arguments.first.value, scopes:, expected_type: @ctx.types.fetch("Subscription"))
    ensure_argument_assignable!(
      actual_type,
      @ctx.types.fetch("Subscription"),
      external: false,
      message: "argument subscription to #{receiver_type}.unsubscribe expects Subscription, got #{actual_type}",
      expression: arguments.first.value,
    )

    @ctx.types.fetch("bool")
  when :event_emit
    if receiver_type.payload_type.nil?
      raise_sema_error("emit expects 0 arguments, got #{arguments.length}") unless arguments.empty?
    else
      raise_sema_error("emit expects 1 argument, got #{arguments.length}") unless arguments.length == 1

      actual_type = infer_expression(arguments.first.value, scopes:, expected_type: receiver_type.payload_type)
      ensure_argument_assignable!(
        actual_type,
        receiver_type.payload_type,
        external: false,
        message: "argument value to #{receiver_type}.emit expects #{receiver_type.payload_type}, got #{actual_type}",
        expression: arguments.first.value,
      )
    end

    @ctx.types.fetch("void")
  when :event_wait
    raise_sema_error("wait expects 0 arguments, got #{arguments.length}") unless arguments.empty?

    Types::Registry.task(event_wait_result_type(receiver_type))
  else
    raise_sema_error("unsupported event method #{kind}")
  end
end

#check_expr_const(decl) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 41

def check_expr_const(decl)
  binding = @ctx.top_level_values.fetch(decl.name)
  validate_consuming_foreign_expression!(decl.value, scopes: [], root_allowed: false)
  validate_hoistable_foreign_expression!(decl.value, scopes: [], root_hoistable: false)

  if binding.type == builtin_type_meta_type
    evaluate_compile_time_const_value(decl.value)
    return
  end

  actual_type = infer_expression(decl.value, scopes: [], expected_type: binding.type)
  ensure_assignable!(
    actual_type,
    binding.type,
    "cannot assign #{actual_type} to constant #{decl.name}: expected #{binding.type}",
    expression: decl.value,
  )
end

#check_expr_names(expr, scopes, binding, names) ⇒ Object



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 397

def check_expr_names(expr, scopes, binding, names)
  case expr
  when AST::Identifier
    check_generic_name(expr.name, scopes, binding, expr, names)
  when AST::MemberAccess
    check_expr_names(expr.receiver, scopes, binding, names)
  when AST::Call
    check_expr_names(expr.callee, scopes, binding, names)
    expr.arguments.each { |arg| check_expr_names(arg.value, scopes, binding, names) }
  when AST::BinaryOp
    check_expr_names(expr.left, scopes, binding, names)
    check_expr_names(expr.right, scopes, binding, names)
  when AST::UnaryOp
    check_expr_names(expr.operand, scopes, binding, names)
  when AST::IfExpr
    check_expr_names(expr.condition, scopes, binding, names)
    check_expr_names(expr.then_expression, scopes, binding, names)
    check_expr_names(expr.else_expression, scopes, binding, names)
  when AST::MatchExpr
    check_expr_names(expr.expression, scopes, binding, names)
    expr.arms.each do |arm|
      arm_names = names.dup
      arm_names.add(arm.binding_name) if arm.binding_name
      check_expr_names(arm.value, scopes, binding, arm_names)
    end
  when AST::IndexAccess
    check_expr_names(expr.receiver, scopes, binding, names)
    check_expr_names(expr.index, scopes, binding, names)
  when AST::Specialization
    check_expr_names(expr.callee, scopes, binding, names)
  when AST::PrefixCast
    check_expr_names(expr.expression, scopes, binding, names)
  when AST::AwaitExpr
    check_expr_names(expr.expression, scopes, binding, names)
  when AST::UnsafeExpr
    check_expr_names(expr.expression, scopes, binding, names)
  when AST::RangeExpr
    check_expr_names(expr.start_expr, scopes, binding, names) if expr.start_expr
    check_expr_names(expr.end_expr, scopes, binding, names) if expr.end_expr
  when AST::ExpressionList
    expr.elements.each { |elem| check_expr_names(elem, scopes, binding, names) }
  when AST::ProcExpr
    body_names = names.dup
    Array(expr.params).each { |p| body_names.add(p.name) if p.respond_to?(:name) }
    expr.body&.each { |s| check_stmt_names(s, scopes, binding, body_names) }
  when AST::DetachExpr
    check_expr_names(expr.body, scopes, binding, names)
  when AST::FormatExprPart
    check_expr_names(expr.expression, scopes, binding, names)
  when AST::IntegerLiteral, AST::FloatLiteral, AST::StringLiteral,
       AST::BooleanLiteral, AST::NullLiteral, AST::CharLiteral,
       AST::FormatString, AST::FormatTextPart
    nil
  end
end

#check_fatal_call(arguments, scopes:) ⇒ Object



843
844
845
846
847
848
849
850
851
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 843

def check_fatal_call(arguments, scopes:)
  raise_sema_error("fatal does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("fatal expects 1 argument, got #{arguments.length}") unless arguments.length == 1

  message_type = infer_expression(arguments.first.value, scopes:, expected_type: @ctx.types.fetch("str"))
  return @ctx.types.fetch("void") if string_like_type?(message_type)

  raise_sema_error("fatal expects str or cstr, got #{message_type}")
end

#check_field_of_call(arguments, scopes:) ⇒ Object



901
902
903
904
905
906
907
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 901

def check_field_of_call(arguments, scopes:)
  raise_sema_error("field_of does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("field_of expects 2 arguments, got #{arguments.length}") unless arguments.length == 2

  evaluate_field_of_call(arguments, scopes:)
  builtin_field_handle_type
end

#check_for_stmt(statement, scopes:, return_type:, allow_return:) ⇒ Object



1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1048

def check_for_stmt(statement, scopes:, return_type:, allow_return:)
  if statement.threaded
    check_threaded_for_stmt(statement, scopes:, return_type:, allow_return:)
    return
  end

  statement.iterables.each do |iterable|
    validate_consuming_foreign_expression!(iterable, scopes:, root_allowed: false)
  end

  raise_sema_error("for loop binder count must match iterable count") unless statement.bindings.length == statement.iterables.length

  binding_infos = if statement.parallel?
                    check_parallel_for_bindings(statement, scopes:)
                  else
                    iterable_type = nil
                    loop_type = if range_expr?(statement.iterable)
                                  check_range_expr_loop(statement.iterable, scopes:)
                                else
                                  iterable_type = infer_expression(statement.iterable, scopes:)
                                  collection_loop_type(iterable_type) || iterator_loop_type(iterable_type)
                                end

                    raise_sema_error("for loop expects start..stop, array[T, N], span[T], or an iterable with iter()/next()") unless loop_type

                    binding_type = if iterable_type
                                     collection_loop_binding_type(iterable_type, loop_type) || loop_type
                                   else
                                     loop_type
                                   end
                    [{ binding: statement.binding, type: binding_type }]
                  end

  with_nested_scope(scopes) do |loop_scopes|
    binding_infos.each do |entry|
      binding = entry[:binding]
      ensure_non_reserved_primitive_name!(binding.name, kind_label: "for binding", line: binding.line, column: binding.column)
      current_actual_scope(loop_scopes)[binding.name] = value_binding(
        name: binding.name,
        type: entry[:type],
        mutable: false,
        kind: :let,
        id: @preassigned_local_binding_ids[binding.object_id],
      )
      record_declaration_binding(binding, current_actual_scope(loop_scopes)[binding.name])
    end
    with_loop do
      check_block(statement.body, scopes: loop_scopes, return_type:, allow_return:)
    end
  end
end

#check_format_string_literal(format_string, scopes:) ⇒ Object



1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1485

def check_format_string_literal(format_string, scopes:)
  format_string.parts.each do |part|
    next unless part.is_a?(AST::FormatExprPart)

    value_type = infer_expression(part.expression, scopes:)

    if part.format_spec
      case part.format_spec[:kind]
      when :precision
        unless value_type.is_a?(Types::Primitive) && value_type.float?
          raise_sema_error("format spec ':.N' is only valid for float and double, got #{value_type}")
        end
      when :hex
        unless format_string_integer_base_spec_supported?(value_type)
          raise_sema_error("format spec ':x' and ':X' are only valid for integer primitives and integer-backed enums/flags, got #{value_type}")
        end
      when :oct
        unless format_string_integer_base_spec_supported?(value_type)
          raise_sema_error("format spec ':o' and ':O' are only valid for integer primitives and integer-backed enums/flags, got #{value_type}")
        end
      when :bin
        unless format_string_integer_base_spec_supported?(value_type)
          raise_sema_error("format spec ':b' and ':B' are only valid for integer primitives and integer-backed enums/flags, got #{value_type}")
        end
      else
        raise_sema_error("unsupported format spec #{part.format_spec.inspect}")
      end
    else
      next if format_string_interpolation_supported?(value_type, context: "formatted string interpolation of #{value_type}")
      raise_sema_error("formatted string interpolation supports str, cstr, bool, numeric primitives, integer-backed enums/flags, and types implementing format_len()/append_format(output: ref[std.string.String]), got #{value_type}")
    end
  end
end

#check_function(binding) ⇒ Object



614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 614

def check_function(binding)
  @local_completion_frames = @local_completion_frames.dup if @local_completion_frames.frozen?

  previous_type_substitutions = @current_type_substitutions
  previous_specialization_owner = @current_specialization_owner
  started_check = false
  return if binding.external
  return if @checked_function_bindings[binding.object_id]
  return if @checking_function_bindings[binding.object_id]

  @checking_function_bindings[binding.object_id] = true
  started_check = true
  @current_type_substitutions = binding.type_substitutions
  @current_specialization_owner = binding.specialization_owner
  with_error_node(binding.ast) do
    with_scope(binding.body_params) do |scopes|
      start_local_completion_frame(binding, scopes)
      if binding.type_params.any?
        if binding.ast.respond_to?(:body)
          check_generic_method_immutable_this(binding, scopes)
          check_generic_method_names(binding, scopes)
        end
        return
      end

      if binding.ast.is_a?(AST::ForeignFunctionDecl)
        record_callable_value_expression_site(binding.ast.mapping) unless binding.ast.mapping.is_a?(AST::Call)
        expression = foreign_mapping_expression(binding.ast)
        actual_type = with_foreign_mapping_context do
          infer_expression(expression, scopes:, expected_type: binding.type.return_type)
        end
        unless types_compatible?(actual_type, binding.type.return_type, expression:) || foreign_identity_projection_compatible?(actual_type, binding.type.return_type)
          raise_sema_error("foreign mapping #{binding.name} expects #{binding.type.return_type}, got #{actual_type}")
        end
      else
        validate_async_function_body!(binding.ast.body) if binding.async
        preassign_local_binding_ids(binding.ast.body)
        run_nullability_pre_pass(binding, scopes)
        if binding.ast.respond_to?(:const) && binding.ast.const
          with_compile_time do
            if binding.async
              with_async_function do
                check_block(binding.ast.body, scopes:, return_type: binding.body_return_type)
              end
            else
              check_block(binding.ast.body, scopes:, return_type: binding.type.return_type)
            end
          end
        elsif binding.async
          with_async_function do
            check_block(binding.ast.body, scopes:, return_type: binding.body_return_type)
          end
        else
          check_block(binding.ast.body, scopes:, return_type: binding.type.return_type)
        end
        check_definite_assignment(binding)
      end
    end
  end
ensure
  return unless started_check

  @checked_function_bindings[binding.object_id] = true
  finish_local_completion_frame(binding)
  @preassigned_local_binding_ids = {}
  @nullability_flow_result = nil
  @current_type_substitutions = previous_type_substitutions
  @current_specialization_owner = previous_specialization_owner
  @checking_function_bindings.delete(binding.object_id)
end

#check_function_call(binding, arguments, scopes:) ⇒ Object



785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 785

def check_function_call(binding, arguments, scopes:)
  arguments = canonicalize_call_arguments(arguments, binding.type.params, binding.name, binding)
  fill_positional_defaults!(arguments, binding)

  expected_params = binding.type.params
  required_count = count_required_params(binding)
  variadic = binding.type.is_a?(Types::Function) && binding.type.variadic
  unless variadic ? arguments.length >= required_count : arguments.length <= expected_params.length && arguments.length >= required_count
    raise_sema_error(arity_error_message(binding.type, binding.name, arguments.length, required_count:))
  end

  expected_params.each_with_index do |parameter, index|
    argument = arguments.fetch(index)
    actual_type = foreign_argument_actual_type(parameter, argument, scopes:, function_name: binding.name, expected_type: parameter.type)
    record_mutating_argument_identifier(argument, parameter)
    if foreign_cstr_boundary_parameter?(parameter)
      unless foreign_cstr_argument_compatible?(actual_type, parameter, expression: foreign_argument_expression(argument))
        raise_sema_error("argument #{parameter.name} to #{binding.name} expects #{parameter.type}, got #{actual_type}")
      end
    else
      unless call_argument_compatible?(actual_type, parameter.type, scopes:, external: binding.external, expression: foreign_argument_expression(argument))
        suggestion = explicit_cast_suggestion(actual_type, parameter.type)
        raise_sema_error("argument #{parameter.name} to #{binding.name} expects #{parameter.type}, got #{actual_type}", suggestion:)
      end
    end
  end

  arguments.drop(expected_params.length).each do |argument|
    infer_expression(argument.value, scopes:)
  end
end

#check_functionsObject



252
253
254
255
256
257
258
259
260
261
262
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 252

def check_functions
  @ctx.top_level_functions.each_value do |binding|
    check_function(binding)
  end

  @ctx.methods.each_value do |method_map|
    method_map.each_value do |binding|
      check_function(binding)
    end
  end
end

#check_functions_collecting(errors) ⇒ Object

Per-function error collection used by check_collecting_errors. Continues past individual function failures, accumulating SemanticErrors.



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 282

def check_functions_collecting(errors)
  @ctx.top_level_functions.each_value do |binding|
    next if @checked_function_bindings[binding.object_id]

    prev_count = @structural_errors.length
    begin
      check_function(binding)
    rescue SemanticError => e
      errors << e
    end
    errors.concat(@structural_errors[prev_count..].to_a)
  end

  @ctx.methods.each_value do |method_map|
    method_map.each_value do |binding|
      next if @checked_function_bindings[binding.object_id]

      prev_count = @structural_errors.length
      begin
        check_function(binding)
      rescue SemanticError => e
        errors << e
      end
      errors.concat(@structural_errors[prev_count..].to_a)
    end
  end
end

#check_gather_stmt(statement, scopes:) ⇒ Object



1187
1188
1189
1190
1191
1192
1193
1194
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1187

def check_gather_stmt(statement, scopes:)
  raise_sema_error("gather requires at least one handle", line: statement.line, column: statement.column) if statement.handles.empty?

  statement.handles.each do |handle|
    handle_type = infer_expression(handle, scopes:)
    raise_sema_error("gather expects Handle, got #{handle_type}", line: statement.line, column: statement.column) unless handle_type.is_a?(Types::Handle)
  end
end

#check_generic_method_immutable_this(binding, scopes) ⇒ Object

Scans generic method bodies for assignments to this through a non-editable receiver. Full body checking is deferred to call-site specialization, but immutable-this violations are type-independent.



601
602
603
604
605
606
607
608
609
610
611
612
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 601

def check_generic_method_immutable_this(binding, scopes)
  this_binding = scopes.first&.values&.find { |v| v.name == "this" }
  return unless this_binding
  return if this_binding.mutable

  binding.ast.body&.each do |statement|
    next unless statement.is_a?(AST::Assignment) && statement.operator == "="
    next unless statement.target.is_a?(AST::MemberAccess) && statement.target.receiver.is_a?(AST::Identifier) && statement.target.receiver.name == "this"

    raise_sema_error("cannot assign through immutable this", statement.target)
  end
end

#check_generic_method_names(binding, scopes) ⇒ Object

Verifies that every bare identifier used as a value expression inside a generic method body resolves to a known name (parameter, local, top-level value, function, type, import, or type parameter). This catches misspelled variables and undeclared names without touching any type-dependent logic, so it is safe to run during structural analysis before concrete types are substituted.



330
331
332
333
334
335
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 330

def check_generic_method_names(binding, scopes)
  names = Set.new
  binding.ast.body&.each do |statement|
    check_stmt_names(statement, scopes, binding, names)
  end
end

#check_generic_name(name, scopes, binding, node, names) ⇒ Object



453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 453

def check_generic_name(name, scopes, binding, node, names)
  return if name == "_" || name == "this"
  return if names.include?(name)
  return if BUILTIN_CALLABLE_NAMES.include?(name)
  return if lookup_value(name, scopes)
  return if @ctx.top_level_functions.key?(name)
  return if @ctx.types.key?(name)
  return if @ctx.imports.key?(name)
  return if binding.type_params.include?(name)

  raise_sema_error("unknown name #{name}", node)
end

#check_get_call(arguments, scopes:) ⇒ Object



852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 852

def check_get_call(arguments, scopes:)
  raise_sema_error("get does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("get expects 2 arguments, got #{arguments.length}") unless arguments.length == 2

  source_expr = arguments.first.value
  index_expr = arguments[1].value
  source_type = infer_expression(source_expr, scopes:)
  index_type = infer_expression(index_expr, scopes:)
  raise_sema_error("get index must be an integer type, got #{index_type}") unless integer_type?(index_type)

  if array_type?(source_type)
    raise_sema_error("get requires an addressable array value") unless addressable_storage_expression?(source_expr, scopes:)

    Types::Registry.nullable(pointer_to(array_element_type(source_type)))
  elsif source_type.is_a?(Types::Span)
    Types::Registry.nullable(pointer_to(source_type.element_type))
  else
    raise_sema_error("get expects an array or span, got #{source_type}")
  end
end

#check_has_attribute_call(arguments, scopes:) ⇒ Object



915
916
917
918
919
920
921
922
923
924
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 915

def check_has_attribute_call(arguments, scopes:)
  raise_sema_error("has_attribute does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("has_attribute expects 2 arguments, got #{arguments.length}") unless arguments.length == 2

  target = resolve_reflection_target_argument(arguments.first.value, scopes:)
  binding = resolve_attribute_name_argument(arguments[1].value)
  validate_attribute_target_compatibility!(target, binding)

  @ctx.types.fetch("bool")
end

#check_hash_call(resolution, arguments, scopes:) ⇒ Object



426
427
428
429
430
431
432
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 426

def check_hash_call(resolution, arguments, scopes:)
  raise_sema_error("hash does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("hash expects 1 argument, got #{arguments.length}") unless arguments.length == 1

  validate_hash_operation_argument!(arguments.first.value, resolution.target_type, scopes:, operation: "hash")
  @ctx.types.fetch("uint")
end

#check_inline_for_stmt(statement, scopes:, return_type:, allow_return:) ⇒ Object



1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1275

def check_inline_for_stmt(statement, scopes:, return_type:, allow_return:)
  iterable = evaluate_compile_time_const_value(statement.iterables.first, scopes:)
  raise_sema_error("inline for iterable must be a compile-time constant") unless iterable.is_a?(Array)
  raise_sema_error("inline for iterable is empty") if iterable.empty?

  loop_var_name = statement.bindings.first.name
  ensure_non_reserved_primitive_name!(loop_var_name, kind_label: "for binding", line: statement.bindings.first.line, column: statement.bindings.first.column)

  # The loop is unrolled at compile time, so check the body once per element:
  # each iteration's element type is validated and any per-element
  # specialization (e.g. `equal[field.type]` for a struct field, or a
  # `format_value[field.type]` recursion) is discovered by sema rather than
  # first appearing during lowering.
  iterable.each do |element|
    with_nested_scope(scopes) do |loop_scopes|
      current_actual_scope(loop_scopes)[loop_var_name] = value_binding(
        name: loop_var_name,
        type: inline_for_element_type(element),
        mutable: false,
        kind: :let,
        id: @preassigned_local_binding_ids[statement.bindings.first.object_id],
        const_value: element,
      )
      with_loop do
        with_compile_time do
          check_block(statement.body, scopes: loop_scopes, return_type:, allow_return:)
        end
      end
    end
  end
end

#check_inline_if_stmt(statement, scopes:, return_type:, allow_return:) ⇒ Object



1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1334

def check_inline_if_stmt(statement, scopes:, return_type:, allow_return:)
  chosen_branch = statement.branches.find do |branch|
    condition_value = evaluate_compile_time_const_value(branch.condition, scopes:)
    raise_sema_error("inline if condition must be a compile-time constant") if condition_value.nil?
    raise_sema_error("inline if condition must be bool") unless condition_value == true || condition_value == false
    condition_value
  end

  if chosen_branch
    with_compile_time do
      check_block(chosen_branch.body, scopes:, return_type:, allow_return:)
    end
  elsif statement.else_body
    with_compile_time do
      check_block(statement.else_body, scopes:, return_type:, allow_return:)
    end
  end
end

#check_inline_match_stmt(statement, scopes:, return_type:, allow_return:) ⇒ Object



1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1353

def check_inline_match_stmt(statement, scopes:, return_type:, allow_return:)
  scrutinee = evaluate_compile_time_const_value(statement.expression, scopes:)
  raise_sema_error("inline match scrutinee must be a compile-time constant") if scrutinee.nil?

  chosen_arm = statement.arms.find do |arm|
    pattern_value = evaluate_compile_time_const_value(arm.pattern, scopes:)
    scrutinee == pattern_value
  end

  if chosen_arm
    with_compile_time do
      check_block(chosen_arm.body, scopes:, return_type:, allow_return:)
    end
  end
end

#check_inline_while_stmt(statement, scopes:, return_type:, allow_return:) ⇒ Object



1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1323

def check_inline_while_stmt(statement, scopes:, return_type:, allow_return:)
  condition = evaluate_compile_time_const_value(statement.condition, scopes:)
  raise_sema_error("inline while condition must be a compile-time constant") if condition.nil?

  with_loop do
    with_compile_time do
      check_block(statement.body, scopes:, return_type:, allow_return:)
    end
  end
end

#check_integer_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:) ⇒ Object



755
756
757
758
759
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 755

def check_integer_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
  each_integer_match_arm(statement, scrutinee_type, scopes:) do |arm|
    check_block(arm.body, scopes:, return_type:, allow_return:)
  end
end

#check_interface_conformance!(receiver_type, interface) ⇒ Object



30
31
32
33
34
35
36
37
# File 'lib/milk_tea/core/semantic_analyzer/interface_conformance.rb', line 30

def check_interface_conformance!(receiver_type, interface)
  interface.methods.each_value do |interface_method|
    method = lookup_local_method_for_interface(receiver_type, interface_method.name)
    raise_sema_error("type #{receiver_type} implements interface #{interface.name} but is missing method #{interface_method.name}") unless method

    check_interface_method_match!(receiver_type, interface, interface_method, method)
  end
end

#check_interface_conformancesObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/milk_tea/core/semantic_analyzer/interface_conformance.rb', line 6

def check_interface_conformances
  expanded_declarations.each do |decl|
    with_error_node(decl) do
      next unless decl.is_a?(AST::StructDecl) || decl.is_a?(AST::OpaqueDecl)
      next if decl.implements.empty?

      receiver_type = @ctx.types.fetch(decl.name)
      resolved_interfaces = []
      seen = {}

      decl.implements.each do |interface_ref|
        interface = resolve_interface_ref(interface_ref)
        raise_sema_error("duplicate interface #{decl.name} implements #{interface.name}") if seen.key?(interface)

        seen[interface] = true
        resolved_interfaces << interface
        check_interface_conformance!(receiver_type, interface)
      end

      @ctx.implemented_interfaces[interface_implementation_key(receiver_type)] = resolved_interfaces.freeze
    end
  end
end

#check_interface_method_match!(receiver_type, interface, interface_method, method) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/milk_tea/core/semantic_analyzer/interface_conformance.rb', line 50

def check_interface_method_match!(receiver_type, interface, interface_method, method)
  if method.ast.is_a?(AST::MethodDef) && method.ast.type_params.any?
    raise_sema_error("type #{receiver_type} method #{method.name} does not satisfy interface #{interface.name}: interface methods cannot be implemented by generic methods")
  end

  if interface_method.kind == :static
    raise_sema_error("type #{receiver_type} method #{method.name} does not satisfy interface #{interface.name}: method kind does not match") unless method.type.receiver_type.nil?
  else
    raise_sema_error("type #{receiver_type} method #{method.name} does not satisfy interface #{interface.name}: method kind does not match") if method.type.receiver_type.nil?

    expected_editable = interface_method.kind == :editable
    actual_editable = method.type.receiver_editable
    if actual_editable != expected_editable
      raise_sema_error("type #{receiver_type} method #{method.name} does not satisfy interface #{interface.name}: receiver editability does not match")
    end
  end

  method_params = method.type.params.map(&:type)
  interface_params = interface_method.params.map(&:type)
  unless method_params == interface_params && method.type.return_type == interface_method.return_type && method.async == interface_method.async
    raise_sema_error("type #{receiver_type} method #{method.name} does not satisfy interface #{interface.name}: signature does not match")
  end
end

#check_layout_type_via_ct(type_ref, context:, scopes:) ⇒ Object



591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 591

def check_layout_type_via_ct(type_ref, context:, scopes:)
  return false unless scopes && type_ref.name.parts.length >= 1

  first_part = type_ref.name.parts.first
  binding = lookup_value(first_part, scopes)
  return false unless binding && !binding.const_value.nil?

  expression = AST.build_chain_from_parts(type_ref.name.parts)
  return false unless expression

  ct_value = evaluate_compile_time_const_value(expression, scopes:)
  if ct_value.is_a?(Types::Struct) || ct_value.is_a?(Types::Primitive) ||
     ct_value.is_a?(Types::Union) || ct_value.is_a?(Types::Nullable) ||
     ct_value.is_a?(Types::StructInstance)
    if sized_layout_type?(ct_value)
      return true
    end
  end

  false
end

#check_local_decl(statement, scopes:, return_type:, allow_return:) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 223

def check_local_decl(statement, scopes:, return_type:, allow_return:)
  if statement.destructure_bindings
    check_local_decl_destructure(statement, scopes:, return_type:, allow_return:)
    return
  end

  current_scope = current_actual_scope(scopes)
  discard_binding = statement.name == "_" || let_else_discard_binding_syntax?(statement)
  raise_sema_error("duplicate local #{statement.name}") if !discard_binding && current_scope.key?(statement.name)
  ensure_non_reserved_primitive_name!(statement.name, kind_label: "local", line: statement.line, column: statement.column) unless discard_binding

  declared_type = statement.type ? resolve_type_ref(statement.type) : nil
  if statement.value
    validate_consuming_foreign_expression!(statement.value, scopes:, root_allowed: false)
    inferred_type = infer_expression(statement.value, scopes:, expected_type: declared_type)
  else
    raise_sema_error("local #{statement.name} without initializer requires an explicit type") unless declared_type

    begin
      zero_initializable_type?(declared_type)
    rescue SemanticError
      raise_sema_error("local #{statement.name} without initializer requires a zero-initializable type, got #{declared_type}")
    end

    inferred_type = declared_type
  end

  storage_type, final_type, const_value =
    if statement.else_body || statement.recovered_else
      check_local_decl_let_else(statement, scopes:, return_type:, allow_return:, discard_binding:, declared_type:, inferred_type:)
    else
      check_local_decl_plain(statement, scopes:, discard_binding:, declared_type:, inferred_type:)
    end

  if noncopyable_event_storage_type?(final_type) && !fresh_noncopyable_event_initializer?(statement.value, final_type, scopes:)
    raise_sema_error("local #{statement.name} cannot copy event storage type #{final_type}")
  end

  unless discard_binding
    current_scope[statement.name] = value_binding(
      name: statement.name,
      type: storage_type,
      mutable: statement.kind == :var,
      kind: statement.kind,
      flow_type: final_type,
      const_value:,
      id: @preassigned_local_binding_ids[statement.object_id],
    )
    record_declaration_binding(statement, current_scope[statement.name])
  end
end

#check_local_decl_destructure(statement, scopes:, return_type:, allow_return:) ⇒ Object



275
276
277
278
279
280
281
282
283
284
285
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 275

def check_local_decl_destructure(statement, scopes:, return_type:, allow_return:)
  value_type = infer_expression(statement.value, scopes:)

  if statement.destructure_type_name
    check_local_decl_struct_destructure(statement, value_type, scopes:)
  elsif value_type.is_a?(Types::Tuple)
    check_local_decl_tuple_destructure(statement, value_type, scopes:)
  else
    raise_sema_error("destructure requires a tuple or struct, got #{value_type}")
  end
end

#check_local_decl_let_else(statement, scopes:, return_type:, allow_return:, discard_binding:, declared_type:, inferred_type:) ⇒ Object



368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 368

def check_local_decl_let_else(statement, scopes:, return_type:, allow_return:,
                                discard_binding:, declared_type:, inferred_type:)
  success_type = let_else_success_type(inferred_type)
  error_type = let_else_error_type(inferred_type)

  if statement.recovered_else
    success_type ||= declared_type || @error_type
    error_type ||= @error_type if statement.else_binding
  else
    raise_sema_error("let-else initializer for #{statement.name} must be nullable, Option[T], or Result[T, E], got #{inferred_type}") unless success_type
  end

  if discard_binding && declared_type
    raise_sema_error("let-else discard binding _ cannot have a type annotation")
  end

  if discard_binding && statement.kind == :var
    raise_sema_error("var-else discard binding _ is not allowed")
  end

  if statement.else_binding && !error_type
    raise_sema_error("let-else error binding for #{statement.name} requires Result[T, E], got #{inferred_type}")
  end

  if declared_type && let_else_source_type?(declared_type)
    raise_sema_error("let-else type annotation for #{statement.name} must be the success type, got #{declared_type}")
  end

  if declared_type
    validate_local_ref_type!(declared_type, statement.name)
    validate_local_proc_type!(declared_type, statement.name, initializer: statement.value)
    ensure_assignable!(
      success_type, declared_type,
      "cannot assign #{success_type} to #{statement.name}: expected #{declared_type}",
      expression: statement.value, scopes:,
      contextual_int_to_float: contextual_int_to_float_target?(declared_type),
      line: statement.line, column: statement.column,
    )
    final_type = declared_type
  else
    raise_sema_error("cannot bind void result to #{statement.name}") if success_type.void? && !discard_binding
    final_type = success_type
  end

  unless discard_binding
    validate_local_ref_type!(final_type, statement.name)
    validate_local_proc_type!(final_type, statement.name, initializer: statement.value)
  end

  else_scopes = scopes
  if statement.else_binding
    ensure_non_reserved_primitive_name!(statement.else_binding.name, kind_label: "let-else error binding", line: statement.else_binding.line, column: statement.else_binding.column)
    else_binding = value_binding(
      name: statement.else_binding.name, type: error_type, mutable: false, kind: :let,
      id: preassigned_local_binding_id_for(statement.else_binding),
    )
    record_declaration_binding(statement.else_binding, else_binding)
    else_scopes = scopes + [{ statement.else_binding.name => else_binding }]
  end

  check_block(statement.else_body, scopes: else_scopes, return_type:, allow_return:) if statement.else_body
  if statement.else_body && !statement.recovered_else
    terminator = if inside_loop?
                   ControlFlow::Termination.block_always_terminates_in_loop?(statement.else_body)
                 else
                   cfg_block_always_terminates?(statement.else_body)
                 end
    unless terminator
      raise_sema_error("else block for #{statement.name} must exit control flow at line #{statement.line}")
    end
  end

  storage_type = statement.kind == :var ? final_type : inferred_type
  [storage_type, final_type, nil]
end

#check_local_decl_plain(statement, scopes:, discard_binding:, declared_type:, inferred_type:) ⇒ Object



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 444

def check_local_decl_plain(statement, scopes:, discard_binding:, declared_type:, inferred_type:)
  if declared_type
    validate_local_ref_type!(declared_type, statement.name)
    validate_local_proc_type!(declared_type, statement.name, initializer: statement.value)
    ensure_assignable!(
      inferred_type, declared_type,
      "cannot assign #{inferred_type} to #{statement.name}: expected #{declared_type}",
      expression: statement.value, scopes:,
      contextual_int_to_float: contextual_int_to_float_target?(declared_type),
      line: statement.line, column: statement.column,
    )
    final_type = declared_type
  else
    raise_sema_error("cannot infer type for #{statement.name} from null") if inferred_type.is_a?(Types::Null)
    raise_sema_error("cannot bind void result to #{statement.name}") if inferred_type.void?
    final_type = inferred_type
  end

  validate_local_ref_type!(final_type, statement.name)
  validate_local_proc_type!(final_type, statement.name, initializer: statement.value)

  const_value = statement.kind == :let && statement.value ? evaluate_compile_time_const_value(statement.value, scopes:) : nil
  [final_type, final_type, const_value]
end

#check_local_decl_struct_destructure(statement, value_type, scopes:) ⇒ Object



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 307

def check_local_decl_struct_destructure(statement, value_type, scopes:)
  type_name = statement.destructure_type_name
  struct_type = if type_name.is_a?(Array)
                  resolve_qualified_type_name(type_name)
                else
                  @ctx.types[type_name]
                end
  display_name = type_name.is_a?(Array) ? type_name.join(".") : type_name
  raise_sema_error("unknown type #{display_name} for struct destructure") unless struct_type
  if struct_type.is_a?(Types::GenericStructDefinition)
    if value_type.is_a?(Types::StructInstance) && value_type.definition == struct_type
      fields = value_type.fields
    else
      raise_sema_error("generic type #{display_name} requires type arguments")
    end
  else
    raise_sema_error("#{display_name} is not a struct") unless struct_type.is_a?(Types::Struct) || struct_type.is_a?(Types::Tuple)
    ensure_assignable!(value_type, struct_type, "cannot destructure #{value_type} as #{display_name}")
    fields = struct_type.fields
  end
  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

  current_scope = current_actual_scope(scopes)
  statement.destructure_bindings.each do |name|
    next if name == "_"
    raise_sema_error("duplicate local #{name} in destructure") if current_scope.key?(name)
    raise_sema_error("unknown field #{display_name}.#{name}") unless fields.key?(name)
    ensure_non_reserved_primitive_name!(name, kind_label: "local", line: statement.line, column: statement.column)
    field_type = fields[name]
    current_scope[name] = value_binding(
      name:,
      type: field_type,
      mutable: false,
      kind: :let,
      id: @preassigned_local_binding_ids[statement.object_id],
    )
    record_declaration_binding(statement, current_scope[name])
  end
end

#check_local_decl_tuple_destructure(statement, value_type, scopes:) ⇒ Object



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 287

def check_local_decl_tuple_destructure(statement, value_type, scopes:)
  raise_sema_error("destructure pattern has #{statement.destructure_bindings.length} bindings but tuple has #{value_type.element_types.length} elements") unless statement.destructure_bindings.length == value_type.element_types.length

  current_scope = current_actual_scope(scopes)
  statement.destructure_bindings.each_with_index do |name, index|
    next if name == "_"
    raise_sema_error("duplicate local #{name} in destructure") if current_scope.key?(name)
    ensure_non_reserved_primitive_name!(name, kind_label: "local", line: statement.line, column: statement.column)
    field_type = value_type.element_types[index]
    current_scope[name] = value_binding(
      name:,
      type: field_type,
      mutable: false,
      kind: :let,
      id: @preassigned_local_binding_ids[statement.object_id],
    )
    record_declaration_binding(statement, current_scope[name])
  end
end

#check_match_stmt(statement, scopes:, return_type:, allow_return:) ⇒ Object



564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 564

def check_match_stmt(statement, scopes:, return_type:, allow_return:)
  validate_consuming_foreign_expression!(statement.expression, scopes:, root_allowed: false)
  scrutinee_type = infer_expression(statement.expression, scopes:)
  if error_type?(scrutinee_type)
    check_recovered_match_stmt(statement, scopes:, return_type:, allow_return:)
  elsif scrutinee_type.is_a?(Types::Enum)
    check_enum_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
  elsif scrutinee_type.is_a?(Types::Variant)
    check_variant_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
  elsif integer_type?(scrutinee_type)
    check_integer_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
  elsif scrutinee_type.is_a?(Types::StringView)
    check_string_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
  elsif scrutinee_type.is_a?(Types::Tuple)
    check_tuple_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
  else
    raise_sema_error("match requires an enum, variant, or integer scrutinee, got #{scrutinee_type}")
  end
end

#check_order_call(resolution, arguments, scopes:) ⇒ Object



445
446
447
448
449
450
451
452
453
454
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 445

def check_order_call(resolution, arguments, scopes:)
  raise_sema_error("order does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("order expects 2 arguments, got #{arguments.length}") unless arguments.length == 2

  arguments.each do |argument|
    validate_hash_operation_argument!(argument.value, resolution.target_type, scopes:, operation: "order")
  end

  @ctx.types.fetch("int")
end

#check_parallel_block_stmt(statement, scopes:, return_type:) ⇒ Object



1177
1178
1179
1180
1181
1182
1183
1184
1185
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1177

def check_parallel_block_stmt(statement, scopes:, return_type:)
  @uses_parallel_for = true
  raise_sema_error("parallel block must contain at least two statements", line: statement.line, column: statement.column) if statement.bodies.length < 2

  statement.bodies.each do |body|
    validate_threaded_for_body!(body)
    check_block(body, scopes:, return_type:, allow_return: false)
  end
end

#check_parallel_for_bindings(statement, scopes:) ⇒ Object



1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1100

def check_parallel_for_bindings(statement, scopes:)
  raise_sema_error("parallel for loops currently support arrays and spans only") if statement.iterables.any? { |iterable| range_expr?(iterable) }

  iterable_types = statement.iterables.map { |iterable| infer_expression(iterable, scopes:) }
  binding_infos = iterable_types.each_with_index.map do |iterable_type, index|
    loop_type = collection_loop_type(iterable_type)
    raise_sema_error("parallel for loops expect arrays or spans for each iterable") unless loop_type

    binding_type = collection_loop_binding_type(iterable_type, loop_type) || loop_type
    { binding: statement.bindings[index], iterable_type:, type: binding_type }
  end

  ensure_parallel_for_static_lengths_match!(binding_infos.map { |entry| entry[:iterable_type] })
  binding_infos
end

#check_ptr_of_call(arguments, scopes:) ⇒ Object



891
892
893
894
895
896
897
898
899
900
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 891

def check_ptr_of_call(arguments, scopes:)
  raise_sema_error("ptr_of does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("ptr_of expects 1 argument, got #{arguments.length}") unless arguments.length == 1

  source_expression = arguments.first.value
  source_type = infer_expression(source_expression, scopes:)
  return pointer_to(referenced_type(source_type)) if ref_type?(source_type)

  pointer_to(infer_addr_source_type(source_expression, scopes:))
end

#check_range_expr_loop(expression, scopes:) ⇒ Object



1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1218

def check_range_expr_loop(expression, scopes:)
  start_type = infer_expression(expression.start_expr, scopes:)
  stop_type = infer_expression(expression.end_expr, scopes:)

  unless integer_type?(start_type) && integer_type?(stop_type)
    raise_sema_error("range bounds must be integer types, got #{start_type} and #{stop_type}")
  end

  if start_type != stop_type
    if expression.start_expr.is_a?(AST::IntegerLiteral)
      start_type = infer_expression(expression.start_expr, scopes:, expected_type: stop_type)
    elsif expression.end_expr.is_a?(AST::IntegerLiteral)
      stop_type = infer_expression(expression.end_expr, scopes:, expected_type: start_type)
    end
  end

  raise_sema_error("range bounds must use matching integer types, got #{start_type} and #{stop_type}") unless start_type == stop_type

  start_type
end

#check_range_index_assignment(statement, scopes:) ⇒ Object



526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 526

def check_range_index_assignment(statement, scopes:)
  target = statement.target
  range = target.index

  raise_sema_error("range index assignment requires an expression list on the right-hand side") unless statement.value.is_a?(AST::ExpressionList)
  raise_sema_error("range index assignment requires integer literal bounds") unless range.start_expr.is_a?(AST::IntegerLiteral) && range.end_expr.is_a?(AST::IntegerLiteral)

  start_val = range.start_expr.value
  end_val = range.end_expr.value
  raise_sema_error("range start must be less than end in range index assignment") unless start_val < end_val

  count = end_val - start_val
  raise_sema_error("range index assignment: range [#{start_val}..#{end_val}) spans #{count} elements but tuple has #{statement.value.elements.length}") unless statement.value.elements.length == count

  receiver_type = infer_lvalue_receiver(
    target.receiver,
    scopes:,
    allow_pointer_identifier: true,
    require_mutable_pointer: true,
    allow_span_param_identifier: true,
  )
  element_type = infer_index_result_type(receiver_type, @ctx.types.fetch("ptr_uint"))

  statement.value.elements.each_with_index do |elem, i|
    elem = elem.is_a?(AST::Argument) ? elem.value : elem
    validate_consuming_foreign_expression!(elem, scopes:, root_allowed: false)
    elem_type = infer_expression(elem, scopes:, expected_type: element_type)
    ensure_assignable!(
      elem_type,
      element_type,
      "range index assignment element #{i}: cannot assign #{elem_type} to #{element_type}",
      expression: elem,
      contextual_int_to_float: contextual_int_to_float_target?(element_type),
      line: statement.line,
    )
  end
end

#check_read_call(arguments, scopes:) ⇒ Object



886
887
888
889
890
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 886

def check_read_call(arguments, scopes:)
  validate_read_call_arguments!(arguments)

  infer_reference_value_type(arguments.first.value, scopes:)
end

#check_recovered_match_arm_body(arm, scopes:, return_type:, allow_return:) ⇒ Object



919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 919

def check_recovered_match_arm_body(arm, scopes:, return_type:, allow_return:)
  arm_scopes = scopes.dup
  if arm.binding_name
    ensure_non_reserved_primitive_name!(arm.binding_name, kind_label: "match binding", line: arm.binding_line, column: arm.binding_column)
    binding = value_binding(
      name: arm.binding_name,
      type: @error_type,
      mutable: true,
      kind: :local,
      id: @preassigned_local_binding_ids.fetch(arm.object_id),
    )
    arm_scopes = arm_scopes + [{ arm.binding_name => binding }]
    record_declaration_binding(arm, binding)
  end
  check_block(arm.body, scopes: arm_scopes, return_type:, allow_return:)
end

#check_recovered_match_stmt(statement, scopes:, return_type:, allow_return:) ⇒ Object



913
914
915
916
917
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 913

def check_recovered_match_stmt(statement, scopes:, return_type:, allow_return:)
  statement.arms.each do |arm|
    check_recovered_match_arm_body(arm, scopes:, return_type:, allow_return:)
  end
end

#check_ref_of_call(arguments, scopes:) ⇒ Object



872
873
874
875
876
877
878
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 872

def check_ref_of_call(arguments, scopes:)
  raise_sema_error("ref_of does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("ref_of expects 1 argument, got #{arguments.length}") unless arguments.length == 1

  source_type = infer_addr_source_type(arguments.first.value, scopes:)
  Types::Registry.generic_instance("ref", [source_type])
end

#check_reinterpret_call(target_type, arguments, scopes:) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 211

def check_reinterpret_call(target_type, arguments, scopes:)
  raise_sema_error("reinterpret requires exactly one argument") unless arguments.length == 1
  raise_sema_error("reinterpret does not support named arguments") if arguments.first.name
  require_unsafe!("reinterpret requires unsafe")

  source_type = infer_expression(arguments.first.value, scopes:)
  unless reinterpretable_type?(source_type) && reinterpretable_type?(target_type)
    raise_sema_error("reinterpret requires non-array concrete sized types, got #{source_type} -> #{target_type}")
  end

  source_size = CompileTime::Layout.size_of(source_type)
  target_size = CompileTime::Layout.size_of(target_type)
  if source_size && target_size && source_size != target_size
    raise_sema_error("reinterpret requires equal-size types, got #{source_type} (#{source_size} byte#{source_size == 1 ? '' : 's'}) -> #{target_type} (#{target_size} byte#{target_size == 1 ? '' : 's'})")
  end

  target_type
end

#check_simd_construction(simd_type, arguments, scopes:) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 54

def check_simd_construction(simd_type, arguments, scopes:)
  raise_sema_error("simd construction requires exactly #{simd_type.lane_count} positional arguments, got #{arguments.length}") unless arguments.length == simd_type.lane_count
  raise_sema_error("simd construction does not accept named arguments") if arguments.any?(&:name)

  element_type = simd_type.element_type
  arguments.each do |argument|
    actual_type = infer_expression(argument.value, scopes:, expected_type: element_type)
    ensure_assignable!(
      actual_type,
      element_type,
      "simd lane expects #{element_type}, got #{actual_type}",
      expression: argument.value,
      contextual_int_to_float: contextual_int_to_float_target?(element_type),
    )
  end
  simd_type
end

#check_simd_method_call(kind, receiver_type, _receiver, arguments, scopes:) ⇒ Object



603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 603

def check_simd_method_call(kind, receiver_type, _receiver, arguments, scopes:)
  raise_sema_error("simd methods do not support named arguments") if arguments.any?(&:name)

  case kind
  when :simd_lane_with
    raise_sema_error("with expects exactly 2 arguments, got #{arguments.length}") unless arguments.length == 2

    index_arg = arguments[0].value
    index_type = infer_expression(index_arg, scopes:)
    raise_sema_error("with index must be an integer, got #{index_type}") unless index_type.integer?

    value_type = infer_expression(arguments[1].value, scopes:, expected_type: receiver_type.element_type)
    ensure_assignable!(
      value_type,
      receiver_type.element_type,
      "with expects lane value of type #{receiver_type.element_type}, got #{value_type}",
    )
    receiver_type
  end
end

#check_stack_escape_call(expression, scopes:) ⇒ Object



1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1386

def check_stack_escape_call(expression, scopes:)
  callee = expression.callee
  return unless callee.is_a?(AST::Identifier)
  return unless %w[ptr_of ref_of const_ptr_of].include?(callee.name)
  return if expression.arguments.empty?

  arg = expression.arguments.first
  source = arg.respond_to?(:value) ? arg.value : arg
  check_stack_local_source(source, scopes:, builtin: callee.name)
end

#check_stack_local_source(expression, scopes:, builtin:) ⇒ Object



1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1397

def check_stack_local_source(expression, scopes:, builtin:)
  case expression
  when AST::Identifier
    binding = lookup_local_binding(expression.name, scopes)
    return unless binding
    return unless %i[let var param].include?(binding.kind)
    return if stack_safe_pointer_source?(binding)
    raise_sema_error(
      "#{builtin}(#{expression.name}) cannot be returned: pointer to function-local storage",
      expression,
    )
  when AST::MemberAccess
    check_stack_local_source(expression.receiver, scopes:, builtin:)
  when AST::IndexAccess
    check_stack_local_source(expression.receiver, scopes:, builtin:)
  end
end

#check_stack_pointer_escape_in_return(expression, scopes:) ⇒ Object

Walks the returned expression tree to detect ptr_of/ref_of/const_ptr_of applied to a stack-local variable whose address must not escape the function. Only flags when the pointer value itself (not a computed result through a function call) reaches the return.



1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1373

def check_stack_pointer_escape_in_return(expression, scopes:)
  return unless expression

  if expression.is_a?(AST::Call)
    check_stack_escape_call(expression, scopes:)
  elsif expression.is_a?(AST::BinaryOp)
    check_stack_pointer_escape_in_return(expression.left, scopes:)
    check_stack_pointer_escape_in_return(expression.right, scopes:)
  elsif expression.is_a?(AST::UnaryOp)
    check_stack_pointer_escape_in_return(expression.operand, scopes:)
  end
end

#check_statement(statement, scopes:, return_type:, allow_return: true) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 59

def check_statement(statement, scopes:, return_type:, allow_return: true)
  with_error_node(statement) do
    case statement
    when AST::ErrorBlockStmt
      if statement.header_type == :unsafe
        @unsafe_statement_lines << statement.line
        begin
          with_unsafe do
            check_block(statement.body, scopes:, return_type:, allow_return:)
          end
        ensure
          @unsafe_statement_lines.pop
        end
      elsif statement.header_type == :if && statement.header_expression
        validate_consuming_foreign_expression!(statement.header_expression, scopes:, root_allowed: false)
        condition_type = infer_expression(statement.header_expression, scopes:, expected_type: @ctx.types.fetch("bool"))
        ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "if condition must be bool, got #{condition_type}", expression: statement.header_expression, line: statement.line, column: statement.column)
        true_refinements = flow_refinements(statement.header_expression, truthy: true, scopes:)
        check_block(statement.body, scopes: scopes_with_refinements(scopes, true_refinements), return_type:, allow_return:)
        return flow_refinements(statement.header_expression, truthy: false, scopes:) if cfg_block_always_terminates?(statement.body)
      elsif statement.header_type == :while && statement.header_expression
        validate_consuming_foreign_expression!(statement.header_expression, scopes:, root_allowed: false)
        condition_type = infer_expression(statement.header_expression, scopes:, expected_type: @ctx.types.fetch("bool"))
        ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "while condition must be bool, got #{condition_type}", expression: statement.header_expression, line: statement.line, column: statement.column)
        with_loop do
          body_scopes = scopes_with_refinements(scopes, flow_refinements(statement.header_expression, truthy: true, scopes:))
          check_block(statement.body, scopes: body_scopes, return_type:, allow_return:)
        end
      elsif statement.header_type == :for
        if statement.header_bindings && statement.header_iterables
          check_for_stmt(recovered_for_statement(statement), scopes:, return_type:, allow_return:)
        else
          with_loop do
            check_block(statement.body, scopes:, return_type:, allow_return:)
          end
        end
      else
        check_block(statement.body, scopes:, return_type:, allow_return:)
      end
    when AST::LocalDecl
      check_local_decl(statement, scopes:, return_type:, allow_return:)
    when AST::ErrorStmt
      nil
    when AST::Assignment
      check_assignment(statement, scopes:)
    when AST::IfStmt
      if statement.inline
        check_inline_if_stmt(statement, scopes:, return_type:, allow_return:)
        next
      end

      false_refinements = {}
      branch_bodies_terminate = []
      statement.branches.each do |branch|
        branch_scopes = scopes_with_refinements(scopes, false_refinements)
        if branch.condition.is_a?(AST::ErrorExpr)
          check_block(branch.body, scopes: branch_scopes, return_type:, allow_return:)
          branch_bodies_terminate << cfg_block_always_terminates?(branch.body)
          next
        end

        validate_consuming_foreign_expression!(branch.condition, scopes: branch_scopes, root_allowed: false)
        condition_type = infer_expression(branch.condition, scopes: branch_scopes, expected_type: @ctx.types.fetch("bool"))
        ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "if condition must be bool, got #{condition_type}", expression: branch.condition, line: branch.line, column: branch.column)
        true_refinements = merge_refinements(false_refinements, flow_refinements(branch.condition, truthy: true, scopes: branch_scopes))
        check_block(branch.body, scopes: scopes_with_refinements(scopes, true_refinements), return_type:, allow_return:)
        branch_bodies_terminate << cfg_block_always_terminates?(branch.body)
        false_refinements = merge_refinements(false_refinements, flow_refinements(branch.condition, truthy: false, scopes: branch_scopes))
      end
      check_block(statement.else_body, scopes: scopes_with_refinements(scopes, false_refinements), return_type:, allow_return:) if statement.else_body
      return false_refinements if statement.else_body.nil? && branch_bodies_terminate.all?
    when AST::MatchStmt
      if statement.inline
        check_inline_match_stmt(statement, scopes:, return_type:, allow_return:)
      else
        check_match_stmt(statement, scopes:, return_type:, allow_return:)
      end
    when AST::UnsafeStmt
      @unsafe_statement_lines << statement.line
      begin
        with_unsafe do
          check_block(statement.body, scopes:, return_type:, allow_return:)
        end
      ensure
        @unsafe_statement_lines.pop
      end
    when AST::StaticAssert
      check_static_assert(statement, scopes:)
    when AST::EmitStmt
      check_emit_stmt(statement)
    when AST::ForStmt
      if statement.inline
        check_inline_for_stmt(statement, scopes:, return_type:, allow_return:)
      else
        check_for_stmt(statement, scopes:, return_type:, allow_return:)
      end
    when AST::ParallelBlockStmt
      check_parallel_block_stmt(statement, scopes:, return_type:)
    when AST::GatherStmt
      check_gather_stmt(statement, scopes:)
    when AST::WhileStmt
      if statement.inline
        check_inline_while_stmt(statement, scopes:, return_type:, allow_return:)
      elsif statement.condition.is_a?(AST::ErrorExpr)
        with_loop do
          check_block(statement.body, scopes:, return_type:, allow_return:)
        end
      else
        validate_consuming_foreign_expression!(statement.condition, scopes:, root_allowed: false)
        condition_type = infer_expression(statement.condition, scopes:, expected_type: @ctx.types.fetch("bool"))
        ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "while condition must be bool, got #{condition_type}", expression: statement.condition, line: statement.line, column: statement.column)
        with_loop do
          body_scopes = scopes_with_refinements(scopes, flow_refinements(statement.condition, truthy: true, scopes:))
          check_block(statement.body, scopes: body_scopes, return_type:, allow_return:)
        end
      end
    when AST::PassStmt
      nil
    when AST::BreakStmt
      raise_sema_error("break must be inside a loop") unless inside_loop?
    when AST::ContinueStmt
      raise_sema_error("continue must be inside a loop") unless inside_loop?
    when AST::ReturnStmt
      raise_sema_error("return is not allowed inside defer blocks") unless allow_return

      validate_consuming_foreign_expression!(statement.value, scopes:, root_allowed: false) if statement.value
      value_type = statement.value ? infer_expression(statement.value, scopes:, expected_type: return_type) : @ctx.types.fetch("void")
      check_stack_pointer_escape_in_return(statement.value, scopes:) if statement.value
      ensure_assignable!(
        value_type,
        return_type,
        "return type mismatch: expected #{return_type}, got #{value_type}",
        expression: statement.value,
        contextual_int_to_float: contextual_int_to_float_target?(return_type),
        line: statement.line,
      )
    when AST::DeferStmt
      if statement.body
        with_loop_barrier do
          check_block(statement.body, scopes:, return_type:, allow_return: false)
        end
      else
        validate_consuming_foreign_expression!(statement.expression, scopes:, root_allowed: true)
        validate_hoistable_foreign_expression!(statement.expression, scopes:, root_hoistable: false)
        infer_expression(statement.expression, scopes:)
      end
    when AST::ExpressionStmt
      validate_consuming_foreign_expression!(statement.expression, scopes:, root_allowed: true)
      if statement.expression.is_a?(AST::UnaryOp) && statement.expression.operator == "?"
        infer_propagate_expression(statement.expression.operand, scopes:, allow_void_success: true)
      else
        infer_expression(statement.expression, scopes:)
      end
      return consuming_foreign_call_refinements(statement.expression, scopes:)
    when AST::WhenStmt
      check_when_stmt(statement, scopes:, return_type:, allow_return:)
    else
      raise_sema_error("unsupported statement #{statement.class.name}")
    end

    nil
  end
end

#check_static_assert(statement, scopes:) ⇒ Object



1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1197

def check_static_assert(statement, scopes:)
  validate_consuming_foreign_expression!(statement.condition, scopes:, root_allowed: false)
  validate_consuming_foreign_expression!(statement.message, scopes:, root_allowed: false)
  validate_hoistable_foreign_expression!(statement.condition, scopes:, root_hoistable: false)
  validate_hoistable_foreign_expression!(statement.message, scopes:, root_hoistable: false)
  condition_type = infer_expression(statement.condition, scopes:, expected_type: @ctx.types.fetch("bool"))
  ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "static_assert condition must be bool, got #{condition_type}")
  condition_value = evaluate_compile_time_const_value(statement.condition, scopes:)
  raise_sema_error("static_assert condition must be a compile-time bool constant") unless condition_value == true || condition_value == false
  raise_sema_error("static_assert message must be a string literal") unless statement.message.is_a?(AST::StringLiteral)

  message_type = infer_expression(statement.message, scopes:, expected_type: @ctx.types.fetch("str"))
  return if string_like_type?(message_type)

  raise_sema_error("static_assert message must be str or cstr, got #{message_type}")
end

#check_stmt_names(stmt, scopes, binding, names) ⇒ Object



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 337

def check_stmt_names(stmt, scopes, binding, names)
  case stmt
  when AST::LocalDecl
    check_expr_names(stmt.value, scopes, binding, names) if stmt.value
    names.add(stmt.name) if stmt.name
    try_record_generic_local_snapshot(stmt, scopes, binding) if stmt.name && stmt.value
  when AST::Assignment
    check_expr_names(stmt.target, scopes, binding, names)
    check_expr_names(stmt.value, scopes, binding, names)
  when AST::ExpressionStmt
    check_expr_names(stmt.expression, scopes, binding, names)
  when AST::ReturnStmt
    check_expr_names(stmt.value, scopes, binding, names) if stmt.value
  when AST::IfStmt
    stmt.branches.each do |b|
      check_expr_names(b.condition, scopes, binding, names)
      branch_names = names.dup
      b.body&.each { |s| check_stmt_names(s, scopes, binding, branch_names) }
    end
    else_names = names.dup
    stmt.else_body&.each { |s| check_stmt_names(s, scopes, binding, else_names) }
  when AST::WhileStmt
    check_expr_names(stmt.condition, scopes, binding, names)
    body_names = names.dup
    stmt.body&.each { |s| check_stmt_names(s, scopes, binding, body_names) }
  when AST::ForStmt
    Array(stmt.iterables).each { |i| check_expr_names(i, scopes, binding, names) }
    body_names = names.dup
    Array(stmt.bindings).each { |b| body_names.add(b.respond_to?(:name) ? b.name : b) }
    stmt.body&.each { |s| check_stmt_names(s, scopes, binding, body_names) }
  when AST::MatchStmt
    check_expr_names(stmt.expression, scopes, binding, names)
    stmt.arms.each do |arm|
      arm_names = names.dup
      arm_names.add(arm.binding_name) if arm.binding_name
      arm.body&.each { |s| check_stmt_names(s, scopes, binding, arm_names) }
    end
  when AST::UnsafeStmt, AST::ParallelBlockStmt
    body_names = names.dup
    stmt.body&.each { |s| check_stmt_names(s, scopes, binding, body_names) }
  when AST::DeferStmt
    check_expr_names(stmt.expression, scopes, binding, names) if stmt.expression
    body_names = names.dup
    stmt.body&.each { |s| check_stmt_names(s, scopes, binding, body_names) }
  when AST::WhenStmt
    check_expr_names(stmt.discriminant, scopes, binding, names)
    stmt.branches.each do |b|
      branch_names = names.dup
      b.body&.each { |s| check_stmt_names(s, scopes, binding, branch_names) }
    end
    else_names = names.dup
    stmt.else_body&.each { |s| check_stmt_names(s, scopes, binding, else_names) }
  when AST::ErrorBlockStmt
    body_names = names.dup
    stmt.body&.each { |s| check_stmt_names(s, scopes, binding, body_names) }
  when AST::BreakStmt, AST::ContinueStmt, AST::PassStmt, AST::StaticAssert, AST::EmitStmt
    nil
  end
end

#check_str_buffer_method_call(kind, receiver, arguments, scopes:) ⇒ Object



466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 466

def check_str_buffer_method_call(kind, receiver, arguments, scopes:)
  method_name = str_buffer_method_name(kind)
  receiver_type = infer_expression(receiver, scopes:)
  raise_sema_error("unknown method #{receiver_type}.#{method_name}") unless str_buffer_type?(receiver_type)

  case kind
  when :str_buffer_clear, :str_buffer_len, :str_buffer_capacity, :str_buffer_as_str, :str_buffer_as_cstr
    raise_sema_error("#{method_name} does not support named arguments") if arguments.any?(&:name)
    raise_sema_error("#{method_name} expects 0 arguments, got #{arguments.length}") unless arguments.empty?
  when :str_buffer_assign, :str_buffer_append, :str_buffer_assign_format, :str_buffer_append_format
    raise_sema_error("#{method_name} does not support named arguments") if arguments.any?(&:name)
    raise_sema_error("#{method_name} expects 1 argument, got #{arguments.length}") unless arguments.length == 1
  else
    raise_sema_error("unsupported str_buffer method #{kind}")
  end

  case kind
  when :str_buffer_clear
    record_editable_receiver_expression(receiver)
    raise_sema_error("cannot call editable method #{receiver_type}.clear on an immutable receiver") unless assignable_receiver?(receiver, scopes)

    @ctx.types.fetch("void")
  when :str_buffer_assign, :str_buffer_append, :str_buffer_assign_format, :str_buffer_append_format
    record_editable_receiver_expression(receiver)
    raise_sema_error("cannot call editable method #{receiver_type}.#{method_name} on an immutable receiver") unless assignable_receiver?(receiver, scopes)

    actual_type = infer_expression(arguments.first.value, scopes:, expected_type: @ctx.types.fetch("str"))
    ensure_argument_assignable!(
      actual_type,
      @ctx.types.fetch("str"),
      external: false,
      message: "argument value to #{receiver_type}.#{method_name} expects str, got #{actual_type}",
      expression: arguments.first.value,
    )

    @ctx.types.fetch("void")
  when :str_buffer_len, :str_buffer_capacity
    @ctx.types.fetch("ptr_uint")
  when :str_buffer_as_str
    raise_sema_error("#{receiver_type}.as_str requires a safe stored receiver") unless safe_reference_source_expression?(receiver, scopes:)

    @ctx.types.fetch("str")
  when :str_buffer_as_cstr
    raise_sema_error("#{receiver_type}.as_cstr requires a safe stored receiver") unless safe_reference_source_expression?(receiver, scopes:)

    @ctx.types.fetch("cstr")
  else
    raise_sema_error("unsupported str_buffer method #{kind}")
  end
end

#check_string_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:) ⇒ Object



761
762
763
764
765
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 761

def check_string_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
  each_string_match_arm(statement, scrutinee_type, scopes:) do |arm|
    check_block(arm.body, scopes:, return_type:, allow_return:)
  end
end

#check_struct_match_pattern(arguments, arm_name, scrutinee_type, arm_scopes, scopes:, arm:) ⇒ Object



964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 964

def check_struct_match_pattern(arguments, arm_name, scrutinee_type, arm_scopes, scopes:, arm:)
  payload_fields = scrutinee_type.arm(arm_name)
  raise_sema_error("variant arm #{scrutinee_type}.#{arm_name} has no payload fields for struct pattern") if payload_fields.nil? || payload_fields.empty?

  # Nested struct detection: if arm has exactly one field whose type is a struct,
  # auto-destructure through to the struct's own fields, but only when none of
  # the arguments reference the arm's own field name directly.
  if payload_fields.size == 1
    native_field = payload_fields.keys.first
    has_native_reference = arguments.any? do |arg|
      next false if !arg.name && arg.value.is_a?(AST::Identifier) && arg.value.name == "_"
      name = arg.name || (arg.value.is_a?(AST::Identifier) ? arg.value.name : nil)
      name == native_field
    end
    unless has_native_reference
      single_field_type = payload_fields.values.first
      payload_fields = single_field_type.fields if single_field_type.is_a?(Types::Struct)
    end
  end

  has_guards = false
  seen_fields = {}
  equality_cover = {}

  arguments.each do |arg|
    if arg.value.is_a?(AST::Identifier) && arg.value.name == "_"
      # _ discard: skip this field, no binding, no guard
      next
    elsif arg.name
      # Equality pattern: kind = Kind.boss
      field_name = arg.name
      raise_sema_error("unknown field #{scrutinee_type}.#{arm_name}.#{field_name}") unless payload_fields.key?(field_name)
      raise_sema_error("duplicate field #{field_name} in struct pattern") if seen_fields.key?(field_name)
      seen_fields[field_name] = true
      has_guards = true

      field_type = payload_fields[field_name]
      actual_type = infer_expression(arg.value, scopes:, expected_type: field_type)
      ensure_assignable!(actual_type, field_type, "field #{field_name} expects #{field_type}, got #{actual_type}", expression: arg.value)

      if field_type.is_a?(Types::Enum) && arg.value.is_a?(AST::MemberAccess)
        equality_cover[field_name] = [] unless equality_cover.key?(field_name)
        equality_cover[field_name] << arg.value.member
      end
    elsif arg.value.is_a?(AST::Identifier)
      # Binding: position
      field_name = arg.value.name
      raise_sema_error("unknown field #{scrutinee_type}.#{arm_name}.#{field_name}") unless payload_fields.key?(field_name)
      raise_sema_error("duplicate field #{field_name} in struct pattern") if seen_fields.key?(field_name)
      seen_fields[field_name] = true

      field_type = payload_fields[field_name]
      binding = value_binding(
        name: field_name,
        type: field_type,
        mutable: false,
        kind: :local,
        id: @preassigned_local_binding_ids[arg.object_id],
      )
      arm_scopes.last[field_name] = binding
    elsif arg.value.is_a?(AST::BinaryOp) && arg.value.left.is_a?(AST::Identifier)
      # Guard: hp > 0
      field_name = arg.value.left.name
      raise_sema_error("unknown field #{scrutinee_type}.#{arm_name}.#{field_name}") unless payload_fields.key?(field_name)
      raise_sema_error("duplicate field #{field_name} in struct pattern") if seen_fields.key?(field_name)
      seen_fields[field_name] = true
      has_guards = true

      field_type = payload_fields[field_name]
      comparison_operators = ["==", "!=", "<", "<=", ">", ">="]
      unless comparison_operators.include?(arg.value.operator)
        raise_sema_error("unsupported guard operator '#{arg.value.operator}' in struct pattern; use ==, !=, <, <=, >, or >=", arg.value)
      end

      operand_type = infer_expression(arg.value.right, scopes:, expected_type: field_type)
      ensure_assignable!(operand_type, field_type, "guard comparison expects #{field_type}, got #{operand_type}", expression: arg.value.right)
    else
      raise_sema_error("invalid field pattern in struct match arm; expected field name, comparison, or equality", expression: arg.value)
    end
  end

  [has_guards, equality_cover]
end

#check_struct_with_call(struct_type, _receiver_expression, arguments, scopes:) ⇒ Object



72
73
74
75
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 72

def check_struct_with_call(struct_type, _receiver_expression, arguments, scopes:)
  check_aggregate_field_arguments(struct_type, arguments, scopes:, context: "struct.with()")
  struct_type
end

#check_threaded_for_stmt(statement, scopes:, return_type:, allow_return:) ⇒ Object



1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1123

def check_threaded_for_stmt(statement, scopes:, return_type:, allow_return:)
  @uses_parallel_for = true
  raise_sema_error("parallel for requires a range expression (start..end)") unless range_expr?(statement.iterable)

  loop_type = check_range_expr_loop(statement.iterable, scopes:)
  validate_threaded_for_body!(statement.body)

  with_nested_scope(scopes) do |loop_scopes|
    binding = statement.binding
    ensure_non_reserved_primitive_name!(binding.name, kind_label: "for binding", line: binding.line, column: binding.column)
    current_actual_scope(loop_scopes)[binding.name] = value_binding(
      name: binding.name,
      type: loop_type,
      mutable: false,
      kind: :let,
      id: @preassigned_local_binding_ids[binding.object_id],
    )
    record_declaration_binding(binding, current_actual_scope(loop_scopes)[binding.name])
    check_block(statement.body, scopes: loop_scopes, return_type:, allow_return: false)
  end
end

#check_top_level_static_assertsObject



628
629
630
631
632
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 628

def check_top_level_static_asserts
  expanded_declarations.grep(AST::StaticAssert).each do |statement|
    check_static_assert(statement, scopes: [])
  end
end

#check_top_level_valuesObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 6

def check_top_level_values
  expanded_declarations.each do |decl|
    with_error_node(decl) do
      case decl
      when AST::ConstDecl
        begin
          if decl.block_body
            check_block_body_const(decl)
          else
            check_expr_const(decl)
          end
        rescue SemanticError => e
          collect_structural_error(e)
        end
      when AST::VarDecl
        binding = @ctx.top_level_values.fetch(decl.name)
        if decl.value
          validate_consuming_foreign_expression!(decl.value, scopes: [], root_allowed: false)
          validate_hoistable_foreign_expression!(decl.value, scopes: [], root_hoistable: false)
          actual_type = infer_expression(decl.value, scopes: [], expected_type: binding.type)
          ensure_assignable!(
            actual_type,
            binding.type,
            "cannot assign #{actual_type} to module variable #{decl.name}: expected #{binding.type}",
            expression: decl.value,
          )
          validate_static_storage_initializer!(decl.value, scopes: [])
        else
          zero_initializable_type?(binding.type)
        end
      end
    end
  end
end

#check_tuple_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:) ⇒ Object



767
768
769
770
771
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 767

def check_tuple_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
  each_tuple_match_arm(statement, scrutinee_type, scopes:) do |arm|
    check_block(arm.body, scopes:, return_type:, allow_return:)
  end
end

#check_variant_arm_construction(callable, arguments, scopes:) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 102

def check_variant_arm_construction(callable, arguments, scopes:)
  variant_type, arm_name = callable
  fields = variant_type.arm(arm_name)

  if fields.nil? || fields.empty?
    raise_sema_error("variant arm #{variant_type}.#{arm_name} has no payload; construct it without arguments") unless arguments.empty?

    return variant_type
  end

  raise_sema_error("variant arm construction requires named arguments") unless arguments.all?(&:name)

  provided = {}
  arguments.each do |argument|
    field_type = fields[argument.name]
    raise_sema_error("unknown field #{variant_type}.#{arm_name}.#{argument.name}") unless field_type
    raise_sema_error("duplicate field #{variant_type}.#{arm_name}.#{argument.name}") if provided.key?(argument.name)

    actual_type = infer_expression(argument.value, scopes:, expected_type: field_type)
    ensure_assignable!(
      actual_type,
      field_type,
      "field #{variant_type}.#{arm_name}.#{argument.name} expects #{field_type}, got #{actual_type}",
      expression: argument.value,
      contextual_int_to_float: contextual_int_to_float_target?(field_type),
    )
    provided[argument.name] = true
  end

  missing = fields.keys - provided.keys
  missing.reject! { |name| fields[name].void? }
  raise_sema_error("variant arm #{variant_type}.#{arm_name} is missing fields: #{missing.join(', ')}") unless missing.empty?

  variant_type
end

#check_variant_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:) ⇒ Object



819
820
821
822
823
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 819

def check_variant_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
  each_variant_match_arm(statement, scrutinee_type, scopes:) do |arm, arm_scopes|
    check_block(arm.body, scopes: arm_scopes, return_type:, allow_return:)
  end
end

#check_when_stmt(statement, scopes:, return_type:, allow_return:) ⇒ Object



1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1251

def check_when_stmt(statement, scopes:, return_type:, allow_return:)
  discriminant_value = evaluate_when_discriminant(statement.discriminant)

  chosen_branch = statement.branches.find do |branch|
    pattern_value = evaluate_compile_time_const_value(branch.pattern, scopes:)
    discriminant_value == pattern_value
  end

  if chosen_branch
    check_block(chosen_branch.body, scopes:, return_type:, allow_return:)
  elsif statement.else_body
    check_block(statement.else_body, scopes:, return_type:, allow_return:)
  else
    raise_sema_error("when discriminant value #{discriminant_value} does not match any branch and no else is provided")
  end
end

#check_zero_call(target_type, arguments, expected_type: nil, operation: "zero") ⇒ Object



230
231
232
233
234
235
236
237
238
239
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 230

def check_zero_call(target_type, arguments, expected_type: nil, operation: "zero")
  raise_sema_error("#{operation} expects 0 arguments, got #{arguments.length}") unless arguments.empty?

  zero_initializable_type?(target_type, operation:)
  if expected_type.is_a?(Types::Nullable) && typed_null_target_type?(expected_type.base) && types_compatible?(target_type, expected_type.base)
    raise_sema_error("use null instead of #{operation}[#{target_type}] in nullable pointer-like context #{expected_type}")
  end

  target_type
end

#collect_emit_declarationsObject



212
213
214
215
# File 'lib/milk_tea/core/semantic_analyzer.rb', line 212

def collect_emit_declarations
  collect_emit_from_declarations(expanded_declarations)
  @ctx.ast.declarations.grep(AST::ConstDecl).each { |decl| @ctx.const_declarations[decl.name] ||= decl }
end

#collect_emit_from_declarations(declarations) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/milk_tea/core/semantic_analyzer.rb', line 217

def collect_emit_from_declarations(declarations)
  declarations.each do |decl|
    case decl
    when AST::FunctionDef
      next unless decl.const && decl.body

      collect_emit_from_statements(decl.body)
    when AST::WhenStmt
      body = when_chosen_body(decl)
      collect_emit_from_declarations(body) if body
    when AST::StructDecl, AST::ExtendingBlock
      # no emit in structs/extending blocks
    end
  end
end

#collect_emit_from_node(node) ⇒ Object



255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/milk_tea/core/semantic_analyzer.rb', line 255

def collect_emit_from_node(node)
  case node
  when AST::FunctionDef
    node.body&.each do |stmt|
      if stmt.is_a?(AST::EmitStmt)
        nested = stmt.declaration
        next if nested.is_a?(AST::ErrorExpr)

        @ctx.ast.declarations << nested
        collect_emit_from_node(nested)
      end
    end
  end
end

#collect_emit_from_statements(statements) ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/milk_tea/core/semantic_analyzer.rb', line 233

def collect_emit_from_statements(statements)
  statements.each do |stmt|
    case stmt
    when AST::EmitStmt
      emit_decl = stmt.declaration
      next if emit_decl.is_a?(AST::ErrorExpr)

      @ctx.ast.declarations << emit_decl
      collect_emit_from_node(emit_decl)
    when AST::WhenStmt
      body = when_chosen_body(stmt) || []
      body.each { |nested| collect_emit_from_statements([nested]) }
    when AST::ForStmt, AST::WhileStmt, AST::IfStmt, AST::MatchStmt
      next unless stmt.inline
      stmt.body&.each { |s| collect_emit_from_statements([s]) }
      if stmt.is_a?(AST::IfStmt)
        stmt.else_body&.each { |s| collect_emit_from_statements([s]) }
      end
    end
  end
end

#collect_structural_error(error) ⇒ Object



329
330
331
332
333
# File 'lib/milk_tea/core/semantic_analyzer.rb', line 329

def collect_structural_error(error)
  raise error unless @collecting_errors

  @structural_errors << error
end

#collect_type_substitutions(pattern_type, actual_type, substitutions, function_name) ⇒ Object



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 286

def collect_type_substitutions(pattern_type, actual_type, substitutions, function_name)
  case pattern_type
  when Types::TypeVar
    existing = substitutions[pattern_type.name]
    if existing && existing != actual_type
      raise_sema_error("conflicting type argument #{pattern_type.name} for function #{function_name}: got #{existing} and #{actual_type}")
    end

    substitutions[pattern_type.name] ||= actual_type
  when Types::Nullable
    candidate = actual_type.is_a?(Types::Nullable) ? actual_type.base : actual_type
    collect_type_substitutions(pattern_type.base, candidate, substitutions, function_name)
  when Types::GenericInstance
    if ref_type?(pattern_type) && !ref_type?(actual_type)
      collect_type_substitutions(referenced_type(pattern_type), actual_type, substitutions, function_name)
      return
    end

    if own_type?(actual_type) && (mutable_pointer_type?(pattern_type) || const_pointer_type?(pattern_type))
      collect_type_substitutions(pointee_type(pattern_type), owned_referent_type(actual_type), substitutions, function_name)
      return
    end

    return unless actual_type.is_a?(Types::GenericInstance)
    return unless actual_type.name == pattern_type.name && actual_type.arguments.length == pattern_type.arguments.length

    pattern_type.arguments.zip(actual_type.arguments).each do |expected_argument, actual_argument|
      next if expected_argument.is_a?(Types::LiteralTypeArg)

      collect_type_substitutions(expected_argument, actual_argument, substitutions, function_name)
    end
  when Types::VariantInstance
    return unless actual_type.is_a?(Types::VariantInstance)
    return unless actual_type.definition == pattern_type.definition || (actual_type.name == pattern_type.name && actual_type.arguments.length == pattern_type.arguments.length)

    pattern_type.arguments.zip(actual_type.arguments).each do |expected_argument, actual_argument|
      next if expected_argument.is_a?(Types::LiteralTypeArg)

      collect_type_substitutions(expected_argument, actual_argument, substitutions, function_name)
    end
  when Types::Span
    return unless actual_type.is_a?(Types::Span)

    collect_type_substitutions(pattern_type.element_type, actual_type.element_type, substitutions, function_name)
  when Types::Task
    return unless actual_type.is_a?(Types::Task)

    collect_type_substitutions(pattern_type.result_type, actual_type.result_type, substitutions, function_name)
  when Types::Proc
    if task_root_proc_type?(pattern_type) && actual_type.is_a?(Types::Task)
      collect_type_substitutions(pattern_type.return_type, actual_type, substitutions, function_name)
      return
    end

    actual_params = case actual_type
    when Types::Proc
      return unless actual_type.params.length == pattern_type.params.length

      actual_type.params
    when Types::Function
      return if actual_type.receiver_type || actual_type.variadic
      return unless actual_type.params.length == pattern_type.params.length

      actual_type.params
    else
      return
    end

    pattern_type.params.zip(actual_params).each do |expected_param, actual_param|
      collect_type_substitutions(expected_param.type, actual_param.type, substitutions, function_name)
    end
    collect_type_substitutions(pattern_type.return_type, actual_type.return_type, substitutions, function_name)
  when Types::StructInstance
    return unless actual_type.is_a?(Types::StructInstance)
    return unless actual_type.definition == pattern_type.definition && actual_type.arguments.length == pattern_type.arguments.length

    pattern_type.arguments.zip(actual_type.arguments).each do |expected_argument, actual_argument|
      collect_type_substitutions(expected_argument, actual_argument, substitutions, function_name)
    end
  when Types::Function
    return unless actual_type.is_a?(Types::Function)
    return unless actual_type.params.length == pattern_type.params.length

    pattern_type.params.zip(actual_type.params).each do |expected_param, actual_param|
      collect_type_substitutions(expected_param.type, actual_param.type, substitutions, function_name)
    end
    collect_type_substitutions(pattern_type.return_type, actual_type.return_type, substitutions, function_name)
  end
end

#collection_loop_binding_type(iterable_type, element_type) ⇒ Object



868
869
870
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 868

def collection_loop_binding_type(iterable_type, element_type)
  super
end

#collection_loop_ref_element_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


872
873
874
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 872

def collection_loop_ref_element_type?(type)
  super
end

#collection_loop_type(type) ⇒ Object



864
865
866
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 864

def collection_loop_type(type)
  super
end

#common_integer_type(left_type, right_type) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 183

def common_integer_type(left_type, right_type)
  left_type = left_type.backing_type if left_type.is_a?(Types::EnumBase)
  right_type = right_type.backing_type if right_type.is_a?(Types::EnumBase)
  return unless left_type.is_a?(Types::Primitive) && right_type.is_a?(Types::Primitive)
  return unless left_type.integer? && right_type.integer?
  return left_type if left_type == right_type
  return unless left_type.fixed_width_integer? && right_type.fixed_width_integer?
  return unless left_type.signed_integer? == right_type.signed_integer?

  left_type.integer_width >= right_type.integer_width ? left_type : right_type
end

#common_numeric_type(left_type, right_type) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 167

def common_numeric_type(left_type, right_type)
  left_type = left_type.backing_type if left_type.is_a?(Types::EnumBase)
  right_type = right_type.backing_type if right_type.is_a?(Types::EnumBase)
  return unless left_type.is_a?(Types::Primitive) && right_type.is_a?(Types::Primitive)
  return unless left_type.numeric? && right_type.numeric?
  return left_type if left_type == right_type

  return common_integer_type(left_type, right_type) if left_type.integer? && right_type.integer?
  return wider_float_type(left_type, right_type) if left_type.float? && right_type.float?

  float_type, integer_type = left_type.float? ? [left_type, right_type] : [right_type, left_type]
  return unless integer_type.integer? && integer_type.fixed_width_integer?

  float_type
end

#conditional_common_type(then_type, else_type, then_expression:, else_expression:) ⇒ Object



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 307

def conditional_common_type(then_type, else_type, then_expression:, else_expression:)
  return then_type if then_type == else_type

  numeric_type = common_numeric_type(then_type, else_type)
  return numeric_type if numeric_type

  if (nullable_type = conditional_null_common_type(then_type, else_type))
    return nullable_type
  end

  if (nullable_type = conditional_null_common_type(else_type, then_type))
    return nullable_type
  end

  return then_type if types_compatible?(else_type, then_type, expression: else_expression)
  return else_type if types_compatible?(then_type, else_type, expression: then_expression)

  nil
end

#conditional_null_common_type(null_type, other_type) ⇒ Object



333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 333

def conditional_null_common_type(null_type, other_type)
  return unless null_type.is_a?(Types::Null)

  if other_type.is_a?(Types::Nullable)
    return other_type if null_type.target_type.nil? || null_type.target_type == other_type.base

    return nil
  end

  return unless nullable_candidate?(other_type)
  return if null_type.target_type && null_type.target_type != other_type

  Types::Registry.nullable(other_type)
end

#consuming_foreign_call_refinements(expression, scopes:) ⇒ Object



1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1231

def consuming_foreign_call_refinements(expression, scopes:)
  foreign_call = resolve_foreign_call_expression(expression, scopes:)
  return {} unless foreign_call

  binding = foreign_call[:binding]
  return {} unless foreign_call_consumes_binding?(binding)

  binding.type.params.each_with_index.each_with_object({}) do |(parameter, index), refinements|
    next unless parameter.passing_mode == :consuming

    argument = foreign_call[:call].arguments.fetch(index)
    argument_binding = foreign_consuming_argument_binding(parameter, argument, scopes:, function_name: binding.name)
    refinements[argument.value.name] = @null_type if argument_binding.storage_type.is_a?(Types::Nullable)
  end
end

#contains_callable_ref_type?(type, visited = {}) ⇒ Boolean

Returns:

  • (Boolean)


1141
1142
1143
1144
1145
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1141

def contains_callable_ref_type?(type, visited = {})
  visitor = ContainsCallableRefTypeVisitor.new
  visitor.visit(type)
  visitor.found?
end

#contains_proc_type?(type, visited = {}) ⇒ Boolean

Returns:

  • (Boolean)


1147
1148
1149
1150
1151
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1147

def contains_proc_type?(type, visited = {})
  visitor = ContainsProcTypeVisitor.new
  visitor.visit(type)
  visitor.found?
end

#contains_type_var?(type) ⇒ Boolean

Returns:

  • (Boolean)


798
799
800
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 798

def contains_type_var?(type)
  super
end

#contextual_int_to_float_fits?(expression, expected_type, scopes) ⇒ Boolean

Returns:

  • (Boolean)


150
151
152
153
154
155
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 150

def contextual_int_to_float_fits?(expression, expected_type, scopes)
  value = evaluate_compile_time_const_value(expression, scopes:)
  return true unless value.is_a?(Numeric)

  float_constant_fits_type?(value, expected_type)
end

#count_required_params(binding) ⇒ Object



1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 1071

def count_required_params(binding)
  return binding.type.params.length unless binding.ast.params.any? { |p| p.respond_to?(:default_value) && p.default_value }

  seen_default = false
  binding.ast.params.count do |p|
    has_default = p.respond_to?(:default_value) && p.default_value
    seen_default = true if has_default
    !has_default && !seen_default
  end
end

#current_actual_scope(scopes) ⇒ Object



6
7
8
9
10
11
12
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 6

def current_actual_scope(scopes)
  scopes.reverse_each do |scope|
    return scope unless scope.is_a?(FlowScope)
  end

  raise_sema_error("missing lexical scope")
end

#current_error_nodeObject



499
500
501
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 499

def current_error_node
  @error_node_stack.reverse_each.find { |node| !node.nil? }
end

#current_return_contextObject



105
106
107
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 105

def current_return_context
  @return_context_stack.last
end

#current_type_param_constraintsObject



10
11
12
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 10

def current_type_param_constraints
  @current_type_param_constraints || {}
end

#current_type_paramsObject



6
7
8
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 6

def current_type_params
  @current_type_substitutions || {}
end

#declare_attributesObject



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 313

def declare_attributes
  expanded_declarations.grep(AST::AttributeDecl).each do |decl|
    with_error_node(decl) do
      raise_sema_error("duplicate attribute #{decl.name}") if @ctx.attributes.key?(decl.name)

      params = []
      seen = {}
      decl.params.each do |param|
        raise_sema_error("duplicate attribute parameter #{decl.name}.#{param.name}") if seen.key?(param.name)

        seen[param.name] = true
        params << Types::Registry.parameter(param.name, resolve_type_ref(param.type))
      end

      @ctx.attributes[decl.name] = AttributeBinding.new(
        name: decl.name,
        targets: decl.targets.freeze,
        params: params.freeze,
        module_name: @ctx.module_name,
        builtin: false,
        ast: decl,
      )
    end
  end
end

#declare_function_binding(decl, receiver_type: nil, declared_receiver_type: nil, receiver_type_param_names: [], receiver_type_param_constraints: {}, external: false) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 52

def declare_function_binding(decl, receiver_type: nil, declared_receiver_type: nil, receiver_type_param_names: [], receiver_type_param_constraints: {}, external: false)
  foreign = decl.is_a?(AST::ForeignFunctionDecl)
  async_function = decl.respond_to?(:async) ? decl.async : false
  if decl.is_a?(AST::MethodDef)
    ensure_non_reserved_primitive_name!(decl.name, kind_label: "function", line: decl.line, column: decl.column)
  end
  type_param_names = receiver_type_param_names + decl.type_params.map(&:name)
  type_param_constraints = receiver_type_param_constraints.merge(resolve_type_param_constraints(decl.type_params))
  raise_sema_error("external function #{decl.name} cannot be generic") if external && type_param_names.any?
  raise_sema_error("main cannot be generic") if decl.name == "main" && type_param_names.any?
  raise_sema_error("external function #{decl.name} cannot be async") if external && async_function
  raise_sema_error("foreign function #{decl.name} cannot be async") if foreign && async_function

  method_kind = decl.is_a?(AST::MethodDef) ? decl.kind : nil
  instance_method = receiver_type && method_kind != :static

  type_params = {}
  type_param_names.each do |name|
    raise_sema_error("duplicate type parameter #{decl.name}[#{name}]") if type_params.key?(name)

    type_params[name] = Types::TypeVar.new(name)
  end

  body_params = []
  if instance_method
    body_params << value_binding(
      name: "this",
      type: receiver_type,
      mutable: method_kind == :editable,
      kind: :param,
    )
  end

  public_params = []
  any_default = false
  decl.params.each do |param|
    begin
      ensure_non_reserved_primitive_name!(param.name, kind_label: "parameter", line: param.line || decl.line, column: param.column)
      type = resolve_type_ref(param.type, type_params:, type_param_constraints:)
      validate_parameter_ref_type!(type, function_name: decl.name, parameter_name: param.name, external:)
      reject_foreign_nullable_value_type!(type, function_name: decl.name, parameter_name: param.name) if foreign || external
      validate_parameter_proc_type!(type, function_name: decl.name, parameter_name: param.name, external:, foreign:)
      raise_sema_error("parameter #{param.name} of #{decl.name} must pass event storage through ref[...] or pointers, got #{type}") if noncopyable_event_storage_type?(type)

      has_default = param.respond_to?(:default_value) && param.default_value
      raise_sema_error("external function #{decl.name} cannot have default parameter values") if external && has_default
      raise_sema_error("foreign function #{decl.name} cannot have default parameter values") if foreign && has_default
      raise_sema_error("default parameter #{param.name} of #{decl.name} must appear after required parameters") if !has_default && any_default
      any_default = true if has_default

      if external && array_type?(type)
        raise_sema_error("external function #{decl.name} cannot take array parameters")
      end

      if param.is_a?(AST::ForeignParam)
        if external
          raise_sema_error("external parameter #{param.name} of #{decl.name} cannot use `as`") if param.boundary_type
          unless %i[plain out inout].include?(param.mode)
            raise_sema_error("external parameter #{param.name} of #{decl.name} cannot use `#{param.mode}`")
          end
        elsif foreign
          raise_sema_error("foreign parameter #{param.name} cannot use `as` with #{param.mode}") if ![:plain, :in].include?(param.mode) && param.boundary_type
          validate_consuming_foreign_parameter!(type, function_name: decl.name, parameter_name: param.name) if param.mode == :consuming
        end

        boundary_type = foreign_parameter_boundary_type(param, type, type_params:, type_param_constraints:)
        validate_foreign_boundary_type!(type, boundary_type, function_name: decl.name, parameter_name: param.name) if foreign && param.boundary_type && param.mode != :in
        validate_in_foreign_parameter!(type, boundary_type, function_name: decl.name, parameter_name: param.name) if foreign && param.mode == :in
        param_binding = value_binding(name: param.name, type: boundary_type || type, mutable: false, kind: :param)
        body_params << param_binding
        record_declaration_binding(param, param_binding)
        if foreign && param.boundary_type
          body_params << value_binding(
            name: foreign_mapping_public_alias_name(param.name),
            type:,
            mutable: false,
            kind: :param,
          )
        end
        public_params << Types::Registry.parameter(param.name, type, passing_mode: param.mode, boundary_type: boundary_type)
      else
        param_binding = value_binding(name: param.name, type:, mutable: false, kind: :param)
        body_params << param_binding
        record_declaration_binding(param, param_binding)
        public_params << Types::Registry.parameter(param.name, type) if external
      end
    rescue SemanticError => e
      collect_structural_error(e)
      param_binding = value_binding(name: param.name, type: @error_type, mutable: false, kind: :param)
      body_params << param_binding
    end
  end

  receiver_editable = false
  call_params = body_params
  function_receiver_type = nil
  if instance_method
    receiver_editable = method_kind == :editable
    call_params = body_params.drop(1)
    function_receiver_type = receiver_type
  end

  call_params = public_params if foreign || external

  seen = {}
  body_params.each do |param|
    raise_sema_error("duplicate parameter #{param.name} in #{decl.name}") if seen.key?(param.name)

    seen[param.name] = true
  end

  body_return_type = decl.return_type ? resolve_type_ref(decl.return_type, type_params:, type_param_constraints:) : @ctx.types.fetch("void")
  validate_return_ref_type!(body_return_type, function_name: decl.name)
  reject_foreign_nullable_value_type!(body_return_type, function_name: decl.name, parameter_name: nil) if foreign || external
  validate_return_proc_type!(body_return_type, function_name: decl.name)
  raise_sema_error("function #{decl.name} cannot return event storage type #{body_return_type}") if noncopyable_event_storage_type?(body_return_type)
  if decl.name == "main" && async_function && body_return_type != @ctx.types.fetch("int") && body_return_type != @ctx.types.fetch("void")
    raise_sema_error("async main must return int or void")
  end
  if foreign && public_params.any? { |param| param.passing_mode == :consuming } && body_return_type != @ctx.types.fetch("void")
    raise_sema_error("foreign function #{decl.name} with consuming parameters must return void")
  end
  if external && array_type?(body_return_type)
    raise_sema_error("external function #{decl.name} cannot return arrays")
  end
  function_return_type = async_function ? Types::Registry.task(body_return_type) : body_return_type

  function_type = Types::Registry.function(
    decl.name,
    params: (foreign || external) ? call_params : call_params.map { |param| Types::Registry.parameter(param.name, param.type) },
    return_type: function_return_type,
    receiver_type: function_receiver_type,
    receiver_editable:,
    variadic: decl.respond_to?(:variadic) ? decl.variadic : false,
    external:,
  )

  FunctionBinding.new(
    name: decl.name,
    type: function_type,
    body_params:,
    body_return_type: body_return_type,
    ast: decl,
    external:,
    async: async_function,
    type_params: type_param_names.freeze,
    type_param_constraints: type_param_constraints.freeze,
    instances: {},
    type_arguments: [].freeze,
    owner: self,
    specialization_owner: nil,
    type_substitutions: {}.freeze,
    declared_receiver_type: declared_receiver_type,
  )
end

#declare_functionsObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 6

def declare_functions
  expanded_declarations.each do |decl|
    with_error_node(decl) do
      case decl
      when AST::FunctionDef
        ensure_available_value_name!(decl.name, kind_label: "function", line: decl.line, column: decl.column)
        @ctx.top_level_functions[decl.name] = declare_function_binding(decl)
      when AST::ExternFunctionDecl
        ensure_available_value_name!(decl.name, kind_label: "function", line: decl.line, column: decl.column)
        if decl.mapping && !decl.mapping.is_a?(AST::StringLiteral)
          raise_sema_error("external function #{decl.name} mapping must be a c-string literal")
        end
        @ctx.top_level_functions[decl.name] = declare_function_binding(decl, external: true)
      when AST::ForeignFunctionDecl
        ensure_available_value_name!(decl.name, kind_label: "function", line: decl.line, column: decl.column)
        @ctx.top_level_functions[decl.name] = declare_function_binding(decl)
      when AST::ExtendingBlock
        dispatch_receiver_type, receiver_type, receiver_type_param_names, receiver_type_param_constraints = resolve_methods_receiver_target(decl.type_name)

        decl.methods.each do |method|
          begin
            binding = with_error_node(method) do
              declare_function_binding(
                method,
                receiver_type:,
                declared_receiver_type: receiver_type,
                receiver_type_param_names:,
                receiver_type_param_constraints:,
              )
            end
            instance_method = receiver_type && method.kind != :static
            method_key = instance_method ? binding.name : "static:#{binding.name}"
            raise_sema_error("duplicate method #{decl.type_name}.#{binding.name}") if @ctx.methods[dispatch_receiver_type].key?(method_key)

            @ctx.methods[dispatch_receiver_type][method_key] = binding
          rescue SemanticError => e
            collect_structural_error(e)
          end
        end
      end
    end
  rescue SemanticError => e
    collect_structural_error(e)
  end
end

#declare_interface_binding(decl) ⇒ Object



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 339

def declare_interface_binding(decl)
  if decl.type_params.any?
    type_param_names = decl.type_params.map(&:name)
    type_params_map = type_param_names.map { |n| [n, Types::TypeVar.new(n)] }.to_h
    methods = {}

    decl.methods.each do |method_decl|
      method = resolve_interface_method_binding(method_decl, type_params: type_params_map, type_param_constraints: {})
      raise_sema_error("duplicate method #{decl.name}.#{method.name}") if methods.key?(method.name)

      methods[method.name] = method
    end

    GenericInterfaceBinding.new(
      name: decl.name,
      type_params: type_param_names.freeze,
      type_param_constraints: {},
      methods: methods.freeze,
      ast: decl,
      module_name: @ctx.module_name,
    )
  else
    methods = {}

    decl.methods.each do |method_decl|
      method = resolve_interface_method_binding(method_decl)
      raise_sema_error("duplicate method #{decl.name}.#{method.name}") if methods.key?(method.name)

      methods[method.name] = method
    end

    InterfaceBinding.new(
      name: decl.name,
      methods: methods.freeze,
      ast: decl,
      module_name: @ctx.module_name,
    )
  end
end

#declare_named_typesObject



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 218

def declare_named_types
  expanded_declarations.each do |decl|
    with_error_node(decl) do
      case decl
      when AST::StructDecl
        validate_struct_layout!(decl)
        validate_explicit_aggregate_c_name!(decl)
        ensure_available_type_name!(decl.name)
        @ctx.types[decl.name] = if decl.type_params.empty?
          Types::Struct.new(
            decl.name,
            module_name: @ctx.module_name,
            external: raw_module?,
            packed: decl.packed,
            alignment: decl.alignment,
            linkage_name: decl.c_name,
            lifetime_params: decl.lifetime_params,
          )
        else
          Types::GenericStructDefinition.new(
            decl.name,
            decl.type_params.map(&:name),
            module_name: @ctx.module_name,
            external: raw_module?,
            packed: decl.packed,
            alignment: decl.alignment,
            linkage_name: decl.c_name,
            lifetime_params: decl.lifetime_params,
          )
        end
        register_nested_struct_types(decl)
      when AST::UnionDecl
        validate_explicit_aggregate_c_name!(decl)
        ensure_available_type_name!(decl.name)
        @ctx.types[decl.name] = Types::Union.new(decl.name, module_name: @ctx.module_name, external: raw_module?, linkage_name: decl.c_name)
      when AST::VariantDecl
        ensure_available_type_name!(decl.name)
        @ctx.types[decl.name] = if decl.type_params.empty?
          Types::Variant.new(decl.name, module_name: @ctx.module_name)
        else
          Types::GenericVariantDefinition.new(
            decl.name,
            decl.type_params.map(&:name),
            module_name: @ctx.module_name,
          )
        end
      when AST::EnumDecl
        ensure_available_type_name!(decl.name)
        @ctx.types[decl.name] = Types::Enum.new(decl.name, module_name: @ctx.module_name, external: raw_module?)
      when AST::FlagsDecl
        ensure_available_type_name!(decl.name)
        @ctx.types[decl.name] = Types::Flags.new(decl.name, module_name: @ctx.module_name, external: raw_module?)
      when AST::OpaqueDecl
        ensure_available_type_name!(decl.name)
        @ctx.types[decl.name] = Types::Opaque.new(
          decl.name,
          module_name: @ctx.module_name,
          external: raw_module?,
          linkage_name: decl.c_name,
        )
      when AST::InterfaceDecl
        ensure_available_type_name!(decl.name)
        @ctx.interfaces[decl.name] = declare_interface_binding(decl)
      end
    end
  rescue SemanticError => e
    collect_structural_error(e)
  end
end

#declare_top_level_valuesObject



735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 735

def declare_top_level_values
  expanded_declarations.each do |decl|
    with_error_node(decl) do
      case decl
      when AST::ConstDecl
        begin
          ensure_available_value_name!(decl.name, kind_label: "constant", line: decl.line, column: decl.column)
          type = resolve_type_ref(decl.type)
          validate_stored_ref_type!(type, "constant #{decl.name}")
          raise_sema_error("constant #{decl.name} cannot store proc values") if contains_proc_type?(type)
          @ctx.top_level_values[decl.name] = value_binding(
            name: decl.name,
            type: type,
            mutable: false,
            kind: :const,
          )
        rescue SemanticError => e
          collect_structural_error(e)
          @ctx.top_level_values[decl.name] = value_binding(
            name: decl.name,
            type: @error_type,
            mutable: false,
            kind: :const,
          ) unless @ctx.top_level_values.key?(decl.name)
        end
      when AST::VarDecl
        ensure_available_value_name!(decl.name, kind_label: "module variable", line: decl.line, column: decl.column)
        raise_sema_error("module variable #{decl.name} requires an explicit type") unless decl.type

        type = resolve_type_ref(decl.type)
        validate_stored_ref_type!(type, "module variable #{decl.name}")
        @ctx.top_level_values[decl.name] = value_binding(
          name: decl.name,
          type: type,
          mutable: true,
          kind: :var,
        )
      when AST::EventDecl
        ensure_available_value_name!(decl.name, kind_label: "event", line: decl.line, column: decl.column)
        @ctx.top_level_values[decl.name] = value_binding(
          name: decl.name,
          type: resolve_event_decl_type(decl),
          mutable: false,
          kind: :event,
        )
      end
    end
  rescue SemanticError => e
    collect_structural_error(e)
  end
end

#define_nested_type_bindings_recursive(parent_decl, parent_name: parent_decl.name) ⇒ Object



792
793
794
795
796
797
798
799
800
801
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 792

def define_nested_type_bindings_recursive(parent_decl, parent_name: parent_decl.name)
  parent_decl.nested_types.each do |nested|
    qualified_name = "#{parent_name}.#{nested.name}"
    nested_type = @ctx.types[qualified_name]
    next unless nested_type.is_a?(Types::Struct)
    nested_scope = resolve_nested_type_bindings(nested, parent_name: qualified_name)
    nested_type.define_nested_types(nested_scope)
    define_nested_type_bindings_recursive(nested, parent_name: qualified_name)
  end
end

#describe_expression(expression) ⇒ Object



348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 348

def describe_expression(expression)
  case expression
  when AST::Identifier
    expression.name
  when AST::MemberAccess
    "#{describe_expression(expression.receiver)}.#{expression.member}"
  when AST::IndexAccess
    "#{describe_expression(expression.receiver)}[...]"
  when AST::Specialization
    "#{describe_expression(expression.callee)}[...]"
  when AST::FormatString
    'f"..."'
  else
    expression.class.name.split("::").last
  end
end

#direct_function_identity_expression?(expression, scopes) ⇒ Boolean

Returns:

  • (Boolean)


93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 93

def direct_function_identity_expression?(expression, scopes)
  case expression
  when AST::Identifier
    return false if lookup_value(expression.name, scopes)
    return false unless @ctx.top_level_functions.key?(expression.name)

    binding = @ctx.top_level_functions.fetch(expression.name)
    !binding.type_params.any? && !foreign_function_binding?(binding)
  when AST::MemberAccess
    return false unless expression.receiver.is_a?(AST::Identifier) && @ctx.imports.key?(expression.receiver.name)

    imported_module = @ctx.imports.fetch(expression.receiver.name)
    return false unless imported_module.functions.key?(expression.member)

    binding = imported_module.functions.fetch(expression.member)
    !binding.type_params.any? && !foreign_function_binding?(binding)
  when AST::Specialization
    binding = resolve_specialized_function_binding(expression)
    binding && !foreign_function_binding?(binding)
  else
    false
  end
end

#direct_function_to_proc_argument_compatible?(actual_type, expected_type, expression, scopes) ⇒ Boolean

Returns:

  • (Boolean)


78
79
80
81
82
83
84
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 78

def direct_function_to_proc_argument_compatible?(actual_type, expected_type, expression, scopes)
  return false unless expression
  return false unless actual_type.is_a?(Types::Function) && proc_type?(expected_type)
  return false unless direct_function_identity_expression?(expression, scopes)

  function_type_matches_proc_type?(actual_type, expected_type)
end

#direct_task_to_proc_argument_compatible?(actual_type, expected_type) ⇒ Boolean

Returns:

  • (Boolean)


86
87
88
89
90
91
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 86

def direct_task_to_proc_argument_compatible?(actual_type, expected_type)
  return false unless actual_type.is_a?(Types::Task)
  return false unless task_root_proc_type?(expected_type)

  actual_type == expected_type.return_type
end

#each_enum_match_arm(statement, scrutinee_type, scopes:) ⇒ Object



590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 590

def each_enum_match_arm(statement, scrutinee_type, scopes:)
  covered_members = {}
  wildcard_seen = false
  statement.arms.each do |arm|
    if arm.pattern.is_a?(AST::ErrorExpr)
      yield arm, scopes
      next
    end

    if wildcard_pattern?(arm.pattern)
      raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
      wildcard_seen = true
      yield arm, scopes
      next
    end
    validate_consuming_foreign_expression!(arm.pattern, scopes:, root_allowed: false)
    validate_hoistable_foreign_expression!(arm.pattern, scopes:, root_hoistable: false)
    pattern_type = infer_expression(arm.pattern, scopes:, expected_type: scrutinee_type)
    ensure_assignable!(pattern_type, scrutinee_type, "match arm expects #{scrutinee_type}, got #{pattern_type}")

    member_name = match_member_name(arm.pattern, scrutinee_type)
    raise_sema_error("match arm must be an enum member of #{scrutinee_type}") unless member_name
    raise_sema_error("duplicate match arm #{scrutinee_type}.#{member_name}") if covered_members.key?(member_name)

    covered_members[member_name] = true
    yield arm, scopes
  end

  return if wildcard_seen

  missing_members = scrutinee_type.members - covered_members.keys
  return if missing_members.empty?

  raise_sema_error("match on #{scrutinee_type} is missing cases: #{missing_members.join(', ')}")
end

#each_integer_match_arm(statement, scrutinee_type, scopes:) ⇒ Object



626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 626

def each_integer_match_arm(statement, scrutinee_type, scopes:)
  has_wildcard = statement.arms.any? { |arm| wildcard_pattern?(arm.pattern) }
  raise_sema_error("match on integer type #{scrutinee_type} requires a wildcard arm (_:)") unless has_wildcard

  covered_values = {}
  wildcard_seen = false
  statement.arms.each do |arm|
    if arm.pattern.is_a?(AST::ErrorExpr)
      yield arm
      next
    end

    if wildcard_pattern?(arm.pattern)
      raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
      wildcard_seen = true
      yield arm
      next
    end

    if arm.pattern.is_a?(AST::RangeExpr)
      start_val = arm.pattern.start_expr
      end_val   = arm.pattern.end_expr
      unless (start_val.is_a?(AST::IntegerLiteral) || start_val.is_a?(AST::CharLiteral)) &&
             (end_val.is_a?(AST::IntegerLiteral) || end_val.is_a?(AST::CharLiteral))
        raise_sema_error("range match arm bounds must be integer or char literals, got #{start_val.class.name}..#{end_val.class.name}")
      end

      raise_sema_error("range match arm start #{start_val.value} must be <= end #{end_val.value}") if start_val.value > end_val.value

      range_key = [start_val.value, end_val.value]
      raise_sema_error("duplicate match arm range #{range_key[0]}..#{range_key[1]}") if covered_values.key?(range_key)

      covered_values[range_key] = true
      yield arm
      next
    end

    unless arm.pattern.is_a?(AST::IntegerLiteral) || arm.pattern.is_a?(AST::CharLiteral)
      raise_sema_error("match arm for integer scrutinee must be an integer literal, char literal, range, or _, got #{arm.pattern.class.name}")
    end

    value = arm.pattern.value
    raise_sema_error("duplicate match arm value #{value}") if covered_values.key?(value)

    covered_values[value] = true
    yield arm
  end
end

#each_string_match_arm(statement, scrutinee_type, scopes:) ⇒ Object



675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 675

def each_string_match_arm(statement, scrutinee_type, scopes:)
  has_wildcard = statement.arms.any? { |arm| wildcard_pattern?(arm.pattern) }
  raise_sema_error("match on str requires a wildcard arm (_:)") unless has_wildcard

  covered_values = {}
  wildcard_seen = false
  statement.arms.each do |arm|
    if arm.pattern.is_a?(AST::ErrorExpr)
      yield arm
      next
    end

    if wildcard_pattern?(arm.pattern)
      raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
      wildcard_seen = true
      yield arm
      next
    end

    unless arm.pattern.is_a?(AST::StringLiteral)
      raise_sema_error("match arm for str scrutinee must be a string literal or _, got #{arm.pattern.class.name}")
    end

    value = arm.pattern.value
    raise_sema_error("duplicate match arm value \"#{value}\"") if covered_values.key?(value)

    covered_values[value] = true
    yield arm
  end
end

#each_tuple_match_arm(statement, scrutinee_type, scopes:) ⇒ Object



706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 706

def each_tuple_match_arm(statement, scrutinee_type, scopes:)
  has_wildcard = statement.arms.any? { |arm| wildcard_pattern?(arm.pattern) }
  raise_sema_error("match on tuple type #{scrutinee_type} requires a wildcard arm (_:)") unless has_wildcard

  arity = scrutinee_type.element_types.length
  covered_values = {}
  wildcard_seen = false

  statement.arms.each do |arm|
    if arm.pattern.is_a?(AST::ErrorExpr)
      yield arm
      next
    end

    if wildcard_pattern?(arm.pattern)
      raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
      wildcard_seen = true
      yield arm
      next
    end

    unless arm.pattern.is_a?(AST::ExpressionList)
      raise_sema_error("match arm for tuple scrutinee must be a tuple literal or _, got #{arm.pattern.class.name}")
    end

    elements = arm.pattern.elements
    raise_sema_error("tuple match arm has #{elements.length} elements but scrutinee has #{arity}") unless elements.length == arity

    values = elements.map do |elem|
      if elem.is_a?(AST::Identifier) && elem.name == "_"
        :wildcard
      elsif elem.is_a?(AST::IntegerLiteral) || elem.is_a?(AST::CharLiteral) ||
            elem.is_a?(AST::StringLiteral) || elem.is_a?(AST::BooleanLiteral)
        elem.value
      else
        raise_sema_error("tuple match arm element must be a literal or _, got #{elem.class.name}")
      end
    end

    all_literal = values.all? { |v| v != :wildcard }
    if all_literal
      raise_sema_error("duplicate tuple match arm #{arm.pattern.elements.map { |e| e.respond_to?(:value) ? e.value.inspect : '_' }.join(', ')}") if covered_values.key?(values)
      covered_values[values] = true
    end

    yield arm
  end
end

#each_variant_match_arm(statement, scrutinee_type, scopes:) ⇒ Object



825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 825

def each_variant_match_arm(statement, scrutinee_type, scopes:)
  covered_arms = {}
  wildcard_seen = false
  equality_cover_table = {}
  statement.arms.each do |arm|
    if arm.pattern.is_a?(AST::ErrorExpr)
      yield arm, scopes
      next
    end

    if wildcard_pattern?(arm.pattern)
      raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
      wildcard_seen = true
      yield arm, scopes
      next
    end
    validate_consuming_foreign_expression!(arm.pattern, scopes:, root_allowed: false)
    validate_hoistable_foreign_expression!(arm.pattern, scopes:, root_hoistable: false)

    arm_name = variant_match_arm_name(arm.pattern, scrutinee_type)
    raise_sema_error("match arm must be a variant arm of #{scrutinee_type}") unless arm_name

    arm_scopes = scopes.dup
    has_guards = false
    equality_cover = {}

    if arm.pattern.is_a?(AST::Call) && !arm.pattern.arguments.empty?
      has_guards, equality_cover = check_struct_match_pattern(arm.pattern.arguments, arm_name, scrutinee_type, arm_scopes, scopes:, arm:)
    end

    unless has_guards
      raise_sema_error("duplicate match arm #{scrutinee_type}.#{arm_name}") if covered_arms.key?(arm_name)
      covered_arms[arm_name] = true
    else
      equality_cover_table[arm_name] = merge_equality_cover(equality_cover_table[arm_name], equality_cover)
    end

    if arm.binding_name
      ensure_non_reserved_primitive_name!(arm.binding_name, kind_label: "match binding", line: arm.binding_line, column: arm.binding_column)
      fields = scrutinee_type.arm(arm_name)
      if fields.nil? || fields.empty?
        raise_sema_error("variant arm #{scrutinee_type}.#{arm_name} has no payload; 'as' binding is not allowed")
      end

      payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
      binding = value_binding(
        name: arm.binding_name,
        type: payload_type,
        mutable: true,
        kind: :local,
        id: @preassigned_local_binding_ids[arm.object_id],
      )
      arm_scopes = arm_scopes + [{ arm.binding_name => binding }]
      record_declaration_binding(arm, binding)
    end
    yield arm, arm_scopes
  end

  equality_cover_table.each do |arm_name, field_covers|
    next if covered_arms.key?(arm_name)
    payload_fields = scrutinee_type.arm(arm_name)
    next unless payload_fields
    covered = field_covers.any? do |field_name, covered_members|
      field_type = payload_fields[field_name]
      next unless field_type.is_a?(Types::Enum)
      all_members = field_type.members
      all_members.any? && (all_members - covered_members).empty?
    end
    covered_arms[arm_name] = true if covered
  end

  return if wildcard_seen

  missing_arms = scrutinee_type.arm_names - covered_arms.keys
  return if missing_arms.empty?

  raise_sema_error("match on #{scrutinee_type} is missing cases: #{missing_arms.join(', ')}")
end

#empty_module_binding(module_path) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 180

def empty_module_binding(module_path)
  ModuleBinding.new(
    name: module_path,
    types: {}, type_declarations: {}, interfaces: {},
    attributes: {}, attribute_applications: {},
    values: {}, functions: {}, methods: {},
    implemented_interfaces: {}, imports: {},
    private_types: {}, private_interfaces: {}, private_attributes: {},
    private_values: {}, private_functions: {}, private_methods: {},
    private_implemented_interfaces: {},
  )
end

#ensure_argument_assignable!(actual_type, expected_type, external:, message:, expression: nil, scopes: nil) ⇒ Object



483
484
485
486
487
488
489
490
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 483

def ensure_argument_assignable!(actual_type, expected_type, external:, message:, expression: nil, scopes: nil)
  line = source_line(expression)
  column = source_column(expression)
  unless argument_types_compatible?(actual_type, expected_type, external:, expression:, scopes:)
    suggestion = explicit_cast_suggestion(actual_type, expected_type)
    raise SemanticError.new(message, line:, column:, path: @path, suggestion:)
  end
end

#ensure_assignable!(actual_type, expected_type, message, expression: nil, scopes: nil, external_numeric: false, external_pointer_null: false, contextual_int_to_float: false, line: nil, column: nil) ⇒ Object



449
450
451
452
453
454
455
456
457
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 449

def ensure_assignable!(actual_type, expected_type, message, expression: nil, scopes: nil, external_numeric: false, external_pointer_null: false, contextual_int_to_float: false, line: nil, column: nil)
  line ||= source_line(expression)
  column ||= source_column(expression)

  unless types_compatible?(actual_type, expected_type, expression:, scopes:, external_numeric:, external_pointer_null:, contextual_int_to_float:)
    suggestion = explicit_cast_suggestion(actual_type, expected_type)
    raise SemanticError.new(message, line:, column:, path: @path, suggestion:)
  end
end

#ensure_available_type_name!(name, line: nil, column: nil, length: nil) ⇒ Object



705
706
707
708
709
710
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 705

def ensure_available_type_name!(name, line: nil, column: nil, length: nil)
  unless raw_module? || (PRELUDE_TYPE_DEFINING_MODULES[name] == @ctx.module_name)
    ensure_non_reserved_type_binding_name!(name, kind_label: "type", line:, column:, length:)
  end
  raise_sema_error("duplicate type #{name}") if @ctx.types.key?(name) || @ctx.interfaces.key?(name)
end

#ensure_available_value_name!(name, kind_label: "value", line: nil, column: nil, length: nil) ⇒ Object



712
713
714
715
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 712

def ensure_available_value_name!(name, kind_label: "value", line: nil, column: nil, length: nil)
  ensure_non_reserved_value_type_name!(name, kind_label:, line:, column:, length:)
  raise_sema_error("duplicate value #{name}") if @ctx.top_level_values.key?(name) || @ctx.top_level_functions.key?(name)
end

#ensure_non_reserved_import_alias_name!(name, kind_label:, line: nil, column: nil, length: nil) ⇒ Object



727
728
729
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 727

def ensure_non_reserved_import_alias_name!(name, kind_label:, line: nil, column: nil, length: nil)
  ensure_non_reserved_name!(name, Types::RESERVED_IMPORT_ALIAS_NAMES, kind_label:, line:, column:, length:)
end

#ensure_non_reserved_name!(name, reserved_names, kind_label:, line: nil, column: nil, length: nil) ⇒ Object



717
718
719
720
721
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 717

def ensure_non_reserved_name!(name, reserved_names, kind_label:, line: nil, column: nil, length: nil)
  return unless reserved_names.include?(name)

  raise_sema_error("#{kind_label} #{name} uses reserved built-in type name #{name}", line:, column:, length:)
end

#ensure_non_reserved_primitive_name!(name, kind_label:, line: nil, column: nil, length: nil) ⇒ Object



787
788
789
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 787

def ensure_non_reserved_primitive_name!(name, kind_label:, line: nil, column: nil, length: nil)
  ensure_non_reserved_value_type_name!(name, kind_label:, line:, column:, length:)
end

#ensure_non_reserved_type_binding_name!(name, kind_label:, line: nil, column: nil, length: nil) ⇒ Object



731
732
733
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 731

def ensure_non_reserved_type_binding_name!(name, kind_label:, line: nil, column: nil, length: nil)
  ensure_non_reserved_name!(name, Types::RESERVED_TYPE_BINDING_NAMES, kind_label:, line:, column:, length:)
end

#ensure_non_reserved_value_type_name!(name, kind_label:, line: nil, column: nil, length: nil) ⇒ Object



723
724
725
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 723

def ensure_non_reserved_value_type_name!(name, kind_label:, line: nil, column: nil, length: nil)
  ensure_non_reserved_name!(name, Types::RESERVED_VALUE_TYPE_NAMES, kind_label:, line:, column:, length:)
end

#ensure_parallel_for_static_lengths_match!(iterable_types) ⇒ Object



1116
1117
1118
1119
1120
1121
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1116

def ensure_parallel_for_static_lengths_match!(iterable_types)
  lengths = iterable_types.filter_map { |iterable_type| array_type?(iterable_type) ? array_length(iterable_type) : nil }
  return if lengths.empty? || lengths.all? { |length| length == lengths.first }

  raise_sema_error("parallel for iterables must have matching lengths")
end

#error_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


1219
1220
1221
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1219

def error_type?(type)
  type.is_a?(Types::Error)
end

#evaluate_attribute_arg_call(arguments, scopes:) ⇒ Object



525
526
527
528
529
530
531
532
533
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 525

def evaluate_attribute_arg_call(arguments, scopes:)
  attribute_handle = evaluate_compile_time_const_value(arguments.first.value, scopes:)
  return nil unless attribute_handle.is_a?(Types::AttributeHandle)

  param_name = reflection_identifier_name(arguments[1].value, context: "attribute_arg")
  return nil unless attribute_handle.argument_values

  attribute_handle.argument_values[param_name]
end

#evaluate_attribute_of_call(arguments, scopes:) ⇒ Object



508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 508

def evaluate_attribute_of_call(arguments, scopes:)
  target = evaluate_reflection_target_argument(arguments.first.value, scopes:)
  binding = resolve_attribute_name_argument(arguments[1].value)
  validate_attribute_target_compatibility!(target, binding)

  application = find_attribute_application(target, binding)
  return nil unless application

  Types::AttributeHandle.new(
    binding.name,
    binding.module_name,
    target,
    binding.params,
    application.argument_values,
  )
end

#evaluate_attributes_of_call(arguments, scopes:) ⇒ Object



572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 572

def evaluate_attributes_of_call(arguments, scopes:)
  raise_sema_error("attributes_of expects 1 or 2 arguments") unless (1..2).include?(arguments.length)

  target = evaluate_reflection_target_argument(arguments.first.value, scopes:)

  if arguments.length == 2
    attribute_binding = resolve_attribute_name_argument(arguments[1].value)
    application = find_attribute_application(target, attribute_binding)
    return [] unless application

    [Types::AttributeHandle.new(
      attribute_binding.name,
      attribute_binding.module_name,
      target,
      attribute_binding.params,
      application.argument_values,
    )]
  else
    resolved_attribute_applications_for_target(target).map do |application|
      Types::AttributeHandle.new(
        application.binding.name,
        application.binding.module_name,
        target,
        application.binding.params,
        application.argument_values,
      )
    end
  end
end

#evaluate_block_body_const(decl, name) ⇒ Object



231
232
233
234
235
236
237
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 231

def evaluate_block_body_const(decl, name)
  @evaluating_const_values << name
  value = evaluate_compile_time_block(decl.block_body)
  @evaluating_const_values.pop
  set_const_value(name, value)
  value
end

#evaluate_callable_of_call(arguments, scopes:) ⇒ Object



1548
1549
1550
1551
1552
1553
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1548

def evaluate_callable_of_call(arguments, scopes:)
  raise_sema_error("callable_of does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("callable_of expects 1 argument, got #{arguments.length}") unless arguments.length == 1

  resolve_callable_handle_argument(arguments.first.value, scopes:)
end

#evaluate_compile_time_block(statements, scopes: nil) ⇒ Object



253
254
255
256
257
258
259
260
261
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 253

def evaluate_compile_time_block(statements, scopes: nil)
  ctx = CompileTime::BlockContext.new(self)
  result = ctx.evaluate_block(statements, scopes:)
  result
rescue CompileTime::ReturnValue => e
  e.value
rescue CompileTime::Error => e
  raise_sema_error(e.message)
end

#evaluate_compile_time_call(expression, scopes: nil) ⇒ Object



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 322

def evaluate_compile_time_call(expression, scopes: nil)
  case expression.callee
  when AST::Identifier
    if (struct_type = @ctx.types[expression.callee.name]) && struct_type.is_a?(Types::Struct)
      fields = {}
      expression.arguments.each do |argument|
        val = CompileTime.evaluate(argument.value, resolve_identifier: lambda { |id|
          if scopes
            binding = lookup_value(id.name, scopes)
            return binding.const_value unless binding&.const_value.nil?
          end
          resolve_current_module_const_value(id.name)
        }, resolve_member_access: lambda { |ma|
          if (receiver_type = resolve_type_expression(ma.receiver))
            next resolve_enum_member_const_value(receiver_type, ma.member)
          end
          nil
        }, resolve_call: lambda { |inner_call|
          evaluate_compile_time_call(inner_call, scopes:)
        })
        return nil unless val
        fields[argument.name] = val
      end
      return fields
    end

    case expression.callee.name
    when "field_of"
      evaluate_field_of_call(expression.arguments, scopes: scopes || [])
    when "fields_of"
      evaluate_fields_of_call(expression.arguments)
    when "callable_of"
      evaluate_callable_of_call(expression.arguments, scopes: scopes || [])
    when "has_attribute"
      evaluate_has_attribute_call(expression.arguments, scopes: scopes || [])
    when "attribute_of"
      evaluate_attribute_of_call(expression.arguments, scopes: scopes || [])
    when "members_of"
      evaluate_members_of_call(expression.arguments)
    when "attributes_of"
      evaluate_attributes_of_call(expression.arguments, scopes: scopes || [])
    else
      func = @ctx.top_level_functions[expression.callee.name]
      if func&.ast&.respond_to?(:const) && func.ast.const
        evaluate_const_function_body(func, expression.arguments, scopes:)
      else
        evaluate_type_returning_call(expression, scopes:)
      end
    end
  when AST::Specialization
    if expression.callee.callee.is_a?(AST::Identifier) && expression.callee.callee.name == "attribute_arg"
      evaluate_attribute_arg_call(expression.arguments, scopes: scopes || [])
    else
      callee_name = expression.callee.callee.is_a?(AST::Identifier) ? expression.callee.callee.name : nil
      if callee_name
        resolved_type = begin
          resolve_type_expression(expression.callee)
        rescue SemanticError
          nil
        end
        if resolved_type && resolved_type.is_a?(Types::Struct)
          fields = {}
          expression.arguments.each do |argument|
            val = CompileTime.evaluate(argument.value, resolve_identifier: lambda { |id|
              if scopes
                binding = lookup_value(id.name, scopes)
                return binding.const_value unless binding&.const_value.nil?
              end
              resolve_current_module_const_value(id.name)
            }, resolve_member_access: lambda { |ma|
              if (receiver_type = resolve_type_expression(ma.receiver))
                next resolve_enum_member_const_value(receiver_type, ma.member)
              end
              nil
            }, resolve_call: lambda { |inner_call|
              evaluate_compile_time_call(inner_call, scopes:)
            })
            return nil unless val
            fields[argument.name] = val
          end
          return fields
        end
        func = @ctx.top_level_functions[callee_name]
        if func&.ast&.respond_to?(:const) && func.ast.const
          evaluate_const_function_body(func, expression.arguments, scopes:)
        else
          evaluate_type_returning_call(expression, scopes:)
        end
      else
        evaluate_type_returning_call(expression, scopes:)
      end
    end
  else
    nil
  end
end

#evaluate_compile_time_const_value(expression, scopes: nil) ⇒ Object



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 263

def evaluate_compile_time_const_value(expression, scopes: nil)
  CompileTime.evaluate(
    expression,
    resolve_identifier: lambda do |identifier_expression|
      if scopes
        binding = lookup_value(identifier_expression.name, scopes)
        return binding.const_value unless binding&.const_value.nil?
      end

      value = resolve_current_module_const_value(identifier_expression.name)
      return value if value

      # Resolve a bare type-parameter name (e.g. `T`) to its substituted
      # concrete type so `inline if T == int` can fold at compile time.
      current_type_params[identifier_expression.name] || @ctx.types[identifier_expression.name]
    end,
    resolve_member_access: lambda do |member_access_expression|
      if member_access_expression.receiver.is_a?(AST::Identifier) && scopes
        binding = lookup_value(member_access_expression.receiver.name, scopes)
        if binding && binding.const_value
          receiver_value = binding.const_value
          case receiver_value
          when Types::FieldHandle
            case member_access_expression.member
            when "name" then next receiver_value.field_name
            when "type" then next receiver_value.struct_handle.struct_type.field(receiver_value.field_name)
            end
          when Types::MemberHandle
            case member_access_expression.member
            when "name" then next receiver_value.name
            when "value" then next receiver_value.value
            end
          when Types::AttributeHandle
            case member_access_expression.member
            when "name" then next receiver_value.name
            end
          end
        end
      end

      if (receiver_type = resolve_type_expression(member_access_expression.receiver))
        next resolve_enum_member_const_value(receiver_type, member_access_expression.member)
      end

      next unless member_access_expression.receiver.is_a?(AST::Identifier)

      resolve_imported_module_const_value(member_access_expression.receiver.name, member_access_expression.member)
    end,
    resolve_type_ref: lambda do |type_ref|
      resolve_type_ref(type_ref)
    end,
    resolve_call: lambda do |call_expression|
      evaluate_compile_time_call(call_expression, scopes:)
    end,
  )
rescue CompileTime::Error => e
  raise_sema_error(e.message)
end

#evaluate_const_function_body(func, arguments, scopes:) ⇒ Object



471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 471

def evaluate_const_function_body(func, arguments, scopes:)
  return nil unless func.ast.params.length == arguments.length

  initial_vars = {}
  func.ast.params.each_with_index do |param, idx|
    arg_expr = arguments[idx].value
    arg_value = if scopes
      evaluate_compile_time_const_value(arg_expr, scopes:)
    else
      CompileTime.evaluate(
        arg_expr,
        resolve_identifier: lambda { |id| resolve_current_module_const_value(id.name) },
        resolve_member_access: nil,
        resolve_type_ref: lambda { |tr| resolve_type_ref(tr) },
        resolve_call: nil,
      )
    end
    return nil unless arg_value

    initial_vars[param.name] = arg_value
  end

  ctx = CompileTime::BlockContext.new(self, initial_variables: initial_vars)
  ctx.evaluate_block(func.ast.body, scopes: nil)
rescue CompileTime::ReturnValue => e
  e.value
rescue CompileTime::Error => e
  raise_sema_error(e.message)
end

#evaluate_enum_member_const_value(expression, enum_type:, member_values:) ⇒ Object



602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 602

def evaluate_enum_member_const_value(expression, enum_type:, member_values:)
  CompileTime.evaluate(
    expression,
    resolve_identifier: lambda do |identifier_expression|
      resolve_current_module_const_value(identifier_expression.name)
    end,
    resolve_member_access: lambda do |member_access_expression|
      if (receiver_type = resolve_type_expression(member_access_expression.receiver))
        next resolve_enum_member_const_value(
          receiver_type,
          member_access_expression.member,
          local_enum_type: enum_type,
          local_member_values: member_values,
        )
      end

      next unless member_access_expression.receiver.is_a?(AST::Identifier)

      resolve_imported_module_const_value(member_access_expression.receiver.name, member_access_expression.member)
    end,
    resolve_type_ref: lambda do |type_ref|
      resolve_type_ref(type_ref)
    end,
  )
end

#evaluate_field_of_call(arguments, scopes:) ⇒ Object



1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1536

def evaluate_field_of_call(arguments, scopes:)
  raise_sema_error("field_of does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("field_of expects 2 arguments, got #{arguments.length}") unless arguments.length == 2

  struct_handle = resolve_struct_handle_argument(arguments.first.value, scopes:)
  field_name = reflection_identifier_name(arguments[1].value, context: "field_of")
  field_handle = CompileTime::Reflection.core_field_handle(struct_handle, field_name)
  raise_sema_error("unknown field #{struct_handle.struct_type}.#{field_name}") unless field_handle

  field_handle
end

#evaluate_fields_of_call(arguments) ⇒ Object



547
548
549
550
551
552
553
554
555
556
557
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 547

def evaluate_fields_of_call(arguments)
  raise_sema_error("fields_of expects 1 argument") unless arguments.length == 1

  type = resolve_type_expression(arguments.first.value)
  raise_sema_error("fields_of requires a type argument") unless type

  handle = struct_handle_for_type(type)
  raise_sema_error("fields_of requires a struct type, got #{type}") unless handle

  CompileTime::Reflection.core_field_handles(handle)
end

#evaluate_has_attribute_call(arguments, scopes:) ⇒ Object



501
502
503
504
505
506
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 501

def evaluate_has_attribute_call(arguments, scopes:)
  target = evaluate_reflection_target_argument(arguments.first.value, scopes:)
  binding = resolve_attribute_name_argument(arguments[1].value)
  validate_attribute_target_compatibility!(target, binding)
  !find_attribute_application(target, binding).nil?
end

#evaluate_members_of_call(arguments) ⇒ Object



559
560
561
562
563
564
565
566
567
568
569
570
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 559

def evaluate_members_of_call(arguments)
  raise_sema_error("members_of expects 1 argument") unless arguments.length == 1

  type = resolve_type_expression(arguments.first.value)
  raise_sema_error("members_of requires a type argument") unless type

  unless type.is_a?(Types::Enum) || type.is_a?(Types::Flags)
    raise_sema_error("members_of requires an enum or flags type, got #{type}")
  end

  CompileTime::Reflection.core_member_handles(type)
end

#evaluate_reflection_target_argument(expression, scopes:) ⇒ Object



535
536
537
538
539
540
541
542
543
544
545
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 535

def evaluate_reflection_target_argument(expression, scopes:)
  if (type_expr = resolve_type_expression(expression))
    handle = struct_handle_for_type(type_expr)
    return handle if handle
  end

  value = evaluate_compile_time_const_value(expression, scopes:)
  return value if value.is_a?(Types::FieldHandle) || value.is_a?(Types::CallableHandle)

  nil
end

#evaluate_top_level_const_value(name) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 191

def evaluate_top_level_const_value(name)
  return @ctx.top_level_values.fetch(name).const_value if @evaluated_const_values.key?(name)

  raise_sema_error("cyclic constant value dependency involving #{name}") if @evaluating_const_values.include?(name)

  decl = @ctx.const_declarations.fetch(name)
  if decl.block_body
    return evaluate_block_body_const(decl, name)
  end

  if decl.value.is_a?(AST::ErrorExpr)
    @evaluated_const_values[name] = true
    return nil
  end

  @evaluating_const_values << name
  value = evaluate_compile_time_const_value(decl.value)
  @evaluating_const_values.pop

  if value.is_a?(Integer)
    binding_ = @ctx.top_level_values.fetch(name)
    check_type = binding_.type
    check_type = check_type.backing_type if check_type.respond_to?(:backing_type)

    if check_type.is_a?(Types::Primitive) && check_type.integer? && (width = check_type.integer_width)
      max_value = if check_type.signed_integer?
                    (1 << (width - 1)) - 1
                  else
                    (1 << width) - 1
                  end
      if value > max_value
        raise_sema_error("constant #{name} value #{value} does not fit in type #{binding_.type}")
      end
    end
  end

  set_const_value(name, value)
  value
end

#evaluate_type_returning_call(expression, scopes:) ⇒ Object



419
420
421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 419

def evaluate_type_returning_call(expression, scopes:)
  callee_name, type_args = extract_type_callee_info(expression)
  return nil unless callee_name

  CompileTime::Reflection.core_evaluate_type_returning(
    callee_name, type_args,
    evaluate_value: ->(v) { evaluate_compile_time_const_value(v, scopes:) },
    resolve_type_ref: ->(tr) { resolve_type_ref(tr) },
    pointer_to: ->(t) { pointer_to(t) },
    const_pointer_to: ->(t) { const_pointer_to(t) },
    top_level_functions: ->(name) { @ctx.top_level_functions[name] },
    evaluate_type_returning_function_body: ->(func, targs) { evaluate_type_returning_function_body(func, targs) },
  )
end

#evaluate_type_returning_function_body(func, type_args) ⇒ Object



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 446

def evaluate_type_returning_function_body(func, type_args)
  value_params = func.ast.type_params.select { |p| p.is_a?(AST::ValueTypeParam) }
  return nil if value_params.empty?

  initial_vars = {}
  value_params.zip(type_args).each do |param, arg|
    arg_value = arg.value
    case arg_value
    when AST::IntegerLiteral
      initial_vars[param.name] = arg_value.value
    when AST::TypeRef
      initial_vars[param.name] = resolve_type_ref(arg_value)
    else
      return nil
    end
  end

  ctx = CompileTime::BlockContext.new(self, initial_variables: initial_vars)
  ctx.evaluate_block(func.ast.body, scopes: nil)
rescue CompileTime::ReturnValue => e
  e.value
rescue CompileTime::Error => e
  raise_sema_error(e.message)
end

#evaluate_when_discriminant(expression) ⇒ Object



1268
1269
1270
1271
1272
1273
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1268

def evaluate_when_discriminant(expression)
  value = evaluate_compile_time_const_value(expression, scopes: [])
  raise_sema_error("when discriminant must be a compile-time constant", expression) if value.nil?

  value
end

#event_carrier_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


767
768
769
770
771
772
773
774
775
776
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 767

def event_carrier_type?(type)
  case type
  when Types::StructInstance
    type.definition.respond_to?(:has_events?) && type.definition.has_events?
  when Types::Struct
    type.respond_to?(:has_events?) && type.has_events?
  else
    false
  end
end

#event_listener_type(event_type) ⇒ Object



624
625
626
627
628
629
630
631
632
633
634
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 624

def event_listener_type(event_type)
  params = []
  params << Types::Registry.parameter("value", event_type.payload_type) if event_type.payload_type

  Types::Registry.function(
    nil,
    params:,
    return_type: @ctx.types.fetch("void"),
    external: false,
  )
end

#event_member_type(receiver_type, name) ⇒ Object



742
743
744
745
746
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 742

def event_member_type(receiver_type, name)
  return unless receiver_type.respond_to?(:event)

  receiver_type.event(name)
end

#event_method_kind(receiver_type, name) ⇒ Object



648
649
650
651
652
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 648

def event_method_kind(receiver_type, name)
  return unless event_type?(receiver_type)

  EVENT_METHOD_KINDS[name]
end

#event_method_name(kind) ⇒ Object



700
701
702
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 700

def event_method_name(kind)
  EVENT_METHOD_NAMES.fetch(kind)
end

#event_receiver_mutable?(receiver_expression, scopes:) ⇒ Boolean

Returns:

  • (Boolean)


708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 708

def event_receiver_mutable?(receiver_expression, scopes:)
  return true if top_level_event_receiver?(receiver_expression, scopes:)
  return false unless receiver_expression.is_a?(AST::MemberAccess)

  receiver_type = infer_lvalue_receiver(
    receiver_expression.receiver,
    scopes:,
    allow_ref_identifier: true,
    allow_pointer_identifier: true,
    require_mutable_pointer: true,
    allow_span_param_identifier: true,
  )
  receiver_type = project_field_receiver_type(receiver_type, require_mutable_pointer: true)
  !event_member_type(receiver_type, receiver_expression.member).nil?
rescue SemanticError
  false
end

#event_stateful_listener_type(event_type, state_type) ⇒ Object



636
637
638
639
640
641
642
643
644
645
646
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 636

def event_stateful_listener_type(event_type, state_type)
  params = [Types::Registry.parameter("state", Types::Registry.generic_instance("ptr", [state_type]))]
  params << Types::Registry.parameter("value", event_type.payload_type) if event_type.payload_type

  Types::Registry.function(
    nil,
    params:,
    return_type: @ctx.types.fetch("void"),
    external: false,
  )
end

#event_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


751
752
753
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 751

def event_type?(type)
  type.is_a?(Types::Event)
end

#event_visible_from_current_module?(event_type) ⇒ Boolean

Returns:

  • (Boolean)


704
705
706
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 704

def event_visible_from_current_module?(event_type)
  event_type.module_name == @ctx.module_name || event_type.visibility == :public
end

#exact_compile_time_numeric_compatibility?(actual_type, expression, expected_type, scopes: nil) ⇒ Boolean

Returns:

  • (Boolean)


133
134
135
136
137
138
139
140
141
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 133

def exact_compile_time_numeric_compatibility?(actual_type, expression, expected_type, scopes: nil)
  return false unless expected_type.is_a?(Types::Primitive) && expected_type.numeric?
  return false if actual_type.is_a?(Types::EnumBase)

  value = evaluate_compile_time_const_value(expression, scopes:)
  return false unless value.is_a?(Numeric)

  numeric_constant_fits_type?(value, expected_type)
end

#expanded_declarationsObject



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 202

def expanded_declarations
  Enumerator.new do |yielder|
    @ctx.ast.declarations.each do |decl|
      case decl
      when AST::WhenStmt
        body = when_chosen_body(decl)
        if body
          body.each { |nested| yielder << nested }
        end
      else
        yielder << decl
      end
    end
  end
end

#explicit_cast_suggestion(actual_type, expected_type) ⇒ Object



459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 459

def explicit_cast_suggestion(actual_type, expected_type)
  if expected_type.is_a?(Types::Span) && array_type?(actual_type) &&
     array_element_type(actual_type) == expected_type.element_type
    return "a span is a mutable view; the array must be a mutable addressable value — bind it with `var` (a `let` array does not coerce)"
  end

  if castable_primitive?(actual_type) && castable_primitive?(expected_type)
    return nil if actual_type == expected_type
    "use an explicit cast: `#{expected_type}<-(value)`"
  end

  if actual_type.is_a?(Types::Nullable) && expected_type.is_a?(Types::Nullable)
    explicit_cast_suggestion(actual_type.base, expected_type.base)
  else
    nil
  end
end

#explicit_declaration_type_for(stmt) ⇒ Object



489
490
491
492
493
494
495
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 489

def explicit_declaration_type_for(stmt)
  annotation = stmt.respond_to?(:type) ? stmt.type : nil
  return nil unless annotation

  name = annotation.respond_to?(:name) ? annotation.name : annotation.to_s
  @ctx.types[name]
end

#expression_contains_await?(expression) ⇒ Boolean

Returns:

  • (Boolean)


245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 245

def expression_contains_await?(expression)
  case expression
  when AST::AwaitExpr
    true
  when AST::Call, AST::Specialization
    expression_contains_await?(expression.callee) || expression.arguments.any? { |argument| expression_contains_await?(argument.value) }
  when AST::UnaryOp
    expression_contains_await?(expression.operand)
  when AST::BinaryOp
    expression_contains_await?(expression.left) || expression_contains_await?(expression.right)
  when AST::IfExpr
    expression_contains_await?(expression.condition) || expression_contains_await?(expression.then_expression) || expression_contains_await?(expression.else_expression)
  when AST::MatchExpr
    expression_contains_await?(expression.expression) || expression.arms.any? { |arm| expression_contains_await?(arm.pattern) || expression_contains_await?(arm.value) }
  when AST::UnsafeExpr
    expression_contains_await?(expression.expression)
  when AST::PrefixCast
    expression_contains_await?(expression.expression)
  when AST::MemberAccess
    expression_contains_await?(expression.receiver)
  when AST::IndexAccess
    expression_contains_await?(expression.receiver) || expression_contains_await?(expression.index)
  when AST::FormatString
    expression.parts.any? { |part| part.is_a?(AST::FormatExprPart) && expression_contains_await?(part.expression) }
  else
    false
  end
end

#expression_end_line(node) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 235

def expression_end_line(node)
  return nil unless node

  lines = [node.line]

  case node
  when AST::MemberAccess
    lines << expression_end_line(node.receiver)
  when AST::IndexAccess
    lines << expression_end_line(node.receiver)
    lines << expression_end_line(node.index)
  when AST::Specialization
    lines << expression_end_line(node.callee)
  when AST::Call
    lines << expression_end_line(node.callee)
    node.arguments.each { |argument| lines << expression_end_line(argument.value) }
  when AST::Argument
    lines << expression_end_line(node.value)
  when AST::UnaryOp
    lines << expression_end_line(node.operand)
  when AST::BinaryOp
    lines << expression_end_line(node.left)
    lines << expression_end_line(node.right)
  when AST::IfExpr
    lines << expression_end_line(node.condition)
    lines << expression_end_line(node.then_expression)
    lines << expression_end_line(node.else_expression)
  when AST::MatchExpr
    lines << expression_end_line(node.expression)
    node.arms.each do |arm|
      lines << expression_end_line(arm.pattern)
      lines << expression_end_line(arm.value)
    end
  when AST::AwaitExpr
    lines << expression_end_line(node.expression)
  when AST::FormatExprPart
    lines << expression_end_line(node.expression)
  when AST::PrefixCast
    lines << expression_end_line(node.expression)
  end

  lines.compact.max
end

#extern_enum_integer_argument_compatibility?(actual_type, expected_type) ⇒ Boolean

Returns:

  • (Boolean)


157
158
159
160
161
162
163
164
165
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 157

def extern_enum_integer_argument_compatibility?(actual_type, expected_type)
  return unless actual_type.is_a?(Types::EnumBase)
  return unless expected_type.is_a?(Types::Primitive) && expected_type.integer? && expected_type.fixed_width_integer?

  backing_type = actual_type.backing_type
  return unless backing_type.is_a?(Types::Primitive) && backing_type.integer? && backing_type.fixed_width_integer?

  backing_type.integer_width == expected_type.integer_width
end

#external_numeric_assignment_target?(expression, scopes:) ⇒ Boolean

Returns:

  • (Boolean)


124
125
126
127
128
129
130
131
132
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 124

def external_numeric_assignment_target?(expression, scopes:)
  case expression
  when AST::MemberAccess
    receiver_type = infer_field_receiver_type(expression.receiver, scopes:, require_mutable_pointer: true)
    receiver_type.respond_to?(:external) && receiver_type.external
  else
    false
  end
end

#external_void_pointer_argument_compatibility?(actual_type, expected_type) ⇒ Boolean

Returns:

  • (Boolean)


117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 117

def external_void_pointer_argument_compatibility?(actual_type, expected_type)
  if actual_type.is_a?(Types::Nullable) && expected_type.is_a?(Types::Nullable)
    return external_void_pointer_argument_compatibility?(actual_type.base, expected_type.base)
  end

  return external_void_pointer_argument_compatibility?(actual_type, expected_type.base) if expected_type.is_a?(Types::Nullable)
  return false if actual_type.is_a?(Types::Nullable)
  return false unless pointer_type?(actual_type) && pointer_type?(expected_type)
  return false if const_pointer_type?(actual_type) && mutable_pointer_type?(expected_type)

  actual_pointee = pointee_type(actual_type)
  expected_pointee = pointee_type(expected_type)

  actual_pointee == @ctx.types.fetch("void") || expected_pointee == @ctx.types.fetch("void")
end

#extract_type_callee_info(expression) ⇒ Object



434
435
436
437
438
439
440
441
442
443
444
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 434

def extract_type_callee_info(expression)
  if expression.is_a?(AST::Call) && expression.callee.is_a?(AST::Identifier)
    [expression.callee.name, nil]
  elsif expression.is_a?(AST::Specialization)
    if expression.callee.is_a?(AST::Identifier)
      [expression.callee.name, expression.arguments]
    elsif expression.callee.is_a?(AST::Specialization) && expression.callee.callee.is_a?(AST::Identifier)
      [expression.callee.callee.name, expression.callee.arguments]
    end
  end
end

#fallback_completion_typeObject



497
498
499
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 497

def fallback_completion_type
  @ctx.types["int"] || @ctx.types.values.first
end

#fill_parameter_defaults!(by_position, params, binding, context_name) ⇒ Object



1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 1040

def fill_parameter_defaults!(by_position, params, binding, context_name)
  return unless binding

  ast_params = binding.ast.params
  return unless ast_params.any? { |p| p.respond_to?(:default_value) && p.default_value }

  params.each_with_index do |param, idx|
    next unless by_position[idx].nil?
    next unless idx < ast_params.length

    ast_param = ast_params[idx]
    next unless ast_param.respond_to?(:default_value) && ast_param.default_value

    by_position[idx] = AST::Argument.new(name: nil, value: ast_param.default_value)
  end
end

#fill_positional_defaults!(arguments, binding) ⇒ Object



1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 1057

def fill_positional_defaults!(arguments, binding)
  return unless binding.ast.params.any? { |p| p.respond_to?(:default_value) && p.default_value }

  ast_params = binding.ast.params
  while arguments.length < ast_params.length
    ast_param = ast_params[arguments.length]
    if ast_param.respond_to?(:default_value) && ast_param.default_value
      arguments << AST::Argument.new(name: nil, value: ast_param.default_value)
    else
      break
    end
  end
end

#finalize_top_level_const_valuesObject



187
188
189
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 187

def finalize_top_level_const_values
  @ctx.const_declarations.each_key { |name| evaluate_top_level_const_value(name) }
end

#find_attribute_application(target, binding) ⇒ Object



25
26
27
28
29
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 25

def find_attribute_application(target, binding)
  resolved_attribute_applications_for_target(target).find do |application|
    same_attribute_binding?(application.binding, binding)
  end
end

#find_reachable_imported_module(module_name) ⇒ Object



98
99
100
101
102
103
104
105
106
107
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 98

def find_reachable_imported_module(module_name)
  visited = {}

  @ctx.imports.each_value do |module_binding|
    found = find_reachable_imported_module_from(module_binding, module_name, visited)
    return found if found
  end

  nil
end

#find_reachable_imported_module_from(module_binding, module_name, visited) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 109

def find_reachable_imported_module_from(module_binding, module_name, visited)
  return nil unless module_binding
  return module_binding if module_binding.name == module_name
  return nil if visited[module_binding.name]

  visited[module_binding.name] = true
  module_binding.imports.each_value do |imported_module|
    found = find_reachable_imported_module_from(imported_module, module_name, visited)
    return found if found
  end

  nil
end

#finish_local_completion_frame(binding) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 111

def finish_local_completion_frame(binding)
  return if @active_local_completion_stack.empty?

  frame = @active_local_completion_stack.pop
  snapshots = frame[:snapshots]
  if snapshots.empty?
    return
  end

  start_line = [binding.ast.line, snapshots.first.line].compact.min
  end_line = snapshots.last.line

  # Extend end_line to cover the actual function body so that completion
  # and hover lookups resolve correctly even when snapshots don't reach
  # the final body line (common for generic functions whose bodies are
  # only partially analysed, and functions whose last statement does not
  # record a snapshot).
  if binding.ast.respond_to?(:body) && binding.ast.body
    body_end = last_ast_line(binding.ast.body)
    end_line = body_end if body_end && body_end > end_line
  end

  @local_completion_frames << LocalCompletionFrame.new(
    start_line:,
    end_line:,
    function_name: frame[:function_name],
    receiver_type: frame[:receiver_type],
    snapshots: snapshots.freeze,
  )
end

#float_literal_expression?(expression) ⇒ Boolean

Returns:

  • (Boolean)


888
889
890
891
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 888

def float_literal_expression?(expression)
  expression.is_a?(AST::FloatLiteral) ||
    (expression.is_a?(AST::UnaryOp) && ["+", "-"].include?(expression.operator) && float_literal_expression?(expression.operand))
end

#flow_refinements(expression, truthy:, scopes:) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 68

def flow_refinements(expression, truthy:, scopes:)
  case expression
  when AST::Call
    if truthy && has_attribute_refinement_call?(expression)
      key = attribute_presence_key_from_call(expression, scopes:)
      return key ? { key => true } : {}
    end
  when AST::UnaryOp
    return flow_refinements(expression.operand, truthy: !truthy, scopes:) if expression.operator == "not"
  when AST::BinaryOp
    case expression.operator
    when "and"
      if truthy
        left_truthy = flow_refinements(expression.left, truthy: true, scopes:)
        right_scopes = scopes_with_refinements(scopes, left_truthy)
        right_truthy = flow_refinements(expression.right, truthy: true, scopes: right_scopes)
        return merge_refinements(left_truthy, right_truthy)
      end
    when "or"
      unless truthy
        left_falsy = flow_refinements(expression.left, truthy: false, scopes:)
        right_scopes = scopes_with_refinements(scopes, left_falsy)
        right_falsy = flow_refinements(expression.right, truthy: false, scopes: right_scopes)
        return merge_refinements(left_falsy, right_falsy)
      end
    when "==", "!="
      return null_test_refinements(expression, truthy:, scopes:)
    end
  end

  {}
end

#foreign_argument_actual_type(parameter, argument, scopes:, function_name:, expected_type: parameter.type) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 119

def foreign_argument_actual_type(parameter, argument, scopes:, function_name:, expected_type: parameter.type)
  case parameter.passing_mode
  when :plain
    infer_expression(argument.value, scopes:, expected_type:)
  when :consuming
    foreign_consuming_argument_binding(parameter, argument, scopes:, function_name:)
    parameter.type
  when :in, :out, :inout
    if (legacy_passing_mode = foreign_argument_legacy_passing_mode(argument))
      raise_sema_error("argument #{parameter.name} to #{function_name} must not use #{legacy_passing_mode}; directional passing is declared on #{function_name}")
    end

    if parameter.passing_mode == :in
      infer_expression(argument.value, scopes:, expected_type: expected_type)
    else
      infer_lvalue(argument.value, scopes:)
    end
  else
    raise_sema_error("unsupported foreign passing mode #{parameter.passing_mode}")
  end
end

#foreign_argument_expression(argument) ⇒ Object



105
106
107
108
109
110
111
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 105

def foreign_argument_expression(argument)
  if argument.value.is_a?(AST::UnaryOp) && ["out", "in", "inout"].include?(argument.value.operator)
    argument.value.operand
  else
    argument.value
  end
end

#foreign_argument_legacy_passing_mode(argument) ⇒ Object



113
114
115
116
117
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 113

def foreign_argument_legacy_passing_mode(argument)
  return nil unless argument.value.is_a?(AST::UnaryOp) && ["out", "in", "inout"].include?(argument.value.operator)

  argument.value.operator
end

#foreign_boundary_nullable_pointer_like_base?(base) ⇒ Boolean

Returns:

  • (Boolean)


12
13
14
15
16
17
18
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 12

def foreign_boundary_nullable_pointer_like_base?(base)
  return true if base.is_a?(Types::Function) || base.is_a?(Types::Proc)
  return true if opaque_type?(base)
  return true if base == @ctx.types.fetch("cstr")

  pointer_type?(base) || const_pointer_type?(base) || ref_type?(base)
end

#foreign_call_consumes_binding?(binding) ⇒ Boolean

Returns:

  • (Boolean)


1171
1172
1173
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1171

def foreign_call_consumes_binding?(binding)
  binding.type.params.any? { |parameter| parameter.passing_mode == :consuming }
end

#foreign_consuming_argument_binding(parameter, argument, scopes:, function_name:) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 141

def foreign_consuming_argument_binding(parameter, argument, scopes:, function_name:)
  unless argument.value.is_a?(AST::Identifier)
    raise_sema_error("consuming argument #{parameter.name} to #{function_name} must be a bare nullable local or parameter binding")
  end

  binding = lookup_value(argument.value.name, scopes)
  unless binding && %i[let var param].include?(binding.kind) && binding.storage_type.is_a?(Types::Nullable) && binding.storage_type.base == parameter.type
    raise_sema_error("consuming argument #{parameter.name} to #{function_name} must be a bare nullable local or parameter binding")
  end

  binding
end

#foreign_cstr_argument_compatible?(actual_type, parameter, expression:) ⇒ Boolean

Returns:

  • (Boolean)


32
33
34
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 32

def foreign_cstr_argument_compatible?(actual_type, parameter, expression:)
  types_compatible?(actual_type, parameter.type, expression:) || actual_type == @ctx.types.fetch("cstr")
end

#foreign_cstr_boundary_parameter?(parameter) ⇒ Boolean

Returns:

  • (Boolean)


28
29
30
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 28

def foreign_cstr_boundary_parameter?(parameter)
  parameter.boundary_type == @ctx.types.fetch("cstr") && parameter.type == @ctx.types.fetch("str")
end

#foreign_function_binding?(binding) ⇒ Boolean

Returns:

  • (Boolean)


79
80
81
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 79

def foreign_function_binding?(binding)
  binding.ast.is_a?(AST::ForeignFunctionDecl)
end

#foreign_mapping_auto_call_shorthand?(expression) ⇒ Boolean

Returns:

  • (Boolean)


92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 92

def foreign_mapping_auto_call_shorthand?(expression)
  case expression
  when AST::Identifier
    true
  when AST::MemberAccess
    foreign_mapping_auto_call_shorthand?(expression.receiver)
  when AST::Specialization
    foreign_mapping_auto_call_shorthand?(expression.callee)
  else
    false
  end
end

#foreign_mapping_context?Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 94

def foreign_mapping_context?
  @foreign_mapping_depth.positive?
end

#foreign_mapping_expression(decl) ⇒ Object



83
84
85
86
87
88
89
90
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 83

def foreign_mapping_expression(decl)
  return decl.mapping unless foreign_mapping_auto_call_shorthand?(decl.mapping)

  AST::Call.new(
    callee: decl.mapping,
    arguments: decl.params.map { |param| AST::Argument.new(name: nil, value: AST::Identifier.new(name: param.name)) },
  )
end

#foreign_mapping_public_alias_name(name) ⇒ Object



65
66
67
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 65

def foreign_mapping_public_alias_name(name)
  "#{name}_public"
end

#foreign_mapping_reference_counts(expression, counts = Hash.new(0)) ⇒ Object



1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1175

def foreign_mapping_reference_counts(expression, counts = Hash.new(0))
  case expression
  when AST::Identifier
    counts[expression.name] += 1
  when AST::MemberAccess
    foreign_mapping_reference_counts(expression.receiver, counts)
  when AST::IndexAccess
    foreign_mapping_reference_counts(expression.receiver, counts)
    foreign_mapping_reference_counts(expression.index, counts)
  when AST::Specialization, AST::Call
    foreign_mapping_reference_counts(expression.callee, counts)
    expression.arguments.each { |argument| foreign_mapping_reference_counts(argument.value, counts) }
  when AST::UnaryOp
    foreign_mapping_reference_counts(expression.operand, counts)
  when AST::BinaryOp
    foreign_mapping_reference_counts(expression.left, counts)
    foreign_mapping_reference_counts(expression.right, counts)
  when AST::IfExpr
    foreign_mapping_reference_counts(expression.condition, counts)
    foreign_mapping_reference_counts(expression.then_expression, counts)
    foreign_mapping_reference_counts(expression.else_expression, counts)
  when AST::UnsafeExpr
    foreign_mapping_reference_counts(expression.expression, counts)
  when AST::PrefixCast
    foreign_mapping_reference_counts(expression.expression, counts)
  end

  counts
end

#foreign_parameter_boundary_type(param, public_type, type_params:, type_param_constraints: current_type_param_constraints) ⇒ Object



36
37
38
39
40
41
42
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 36

def foreign_parameter_boundary_type(param, public_type, type_params:, type_param_constraints: current_type_param_constraints)
  return resolve_type_ref(param.boundary_type, type_params:, type_param_constraints:) if param.boundary_type
  return const_pointer_to(public_type) if param.mode == :in
  return pointer_to(foreign_slot_boundary_value_type(public_type)) if [:out, :inout].include?(param.mode)

  nil
end

#foreign_slot_boundary_value_type(public_type) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 44

def foreign_slot_boundary_value_type(public_type)
  if public_type.is_a?(Types::Nullable) && pointer_type?(public_type.base)
    return public_type.base
  end

  public_type
end

#format_string_integer_base_spec_supported?(type) ⇒ Boolean

Returns:

  • (Boolean)


1519
1520
1521
1522
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1519

def format_string_integer_base_spec_supported?(type)
  resolved = type.is_a?(Types::EnumBase) ? type.backing_type : type
  resolved.is_a?(Types::Primitive) && resolved.integer?
end

#format_string_interpolation_supported?(type, context:) ⇒ Boolean

Returns:

  • (Boolean)


1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1524

def format_string_interpolation_supported?(type, context:)
  return true if type == @ctx.types.fetch("str")
  return true if type == @ctx.types.fetch("cstr")
  return true if type == @ctx.types.fetch("bool")
  return true if type.is_a?(Types::Primitive) && type.integer?
  return true if type.is_a?(Types::Primitive) && type.float?
  return true if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
  return true if resolve_explicit_format_binding(type, context:)

  false
end

#freeze_scope_bindings(scope) ⇒ Object



1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1104

def freeze_scope_bindings(scope)
  frozen_scope = scope.is_a?(FlowScope) ? FlowScope.new : {}
  scope.each do |name, binding|
    frozen_scope[name] = ValueBinding.new(
      id: binding.id,
      name: binding.name,
      storage_type: binding.storage_type,
      flow_type: binding.flow_type,
      mutable: false,
      kind: binding.kind,
      const_value: binding.const_value,
    )
  end
  frozen_scope
end

#fresh_noncopyable_event_initializer?(expression, target_type, scopes:) ⇒ Boolean

Returns:

  • (Boolean)


748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 748

def fresh_noncopyable_event_initializer?(expression, target_type, scopes:)
  return true unless expression

  case expression
  when AST::Call
    callable_kind, callable, _receiver = resolve_callable(expression.callee, scopes:)
    callable_kind == :struct && callable == target_type
  when AST::Specialization
    return false unless expression.callee.is_a?(AST::Identifier)
    return false unless %w[zero default].include?(expression.callee.name)
    return false unless expression.arguments.length == 1

    type_arg = expression.arguments.first.value
    type_arg.is_a?(AST::TypeRef) && resolve_type_ref(type_arg) == target_type
  else
    false
  end
rescue SemanticError
  false
end

#function_pointer_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


333
334
335
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 333

def function_pointer_type?(type)
  type.is_a?(Types::Function)
end

#function_type_for_name(name) ⇒ Object



109
110
111
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 109

def function_type_for_name(name)
  @ctx.top_level_functions.fetch(name).type
end

#generic_integer_type_argument?(argument) ⇒ Boolean

Returns:

  • (Boolean)


790
791
792
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 790

def generic_integer_type_argument?(argument)
  integer_type_argument?(argument) || argument.is_a?(Types::TypeVar)
end

#harmonize_binary_float_literal_types(left_expression, right_expression, left_type, right_type, scopes:) ⇒ Object



876
877
878
879
880
881
882
883
884
885
886
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 876

def harmonize_binary_float_literal_types(left_expression, right_expression, left_type, right_type, scopes:)
  if float_literal_expression?(left_expression) && right_type.is_a?(Types::Primitive) && right_type.float?
    left_type = infer_expression(left_expression, scopes:, expected_type: right_type)
  end

  if float_literal_expression?(right_expression) && left_type.is_a?(Types::Primitive) && left_type.float?
    right_type = infer_expression(right_expression, scopes:, expected_type: left_type)
  end

  [left_type, right_type]
end

#harmonize_binary_integer_literal_types(left_expression, right_expression, left_type, right_type, scopes:) ⇒ Object



893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 893

def harmonize_binary_integer_literal_types(left_expression, right_expression, left_type, right_type, scopes:)
  if integer_literal_expression?(left_expression) && right_type.is_a?(Types::Primitive) && right_type.integer?
    if exact_compile_time_numeric_compatibility?(left_type, left_expression, right_type, scopes:)
      left_type = infer_expression(left_expression, scopes:, expected_type: right_type)
    end
  end

  if integer_literal_expression?(right_expression) && left_type.is_a?(Types::Primitive) && left_type.integer?
    if exact_compile_time_numeric_compatibility?(right_type, right_expression, left_type, scopes:)
      right_type = infer_expression(right_expression, scopes:, expected_type: left_type)
    end
  end

  [left_type, right_type]
end

#has_attribute_refinement_call?(expression) ⇒ Boolean

Returns:

  • (Boolean)


171
172
173
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 171

def has_attribute_refinement_call?(expression)
  expression.callee.is_a?(AST::Identifier) && expression.callee.name == "has_attribute" && expression.arguments.length == 2 && expression.arguments.none?(&:name)
end

#implicit_ref_argument_compatible?(actual_type, expected_type, expression, scopes) ⇒ Boolean

Returns:

  • (Boolean)


16
17
18
19
20
21
22
23
24
25
26
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 16

def implicit_ref_argument_compatible?(actual_type, expected_type, expression, scopes)
  return false unless expression && scopes
  return false unless ref_type?(expected_type)
  return false unless actual_type == referenced_type(expected_type)
  return false unless safe_reference_source_expression?(expression, scopes:)

  infer_lvalue(expression, scopes:)
  true
rescue SemanticError
  false
end

#import_suggestion_for_type(type_name) ⇒ Object



1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1426

def import_suggestion_for_type(type_name)
  return nil if @ctx.global_import_index.nil? || @ctx.global_import_index.empty?

  type_str = type_name.to_s
  return nil if type_str.empty?

  if @ctx.global_import_index.key?(type_str)
    entry = @ctx.global_import_index[type_str]
    return nil if entry.nil?
    module_path = entry.is_a?(Array) ? entry.first : entry
    if module_path && !@ctx.imports.key?(module_path.to_s.split(".").first) && !@ctx.imports.key?(module_path.to_s)
      return "type '#{type_str}' is available via 'import #{module_path}'"
    end
  end

  nil
end

#imported_module_binding_for_name(module_name) ⇒ Object



1629
1630
1631
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1629

def imported_module_binding_for_name(module_name)
  @ctx.imports.each_value.find { |binding| binding.name == module_name }
end

#imported_module_with_private_method(receiver_type, method_name) ⇒ Object



1005
1006
1007
1008
1009
1010
1011
1012
1013
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1005

def imported_module_with_private_method(receiver_type, method_name)
  imported_module = imported_module_with_private_method_local(receiver_type, method_name)
  return imported_module if imported_module

  fallback_owner = specialization_lookup_owner
  return nil unless fallback_owner

  fallback_owner.imported_module_with_private_method_local(receiver_type, method_name)
end

#imported_module_with_private_method_local(receiver_type, method_name) ⇒ Object



1015
1016
1017
1018
1019
1020
1021
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1015

def imported_module_with_private_method_local(receiver_type, method_name)
  @ctx.imports.each_value do |module_binding|
    return module_binding if module_binding.private_method?(receiver_type, method_name)
  end

  nil
end

#infer_addr_source_type(expression, scopes:) ⇒ Object



1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1282

def infer_addr_source_type(expression, scopes:)
  raise_sema_error("ref_of requires a mutable safe lvalue source") unless safe_reference_source_expression?(expression, scopes:)

  source_type = infer_lvalue(expression, scopes:)
  if contains_ref_type?(source_type)
    unless (source_type.is_a?(Types::Struct) || source_type.is_a?(Types::StructInstance)) && source_type.lifetime_params&.any?
      raise_sema_error("ref_of cannot target ref values")
    end
  end

  source_type
end

#infer_await_expression(expression, scopes:) ⇒ Object



867
868
869
870
871
872
873
874
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 867

def infer_await_expression(expression, scopes:)
  raise_sema_error("await is only allowed inside async functions") unless inside_async_function?

  task_type = infer_expression(expression.expression, scopes:)
  raise_sema_error("await expects Task[T], got #{task_type}") unless task_type.is_a?(Types::Task)

  task_type.result_type
end

#infer_binary(expression, scopes:, expected_type: nil) ⇒ Object



558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 558

def infer_binary(expression, scopes:, expected_type: nil)
  propagated_type = propagating_expected_type(expression.operator, expected_type)
  left_type = infer_expression(expression.left, scopes:, expected_type: propagated_type)

  right_scopes = case expression.operator
                 when "and"
                   scopes_with_refinements(scopes, flow_refinements(expression.left, truthy: true, scopes:))
                 when "or"
                   scopes_with_refinements(scopes, flow_refinements(expression.left, truthy: false, scopes:))
                 else
                   scopes
                 end

  right_expected_type = case expression.operator
                        when "<<", ">>"
                          propagated_type || left_type
                        when "+", "-", "*", "/", "%"
                          propagated_type || left_type
                        when "|", "&", "^"
                          left_type
                        else
                          left_type
                        end

  right_type = infer_expression(expression.right, scopes: right_scopes, expected_type: right_expected_type)
  left_type, right_type = harmonize_binary_float_literal_types(
    expression.left,
    expression.right,
    left_type,
    right_type,
    scopes: right_scopes,
  )

  left_type, right_type = harmonize_binary_integer_literal_types(
    expression.left,
    expression.right,
    left_type,
    right_type,
    scopes: right_scopes,
  )

  case expression.operator
  when "and", "or"
    ensure_assignable!(left_type, @ctx.types.fetch("bool"), "operator #{expression.operator} requires bool operands")
    ensure_assignable!(right_type, @ctx.types.fetch("bool"), "operator #{expression.operator} requires bool operands")
    @ctx.types.fetch("bool")
  when "|", "&", "^"
    simd_result = simd_bitwise_result(left_type, right_type)
    return simd_result if simd_result

    # For Flags/Enum types, the operands must match and be bitwise-capable.
    unless left_type == right_type && (bitwise_type?(left_type) || left_type.is_a?(Types::Flags))
      raise_sema_error("operator #{expression.operator} requires matching integer or flags types, got #{left_type} and #{right_type}")
    end

    left_type
  when "+", "-", "*", "/"
    if expression.operator == "+" && (string_like_type?(left_type) || string_like_type?(right_type))
      raise_sema_error("operator + does not support str/cstr concatenation; use continued string literals for static text or string.String/str_buffer for dynamic text")
    end

    pointer_result = pointer_arithmetic_result(expression.operator, left_type, right_type)
    return pointer_result if pointer_result

    vector_result = vector_arithmetic_result(expression.operator, left_type, right_type)
    return vector_result if vector_result

    result_type = common_numeric_type(left_type, right_type)
    unless result_type
      if left_type.respond_to?(:signed_integer?) && right_type.respond_to?(:signed_integer?) &&
         left_type.integer? && right_type.integer? &&
         left_type.signed_integer? != right_type.signed_integer?
        raise_sema_error("operator #{expression.operator} requires compatible numeric types, got #{left_type} and #{right_type} (use an explicit cast to resolve signed/unsigned mismatch)")
      else
        raise_sema_error("operator #{expression.operator} requires compatible numeric types, got #{left_type} and #{right_type}")
      end
    end

    result_type
  when "%"
    simd_result = simd_mod_result(left_type, right_type)
    return simd_result if simd_result

    result_type = common_integer_type(left_type, right_type)
    unless result_type
      raise_sema_error("operator % requires compatible integer types, got #{left_type} and #{right_type}")
    end

    result_type
  when "<<", ">>"
    simd_result = simd_shift_result(left_type, right_type)
    return simd_result if simd_result

    unless left_type.is_a?(Types::Primitive) && left_type.integer? && right_type.is_a?(Types::Primitive) && right_type.integer?
      raise_sema_error("operator #{expression.operator} requires integer operands, got #{left_type} and #{right_type}")
    end

    left_type
  when "<", "<=", ">", ">="
    unless common_numeric_type(left_type, right_type)
      raise_sema_error("operator #{expression.operator} requires compatible numeric types, got #{left_type} and #{right_type}")
    end

    @ctx.types.fetch("bool")
  when "==", "!="
    unless c_natively_equality_comparable_type?(left_type) && c_natively_equality_comparable_type?(right_type)
      bad_type = c_natively_equality_comparable_type?(right_type) ? left_type : right_type
      if struct_instance_type?(bad_type)
        raise_sema_error("operator #{expression.operator} is not supported for struct type #{bad_type}; use equal[#{bad_type}](...) instead")
      else
        raise_sema_error("operator #{expression.operator} is not supported for type #{bad_type}")
      end
    end
    unless common_numeric_type(left_type, right_type) || types_compatible?(left_type, right_type) || types_compatible?(right_type, left_type)
      raise_sema_error("operator #{expression.operator} requires comparable types, got #{left_type} and #{right_type}")
    end

    @ctx.types.fetch("bool")
  else
    raise_sema_error("unsupported binary operator #{expression.operator}")
  end
end

#infer_call(expression, scopes:, expected_type: nil) ⇒ Object



925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 925

def infer_call(expression, scopes:, expected_type: nil)
  callable_kind, callable, receiver = resolve_callable(expression.callee, scopes:)
  @resolved_call_kinds[@ctx.ast.node_ids[expression.callee.object_id]] = callable_kind

  case callable_kind
  when :function
    callable = specialize_function_binding(
      callable,
      expression.arguments,
      scopes:,
      receiver_type: callable_receiver_type_for_specialization(expression.callee, scopes:),
    )

    check_function_call(callable, expression.arguments, scopes:)
    validate_specialized_function_body(callable) unless callable.type_arguments.empty?
    callable.type.return_type
  when :method
    callable = specialize_function_binding(
      callable,
      expression.arguments,
      scopes:,
      receiver_type: infer_method_receiver_type(receiver, scopes:, member_name: expression.callee.member),
    ) if callable.type_params.any?
    record_editable_receiver_expression(receiver) if callable.type.receiver_editable
    raise_sema_error("cannot call editable method #{callable.name} on an immutable receiver") if callable.type.receiver_editable && !assignable_receiver?(receiver, scopes)

    check_function_call(callable, expression.arguments, scopes:)
    validate_specialized_function_body(callable) unless callable.type_arguments.empty?
    callable.type.return_type
  when :callable_value
    check_callable_value_call(callable, expression.arguments, scopes:, callee_expression: expression.callee)
    callable.return_type
  when :str_buffer_clear, :str_buffer_assign, :str_buffer_append, :str_buffer_assign_format, :str_buffer_append_format,
    :str_buffer_len, :str_buffer_capacity, :str_buffer_as_str, :str_buffer_as_cstr
    check_str_buffer_method_call(callable_kind, receiver, expression.arguments, scopes:)
  when :array_as_span
    raise_sema_error("as_span does not support named arguments") if expression.arguments.any?(&:name)
    raise_sema_error("as_span expects 0 arguments, got #{expression.arguments.length}") unless expression.arguments.empty?
    Types::Registry.span(array_element_type(callable))
  when :event_subscribe, :event_subscribe_once, :event_unsubscribe, :event_emit, :event_wait
    check_event_method_call(callable_kind, receiver, expression.arguments, scopes:)
  when :atomic_load, :atomic_store, :atomic_add, :atomic_sub, :atomic_exchange, :atomic_compare_exchange
    check_atomic_method_call(callable_kind, callable, receiver, expression.arguments, scopes:)
  when :simd_lane_with
    check_simd_method_call(callable_kind, callable, receiver, expression.arguments, scopes:)
  when :struct
    check_aggregate_construction(callable, expression.arguments, scopes:)
  when :simd
    check_simd_construction(callable, expression.arguments, scopes:)
  when :struct_with
    check_struct_with_call(callable, receiver, expression.arguments, scopes:)
  when :variant_arm_ctor
    check_variant_arm_construction(callable, expression.arguments, scopes:)
  when :array
    check_array_construction(callable, expression.arguments, scopes:)
  when :reinterpret
    check_reinterpret_call(callable, expression.arguments, scopes:)
  when :hash
    check_hash_call(callable, expression.arguments, scopes:)
  when :equal
    check_equal_call(callable, expression.arguments, scopes:)
  when :order
    check_order_call(callable, expression.arguments, scopes:)
  when :fatal
    check_fatal_call(expression.arguments, scopes:)
  when :ref_of
    check_ref_of_call(expression.arguments, scopes:)
  when :const_ptr_of
    check_const_ptr_of_call(expression.arguments, scopes:)
  when :read
    check_read_call(expression.arguments, scopes:)
  when :ptr_of
    check_ptr_of_call(expression.arguments, scopes:)
  when :field_of
    check_field_of_call(expression.arguments, scopes:)
  when :callable_of
    check_callable_of_call(expression.arguments, scopes:)
  when :attribute_of
    check_attribute_of_call(expression.arguments, scopes:)
  when :has_attribute
    check_has_attribute_call(expression.arguments, scopes:)
  when :get
    check_get_call(expression.arguments, scopes:)
  when :attribute_arg
    check_attribute_arg_call(callable, expression.arguments, scopes:)
  when :dyn_method
    check_dyn_method_call(callable, receiver, expression.arguments, scopes:)
    callable.return_type
  when :adapt
    check_adapt_call(callable, expression.arguments, scopes:)
  else
    raise_sema_error("#{describe_expression(expression.callee)} is not callable")
  end
end

#infer_call_return_type(call, scopes) ⇒ Object



543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 543

def infer_call_return_type(call, scopes)
  callee = call.callee
  callee = callee.callee while callee.is_a?(AST::Specialization)

  case callee
  when AST::Identifier
    func = @ctx.top_level_functions[callee.name]
    return func.type.return_type if func

    member = callee.name
    this_binding = lookup_value("this", scopes)
    if this_binding && (rt = receiver_type_for_fields(this_binding.type))
      method = @ctx.methods.dig(rt, member)
      return method.type.return_type if method
    end
  when AST::MemberAccess
    name = resolve_receiver_name_for_call_inference(callee, scopes)
    member = callee.member
    return nil unless name

    if (import = @ctx.imports[name])
      func = import.functions[member]
      return func.type.return_type if func
    end

    receiver_type = @ctx.types[name]
    if receiver_type && @ctx.methods.key?(receiver_type)
      method = @ctx.methods[receiver_type]["static:#{member}"] || @ctx.methods[receiver_type][member]
      return method.type.return_type if method
    end
  end
  nil
end

#infer_enum_match_expression(expression, scrutinee_type, scopes:, expected_type:) ⇒ Object



775
776
777
778
779
780
781
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 775

def infer_enum_match_expression(expression, scrutinee_type, scopes:, expected_type:)
  arm_entries = []
  each_enum_match_arm(expression, scrutinee_type, scopes:) do |arm, arm_scopes|
    arm_entries << [infer_match_expression_arm_value(arm, scopes: arm_scopes, expected_type:), arm.value]
  end
  match_expression_common_type(arm_entries, expected_type)
end

#infer_expression(expression, scopes:, expected_type: nil) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 134

def infer_expression(expression, scopes:, expected_type: nil)
  with_error_node(expression) do
    type = case expression
           when AST::ErrorExpr
      expected_type || @error_type
    when AST::IntegerLiteral
      infer_integer_literal(expression, expected_type)
    when AST::FloatLiteral
      infer_float_literal(expression, expected_type)
    when AST::CharLiteral
      @ctx.types.fetch("ubyte")
    when AST::SizeofExpr
      unless check_layout_type_via_ct(expression.type, context: "size_of", scopes:)
        infer_layout_query_type(expression.type, context: "size_of")
      end
      @ctx.types.fetch("ptr_uint")
    when AST::AlignofExpr
      unless check_layout_type_via_ct(expression.type, context: "align_of", scopes:)
        infer_layout_query_type(expression.type, context: "align_of")
      end
      @ctx.types.fetch("ptr_uint")
    when AST::OffsetofExpr
      infer_offsetof_type(expression.type, expression.field, scopes:)
      resolve_and_store_offset(expression, scopes:)
      @ctx.types.fetch("ptr_uint")
    when AST::StringLiteral
      @ctx.types.fetch(expression.cstring ? "cstr" : "str")
    when AST::FormatString
      check_format_string_literal(expression, scopes:)
      @ctx.types.fetch("str")
    when AST::BooleanLiteral
      @ctx.types.fetch("bool")
    when AST::NullLiteral
      infer_null_literal(expression)
    when AST::Identifier
      infer_identifier(expression, scopes:, expected_type:)
    when AST::MemberAccess
      infer_member_access(expression, scopes:, expected_type:)
    when AST::IndexAccess
      infer_index_access(expression, scopes:)
    when AST::UnaryOp
      infer_unary(expression, scopes:, expected_type:)
    when AST::BinaryOp
      infer_binary(expression, scopes:, expected_type:)
    when AST::IfExpr
      infer_if_expression(expression, scopes:, expected_type:)
    when AST::MatchExpr
      infer_match_expression(expression, scopes:, expected_type:)
    when AST::UnsafeExpr
      infer_unsafe_expression(expression, scopes:, expected_type:)
    when AST::ProcExpr
      infer_proc_expression(expression, scopes:, expected_type:)
    when AST::AwaitExpr
      infer_await_expression(expression, scopes:)
    when AST::DetachExpr
      @uses_parallel_for = true
      validate_detach_expression!(expression.body.first&.expression || expression.body, scopes:)
      check_block(expression.body, scopes:, return_type: @ctx.types.fetch("void"), allow_return: false)
      Types::Handle.new
    when AST::Call
      infer_call(expression, scopes:, expected_type:)
    when AST::PrefixCast
      target_type = resolve_type_ref(expression.target_type)
      check_cast_call(target_type, [AST::Argument.new(name: nil, value: expression.expression)], scopes:)
      target_type
    when AST::Specialization
      if expression.callee.is_a?(AST::Identifier)
        if expression.callee.name == "zero"
          callable_kind, callable, = resolve_callable(expression, scopes:)
          return check_zero_call(callable, [], expected_type:) if callable_kind == :zero
        end

        if expression.callee.name == "default"
          resolution = resolve_default_specialization(expression)
          return resolution.target_type
        end
      end

      if (callable_resolution = resolve_specialized_callable_binding(expression, scopes:))
        callable_kind, function_binding, = callable_resolution
        raise_sema_error("specialized method #{describe_expression(expression)} must be called") if callable_kind == :method

        return function_binding.type
      end

      raise_sema_error("specialized name #{describe_expression(expression)} must be called")
    when AST::RangeExpr
      raise_sema_error("range expression can only be used as a for-loop iterable or range index target")
    when AST::ExpressionList
      if expected_type && expected_type.is_a?(Types::GenericInstance) && expected_type.name == "array"
        element_type = expected_type.arguments.first
        expression.elements.each do |element|
          value = element.is_a?(AST::Argument) ? element.value : element
          actual = infer_expression(value, scopes:, expected_type: element_type)
          ensure_assignable!(actual, element_type, "array element type mismatch: expected #{element_type}, got #{actual}", expression: value)
        end
        expected_type
      else
        names = []
        element_types = []
        expression.elements.each do |element|
          if element.is_a?(AST::Argument)
            names << element.name
            element_types << infer_expression(element.value, scopes:)
          else
            names << nil
            element_types << infer_expression(element, scopes:)
          end
        end
        has_named = names.any?
        Types::Registry.tuple(element_types, field_names: has_named ? names : nil)
      end
    else
      raise_sema_error("unsupported expression #{expression.class.name}")
    end

    @resolved_expr_types[@ctx.ast.node_ids[expression.object_id]] = type
    type
  end
end

#infer_field_handle_member(expression, scopes:) ⇒ Object



1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1648

def infer_field_handle_member(expression, scopes:)
  case expression.member
  when "name"
    @ctx.types.fetch("str")
  when "type"
    handle = evaluate_compile_time_const_value(expression.receiver, scopes:)
    return @error_type unless handle.is_a?(Types::FieldHandle)

    resolve_type_ref(handle.field_declaration.type)
  else
    raise_sema_error("unknown member #{expression.member} of field_handle")
  end
end

#infer_field_receiver_type(receiver_expression, scopes:, require_mutable_pointer: false) ⇒ Object



1319
1320
1321
1322
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1319

def infer_field_receiver_type(receiver_expression, scopes:, require_mutable_pointer: false)
  receiver_type = infer_expression(receiver_expression, scopes:)
  project_field_receiver_type(receiver_type, require_mutable_pointer:)
end

#infer_float_literal(expression, expected_type) ⇒ Object



296
297
298
299
300
301
302
303
304
305
306
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 296

def infer_float_literal(expression, expected_type)
  if expression.lexeme.end_with?("f")
    @ctx.types.fetch("float")
  elsif expression.lexeme.end_with?("d")
    @ctx.types.fetch("double")
  elsif expected_type.is_a?(Types::Primitive) && expected_type.float?
    expected_type
  else
    @ctx.types.fetch("float")
  end
end

#infer_function_type_arguments(binding, arguments, scopes:, receiver_type: nil) ⇒ Object



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 255

def infer_function_type_arguments(binding, arguments, scopes:, receiver_type: nil)
  expected_params = binding.type.params
  unless call_arity_matches?(binding.type, arguments.length)
    raise_sema_error(arity_error_message(binding.type, binding.name, arguments.length))
  end

  substitutions = infer_receiver_type_substitutions(binding, receiver_type)
  expected_params.each_with_index do |parameter, index|
    argument = arguments.fetch(index)
    candidate_type = substitute_type(parameter.type, substitutions)
    expected_argument_type = if callable_type?(candidate_type)
      candidate_type
    elsif contains_type_var?(candidate_type)
      nil
    else
      candidate_type
    end
    actual_type = foreign_argument_actual_type(parameter, argument, scopes:, function_name: binding.name, expected_type: expected_argument_type)
    collect_type_substitutions(parameter.type, actual_type, substitutions, binding.name)
  end

  binding.type_params.map do |name|
    inferred = substitutions[name]
    raise_sema_error("cannot infer type argument #{name} for function #{binding.name}") unless inferred

    raise_sema_error("generic function #{binding.name} cannot be instantiated with ref types") if contains_ref_type?(inferred)

    inferred
  end
end

#infer_identifier(expression, scopes:, expected_type: nil) ⇒ Object



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 308

def infer_identifier(expression, scopes:, expected_type: nil)
  binding = lookup_value(expression.name, scopes)
  if binding
    record_identifier_binding(expression, binding)
    return binding.type
  end

  if @ctx.top_level_functions.key?(expression.name)
    raise_sema_error("generic function #{expression.name} must be called") if @ctx.top_level_functions.fetch(expression.name).type_params.any?

    function_type = function_type_for_name(expression.name)
    if expected_type
      record_callable_value_identifier_site(expression)
      return function_type
    end

    raise_sema_error("function #{expression.name} must be called")
  end

  raise_sema_error("module #{expression.name} cannot be used as a value") if @ctx.imports.key?(expression.name)
  if @ctx.types.key?(expression.name)
    return Types::BUILTIN_TYPE_META_TYPE if expected_type.is_a?(Types::TypeType)
    return @ctx.types.fetch(expression.name)
  end

  if @ctx.top_level_functions && @ctx.types && scopes
    func_names = @ctx.top_level_functions.keys.map(&:to_s)
    type_names = @ctx.types.keys.map(&:to_s)
    scoped_names = scopes.flat_map { |s| s.is_a?(Hash) ? s.keys : [] }.map(&:to_s)
    suggestion = suggest_name(expression.name, func_names + type_names + scoped_names)
  end
  raise_sema_error("unknown name #{expression.name}", expression, suggestion: suggestion ? "did you mean '#{suggestion}'?" : nil)
end

#infer_if_expression(expression, scopes:, expected_type: nil) ⇒ Object



681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 681

def infer_if_expression(expression, scopes:, expected_type: nil)
  condition_type = infer_expression(expression.condition, scopes:, expected_type: @ctx.types.fetch("bool"))
  ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "if expression condition must be bool, got #{condition_type}")

  then_scopes = scopes_with_refinements(scopes, flow_refinements(expression.condition, truthy: true, scopes:))
  else_scopes = scopes_with_refinements(scopes, flow_refinements(expression.condition, truthy: false, scopes:))
  then_type = infer_expression(expression.then_expression, scopes: then_scopes, expected_type:)
  else_type = infer_expression(expression.else_expression, scopes: else_scopes, expected_type:)

  return expected_type if expected_type &&
    types_compatible?(then_type, expected_type, expression: expression.then_expression) &&
    types_compatible?(else_type, expected_type, expression: expression.else_expression)

  common_type = conditional_common_type(
    then_type,
    else_type,
    then_expression: expression.then_expression,
    else_expression: expression.else_expression,
  )
  return common_type if common_type

  raise_sema_error("if expression branches require compatible types, got #{then_type} and #{else_type}")
end

#infer_index_access(expression, scopes:) ⇒ Object



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 439

def infer_index_access(expression, scopes:)
  receiver_type = infer_expression(expression.receiver, scopes:)
  index_type = infer_expression(expression.index, scopes:)

  if soa_type?(receiver_type)
    return receiver_type.element_type
  end

  if simd_type?(receiver_type)
    return receiver_type.element_type
  end

  if array_type?(receiver_type) && !unsafe_context? && !addressable_storage_expression?(expression.receiver, scopes:)
    raise_sema_error("safe array indexing requires an addressable array value; bind it to a local first")
  end

  if array_type?(receiver_type) && expression.index.is_a?(AST::IntegerLiteral)
    length = array_length(receiver_type)
    index_val = expression.index.value
    if index_val >= length || index_val < 0
      raise_sema_error("array index #{index_val} is out of bounds for array[T, #{length}]", expression)
    end
  end

  if array_type?(receiver_type) && expression.index.is_a?(AST::UnaryOp) && expression.index.operator == "-" &&
     expression.index.operand.is_a?(AST::IntegerLiteral)
    length = array_length(receiver_type)
    index_val = -expression.index.operand.value
    raise_sema_error("array index #{index_val} is out of bounds for array[T, #{length}]", expression)
  end

  infer_index_result_type(receiver_type, index_type)
end

#infer_index_result_type(receiver_type, index_type) ⇒ Object



922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 922

def infer_index_result_type(receiver_type, index_type)
  raise_sema_error("index must be an integer type, got #{index_type}") unless integer_type?(index_type)

  if array_type?(receiver_type)
    return array_element_type(receiver_type)
  end

  if span_type?(receiver_type)
    return receiver_type.element_type
  end

  if soa_type?(receiver_type)
    return receiver_type.element_type
  end

  if simd_type?(receiver_type)
    return receiver_type.element_type
  end

  if pointer_type?(receiver_type)
    require_unsafe!("pointer indexing requires unsafe") unless own_type?(receiver_type)

    return pointee_type(receiver_type)
  end

  raise_sema_error("cannot index #{receiver_type}")
end

#infer_integer_literal(expression, expected_type) ⇒ Object



266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 266

def infer_integer_literal(expression, expected_type)
  # Integer type suffixes: 42u8, 0xFFub, 100uz, etc.
  suffix_type = integer_suffix_type(expression.lexeme)
  return @ctx.types.fetch(suffix_type) if suffix_type

  if expected_type.is_a?(Types::Primitive) && expected_type.integer? &&
     value_fits_integer_type?(expression.value, expected_type)
    expected_type
  else
    @ctx.types.fetch("int")
  end
end

#infer_integer_match_expression(expression, scrutinee_type, scopes:, expected_type:) ⇒ Object



783
784
785
786
787
788
789
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 783

def infer_integer_match_expression(expression, scrutinee_type, scopes:, expected_type:)
  arm_entries = []
  each_integer_match_arm(expression, scrutinee_type, scopes:) do |arm|
    arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
  end
  match_expression_common_type(arm_entries, expected_type)
end

#infer_layout_query_type(type_ref, context:) ⇒ Object



584
585
586
587
588
589
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 584

def infer_layout_query_type(type_ref, context:)
  type = resolve_type_ref(type_ref)
  return type if sized_layout_type?(type)

  raise_sema_error("#{context} requires a concrete sized type, got #{type_ref}")
end

#infer_local_initializer_type(stmt, scopes) ⇒ Object



483
484
485
486
487
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 483

def infer_local_initializer_type(stmt, scopes)
  return nil unless stmt.value

  infer_simple_local_type(stmt.value, scopes)
end

#infer_lvalue(expression, scopes:) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 6

def infer_lvalue(expression, scopes:)
  case expression
  when AST::Identifier
    binding = lookup_value(expression.name, scopes)
    unless binding
      scoped_names = scopes.flat_map { |s| s.is_a?(Hash) ? s.keys : [] }.map(&:to_s)
      suggestion = suggest_name(expression.name, scoped_names)
      raise_sema_error("unknown name #{expression.name}", expression, suggestion: suggestion ? "did you mean '#{suggestion}'?" : nil)
    end
    record_identifier_binding(expression, binding)
    raise_sema_error("cannot assign to immutable #{expression.name}") unless binding.mutable

    binding.storage_type
  when AST::MemberAccess
    receiver_type = infer_lvalue_receiver(expression.receiver, scopes:, allow_ref_identifier: true, allow_pointer_identifier: true, allow_span_param_identifier: true)
    receiver_type = project_field_receiver_type(receiver_type, require_mutable_pointer: true)
    unless aggregate_type?(receiver_type)
      raise_sema_error("cannot assign to member #{expression.member} of #{receiver_type}")
    end

    field_type = receiver_type.field(expression.member)
    raise_sema_error("unknown field #{receiver_type}.#{expression.member}") unless field_type

    field_type
  when AST::IndexAccess
    receiver_type = infer_lvalue_receiver(
      expression.receiver,
      scopes:,
      allow_pointer_identifier: true,
      require_mutable_pointer: true,
      allow_span_param_identifier: true,
    )

    index_type = infer_expression(expression.index, scopes:)
    infer_index_result_type(receiver_type, index_type)
  when AST::Call
    if read_call?(expression)
      validate_read_call_arguments!(expression.arguments)
      return infer_reference_value_type(expression.arguments.first.value, scopes:)
    end

    raise_sema_error("invalid assignment target")
  when AST::BinaryOp
    raise_sema_error("invalid assignment target")
  else
    raise_sema_error("invalid assignment target")
  end
end

#infer_lvalue_receiver(expression, scopes:, allow_ref_identifier: false, allow_pointer_identifier: false, require_mutable_pointer: false, allow_span_param_identifier: false) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 55

def infer_lvalue_receiver(expression, scopes:, allow_ref_identifier: false, allow_pointer_identifier: false, require_mutable_pointer: false, allow_span_param_identifier: false)
  case expression
  when AST::Identifier
    binding = lookup_value(expression.name, scopes)
    raise_sema_error("unknown name #{expression.name}") unless binding
    record_identifier_binding(expression, binding)

    return referenced_type(binding.type) if allow_ref_identifier && ref_type?(binding.type)
    if allow_pointer_identifier && pointer_type?(binding.type)
      require_unsafe!("raw pointer dereference requires unsafe") unless own_type?(binding.type)
      raise_sema_error("cannot assign through read-only raw pointer #{binding.type}") if require_mutable_pointer && const_pointer_type?(binding.type)

      return binding.type
    end
    if allow_span_param_identifier && binding.kind == :param && span_type?(binding.type)
      return binding.type
    end

    raise_sema_error("cannot assign through immutable #{expression.name}") unless binding.mutable

    binding.type
  when AST::MemberAccess
    receiver_type = infer_lvalue_receiver(
      expression.receiver,
      scopes:,
      allow_ref_identifier:,
      allow_pointer_identifier:,
      require_mutable_pointer:,
      allow_span_param_identifier:,
    )
    receiver_type = project_field_receiver_type(receiver_type, require_mutable_pointer:)
    unless aggregate_type?(receiver_type)
      raise_sema_error("cannot access member #{expression.member} of #{receiver_type}")
    end

    field_type = receiver_type.field(expression.member)
    raise_sema_error("unknown field #{receiver_type}.#{expression.member}") unless field_type

    field_type
  when AST::IndexAccess
    receiver_type = infer_lvalue_receiver(
      expression.receiver,
      scopes:,
      allow_ref_identifier:,
      allow_pointer_identifier:,
      require_mutable_pointer:,
      allow_span_param_identifier:,
    )
    index_type = infer_expression(expression.index, scopes:)
    infer_index_result_type(receiver_type, index_type)
  when AST::Call
    if read_call?(expression)
      validate_read_call_arguments!(expression.arguments)
      return infer_reference_value_type(expression.arguments.first.value, scopes:)
    end

    raise_sema_error("invalid assignment target")
  when AST::BinaryOp
    require_unsafe!("raw pointer arithmetic as lvalue receiver requires unsafe")
    type = infer_expression(expression, scopes:)
    raise_sema_error("binary op lvalue receiver must be a pointer") unless pointer_type?(type)
    raise_sema_error("cannot assign through read-only raw pointer #{type}") if require_mutable_pointer && const_pointer_type?(type)

    type
  else
    raise_sema_error("invalid assignment target")
  end
end

#infer_match_expression(expression, scopes:, expected_type: nil) ⇒ Object



732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 732

def infer_match_expression(expression, scopes:, expected_type: nil)
  validate_consuming_foreign_expression!(expression.expression, scopes:, root_allowed: false)
  validate_hoistable_foreign_expression!(expression.expression, scopes:, root_hoistable: false)
  scrutinee_type = infer_expression(expression.expression, scopes:)

  if error_type?(scrutinee_type)
    infer_recovered_match_expression(expression, scopes:, expected_type:)
  elsif scrutinee_type.is_a?(Types::Enum)
    infer_enum_match_expression(expression, scrutinee_type, scopes:, expected_type:)
  elsif scrutinee_type.is_a?(Types::Variant)
    infer_variant_match_expression(expression, scrutinee_type, scopes:, expected_type:)
  elsif integer_type?(scrutinee_type)
    infer_integer_match_expression(expression, scrutinee_type, scopes:, expected_type:)
  elsif scrutinee_type.is_a?(Types::StringView)
    infer_string_match_expression(expression, scrutinee_type, scopes:, expected_type:)
  elsif scrutinee_type.is_a?(Types::Tuple)
    infer_tuple_match_expression(expression, scrutinee_type, scopes:, expected_type:)
  else
    raise_sema_error("match requires an enum, variant, or integer scrutinee, got #{scrutinee_type}")
  end
end

#infer_match_expression_arm_value(arm, scopes:, expected_type:) ⇒ Object



815
816
817
818
819
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 815

def infer_match_expression_arm_value(arm, scopes:, expected_type:)
  validate_consuming_foreign_expression!(arm.value, scopes:, root_allowed: false)
  validate_hoistable_foreign_expression!(arm.value, scopes:, root_hoistable: false)
  infer_expression(arm.value, scopes:, expected_type:)
end

#infer_member_access(expression, scopes:, expected_type: nil) ⇒ Object



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 342

def infer_member_access(expression, scopes:, expected_type: nil)
  type = resolve_type_expression(expression.receiver)
  if type
    return @error_type if error_type?(type)

    member_type = resolve_type_member(type, expression.member)
    return member_type if member_type

    if type.is_a?(Types::Variant) && type.arm_names.include?(expression.member)
      raise_sema_error("variant arm #{type}.#{expression.member} has payload; construct it with #{type}.#{expression.member}(field: value, ...)")
    end

    if (method = lookup_method(type, expression.member))
      raise_sema_error("associated function #{type}.#{expression.member} must be called") unless method.type.receiver_type.nil?
      raise_sema_error("method #{type}.#{expression.member} must be called")
    end

    raise_sema_error("unknown member #{type}.#{expression.member}")
  end

  if expression.receiver.is_a?(AST::Identifier) && @ctx.imports.key?(expression.receiver.name)
    imported_module = @ctx.imports.fetch(expression.receiver.name)
    value = imported_module.values[expression.member]
    return value.type if value

    if imported_module.functions.key?(expression.member)
      function = imported_module.functions.fetch(expression.member)
      raise_sema_error("generic function #{expression.receiver.name}.#{expression.member} must be called") if function.type_params.any?
      if expected_type
        record_callable_value_member_access_site(expression)
        return function.type
      end

      raise_sema_error("function #{expression.receiver.name}.#{expression.member} must be called")
    end

    if imported_module.types.key?(expression.member)
      raise_sema_error("type #{expression.receiver.name}.#{expression.member} cannot be used as a value")
    end

    if imported_module.private_value?(expression.member) || imported_module.private_function?(expression.member) || imported_module.private_type?(expression.member)
      raise_sema_error("#{expression.receiver.name}.#{expression.member} is private to module #{imported_module.name}")
    end

    raise_sema_error("unknown member #{expression.receiver.name}.#{expression.member}")
  end

  field_receiver_type = infer_field_receiver_type(expression.receiver, scopes:)
  method_receiver_type = infer_method_receiver_type(expression.receiver, scopes:, member_name: expression.member)
  return @error_type if error_type?(field_receiver_type) || error_type?(method_receiver_type)

  if array_type?(field_receiver_type) && expression.member == "as_span"
    element_type = array_element_type(field_receiver_type)
    return Types::Registry.span(element_type)
  end
  if char_array_removed_text_method?(method_receiver_type, expression.member)
    raise_sema_error("#{method_receiver_type}.#{expression.member} is not available; array[char, N] is raw storage, use str_buffer[N] or an explicit helper")
  end
  if str_buffer_type?(method_receiver_type) && str_buffer_method_kind(method_receiver_type, expression.member)
    raise_sema_error("method #{method_receiver_type}.#{expression.member} must be called")
  end
  if event_type?(method_receiver_type) && event_method_kind(method_receiver_type, expression.member)
    raise_sema_error("method #{method_receiver_type}.#{expression.member} must be called")
  end

  unless aggregate_type?(field_receiver_type)
    if field_receiver_type == builtin_field_handle_type
      return infer_field_handle_member(expression, scopes:)
    end
    if field_receiver_type == builtin_member_handle_type
      return infer_member_handle_member(expression)
    end
    raise_sema_error("cannot access member #{expression.member} of #{field_receiver_type}")
  end

  field_type = field_receiver_type.field(expression.member)
  return field_type if field_type

  if (event_type = event_member_type(field_receiver_type, expression.member))
    unless event_visible_from_current_module?(event_type)
      raise_sema_error("event #{field_receiver_type}.#{expression.member} is private to module #{event_type.module_name}")
    end

    return event_type
  end

  if lookup_method(method_receiver_type, expression.member)
    raise_sema_error("method #{method_receiver_type.name}.#{expression.member} must be called")
  end

  if (imported_module = imported_module_with_private_method(method_receiver_type, expression.member))
    raise_sema_error("#{method_receiver_type}.#{expression.member} is private to module #{imported_module.name}")
  end

  raise_sema_error("unknown field #{field_receiver_type}.#{expression.member}")
end

#infer_member_access_field_type(value, scopes) ⇒ Object



528
529
530
531
532
533
534
535
536
537
538
539
540
541
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 528

def infer_member_access_field_type(value, scopes)
  receiver_type = case value.receiver
  when AST::Identifier
    if value.receiver.name == "this"
      (binding = lookup_value("this", scopes)) ? binding.type : nil
    else
      (binding = lookup_value(value.receiver.name, scopes)) ? binding.type : nil
    end
  end
  return nil unless receiver_type

  field_receiver = receiver_type_for_fields(receiver_type)
  field_receiver&.respond_to?(:fields) ? field_receiver.fields[value.member] : nil
end

#infer_member_handle_member(expression) ⇒ Object



1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1662

def infer_member_handle_member(expression)
  case expression.member
  when "name"
    @ctx.types.fetch("str")
  when "value"
    @ctx.types.fetch("int")
  else
    raise_sema_error("unknown member #{expression.member} of member_handle")
  end
end

#infer_method_receiver_type(receiver_expression, scopes:, member_name: nil) ⇒ Object



1314
1315
1316
1317
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1314

def infer_method_receiver_type(receiver_expression, scopes:, member_name: nil)
  receiver_type = infer_expression(receiver_expression, scopes:)
  project_method_receiver_type(receiver_type, member_name:)
end

#infer_null_literal(expression) ⇒ Object



200
201
202
203
204
205
206
207
208
209
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 200

def infer_null_literal(expression)
  return @null_type unless expression.type

  target_type = resolve_type_ref(expression.type)
  unless typed_null_target_type?(target_type)
    raise_sema_error("typed null requires pointer-like type, got #{target_type}")
  end

  Types::Null.new(target_type)
end

#infer_offsetof_type(type_ref, field_name, scopes: nil) ⇒ Object



629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 629

def infer_offsetof_type(type_ref, field_name, scopes: nil)
  type = resolve_type_ref(type_ref)
  unless layout_aggregate_type?(type)
    raise_sema_error("offset_of requires a struct, union, span, or str type, got #{type}")
  end

  field_type = type.field(field_name)
  return type if field_type

  if scopes
    binding = lookup_value(field_name, scopes)
    if binding && binding.storage_type.is_a?(Types::ReflectionHandleType) && binding.storage_type.name == "field_handle"
      return type
    end
  end

  raise_sema_error("unknown field #{type}.#{field_name}")
end

#infer_option_propagation(source_type, allow_void_success:) ⇒ Object



541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 541

def infer_option_propagation(source_type, allow_void_success:)
  success_type = let_else_success_type(source_type)
  raise_sema_error("propagation requires a non-void Option success type") if success_type == @ctx.types.fetch("void") && !allow_void_success

  context = current_return_context
  raise_sema_error("propagation is only allowed inside function and proc bodies") unless context
  raise_sema_error("propagation is not allowed inside defer blocks") unless context[:allow_return]

  return_type = context[:return_type]
  unless option_let_else_type?(return_type)
    raise_sema_error("propagation requires enclosing function/proc to return Option[_], got #{return_type}")
  end

  success_type
end

#infer_proc_expression(expression, scopes:, expected_type: nil) ⇒ Object



847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 847

def infer_proc_expression(expression, scopes:, expected_type: nil)
  proc_type = resolve_type_ref(AST::ProcType.new(params: expression.params, return_type: expression.return_type))
  if expected_type && !proc_type_compatible?(proc_type, expected_type)
    raise_sema_error("proc expression expects #{proc_type}, got #{expected_type}")
  end

  proc_scopes = scopes.map { |scope| freeze_scope_bindings(scope) }
  proc_scope = {}
  expression.params.each do |param|
    ensure_non_reserved_primitive_name!(param.name, kind_label: "parameter", line: param.line, column: param.column)
    param_type = resolve_type_ref(param.type)
    validate_parameter_ref_type!(param_type, function_name: "proc", parameter_name: param.name, external: false)
    validate_parameter_proc_type!(param_type, function_name: "proc", parameter_name: param.name, external: false, foreign: false)
    proc_scope[param.name] = value_binding(name: param.name, type: param_type, mutable: false, kind: :param)
  end

  check_block(expression.body, scopes: proc_scopes + [proc_scope], return_type: proc_type.return_type, allow_return: true)
  proc_type
end

#infer_propagate_expression(operand, scopes:, allow_void_success: false) ⇒ Object



508
509
510
511
512
513
514
515
516
517
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 508

def infer_propagate_expression(operand, scopes:, allow_void_success: false)
  source_type = infer_expression(operand, scopes:)
  if result_let_else_type?(source_type)
    infer_result_propagation(source_type, allow_void_success:)
  elsif option_let_else_type?(source_type)
    infer_option_propagation(source_type, allow_void_success:)
  else
    raise_sema_error("propagation expects Result[T, E] or Option[T], got #{source_type}")
  end
end

#infer_receiver_type_substitutions(binding, receiver_type) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 6

def infer_receiver_type_substitutions(binding, receiver_type)
  declared_receiver_type = binding.declared_receiver_type
  return {} unless declared_receiver_type
  case declared_receiver_type
  when Types::Nullable
    unless receiver_type.is_a?(Types::Nullable)
      raise_sema_error("cannot use method #{binding.name} with receiver #{receiver_type}")
    end

    infer_receiver_type_substitutions(
      binding.with(declared_receiver_type: declared_receiver_type.base),
      receiver_type.base,
    )
  when Types::StructInstance
    return {} unless declared_receiver_type.definition.is_a?(Types::GenericStructDefinition)

    unless receiver_type.is_a?(Types::StructInstance) && receiver_type.definition == declared_receiver_type.definition
      raise_sema_error("cannot use method #{binding.name} with receiver #{receiver_type}")
    end

    declared_receiver_type.definition.type_params.zip(receiver_type.arguments).to_h
  when Types::VariantInstance
    return {} unless declared_receiver_type.definition.is_a?(Types::GenericVariantDefinition)

    unless receiver_type.is_a?(Types::VariantInstance) && receiver_type.definition == declared_receiver_type.definition
      raise_sema_error("cannot use method #{binding.name} with receiver #{receiver_type}")
    end

    declared_receiver_type.definition.type_params.zip(receiver_type.arguments).to_h
  when Types::GenericInstance
    unless receiver_type.is_a?(Types::GenericInstance) && receiver_type.name == declared_receiver_type.name && receiver_type.arguments.length == declared_receiver_type.arguments.length
      raise_sema_error("cannot use method #{binding.name} with receiver #{receiver_type}")
    end

    declared_receiver_type.arguments.zip(receiver_type.arguments).each_with_object({}) do |(declared_argument, actual_argument), substitutions|
      if declared_argument.is_a?(Types::TypeVar)
        substitutions[declared_argument.name] = actual_argument
      elsif declared_argument != actual_argument
        raise_sema_error("cannot use method #{binding.name} with receiver #{receiver_type}")
      end
    end
  when Types::Span
    return {} unless receiver_type.is_a?(Types::Span)

    substitutions = {}
    if declared_receiver_type.element_type.is_a?(Types::TypeVar)
      substitutions[declared_receiver_type.element_type.name] = receiver_type.element_type
    elsif declared_receiver_type.element_type != receiver_type.element_type
      raise_sema_error("cannot use method #{binding.name} with receiver #{receiver_type}")
    end
    substitutions
  when Types::Task
    return {} unless receiver_type.is_a?(Types::Task)

    substitutions = {}
    if declared_receiver_type.result_type.is_a?(Types::TypeVar)
      substitutions[declared_receiver_type.result_type.name] = receiver_type.result_type
    elsif declared_receiver_type.result_type != receiver_type.result_type
      raise_sema_error("cannot use method #{binding.name} with receiver #{receiver_type}")
    end
    substitutions
  when Types::SoA
    return {} unless receiver_type.is_a?(Types::SoA)

    substitutions = {}
    if declared_receiver_type.element_type.is_a?(Types::TypeVar)
      substitutions[declared_receiver_type.element_type.name] = receiver_type.element_type
    elsif declared_receiver_type.element_type != receiver_type.element_type
      raise_sema_error("cannot use method #{binding.name} with receiver #{receiver_type}")
    end
    substitutions
  when Types::Simd
    return {} unless receiver_type.is_a?(Types::Simd)

    substitutions = {}
    if declared_receiver_type.element_type.is_a?(Types::TypeVar)
      substitutions[declared_receiver_type.element_type.name] = receiver_type.element_type
    elsif declared_receiver_type.element_type != receiver_type.element_type
      raise_sema_error("cannot use method #{binding.name} with receiver #{receiver_type}")
    end
    substitutions
  else
    {}
  end
end

#infer_recovered_match_expression(expression, scopes:, expected_type:) ⇒ Object



754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 754

def infer_recovered_match_expression(expression, scopes:, expected_type:)
  arm_entries = expression.arms.map do |arm|
    arm_scopes = scopes
    if arm.binding_name
      ensure_non_reserved_primitive_name!(arm.binding_name, kind_label: "match binding", line: arm.binding_line, column: arm.binding_column)
      binding = value_binding(
        name: arm.binding_name,
        type: @error_type,
        mutable: false,
        kind: :local,
        id: @preassigned_local_binding_ids.fetch(arm.object_id),
      )
      arm_scopes = scopes + [{ arm.binding_name => binding }]
      record_declaration_binding(arm, binding)
    end
    [infer_match_expression_arm_value(arm, scopes: arm_scopes, expected_type:), arm.value]
  end

  match_expression_common_type(arm_entries, expected_type)
end

#infer_reference_value_type(handle_expression, scopes:) ⇒ Object



1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1300

def infer_reference_value_type(handle_expression, scopes:)
  handle_type = infer_expression(handle_expression, scopes:)
  return referenced_type(handle_type) if ref_type?(handle_type)

  pointee = pointee_type(handle_type)
  if pointee
    require_unsafe!("raw pointer dereference requires unsafe") unless own_type?(handle_type)

    return pointee
  end

  raise_sema_error("read expects ref[...] or ptr[...], got #{handle_type}")
end

#infer_result_propagation(source_type, allow_void_success:) ⇒ Object



519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 519

def infer_result_propagation(source_type, allow_void_success:)
  success_type = let_else_success_type(source_type)
  error_type = let_else_error_type(source_type)
  raise_sema_error("propagation requires a non-void Result success type") if success_type == @ctx.types.fetch("void") && !allow_void_success

  context = current_return_context
  raise_sema_error("propagation is only allowed inside function and proc bodies") unless context
  raise_sema_error("propagation is not allowed inside defer blocks") unless context[:allow_return]

  return_type = context[:return_type]
  unless result_let_else_type?(return_type)
    raise_sema_error("propagation requires enclosing function/proc to return Result[_, #{error_type}], got #{return_type}")
  end

  return_error_type = let_else_error_type(return_type)
  unless return_error_type == error_type
    raise_sema_error("propagation error type #{error_type} must match enclosing Result error type #{return_error_type}")
  end

  success_type
end

#infer_ro_addr_source_type(expression, scopes:) ⇒ Object



1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1269

def infer_ro_addr_source_type(expression, scopes:)
  raise_sema_error("const_ptr_of requires a safe lvalue source") unless safe_reference_source_expression?(expression, scopes:)

  source_type = infer_expression(expression, scopes:)
  if contains_ref_type?(source_type)
    unless (source_type.is_a?(Types::Struct) || source_type.is_a?(Types::StructInstance)) && source_type.lifetime_params&.any?
      raise_sema_error("const_ptr_of cannot target ref values")
    end
  end

  source_type
end

#infer_simple_local_type(value, scopes) ⇒ Object



501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 501

def infer_simple_local_type(value, scopes)
  case value
  when AST::Call
    infer_call_return_type(value, scopes)
  when AST::MemberAccess
    infer_member_access_field_type(value, scopes)
  when AST::Identifier
    (binding = lookup_value(value.name, scopes)) ? binding.type : nil
  when AST::IntegerLiteral
    @ctx.types["int"]
  when AST::FloatLiteral
    @ctx.types["float"] || @ctx.types["double"]
  when AST::CharLiteral
    @ctx.types["ubyte"]
  when AST::BooleanLiteral
    @ctx.types["bool"]
  when AST::StringLiteral
    @ctx.types["str"]
  when AST::PrefixCast
    if value.target_type.respond_to?(:name)
      @ctx.types[value.target_type.name] || @ctx.types["int"]
    else
      @ctx.types["int"]
    end
  end
end

#infer_string_match_expression(expression, scrutinee_type, scopes:, expected_type:) ⇒ Object



791
792
793
794
795
796
797
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 791

def infer_string_match_expression(expression, scrutinee_type, scopes:, expected_type:)
  arm_entries = []
  each_string_match_arm(expression, scrutinee_type, scopes:) do |arm|
    arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
  end
  match_expression_common_type(arm_entries, expected_type)
end

#infer_tuple_match_expression(expression, scrutinee_type, scopes:, expected_type:) ⇒ Object



799
800
801
802
803
804
805
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 799

def infer_tuple_match_expression(expression, scrutinee_type, scopes:, expected_type:)
  arm_entries = []
  each_tuple_match_arm(expression, scrutinee_type, scopes:) do |arm|
    arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
  end
  match_expression_common_type(arm_entries, expected_type)
end

#infer_unary(expression, scopes:, expected_type: nil) ⇒ Object



473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 473

def infer_unary(expression, scopes:, expected_type: nil)
  return infer_propagate_expression(expression.operand, scopes:) if expression.operator == "?"

  operand_type = infer_expression(expression.operand, scopes:, expected_type:)

  case expression.operator
  when "not"
    ensure_assignable!(operand_type, @ctx.types.fetch("bool"), "operator not requires bool, got #{operand_type}")
    @ctx.types.fetch("bool")
  when "+", "-"
    raise_sema_error("operator #{expression.operator} requires a numeric operand, got #{operand_type}") unless operand_type.numeric?

    if expression.operator == "-" && operand_type.integer?
      ct_value = evaluate_compile_time_const_value(expression, scopes:)
      if ct_value.is_a?(Integer) && !value_fits_integer_type?(ct_value, operand_type)
        raise_sema_error("negated value #{ct_value} does not fit in type #{operand_type}", expression)
      end
    end

    operand_type
  when "~"
    if simd_type?(operand_type)
      raise_sema_error("operator ~ on simd requires integer element type, got #{operand_type.element_type}") unless operand_type.element_type.integer?
      return operand_type
    end
    raise_sema_error("operator ~ requires an integer or flags operand, got #{operand_type}") unless bitwise_type?(operand_type)

    operand_type
  when "out", "in", "inout"
    raise_sema_error("#{expression.operator} is only allowed for foreign call arguments")
  else
    raise_sema_error("unsupported unary operator #{expression.operator}")
  end
end

#infer_unsafe_expression(expression, scopes:, expected_type: nil) ⇒ Object



255
256
257
258
259
260
261
262
263
264
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 255

def infer_unsafe_expression(expression, scopes:, expected_type: nil)
  @unsafe_statement_lines << expression.line
  begin
    with_unsafe do
      infer_expression(expression.expression, scopes:, expected_type:)
    end
  ensure
    @unsafe_statement_lines.pop
  end
end

#infer_variant_match_expression(expression, scrutinee_type, scopes:, expected_type:) ⇒ Object



807
808
809
810
811
812
813
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 807

def infer_variant_match_expression(expression, scrutinee_type, scopes:, expected_type:)
  arm_entries = []
  each_variant_match_arm(expression, scrutinee_type, scopes:) do |arm, arm_scopes|
    arm_entries << [infer_match_expression_arm_value(arm, scopes: arm_scopes, expected_type:), arm.value]
  end
  match_expression_common_type(arm_entries, expected_type)
end

#inline_for_element_type(element) ⇒ Object



1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1307

def inline_for_element_type(element)
  if element.is_a?(Types::FieldHandle)
    builtin_field_handle_type
  elsif element.is_a?(Types::CallableHandle)
    builtin_callable_handle_type
  elsif element.is_a?(Types::AttributeHandle)
    builtin_attribute_handle_type
  elsif element.is_a?(Types::MemberHandle)
    builtin_member_handle_type
  elsif element.is_a?(Types::StructHandle)
    builtin_struct_handle_type
  else
    @ctx.types.fetch("int")
  end
end

#inline_foreign_call_requires_hoisting_message(foreign_call, scopes:) ⇒ Object



1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1122

def inline_foreign_call_requires_hoisting_message(foreign_call, scopes:)
  binding = foreign_call[:binding]
  call = foreign_call[:call]
  reference_counts = foreign_mapping_reference_counts(foreign_mapping_expression(binding.ast))

  binding.ast.params.each_with_index do |param_ast, index|
    public_alias = param_ast.boundary_type ? foreign_mapping_public_alias_name(param_ast.name) : nil
    total_references = reference_counts.fetch(param_ast.name, 0)
    total_references += reference_counts.fetch(public_alias, 0) if public_alias
    next if total_references <= 1 || simple_foreign_argument_expression?(call.arguments.fetch(index).value)

    return inline_foreign_hoisting_message(binding.name, param_ast.name, reason: "is referenced multiple times in its mapping")
  end

  binding.ast.params.each_with_index do |param_ast, index|
    parameter = binding.type.params.fetch(index)
    argument_expression = call.arguments.fetch(index).value
    next unless automatic_foreign_cstr_temp_needed?(parameter, argument_expression, scopes:) || automatic_foreign_cstr_list_temp_needed?(parameter)

    return inline_foreign_hoisting_message(binding.name, param_ast.name, reason: "needs temporary foreign text storage")
  end

  nil
end

#inline_foreign_hoisting_message(binding_name, parameter_name, reason:) ⇒ Object



1147
1148
1149
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1147

def inline_foreign_hoisting_message(binding_name, parameter_name, reason:)
  "foreign call #{binding_name} cannot be used inline because #{parameter_name} #{reason}; use it as a statement, local initializer, assignment, or return expression"
end

#inside_async_function?Boolean

Returns:

  • (Boolean)


86
87
88
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 86

def inside_async_function?
  @async_function_depth.positive?
end

#inside_loop?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 90

def inside_loop?
  @loop_depth.positive?
end

#install_builtin_attributesObject



110
111
112
113
114
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 110

def install_builtin_attributes
  BUILTIN_ATTRIBUTE_NAMES.each do |name|
    @ctx.attributes[name] = MilkTea.builtin_attribute_binding(name, @ctx.types)
  end
end

#install_builtin_typesObject



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 33

def install_builtin_types
  INSTALLABLE_BUILTIN_TYPE_NAMES.each do |name|
    @ctx.types[name] = case name
    when "str"
      Types::Registry.string_view
    when "Subscription"
      builtin_subscription_type
    when "EventError"
      builtin_event_error_type
    when "struct_handle"
      builtin_struct_handle_type
    when "field_handle"
      builtin_field_handle_type
    when "callable_handle"
      builtin_callable_handle_type
    when "attribute_handle"
      builtin_attribute_handle_type
    when "member_handle"
      builtin_member_handle_type
    when "type"
      builtin_type_meta_type
    when "vec2"
      Types::Vector.new("vec2", element_type: Types::BUILTIN_VECTOR_ELEMENT, width: 2)
    when "vec3"
      Types::Vector.new("vec3", element_type: Types::BUILTIN_VECTOR_ELEMENT, width: 3)
    when "vec4"
      Types::Vector.new("vec4", element_type: Types::BUILTIN_VECTOR_ELEMENT, width: 4)
    when "ivec2"
      Types::Vector.new("ivec2", element_type: Types::BUILTIN_IVECTOR_ELEMENT, width: 2)
    when "ivec3"
      Types::Vector.new("ivec3", element_type: Types::BUILTIN_IVECTOR_ELEMENT, width: 3)
    when "ivec4"
      Types::Vector.new("ivec4", element_type: Types::BUILTIN_IVECTOR_ELEMENT, width: 4)
    when "mat3"
      Types::Matrix.new("mat3", dim: 3)
    when "mat4"
      Types::Matrix.new("mat4", dim: 4)
    when "quat"
      Types::Quaternion.new("quat")
    else
      Types::Registry.primitive(name)
    end
  end
end

#install_fallback_builtin_type(module_path) ⇒ Object



193
194
195
196
197
198
199
200
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 193

def install_fallback_builtin_type(module_path)
  case module_path
  when "std.option"
    @ctx.types["Option"] = Types::BUILTIN_OPTION_TYPE unless @ctx.types.key?("Option")
  when "std.result"
    @ctx.types["Result"] = Types::BUILTIN_RESULT_TYPE unless @ctx.types.key?("Result")
  end
end

#install_importsObject



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 142

def install_imports
  @ctx.ast.imports.each do |import|
    with_error_node(import) do
      alias_name = import.alias_name || import.path.parts.last
      ensure_non_reserved_import_alias_name!(alias_name, kind_label: "import alias", line: import.line, column: import.column)
      raise_sema_error("duplicate import alias #{alias_name}") if @ctx.imports.key?(alias_name)

      module_binding = @ctx.imported_modules[import.path.to_s]
      next if @allow_missing_imports && module_binding.nil?
      raise_sema_error("unknown import #{import.path}") unless module_binding

      @ctx.imports[alias_name] = module_binding
    end
  end
end

#install_prelude_typesObject



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 160

def install_prelude_types
  PRELUDE_MODULE_PATHS.each do |module_path|
    next if module_path == @ctx.module_name

    module_binding = @ctx.imported_modules[module_path]
    unless module_binding
      install_fallback_builtin_type(module_path)
      @ctx.imports[module_path] = empty_module_binding(module_path) unless @ctx.imports.key?(module_path)
      next
    end

    module_binding.types.each do |type_name, type|
      @ctx.types[type_name] = type unless @ctx.types.key?(type_name)
    end

    already_imported = @ctx.imports.any? { |_, existing_binding| existing_binding == module_binding }
    @ctx.imports[module_path] = module_binding unless @ctx.imports.key?(module_path) || already_imported
  end
end

#instantiate_function_binding(binding, type_arguments) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 204

def instantiate_function_binding(binding, type_arguments)
  if binding.type_params.empty?
    raise_sema_error("function #{binding.name} is not generic and cannot be specialized")
  end

  unless binding.type_params.length == type_arguments.length
    raise_sema_error("function #{binding.name} expects #{binding.type_params.length} type arguments, got #{type_arguments.length}")
  end

  if type_arguments.any? { |type_argument| contains_ref_type?(type_argument) }
    raise_sema_error("generic function #{binding.name} cannot be instantiated with ref types")
  end

  key = type_arguments.freeze
  return binding.instances.fetch(key) if binding.instances.key?(key)

  substitutions = binding.type_params.zip(type_arguments).to_h
  validate_function_type_param_constraints!(binding, substitutions)
  type = substitute_type(binding.type, substitutions)
  body_params = binding.body_params.map { |param| substitute_value_binding(param, substitutions) }
  validate_specialized_function_binding!(binding.name, type, body_params)

  instance = FunctionBinding.new(
    name: binding.name,
    type:,
    body_params:,
    body_return_type: substitute_type(binding.body_return_type, substitutions),
    ast: binding.ast,
    external: binding.external,
    async: binding.async,
    type_params: [].freeze,
    type_param_constraints: {}.freeze,
    instances: {},
    type_arguments: key,
    owner: binding.owner,
    specialization_owner: @current_specialization_owner || (binding.owner == self ? nil : self),
    type_substitutions: substitutions.freeze,
    declared_receiver_type: binding.declared_receiver_type ? substitute_type(binding.declared_receiver_type, substitutions) : nil,
  )
  binding.instances[key] = instance
end

#instantiate_function_binding_with_receiver(binding, explicit_type_arguments, receiver_type: nil) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 176

def instantiate_function_binding_with_receiver(binding, explicit_type_arguments, receiver_type: nil)
  if binding.type_params.empty?
    raise_sema_error("function #{binding.name} is not generic and cannot be specialized")
  end

  receiver_substitutions = infer_receiver_type_substitutions(binding, receiver_type)
  remaining_type_params = binding.type_params.reject { |name| receiver_substitutions.key?(name) }
  unless remaining_type_params.length == explicit_type_arguments.length
    raise_sema_error("function #{binding.name} expects #{remaining_type_params.length} type arguments, got #{explicit_type_arguments.length}")
  end

  substitutions = receiver_substitutions.dup
  remaining_type_params.zip(explicit_type_arguments).each do |name, type_argument|
    raise_sema_error("generic function #{binding.name} cannot be instantiated with ref types") if contains_ref_type?(type_argument)

    substitutions[name] = type_argument
  end

  type_arguments = binding.type_params.map do |name|
    inferred = substitutions[name]
    raise_sema_error("cannot infer type argument #{name} for function #{binding.name}") unless inferred

    inferred
  end

  instantiate_function_binding(binding, type_arguments)
end

#integer_constant_fits_in_char?(expression, scopes) ⇒ Boolean

Returns:

  • (Boolean)


143
144
145
146
147
148
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 143

def integer_constant_fits_in_char?(expression, scopes)
  value = evaluate_compile_time_const_value(expression, scopes:)
  return true unless value.is_a?(Integer)

  value >= 0 && value <= 255
end

#integer_literal_expression?(expression) ⇒ Boolean

Returns:

  • (Boolean)


909
910
911
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 909

def integer_literal_expression?(expression)
  expression.is_a?(AST::IntegerLiteral)
end

#integer_suffix_type(lexeme) ⇒ Object



292
293
294
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 292

def integer_suffix_type(lexeme)
  INTEGER_SUFFIX_TYPES.sort_by { |k, _| -k.length }.find { |suffix, _| lexeme.end_with?(suffix) }&.last
end

#integer_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


839
840
841
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 839

def integer_type?(type)
  type.is_a?(Types::Primitive) && type.integer?
end

#integer_type_argument?(argument) ⇒ Boolean

Returns:

  • (Boolean)


786
787
788
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 786

def integer_type_argument?(argument)
  argument.is_a?(Types::LiteralTypeArg) && argument.value.is_a?(Integer)
end

#interface_implementation_key(type) ⇒ Object



158
159
160
161
162
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 158

def interface_implementation_key(type)
  return type.definition if type.is_a?(Types::StructInstance)

  type
end

#iterator_loop_type(type) ⇒ Object



876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 876

def iterator_loop_type(type)
  type = referenced_type(type) if ref_type?(type)
  iter_method = lookup_method(type, "iter")
  return nil unless iter_method

  iter_method = instantiate_function_binding_with_receiver(iter_method, [], receiver_type: type) if iter_method.type_params.any?

  unless iter_method.type.params.empty?
    raise_sema_error("for iterator #{type}.iter expects 0 arguments")
  end
  if iter_method.type.receiver_editable
    raise_sema_error("for iterator #{type}.iter cannot be an editable method")
  end

  iterator_type = iter_method.type.return_type
  next_method = lookup_method(iterator_type, "next")
  raise_sema_error("for iterator #{iterator_type} must define next()") unless next_method
  next_method = instantiate_function_binding_with_receiver(next_method, [], receiver_type: iterator_type) if next_method.type_params.any?
  unless next_method.type.params.empty?
    raise_sema_error("for iterator #{iterator_type}.next expects 0 arguments")
  end

  next_type = next_method.type.return_type
  if next_type.is_a?(Types::Nullable) && typed_null_target_type?(next_type.base)
    return next_type.base
  end

  if next_type == @ctx.types.fetch("bool")
    current_method = lookup_method(iterator_type, "current")
    raise_sema_error("for iterator #{iterator_type} must define current() when next() returns bool") unless current_method
    current_method = instantiate_function_binding_with_receiver(current_method, [], receiver_type: iterator_type) if current_method.type_params.any?
    unless current_method.type.params.empty?
      raise_sema_error("for iterator #{iterator_type}.current expects 0 arguments")
    end
    raise_sema_error("for iterator #{iterator_type}.current cannot return void") if current_method.type.return_type == @ctx.types.fetch("void")

    return current_method.type.return_type
  end

  raise_sema_error("for iterator #{iterator_type}.next must return bool or a nullable pointer-like item, got #{next_type}")
end

#last_ast_line(statements) ⇒ Object

Returns the highest line number across a list of AST statement nodes, recursing into nested bodies (if/else/match/while/for/defer/unsafe).



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 367

def last_ast_line(statements)
  return nil if statements.nil? || statements.empty?

  statements.filter_map do |stmt|
    case stmt
    when AST::IfStmt
      branch_lines = stmt.branches.filter_map { |b| last_ast_line(b.body) }
      else_lines = last_ast_line(stmt.else_body)
      [stmt.line, *branch_lines, else_lines].compact.max
    when AST::WhileStmt, AST::ForStmt, AST::UnsafeStmt, AST::ErrorBlockStmt
      [stmt.line, last_ast_line(stmt.body)].compact.max
    when AST::MatchStmt
      arm_lines = stmt.arms.filter_map do |arm|
        if arm.respond_to?(:body)
          last_ast_line(arm.body)
        elsif arm.respond_to?(:value)
          arm.value.line
        end
      end
      [stmt.line, *arm_lines].compact.max
    when AST::DeferStmt
      [stmt.line, last_ast_line(stmt.body)].compact.max
    when AST::WhenStmt
      branch_lines = stmt.branches.filter_map { |b| last_ast_line(b.body) }
      else_lines = last_ast_line(stmt.else_body)
      [stmt.line, *branch_lines, else_lines].compact.max
    else
      stmt.line
    end
  end.compact.max
end

#layout_aggregate_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


693
694
695
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 693

def layout_aggregate_type?(type)
  type.respond_to?(:field) && !type.is_a?(Types::Opaque) && !type.is_a?(Types::EnumBase)
end

#let_else_discard_binding_syntax?(statement) ⇒ Boolean

Returns:

  • (Boolean)


777
778
779
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 777

def let_else_discard_binding_syntax?(statement)
  statement.is_a?(AST::LocalDecl) && (statement.else_body || statement.recovered_else) && statement.name == "_"
end

#let_else_error_type(type) ⇒ Object



794
795
796
797
798
799
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 794

def let_else_error_type(type)
  return @error_type if error_type?(type)
  return unless result_let_else_type?(type)

  type.arm("failure").fetch("error")
end

#let_else_source_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


781
782
783
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 781

def let_else_source_type?(type)
  type.is_a?(Types::Nullable) || result_let_else_type?(type) || option_let_else_type?(type)
end

#let_else_success_type(type) ⇒ Object



785
786
787
788
789
790
791
792
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 785

def let_else_success_type(type)
  return @error_type if error_type?(type)
  return type.base if type.is_a?(Types::Nullable)
  return type.arm("some").fetch("value") if option_let_else_type?(type)
  return unless result_let_else_type?(type)

  type.arm("success").fetch("value")
end

#levenshtein(s, t) ⇒ Object



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 293

def levenshtein(s, t)
  m = s.length
  n = t.length
  return m if n.zero?
  return n if m.zero?
  d = Array.new(m + 1) { Array.new(n + 1, 0) }
  (0..m).each { |i| d[i][0] = i }
  (0..n).each { |j| d[0][j] = j }
  (1..m).each do |i|
    (1..n).each do |j|
      cost = s[i - 1] == t[j - 1] ? 0 : 1
      d[i][j] = [d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost].min
    end
  end
  d[m][n]
end

#literal_type_argument_name_candidate?(type_ref) ⇒ Boolean

Returns:

  • (Boolean)


397
398
399
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 397

def literal_type_argument_name_candidate?(type_ref)
  type_ref.arguments.empty? && !type_ref.nullable
end

#lookup_local_binding(name, scopes) ⇒ Object



1415
1416
1417
1418
1419
1420
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1415

def lookup_local_binding(name, scopes)
  scopes.reverse_each do |scope|
    return scope[name] if scope.key?(name)
  end
  nil
end

#lookup_local_method_for_interface(receiver_type, name) ⇒ Object



39
40
41
42
43
44
45
46
47
48
# File 'lib/milk_tea/core/semantic_analyzer/interface_conformance.rb', line 39

def lookup_local_method_for_interface(receiver_type, name)
  dispatch_receiver_type = method_dispatch_receiver_type(receiver_type)

  method = @ctx.methods.fetch(receiver_type, {})[name]
  method ||= @ctx.methods.fetch(dispatch_receiver_type, {})[name] unless dispatch_receiver_type == receiver_type
  static_name = "static:#{name}"
  method ||= @ctx.methods.fetch(receiver_type, {})[static_name]
  method ||= @ctx.methods.fetch(dispatch_receiver_type, {})[static_name] unless dispatch_receiver_type == receiver_type
  method
end

#lookup_method(receiver_type, name) ⇒ Object



22
23
24
25
26
27
28
29
30
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 22

def lookup_method(receiver_type, name)
  method = lookup_method_local_or_imported(receiver_type, name)
  return method if method

  fallback_owner = specialization_lookup_owner
  return nil unless fallback_owner

  fallback_owner.lookup_method_local_or_imported(receiver_type, name)
end

#lookup_method_local_or_imported(receiver_type, name) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 43

def lookup_method_local_or_imported(receiver_type, name)
  dispatch_receiver_type = method_dispatch_receiver_type(receiver_type)

  method = @ctx.methods.fetch(receiver_type, {})[name]
  method ||= @ctx.methods.fetch(dispatch_receiver_type, {})[name] unless dispatch_receiver_type == receiver_type
  return method if method

  imported_candidates = []

  @ctx.imports.each_value do |module_binding|
    imported_method = module_binding.methods.fetch(receiver_type, {})[name]
    if imported_method.nil? && dispatch_receiver_type != receiver_type
      imported_method = module_binding.methods.fetch(dispatch_receiver_type, {})[name]
    end

    imported_candidates << [module_binding, imported_method] if imported_method
  end

  if imported_candidates.empty?
    owner_module = reachable_module_binding_for_type(receiver_type)
    return nil unless owner_module

    return module_binding_method(owner_module, receiver_type, dispatch_receiver_type, name)
  end

  if imported_candidates.length > 1
    modules = imported_candidates.map { |module_binding, _binding| module_binding.name }.join(", ")
    display_name = name.delete_prefix("static:")
    raise_sema_error("ambiguous imported method #{receiver_type}.#{display_name}; found in modules #{modules}")
  end

  imported_candidates.first.last
end

#lookup_static_method(receiver_type, name) ⇒ Object



32
33
34
35
36
37
38
39
40
41
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 32

def lookup_static_method(receiver_type, name)
  static_name = "static:#{name}"
  method = lookup_method_local_or_imported(receiver_type, static_name)
  return method if method

  fallback_owner = specialization_lookup_owner
  return nil unless fallback_owner

  fallback_owner.lookup_method_local_or_imported(receiver_type, static_name)
end

#lookup_value(name, scopes) ⇒ Object



14
15
16
17
18
19
20
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 14

def lookup_value(name, scopes)
  scopes.reverse_each do |scope|
    return scope[name] if scope.key?(name)
  end

  @ctx.top_level_values[name]
end

#mark_current_unsafe_required!Object



13
14
15
16
17
18
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 13

def mark_current_unsafe_required!
  current_line = @unsafe_statement_lines.last
  return unless current_line

  @required_unsafe_lines << current_line
end

#match_expression_common_type(arm_entries, expected_type) ⇒ Object



821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 821

def match_expression_common_type(arm_entries, expected_type)
  return expected_type || @error_type if arm_entries.empty?

  if expected_type && arm_entries.all? { |type, expr| types_compatible?(type, expected_type, expression: expr) }
    return expected_type
  end

  common_type, common_expression = arm_entries.first
  arm_entries.drop(1).each do |type, expr|
    next if type == common_type

    merged_type = conditional_common_type(
      common_type,
      type,
      then_expression: common_expression,
      else_expression: expr,
    )
    raise_sema_error("match expression arms require compatible types, got #{common_type} and #{type}") unless merged_type

    common_type = merged_type
    common_expression = expr
  end

  common_type
end

#match_member_name(expression, enum_type) ⇒ Object



965
966
967
968
969
970
971
972
973
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 965

def match_member_name(expression, enum_type)
  return unless expression.is_a?(AST::MemberAccess)

  receiver_type = resolve_type_expression(expression.receiver)
  return unless receiver_type == enum_type
  return expression.member if enum_type.member(expression.member)

  nil
end

#matrix_op_result(operator, left_type, right_type) ⇒ Object



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 277

def matrix_op_result(operator, left_type, right_type)
  if matrix_type?(left_type) && matrix_type?(right_type) && left_type.dim == right_type.dim
    return left_type if operator == "+" || operator == "-"
    return left_type if operator == "*"
  end

  if matrix_type?(left_type) && vector_type?(right_type) && left_type.dim == right_type.width && right_type.element_type == Types::BUILTIN_VECTOR_ELEMENT
    return Types::Vector.new(right_type.name, element_type: right_type.element_type, width: right_type.width) if operator == "*"
  end

  if matrix_type?(left_type) && right_type.numeric?
    return left_type if operator == "*" || operator == "/"
  end

  nil
end

#matrix_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


709
710
711
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 709

def matrix_type?(type)
  type.is_a?(Types::Matrix)
end

#merge_equality_cover(existing, new_cover) ⇒ Object



904
905
906
907
908
909
910
911
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 904

def merge_equality_cover(existing, new_cover)
  return new_cover unless existing
  merged = existing.dup
  new_cover.each do |field, members|
    merged[field] = (merged[field] || []) + members
  end
  merged
end

#merge_refinements(existing, incoming) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 55

def merge_refinements(existing, incoming)
  merged = existing.dup
  incoming.each do |name, refined_type|
    if merged.key?(name) && merged[name] != refined_type
      merged.delete(name)
    else
      merged[name] = refined_type
    end
  end

  merged
end

#merged_scope_bindings(scopes) ⇒ Object



161
162
163
164
165
166
167
168
169
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 161

def merged_scope_bindings(scopes)
  scopes.each_with_object({}) do |scope, bindings|
    scope.each do |name, binding|
      next unless name.is_a?(String)

      bindings[name] = binding
    end
  end
end

#methods_receiver_type_argument_names!(type_ref) ⇒ Object



1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1061

def methods_receiver_type_argument_names!(type_ref)
  names = type_ref.arguments.map do |argument|
    value = argument.value
    next unless value.is_a?(AST::TypeRef)
    next unless value.arguments.empty? && !value.nullable && value.name.parts.length == 1

    value.name.parts.first
  end

  raise_sema_error("extending target #{type_ref} must use the receiver type parameters directly") if names.any?(&:nil?)

  names
end

#module_binding_method(module_binding, receiver_type, dispatch_receiver_type, name) ⇒ Object



77
78
79
80
81
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 77

def module_binding_method(module_binding, receiver_type, dispatch_receiver_type, name)
  method = module_binding.methods.fetch(receiver_type, {})[name]
  method ||= module_binding.methods.fetch(dispatch_receiver_type, {})[name] unless dispatch_receiver_type == receiver_type
  method
end

#module_nameObject



136
137
138
# File 'lib/milk_tea/core/semantic_analyzer.rb', line 136

def module_name
  @ctx.module_name
end

#noncopyable_event_storage_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


778
779
780
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 778

def noncopyable_event_storage_type?(type)
  event_type?(type) || event_carrier_type?(type)
end

#null_test_refinements(expression, truthy:, scopes:) ⇒ Object



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 289

def null_test_refinements(expression, truthy:, scopes:)
  identifier_expression = nil
  if expression.left.is_a?(AST::Identifier) && expression.right.is_a?(AST::NullLiteral)
    identifier_expression = expression.left
  elsif expression.left.is_a?(AST::NullLiteral) && expression.right.is_a?(AST::Identifier)
    identifier_expression = expression.right
  else
    return {}
  end

  binding = lookup_value(identifier_expression.name, scopes)
  return {} unless binding&.storage_type.is_a?(Types::Nullable)

  null_result = expression.operator == "==" ? truthy : !truthy
  refined_type = null_result ? @null_type : binding.storage_type.base
  { identifier_expression.name => refined_type }
end

#nullable_candidate?(type) ⇒ Boolean

Returns:

  • (Boolean)


327
328
329
330
331
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 327

def nullable_candidate?(type)
  return false if ref_type?(type)

  sized_layout_type?(type) || pointer_type?(type) || type.is_a?(Types::Nullable)
end

#option_let_else_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


801
802
803
804
805
806
807
808
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 801

def option_let_else_type?(type)
  return false unless type.is_a?(Types::Variant)

  some_fields = type.arm("some")
  none_fields = type.arm("none")
  some_fields && some_fields.length == 1 && some_fields.key?("value") &&
    none_fields && none_fields.empty?
end

#pointer_arithmetic_result(operator, left_type, right_type) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 199

def pointer_arithmetic_result(operator, left_type, right_type)
  if pointer_type?(left_type) && integer_type?(right_type)
    require_unsafe!("pointer arithmetic requires unsafe") unless own_type?(left_type)

    return left_type if operator == "+" || operator == "-"
  end

  if operator == "+" && integer_type?(left_type) && pointer_type?(right_type)
    require_unsafe!("pointer arithmetic requires unsafe") unless own_type?(right_type)

    return right_type
  end

  nil
end

#pointer_cast?(source_type, target_type) ⇒ Boolean

Returns:

  • (Boolean)


307
308
309
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 307

def pointer_cast?(source_type, target_type)
  pointer_cast_type?(source_type) && pointer_cast_type?(target_type)
end

#pointer_cast_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 315

def pointer_cast_type?(type)
  return typed_null_target_type?(type.target_type) if type.is_a?(Types::Null)
  return true if type == @ctx.types.fetch("cstr")
  if type.is_a?(Types::Nullable)
    return true if function_pointer_type?(type.base)

    return pointer_type?(type.base)
  end

  return true if function_pointer_type?(type)

  pointer_type?(type)
end

#pointer_to(type) ⇒ Object



794
795
796
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 794

def pointer_to(type)
  Types.pointer_to(type)
end

#power_of_two?(value) ⇒ Boolean

Returns:

  • (Boolean)


697
698
699
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 697

def power_of_two?(value)
  (value & (value - 1)).zero?
end

#preassign_local_binding_ids(statements) ⇒ Object



96
97
98
99
# File 'lib/milk_tea/core/semantic_analyzer/nullability.rb', line 96

def preassign_local_binding_ids(statements)
  @preassigned_local_binding_ids = {}
  preassign_local_binding_ids_in_statements(statements || [])
end

#preassign_local_binding_ids_in_expression(expression) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/milk_tea/core/semantic_analyzer/nullability.rb', line 170

def preassign_local_binding_ids_in_expression(expression)
  case expression
  when nil, AST::Identifier, AST::IntegerLiteral, AST::FloatLiteral, AST::StringLiteral,
       AST::BooleanLiteral, AST::NullLiteral, AST::SizeofExpr, AST::AlignofExpr,
       AST::OffsetofExpr, AST::ErrorExpr
    nil
  when AST::MemberAccess
    preassign_local_binding_ids_in_expression(expression.receiver)
  when AST::IndexAccess
    preassign_local_binding_ids_in_expression(expression.receiver)
    preassign_local_binding_ids_in_expression(expression.index)
  when AST::Specialization, AST::Call
    preassign_local_binding_ids_in_expression(expression.callee)
    expression.arguments.each { |argument| preassign_local_binding_ids_in_expression(argument.value) }
  when AST::UnaryOp
    preassign_local_binding_ids_in_expression(expression.operand)
  when AST::BinaryOp
    preassign_local_binding_ids_in_expression(expression.left)
    preassign_local_binding_ids_in_expression(expression.right)
  when AST::RangeExpr
    preassign_local_binding_ids_in_expression(expression.start_expr)
    preassign_local_binding_ids_in_expression(expression.end_expr)
  when AST::IfExpr
    preassign_local_binding_ids_in_expression(expression.condition)
    preassign_local_binding_ids_in_expression(expression.then_expression)
    preassign_local_binding_ids_in_expression(expression.else_expression)
  when AST::MatchExpr
    preassign_local_binding_ids_in_expression(expression.expression)
    expression.arms.each do |arm|
      preassign_local_binding_ids_in_expression(arm.pattern)
      @preassigned_local_binding_ids[arm.object_id] ||= allocate_binding_id if arm.binding_name
      preassign_local_binding_ids_in_expression(if arm.respond_to?(:value) then arm.value else arm.body end)
    end
  when AST::UnsafeExpr
    preassign_local_binding_ids_in_expression(expression.expression)
  when AST::PrefixCast
    preassign_local_binding_ids_in_expression(expression.expression)
  when AST::AwaitExpr
    preassign_local_binding_ids_in_expression(expression.expression)
  when AST::FormatString
    expression.parts.each do |part|
      preassign_local_binding_ids_in_expression(part.expression) if part.is_a?(AST::FormatExprPart)
    end
  when AST::ProcExpr
    preassign_local_binding_ids_in_statements(expression.body)
  end
end

#preassign_local_binding_ids_in_statements(statements) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/milk_tea/core/semantic_analyzer/nullability.rb', line 101

def preassign_local_binding_ids_in_statements(statements)
  statements.each do |statement|
    case statement
    when AST::ErrorBlockStmt
      preassign_local_binding_ids_in_expression(statement.header_expression) if statement.header_expression
      Array(statement.header_iterables).each { |iterable| preassign_local_binding_ids_in_expression(iterable) }
      Array(statement.header_bindings).each do |binding|
        @preassigned_local_binding_ids[binding.object_id] ||= allocate_binding_id
      end if statement.header_type == :for
      preassign_local_binding_ids_in_statements(statement.body || [])
    when AST::LocalDecl
      @preassigned_local_binding_ids[statement.object_id] ||= allocate_binding_id
      preassign_local_binding_ids_in_expression(statement.value) if statement.value
      if statement.else_binding && (statement.else_body || statement.recovered_else)
        @preassigned_local_binding_ids[statement.else_binding.object_id] ||= allocate_binding_id
      end
      preassign_local_binding_ids_in_statements(statement.else_body || [])
    when AST::Assignment
      preassign_local_binding_ids_in_expression(statement.target)
      preassign_local_binding_ids_in_expression(statement.value)
    when AST::IfStmt
      statement.branches.each do |branch|
        preassign_local_binding_ids_in_expression(branch.condition)
        preassign_local_binding_ids_in_statements(branch.body || [])
      end
      preassign_local_binding_ids_in_statements(statement.else_body || [])
    when AST::MatchStmt
      preassign_local_binding_ids_in_expression(statement.expression)
      statement.arms.each do |arm|
        preassign_local_binding_ids_in_expression(arm.pattern)
        @preassigned_local_binding_ids[arm.object_id] ||= allocate_binding_id if arm.binding_name

        # Preassign binding IDs for struct pattern bindings
        if arm.pattern.is_a?(AST::Call) && !arm.pattern.arguments.empty?
          arm.pattern.arguments.each do |arg|
            next if arg.name
            next unless arg.value.is_a?(AST::Identifier)

            @preassigned_local_binding_ids[arg.object_id] ||= allocate_binding_id
          end
        end

        preassign_local_binding_ids_in_statements(if arm.respond_to?(:body) then (arm.body || []) else [arm.value].compact end)
      end
    when AST::UnsafeStmt
      preassign_local_binding_ids_in_statements(statement.body || [])
    when AST::WhileStmt
      preassign_local_binding_ids_in_expression(statement.condition)
      preassign_local_binding_ids_in_statements(statement.body || [])
    when AST::ForStmt
      statement.bindings.each do |binding|
        @preassigned_local_binding_ids[binding.object_id] ||= allocate_binding_id
      end
      statement.iterables.each { |iterable| preassign_local_binding_ids_in_expression(iterable) }
      preassign_local_binding_ids_in_statements(statement.body || [])
    when AST::DeferStmt
      preassign_local_binding_ids_in_expression(statement.expression) if statement.expression
      preassign_local_binding_ids_in_statements(statement.body || []) if statement.body
    when AST::ReturnStmt
      preassign_local_binding_ids_in_expression(statement.value) if statement.value
    when AST::StaticAssert
      preassign_local_binding_ids_in_expression(statement.condition)
      preassign_local_binding_ids_in_expression(statement.message)
    when AST::ExpressionStmt
      preassign_local_binding_ids_in_expression(statement.expression)
    end
  end
end

#preassigned_local_binding_id_for(node) ⇒ Object



1215
1216
1217
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1215

def preassigned_local_binding_id_for(node)
  @preassigned_local_binding_ids[node.object_id] ||= allocate_binding_id
end

#precheck_binding_resolution(statements, scopes) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/milk_tea/core/semantic_analyzer/nullability.rb', line 218

def precheck_binding_resolution(statements, scopes)
  declaration_binding_ids = {}
  identifier_binding_ids = {}

  initial_scope = {}
  scopes.each do |scope|
    scope.each do |name, binding|
      initial_scope[name] = binding.id if binding.respond_to?(:id) && binding.id
    end
  end

  walk_statements_for_precheck_resolution(
    statements || [],
    [initial_scope],
    declaration_binding_ids,
    identifier_binding_ids,
  )

  BindingResolution.new(
    identifier_binding_ids: identifier_binding_ids,
    declaration_binding_ids: declaration_binding_ids,
    mutating_argument_identifier_ids: {},
    editable_receiver_expression_ids: {},
    mutable_lvalue_argument_identifier_ids: {},
    binding_types: {},
  )
end

#proc_storage_supported_type?(type, visited = {}) ⇒ Boolean

Returns:

  • (Boolean)


1153
1154
1155
1156
1157
1158
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1153

def proc_storage_supported_type?(type, visited = {})
  return true unless contains_proc_type?(type)
  visitor = ProcStorageSupportedVisitor.new
  visitor.visit(type)
  visitor.result?
end

#proc_type_compatible?(actual_type, expected_type) ⇒ Boolean

Returns:

  • (Boolean)


1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1092

def proc_type_compatible?(actual_type, expected_type)
  return true unless expected_type
  if proc_type?(expected_type)
    return true if expected_type.is_a?(Types::Proc) && actual_type.is_a?(Types::Proc) &&
      contains_type_var?(expected_type)
    return actual_type == expected_type
  end

  false
end

#project_field_receiver_type(receiver_type, require_mutable_pointer: false) ⇒ Object



1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1324

def project_field_receiver_type(receiver_type, require_mutable_pointer: false)
  return referenced_type(receiver_type) if ref_type?(receiver_type)
  return receiver_type unless pointer_type?(receiver_type)

  require_unsafe!("raw pointer dereference requires unsafe") unless own_type?(receiver_type)
  if require_mutable_pointer && const_pointer_type?(receiver_type)
    raise_sema_error("cannot assign through read-only raw pointer #{receiver_type}")
  end

  pointee_type(receiver_type)
end

#project_method_receiver_type(receiver_type, member_name: nil) ⇒ Object



1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1336

def project_method_receiver_type(receiver_type, member_name: nil)
  receiver_type = referenced_type(receiver_type) if ref_type?(receiver_type)
  return receiver_type unless pointer_type?(receiver_type)

  return receiver_type if member_name && lookup_method(receiver_type, member_name)

  require_unsafe!("raw pointer dereference requires unsafe") unless own_type?(receiver_type)

  pointee_type(receiver_type)
end

#propagating_expected_type(operator, expected_type) ⇒ Object



913
914
915
916
917
918
919
920
921
922
923
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 913

def propagating_expected_type(operator, expected_type)
  case operator
  when "+", "-", "*", "/", "%", "<<", ">>"
    return expected_type if expected_type.is_a?(Types::Primitive) && expected_type.numeric?
  when "|", "&", "^"
    return expected_type if expected_type.is_a?(Types::Primitive) && expected_type.integer?
    return expected_type if expected_type.is_a?(Types::Flags)
  end

  nil
end

#qualified_attribute_name(binding) ⇒ Object



35
36
37
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 35

def qualified_attribute_name(binding)
  binding.module_name ? "#{binding.module_name}.#{binding.name}" : binding.name
end

#quat_vec4_compatible?(a, b) ⇒ Boolean

Returns:

  • (Boolean)


60
61
62
63
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 60

def quat_vec4_compatible?(a, b)
  (a.is_a?(Types::Quaternion) && b.is_a?(Types::Vector) && b.width == 4 && b.element_type.name == "float") ||
    (b.is_a?(Types::Quaternion) && a.is_a?(Types::Vector) && a.width == 4 && a.element_type.name == "float")
end

#quaternion_op_result(operator, left_type, right_type) ⇒ Object



294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 294

def quaternion_op_result(operator, left_type, right_type)
  if quaternion_type?(left_type) && quaternion_type?(right_type)
    return left_type if operator == "+" || operator == "-"
    return left_type if operator == "*"
  end

  if quaternion_type?(left_type) && vector_type?(right_type) && right_type.width == 3 && right_type.element_type == Types::BUILTIN_VECTOR_ELEMENT
    return right_type if operator == "*"
  end

  nil
end

#quaternion_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


713
714
715
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 713

def quaternion_type?(type)
  type.is_a?(Types::Quaternion)
end

#raise_sema_error(message, node = nil, line: nil, column: nil, length: nil, suggestion: nil) ⇒ Object

Raises:



503
504
505
506
507
508
509
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 503

def raise_sema_error(message, node = nil, line: nil, column: nil, length: nil, suggestion: nil)
  target = node || current_error_node
  line ||= source_line(target)
  column ||= source_column(target)
  length ||= source_length(target)
  raise SemanticError.new(message, line:, column:, length:, path: @path, suggestion:)
end

#raw_module?Boolean

Returns:

  • (Boolean)


1351
1352
1353
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1351

def raw_module?
  @ctx.module_kind == :raw_module
end

#reachable_module_binding_for_type(receiver_type) ⇒ Object



83
84
85
86
87
88
89
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 83

def reachable_module_binding_for_type(receiver_type)
  module_name = receiver_type_module_name(receiver_type)
  return nil unless module_name
  return nil if module_name == @ctx.module_name

  find_reachable_imported_module(module_name)
end

#read_call?(expression) ⇒ Boolean

Returns:

  • (Boolean)


1347
1348
1349
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1347

def read_call?(expression)
  expression.is_a?(AST::Call) && expression.callee.is_a?(AST::Identifier) && expression.callee.name == "read"
end

#receiver_type_for_fields(type) ⇒ Object



591
592
593
594
595
596
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 591

def receiver_type_for_fields(type)
  type = type.base while type.is_a?(Types::Nullable)
  type = type.arguments.first if type.respond_to?(:name) && type.name == 'ref' && type.respond_to?(:arguments) && type.arguments&.length == 1
  type = type.definition if type.respond_to?(:definition)
  type
end

#receiver_type_module_name(receiver_type) ⇒ Object



91
92
93
94
95
96
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 91

def receiver_type_module_name(receiver_type)
  return receiver_type_module_name(receiver_type.base) if receiver_type.is_a?(Types::Nullable)
  return receiver_type.module_name if receiver_type.respond_to?(:module_name)

  nil
end

#record_callable_value_expression_site(expression) ⇒ Object



1400
1401
1402
1403
1404
1405
1406
1407
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1400

def record_callable_value_expression_site(expression)
  case expression
  when AST::Identifier
    record_callable_value_identifier_site(expression)
  when AST::MemberAccess
    record_callable_value_member_access_site(expression)
  end
end

#record_callable_value_identifier_site(expression) ⇒ Object



1393
1394
1395
1396
1397
1398
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1393

def record_callable_value_identifier_site(expression)
  return unless expression.is_a?(AST::Identifier)
  return unless expression.line && expression.column

  @callable_value_identifier_sites[[expression.line, expression.column]] = true
end

#record_callable_value_member_access_site(expression) ⇒ Object



1409
1410
1411
1412
1413
1414
1415
1416
1417
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1409

def record_callable_value_member_access_site(expression)
  return unless expression.is_a?(AST::MemberAccess)
  return unless expression.receiver.is_a?(AST::Identifier)
  return unless expression.receiver.line && expression.receiver.column

  @callable_value_member_access_sites[
    [expression.receiver.name, expression.receiver.line, expression.receiver.column, expression.member]
  ] = true
end

#record_declaration_binding(node, binding) ⇒ Object



1419
1420
1421
1422
1423
1424
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1419

def record_declaration_binding(node, binding)
  return unless node
  return unless binding&.id

  @declaration_binding_ids[node.object_id] = binding.id
end

#record_editable_receiver_expression(receiver) ⇒ Object



1473
1474
1475
1476
1477
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1473

def record_editable_receiver_expression(receiver)
  return unless receiver

  @editable_receiver_expression_ids[receiver.object_id] = true
end

#record_identifier_binding(expression, binding) ⇒ Object



1386
1387
1388
1389
1390
1391
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1386

def record_identifier_binding(expression, binding)
  return unless expression.is_a?(AST::Identifier)
  return unless binding&.id

  @identifier_binding_ids[expression.object_id] = binding.id
end

#record_local_completion_snapshot(line, column, scopes) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 142

def record_local_completion_snapshot(line, column, scopes)
  return if @active_local_completion_stack.empty?
  return if line.nil?

  snapshot = LocalCompletionSnapshot.new(
    line:,
    column: (column || 0),
    bindings: merged_scope_bindings(scopes).freeze,
  )

  snapshots = @active_local_completion_stack.last[:snapshots]
  prev = snapshots.last
  if prev && prev.line == snapshot.line && prev.column == snapshot.column
    snapshots[-1] = snapshot
  else
    snapshots << snapshot
  end
end

#record_mutable_lvalue_argument_identifier(expression) ⇒ Object



1479
1480
1481
1482
1483
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1479

def record_mutable_lvalue_argument_identifier(expression)
  return unless expression.is_a?(AST::Identifier)

  @mutable_lvalue_argument_identifier_ids[expression.object_id] = true
end

#record_mutating_argument_identifier(argument, parameter) ⇒ Object



817
818
819
820
821
822
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 817

def record_mutating_argument_identifier(argument, parameter)
  return unless %i[out inout].include?(parameter.passing_mode)
  return unless argument.value.is_a?(AST::Identifier)

  @mutating_argument_identifier_ids[argument.value.object_id] = true
end

#recovered_for_statement(statement) ⇒ Object



279
280
281
282
283
284
285
286
287
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 279

def recovered_for_statement(statement)
  AST::ForStmt.new(
    bindings: Array(statement.header_bindings),
    iterables: Array(statement.header_iterables),
    body: statement.body,
    line: statement.line,
    column: statement.column,
  )
end

#ref_to_pointer_cast?(source_type, target_type) ⇒ Boolean

Returns:

  • (Boolean)


311
312
313
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 311

def ref_to_pointer_cast?(source_type, target_type)
  ref_type?(source_type) && pointer_cast_type?(target_type)
end

#ref_types_compatible?(actual_type, expected_type) ⇒ Boolean

Returns:

  • (Boolean)


54
55
56
57
58
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 54

def ref_types_compatible?(actual_type, expected_type)
  return false unless ref_type?(actual_type) && ref_type?(expected_type)

  referenced_type(actual_type) == referenced_type(expected_type)
end

#reflection_identifier_name(expression, context:) ⇒ Object



1596
1597
1598
1599
1600
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1596

def reflection_identifier_name(expression, context:)
  raise_sema_error("#{context} expects an identifier argument") unless expression.is_a?(AST::Identifier)

  expression.name
end

#register_nested_struct_types(parent_decl, parent_name: parent_decl.name) ⇒ Object



848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 848

def register_nested_struct_types(parent_decl, parent_name: parent_decl.name)
  parent_decl.nested_types.each do |nested|
    qualified_name = "#{parent_name}.#{nested.name}"
    ensure_available_type_name!(qualified_name)
    nested_c_name = if @ctx.module_name && !raw_module?
      "#{@ctx.module_name.to_s.tr('.', '_')}_#{qualified_name.tr('.', '_')}"
    else
      qualified_name.tr('.', '_')
    end
    if nested.type_params.empty?
      @ctx.types[qualified_name] = Types::Struct.new(nested.name, module_name: @ctx.module_name, external: raw_module?, packed: nested.packed, alignment: nested.alignment, linkage_name: nested_c_name, lifetime_params: nested.lifetime_params)
    else
      @ctx.types[qualified_name] = Types::GenericStructDefinition.new(nested.name, nested.type_params.map(&:name), module_name: @ctx.module_name, external: raw_module?, packed: nested.packed, alignment: nested.alignment, linkage_name: nested_c_name, lifetime_params: nested.lifetime_params)
    end
    register_nested_struct_types(nested, parent_name: qualified_name)
  end
end

#reinterpretable_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


661
662
663
664
665
666
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 661

def reinterpretable_type?(type)
  return false if array_type?(type)
  return false if type.is_a?(Types::Primitive) && type.void?

  sized_layout_type?(type)
end

#reject_foreign_nullable_value_type!(type, function_name:, parameter_name:) ⇒ Object



20
21
22
23
24
25
26
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 20

def reject_foreign_nullable_value_type!(type, function_name:, parameter_name:)
  return unless type.is_a?(Types::Nullable)
  return if foreign_boundary_nullable_pointer_like_base?(type.base)

  location = parameter_name ? "parameter #{parameter_name}" : "return type"
  raise_sema_error("#{location} of foreign/external function #{function_name} cannot use nullable value type #{type}; nullable at an FFI boundary is only supported for pointer-like types (use ptr[...]? or pass an explicit struct)")
end

#require_unsafe!(message, line: nil, column: nil) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 20

def require_unsafe!(message, line: nil, column: nil)
  if unsafe_context?
    mark_current_unsafe_required!
    return
  end

  suggestion = "wrap in an unsafe block: `unsafe: <expression>`"
  if line || column
    raise SemanticError.new(message, line:, column:, path: @path, suggestion:)
  end

  raise_sema_error(message, suggestion:)
end

#resolve_adapt_interface(type_arg) ⇒ Object



1006
1007
1008
1009
1010
1011
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 1006

def resolve_adapt_interface(type_arg)
  parts = type_arg.name.parts
  type_args = type_arg.arguments.map { |a| a.value }
  interface_ref = AST::QualifiedName.new(parts:, type_arguments: type_args)
  resolve_interface_ref(interface_ref)
end

#resolve_aggregate_fieldsObject



447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 447

def resolve_aggregate_fields
  expanded_declarations.each do |decl|
    with_error_node(decl) do
      next unless decl.is_a?(AST::StructDecl) || decl.is_a?(AST::UnionDecl)

      struct_type = @ctx.types.fetch(decl.name)
      struct_type.ast_declaration = decl if struct_type.respond_to?(:ast_declaration=)
      type_params = if struct_type.is_a?(Types::GenericStructDefinition)
        seen = {}
        struct_type.type_params.each_with_object({}) do |name, params|
          raise_sema_error("duplicate type parameter #{decl.name}[#{name}]") if seen.key?(name)

          seen[name] = true
          params[name] = Types::TypeVar.new(name)
        end
      else
        {}
      end
      decl.lifetime_params.each do |lt|
        type_params[lt] = Types::LifetimeRef.new(lt)
      end if decl.respond_to?(:lifetime_params)
      type_param_constraints = struct_type.is_a?(Types::GenericStructDefinition) ? struct_type.type_param_constraints : {}
      fields = {}
      events = {}
      field_errors = []

      auto_lifetimes = []

      nested_scope = decl.is_a?(AST::StructDecl) ? resolve_nested_type_bindings(decl) : {}
      if decl.is_a?(AST::StructDecl) && struct_type.respond_to?(:define_nested_types)
        struct_type.define_nested_types(nested_scope)
        define_nested_type_bindings_recursive(decl, parent_name: decl.name)
      end

      decl.fields.each do |field|
        raise_sema_error("duplicate field #{decl.name}.#{field.name}") if fields.key?(field.name)
        raise_sema_error("duplicate member #{decl.name}.#{field.name}") if events.key?(field.name)
        unless raw_module?
          ensure_non_reserved_value_type_name!(
            field.name,
            kind_label: "field #{decl.name}",
            line: field.line || decl.line,
            column: field.column,
          )
        end

        begin
          field_type = resolve_type_ref(field.type, type_params:, type_param_constraints:, nested_types: nested_scope)

          # Auto-generate implicit lifetimes for bare ref[T] in struct fields
          if ref_type_without_lifetime?(field_type)
            auto_lt_name = "@_mt_#{auto_lifetimes.length}"
            auto_lifetimes << auto_lt_name
            type_params[auto_lt_name] = Types::LifetimeRef.new(auto_lt_name)
            field_type = Types::Registry.generic_instance("ref", [auto_lt_name] + field_type.arguments)
          end
          if decl.respond_to?(:lifetime_params) && (lt = ref_lifetime(field_type))
            unless decl.lifetime_params.include?(lt) || auto_lifetimes.include?(lt)
              raise_sema_error("field #{decl.name}.#{field.name} uses lifetime #{lt} not declared on struct")
            end
          end
          allow_lts = decl.respond_to?(:lifetime_params) ? (decl.lifetime_params + auto_lifetimes) : []
          validate_stored_ref_type!(field_type, "field #{decl.name}.#{field.name}", allow_lifetimes: allow_lts)
          unless proc_storage_supported_type?(field_type)
            raise_sema_error("field #{decl.name}.#{field.name} uses unsupported proc nesting")
          end
          fields[field.name] = field_type
        rescue SemanticError => e
          fields[field.name] = @error_type
          field_errors << e
          @structural_errors << e if @structural_errors
        end
      end

      unless field_errors.empty?
        struct_type.define_fields(fields)
        raise field_errors.first
      end

      if decl.is_a?(AST::StructDecl)
        decl.events.each do |event_decl|
          raise_sema_error("duplicate event #{decl.name}.#{event_decl.name}") if events.key?(event_decl.name)
          raise_sema_error("duplicate member #{decl.name}.#{event_decl.name}") if fields.key?(event_decl.name)
          unless raw_module?
            ensure_non_reserved_type_binding_name!(
              event_decl.name,
              kind_label: "event #{decl.name}",
              line: event_decl.line,
              column: event_decl.column,
            )
          end

          begin
            events[event_decl.name] = resolve_event_decl_type(
              event_decl,
              type_params:,
              type_param_constraints:,
              owner_type_name: decl.name,
              nested_types: nested_scope,
            )
          rescue SemanticError => e
            collect_structural_error(e)
          end
        end
      end

      if decl.is_a?(AST::StructDecl)
        resolve_nested_struct_fields(decl, type_params:, type_param_constraints:, external_nested_scope: nested_scope)
      end
      struct_type.define_fields(fields)
      struct_type.define_events(events) if struct_type.respond_to?(:define_events)
    end
  rescue SemanticError => e
    collect_structural_error(e)
  end
end

#resolve_and_store_offset(expression, scopes:) ⇒ Object



616
617
618
619
620
621
622
623
624
625
626
627
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 616

def resolve_and_store_offset(expression, scopes:)
  type = resolve_type_ref(expression.type)
  return unless layout_aggregate_type?(type)

  binding = lookup_value(expression.field, scopes)
  return unless binding && binding.const_value.is_a?(Types::FieldHandle)

  offset = CompileTime::Layout.offset_of(type, binding.const_value.field_name)
  return unless offset

  @const_values[@ctx.ast.node_ids[expression.object_id]] = offset
end

#resolve_attribute_binding(name) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/milk_tea/core/semantic_analyzer/attributes.rb', line 158

def resolve_attribute_binding(name)
  parts = name.parts

  if parts.length == 1
    binding = @ctx.attributes[parts.first]
    raise_sema_error("unknown attribute #{name}") unless binding

    return binding
  end

  if parts.length == 2 && @ctx.imports.key?(parts.first)
    imported_module = @ctx.imports.fetch(parts.first)
    raise_sema_error("#{parts.first}.#{parts.last} is private to module #{imported_module.name}") if imported_module.private_attribute?(parts.last)

    binding = imported_module.attributes[parts.last]
    raise_sema_error("unknown attribute #{name}") unless binding

    return binding
  end

  raise_sema_error("unknown attribute #{name}")
end

#resolve_attribute_name_argument(expression) ⇒ Object



1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1583

def resolve_attribute_name_argument(expression)
  case expression
  when AST::Identifier
    resolve_attribute_binding(AST::QualifiedName.new(parts: [expression.name]))
  when AST::MemberAccess
    raise_sema_error("attribute name must use a module qualifier") unless expression.receiver.is_a?(AST::Identifier)

    resolve_attribute_binding(AST::QualifiedName.new(parts: [expression.receiver.name, expression.member]))
  else
    raise_sema_error("attribute name must be an identifier or module-qualified attribute name")
  end
end

#resolve_callable(callee, scopes:) ⇒ Object



1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1247

def resolve_callable(callee, scopes:)
  case callee
  when AST::Identifier
    if (binding = lookup_value(callee.name, scopes))
      return [:callable_value, binding.type, nil] if callable_type?(binding.type)

      raise_sema_error("#{callee.name} is not callable")
    end

    return [:function, @ctx.top_level_functions.fetch(callee.name), nil] if @ctx.top_level_functions.key?(callee.name)
    return [:fatal, nil, nil] if callee.name == "fatal"
    return [:ref_of, nil, nil] if callee.name == "ref_of"
    return [:const_ptr_of, nil, nil] if callee.name == "const_ptr_of"
    return [:read, nil, nil] if callee.name == "read"
    return [:ptr_of, nil, nil] if callee.name == "ptr_of"
    return [:field_of, nil, nil] if callee.name == "field_of"
    return [:callable_of, nil, nil] if callee.name == "callable_of"
    return [:attribute_of, nil, nil] if callee.name == "attribute_of"
    return [:has_attribute, nil, nil] if callee.name == "has_attribute"
    return [:get, nil, nil] if callee.name == "get"

    type = @ctx.types[callee.name]
    return [:struct, type, nil] if type.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)
    if type.is_a?(Types::GenericStructDefinition) || type.is_a?(Types::GenericVariantDefinition)
      raise_sema_error("generic type #{callee.name} requires type arguments")
    end

    raise_sema_error("unknown callable #{callee.name}")
  when AST::MemberAccess
    if callee.receiver.is_a?(AST::Identifier) && @ctx.imports.key?(callee.receiver.name)
      imported_module = @ctx.imports.fetch(callee.receiver.name)
      return [:function, imported_module.functions.fetch(callee.member), nil] if imported_module.functions.key?(callee.member)
      imported_type = imported_module.types[callee.member]
      if imported_type.is_a?(Types::GenericStructDefinition) || imported_type.is_a?(Types::GenericVariantDefinition)
        raise_sema_error("generic type #{callee.receiver.name}.#{callee.member} requires type arguments", callee)
      end

      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)
        return [:struct, imported_module.types.fetch(callee.member), nil]
      end

      if imported_type.is_a?(Types::Variant)
        arm_name = callee.member
        unless imported_type.arm_names.include?(arm_name)
          raise_sema_error("unknown arm #{arm_name} for variant #{imported_type}")
        end

        return [:variant_arm_ctor, [imported_type, arm_name], nil]
      end

      if imported_module.private_function?(callee.member) || imported_module.private_type?(callee.member) || imported_module.private_value?(callee.member)
        raise_sema_error("#{callee.receiver.name}.#{callee.member} is private to module #{imported_module.name}")
      end

      raise_sema_error("unknown callable #{callee.receiver.name}.#{callee.member}") unless @ctx.types.key?(callee.receiver.name)
    end

    if (type_expr = resolve_type_expression(callee.receiver))
      if type_expr.is_a?(Types::Variant)
        arm_name = callee.member
        unless type_expr.arm_names.include?(arm_name)
          raise_sema_error("unknown arm #{arm_name} for variant #{type_expr}")
        end

        return [:variant_arm_ctor, [type_expr, arm_name], nil]
      end

      if type_expr.respond_to?(:nested_types) && type_expr.nested_types.key?(callee.member)
        return [:struct, type_expr.nested_types[callee.member], nil]
      end

      method = lookup_method(type_expr, callee.member)
      method ||= lookup_static_method(type_expr, callee.member)
      return [:function, method, nil] if method && method.type.receiver_type.nil?

      raise_sema_error("unknown associated function #{type_expr}.#{callee.member}")
    end

    method_receiver_type = infer_method_receiver_type(callee.receiver, scopes:, member_name: callee.member)

    if dyn_type?(method_receiver_type)
      interface = method_receiver_type.interface_binding
      method_binding = interface.methods[callee.member]
      raise_sema_error("no method '#{callee.member}' on interface #{interface.name}") unless method_binding
      raise_sema_error("cannot call static method '#{callee.member}' on dyn value") if method_binding.kind == :static
      return [:dyn_method, method_binding, callee.receiver]
    end

    method = lookup_method(method_receiver_type, callee.member)
    return [:method, method, callee.receiver] if method

    if callee.member == "with" && struct_with_target_type?(method_receiver_type)
      return [:struct_with, method_receiver_type, callee.receiver]
    end

    if char_array_removed_text_method?(method_receiver_type, callee.member)
      raise_sema_error("#{method_receiver_type}.#{callee.member} is not available; array[char, N] is raw storage, use str_buffer[N] or an explicit helper")
    end

    if (str_buffer_method = str_buffer_method_kind(method_receiver_type, callee.member))
      return [str_buffer_method, method_receiver_type, callee.receiver]
    end

    if (event_method = event_method_kind(method_receiver_type, callee.member))
      return [event_method, method_receiver_type, callee.receiver]
    end

    if (atomic_method = atomic_method_kind(method_receiver_type, callee.member))
      return [atomic_method, method_receiver_type, callee.receiver]
    end

    if (simd_method = simd_method_kind(method_receiver_type, callee.member))
      return [simd_method, method_receiver_type, callee.receiver]
    end

    field_receiver_type = infer_field_receiver_type(callee.receiver, scopes:)
    if array_type?(field_receiver_type) && callee.member == "as_span"
      return [:array_as_span, field_receiver_type, callee.receiver]
    end

    return [:callable_value, field_receiver_type.field(callee.member), nil] if aggregate_type?(field_receiver_type) && callable_type?(field_receiver_type.field(callee.member))
    return [:callable_value, field_receiver_type.field(callee.member), nil] if aggregate_type?(field_receiver_type) && callable_type?(field_receiver_type.field(callee.member))

    if (imported_module = imported_module_with_private_method(method_receiver_type, callee.member))
      raise_sema_error("#{method_receiver_type}.#{callee.member} is private to module #{imported_module.name}")
    end

    raise_sema_error("unknown method #{method_receiver_type}.#{callee.member}")
  when AST::Specialization
    if callee.callee.is_a?(AST::Identifier) && callee.callee.name == "reinterpret"
      raise_sema_error("reinterpret requires exactly one type argument") unless callee.arguments.length == 1

      type_arg = callee.arguments.first.value
      raise_sema_error("reinterpret type argument must be a type") unless type_arg.is_a?(AST::TypeRef)

      return [:reinterpret, resolve_type_ref(type_arg), nil]
    end

    if callee.callee.is_a?(AST::Identifier) && callee.callee.name == "array"
      raise_sema_error("array requires exactly two type arguments") unless callee.arguments.length == 2

      array_type = resolve_type_ref(AST::TypeRef.new(name: AST::QualifiedName.new(parts: ["array"]), arguments: callee.arguments, nullable: false))
      raise_sema_error("array specialization must be array[T, N]") unless array_type?(array_type)

      return [:array, array_type, nil]
    end

    if callee.callee.is_a?(AST::Identifier) && callee.callee.name == "simd"
      raise_sema_error("simd requires exactly two type arguments") unless callee.arguments.length == 2

      simd_type = resolve_type_ref(AST::TypeRef.new(name: AST::QualifiedName.new(parts: ["simd"]), arguments: callee.arguments, nullable: false))
      return [:simd, simd_type, nil]
    end

    if callee.callee.is_a?(AST::Identifier) && callee.callee.name == "span"
      raise_sema_error("span requires exactly one type argument") unless callee.arguments.length == 1

      span_type = resolve_type_ref(AST::TypeRef.new(name: AST::QualifiedName.new(parts: ["span"]), arguments: callee.arguments, nullable: false))
      raise_sema_error("span specialization must be span[T]") unless span_type?(span_type)

      return [:struct, span_type, nil]
    end

    if callee.callee.is_a?(AST::Identifier) && callee.callee.name == "zero"
      raise_sema_error("zero requires exactly one type argument") unless callee.arguments.length == 1

      type_arg = callee.arguments.first.value
      raise_sema_error("zero type argument must be a type") unless type_arg.is_a?(AST::TypeRef)

      return [:zero, resolve_type_ref(type_arg), nil]
    end

    if callee.callee.is_a?(AST::Identifier) && callee.callee.name == "default"
      return [:default, resolve_default_specialization(callee), nil]
    end

    if callee.callee.is_a?(AST::Identifier) && callee.callee.name == "hash"
      return [:hash, resolve_hash_specialization(callee), nil]
    end

    if callee.callee.is_a?(AST::Identifier) && callee.callee.name == "equal"
      return [:equal, resolve_equal_specialization(callee), nil]
    end

    if callee.callee.is_a?(AST::Identifier) && callee.callee.name == "order"
      return [:order, resolve_order_specialization(callee), nil]
    end

    if callee.callee.is_a?(AST::Identifier) && callee.callee.name == "attribute_arg"
      raise_sema_error("attribute_arg requires exactly one type argument") unless callee.arguments.length == 1

      type_arg = callee.arguments.first.value
      raise_sema_error("attribute_arg type argument must be a type") unless type_arg.is_a?(AST::TypeRef)

      return [:attribute_arg, resolve_type_ref(type_arg), nil]
    end

    if callee.callee.is_a?(AST::Identifier) && callee.callee.name == "adapt"
      raise_sema_error("adapt requires exactly one type argument") unless callee.arguments.length == 1

      type_arg = callee.arguments.first.value
      raise_sema_error("adapt type argument must be a type") unless type_arg.is_a?(AST::TypeRef)

      interface = resolve_adapt_interface(type_arg)
      return [:adapt, interface, nil]
    end

    if (callable_resolution = resolve_specialized_callable_binding(callee, scopes:))
      return callable_resolution
    end

    if (type_ref = type_ref_from_specialization(callee))
      specialized_type = resolve_type_ref(type_ref)
      return [:struct, specialized_type, nil] if specialized_type.is_a?(Types::Struct) || task_type?(specialized_type) || specialized_type.is_a?(Types::Vector) || specialized_type.is_a?(Types::Matrix) || specialized_type.is_a?(Types::Quaternion) || specialized_type.is_a?(Types::Simd)
    end

    raise_sema_error("unsupported callable specialization #{describe_expression(callee)}")
  else
    callee_type = infer_expression(callee, scopes:)
    return [:callable_value, callee_type, nil] if callable_type?(callee_type)

    raise_sema_error("unsupported callee #{describe_expression(callee)}")
  end
end

#resolve_callable_handle_argument(expression, scopes:) ⇒ Object



1576
1577
1578
1579
1580
1581
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1576

def resolve_callable_handle_argument(expression, scopes:)
  callable_kind, callable, _receiver = resolve_callable(expression, scopes:)
  raise_sema_error("callable_of expects a callable declaration name") unless callable_kind == :function

  Types::CallableHandle.new(describe_expression(expression), callable.ast)
end

#resolve_compile_time_type_ref(type_ref) ⇒ Object

Resolves a bare dotted reflection type expression such as field.type (where field is a compile-time field_handle bound in scope) to the concrete Types::Base it denotes. Returns nil when the type_ref is not a compile-time type expression resolvable in the current scopes. Only the plain <local>.member form is supported (no calls/indexing/type args).



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 356

def resolve_compile_time_type_ref(type_ref)
  return nil unless @type_resolution_scopes
  return nil if type_ref.arguments.any? || type_ref.nullable

  parts = type_ref.name.parts
  return nil unless parts.length >= 2

  binding = lookup_value(parts.first, @type_resolution_scopes)
  return nil unless binding && !binding.const_value.nil?

  expression = AST.build_chain_from_parts(parts)
  return nil unless expression

  value = evaluate_compile_time_const_value(expression, scopes: @type_resolution_scopes)
  value.is_a?(Types::Base) ? value : nil
end

#resolve_current_module_const_value(name) ⇒ Object



414
415
416
417
418
419
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 414

def resolve_current_module_const_value(name)
  binding = @ctx.top_level_values[name]
  return unless binding&.kind == :const

  evaluate_top_level_const_value(name)
end

#resolve_default_specialization(callee) ⇒ Object



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 241

def resolve_default_specialization(callee)
  raise_sema_error("default requires exactly one type argument") unless callee.arguments.length == 1

  type_arg = callee.arguments.first.value
  raise_sema_error("default type argument must be a type") unless type_arg.is_a?(AST::TypeRef)

  target_type = resolve_type_ref(type_arg)
  binding = resolve_explicit_default_binding(target_type, context: "default[#{target_type}]")
  raise_sema_error("default[#{target_type}] requires associated function #{target_type}.default()") unless binding

  DefaultResolution.new(
    target_type:,
    binding:,
  )
end

#resolve_enum_member_const_value(receiver_type, member_name, local_enum_type: nil, local_member_values: nil) ⇒ Object



438
439
440
441
442
443
444
445
446
447
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 438

def resolve_enum_member_const_value(receiver_type, member_name, local_enum_type: nil, local_member_values: nil)
  return unless receiver_type.is_a?(Types::EnumBase)
  return unless receiver_type.member(member_name)

  if local_enum_type && receiver_type == local_enum_type && local_member_values&.key?(member_name)
    return local_member_values[member_name]
  end

  receiver_type.member_value(member_name)
end

#resolve_enum_membersObject



564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 564

def resolve_enum_members
  expanded_declarations.each do |decl|
    with_error_node(decl) do
      next unless decl.is_a?(AST::EnumDecl) || decl.is_a?(AST::FlagsDecl)

      enum_type = @ctx.types.fetch(decl.name)
      backing_type = resolve_type_ref(decl.backing_type)
      unless backing_type.is_a?(Types::Primitive) && backing_type.integer?
        raise_sema_error("#{decl.name} backing type must be an integer primitive, got #{backing_type}")
      end

      member_names = []
      decl.members.each do |member|
        raise_sema_error("duplicate member #{decl.name}.#{member.name}") if member_names.include?(member.name)
        unless raw_module?
          ensure_non_reserved_value_type_name!(
            member.name,
            kind_label: "member #{decl.name}",
            line: member.line || decl.line,
            column: member.column,
          )
        end

        member_names << member.name
      end

      enum_type.define_members(backing_type, member_names)

      member_values = {}

      decl.members.each do |member|
        begin
          if member.value
            actual_type = infer_expression(member.value, scopes: [], expected_type: backing_type)
            const_value = evaluate_enum_member_const_value(member.value, enum_type:, member_values:)
          else
            const_value = (member_values.values.last&.succ || 0)
            actual_type = backing_type
          end

          compatible = types_compatible?(actual_type, backing_type, expression: member.value, scopes: [])
          compatible ||= actual_type.is_a?(Types::EnumBase) && types_compatible?(actual_type.backing_type, backing_type, expression: member.value, scopes: [])
          compatible ||= const_value.is_a?(Integer) && numeric_constant_fits_type?(const_value, backing_type)
          raise_sema_error("member #{decl.name}.#{member.name} expects #{backing_type}, got #{actual_type}", member) unless compatible

          raise_sema_error("member #{decl.name}.#{member.name} must be a compile-time integer constant", member) unless const_value.is_a?(Integer)

          member_values[member.name] = const_value
        rescue SemanticError => e
          collect_structural_error(e)
        end
      end

      enum_type.define_member_values(member_values)
    end
  rescue SemanticError => e
    collect_structural_error(e)
  end
end

#resolve_equal_specialization(callee) ⇒ Object



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 273

def resolve_equal_specialization(callee)
  raise_sema_error("equal requires exactly one type argument") unless callee.arguments.length == 1

  type_arg = callee.arguments.first.value
  raise_sema_error("equal type argument must be a type") unless type_arg.is_a?(AST::TypeRef)

  target_type = resolve_type_ref(type_arg)
  binding = resolve_explicit_equal_binding(target_type, context: "equal[#{target_type}]")
  raise_sema_error("equal[#{target_type}] requires associated function #{target_type}.equal(left: const_ptr[#{target_type}], right: const_ptr[#{target_type}]) -> bool") unless binding

  EqualResolution.new(
    target_type:,
    binding:,
  )
end

#resolve_event_decl_type(decl, type_params: {}, type_param_constraints: {}, owner_type_name: nil, nested_types: nil) ⇒ Object



426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 426

def resolve_event_decl_type(decl, type_params: {}, type_param_constraints: {}, owner_type_name: nil, nested_types: nil)
  raise_sema_error("event #{decl.name} capacity must be positive") unless decl.capacity.is_a?(Integer) && decl.capacity.positive?

  payload_type = decl.payload_type ? resolve_type_ref(decl.payload_type, type_params:, type_param_constraints:, nested_types:) : nil
  if payload_type
    raise_sema_error("event #{decl.name} payload cannot be ref[T] in v1") if ref_type?(payload_type)
    validate_stored_ref_type!(payload_type, "event #{decl.name} payload")
    raise_sema_error("event #{decl.name} payload uses unsupported proc nesting") unless proc_storage_supported_type?(payload_type)
    raise_sema_error("event #{decl.name} payload cannot use event storage type #{payload_type}") if noncopyable_event_storage_type?(payload_type)
  end

  Types::Event.new(
    decl.name,
    capacity: decl.capacity,
    payload_type: payload_type,
    module_name: @ctx.module_name,
    visibility: decl.visibility,
    owner_type_name: owner_type_name,
  )
end

#resolve_explicit_associated_binding(target_type, method_name, requirement_message:) ⇒ Object



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 390

def resolve_explicit_associated_binding(target_type, method_name, requirement_message:)
  method = lookup_static_method(target_type, method_name)
  if method
    raise_sema_error(requirement_message) unless method.type.receiver_type.nil?

    method = instantiate_function_binding_with_receiver(method, [], receiver_type: target_type) if method.type_params.any?
    yield method

    return method
  end

  if (imported_module = imported_module_with_private_method(target_type, method_name))
    raise_sema_error("#{target_type}.#{method_name} is private to module #{imported_module.name}")
  end

  nil
end

#resolve_explicit_default_binding(target_type, context:) ⇒ Object



305
306
307
308
309
310
311
312
313
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 305

def resolve_explicit_default_binding(target_type, context:)
  requirement_message = "#{context} requires associated function #{target_type}.default()"
  resolve_explicit_associated_binding(target_type, "default", requirement_message:) do |method|
    raise_sema_error("#{context} requires #{target_type}.default() to take 0 arguments") unless method.type.params.empty?
    unless types_compatible?(method.type.return_type, target_type)
      raise_sema_error("#{context} requires #{target_type}.default() to return #{target_type}, got #{method.type.return_type}")
    end
  end
end

#resolve_explicit_equal_binding(target_type, context:) ⇒ Object



327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 327

def resolve_explicit_equal_binding(target_type, context:)
  requirement_message = "#{context} requires associated function #{target_type}.equal(left: const_ptr[#{target_type}], right: const_ptr[#{target_type}]) -> bool"
  resolve_explicit_associated_binding(target_type, "equal", requirement_message:) do |method|
    expected_param_types = [const_pointer_to(target_type), const_pointer_to(target_type)]
    unless method.type.params.map(&:type) == expected_param_types
      raise_sema_error("#{context} requires #{target_type}.equal(left: const_ptr[#{target_type}], right: const_ptr[#{target_type}]) -> bool")
    end
    unless method.type.return_type == @ctx.types.fetch("bool")
      raise_sema_error("#{context} requires #{target_type}.equal(left: const_ptr[#{target_type}], right: const_ptr[#{target_type}]) -> bool, got #{method.type.return_type}")
    end
  end
end

#resolve_explicit_format_append_binding(target_type, context:) ⇒ Object



377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 377

def resolve_explicit_format_append_binding(target_type, context:)
  requirement_message = "#{context} requires method #{target_type}.append_format(output: ref[std.string.String]) -> void"
  resolve_explicit_instance_binding(target_type, "append_format", requirement_message:) do |method|
    raise_sema_error("#{context} requires #{target_type}.append_format() to be non-editable") if method.type.receiver_editable
    unless method.type.params.length == 1 && string_builder_ref_type?(method.type.params.first.type)
      raise_sema_error("#{context} requires #{target_type}.append_format(output: ref[std.string.String]) -> void")
    end
    unless method.type.return_type == @ctx.types.fetch("void")
      raise_sema_error("#{context} requires #{target_type}.append_format(output: ref[std.string.String]) -> void, got #{method.type.return_type}")
    end
  end
end

#resolve_explicit_format_binding(target_type, context:) ⇒ Object



353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 353

def resolve_explicit_format_binding(target_type, context:)
  length_binding = resolve_explicit_format_len_binding(target_type, context:)
  append_binding = resolve_explicit_format_append_binding(target_type, context:)

  return [length_binding, append_binding] if length_binding && append_binding

  if length_binding || append_binding
    raise_sema_error("#{context} requires methods #{target_type}.format_len() -> ptr_uint and #{target_type}.append_format(output: ref[std.string.String]) -> void")
  end

  nil
end

#resolve_explicit_format_len_binding(target_type, context:) ⇒ Object



366
367
368
369
370
371
372
373
374
375
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 366

def resolve_explicit_format_len_binding(target_type, context:)
  requirement_message = "#{context} requires method #{target_type}.format_len() -> ptr_uint"
  resolve_explicit_instance_binding(target_type, "format_len", requirement_message:) do |method|
    raise_sema_error("#{context} requires #{target_type}.format_len() to take 0 arguments") unless method.type.params.empty?
    raise_sema_error("#{context} requires #{target_type}.format_len() to be non-editable") if method.type.receiver_editable
    unless method.type.return_type == @ctx.types.fetch("ptr_uint")
      raise_sema_error("#{context} requires #{target_type}.format_len() -> ptr_uint, got #{method.type.return_type}")
    end
  end
end

#resolve_explicit_hash_binding(target_type, context:) ⇒ Object



315
316
317
318
319
320
321
322
323
324
325
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 315

def resolve_explicit_hash_binding(target_type, context:)
  requirement_message = "#{context} requires associated function #{target_type}.hash(value: const_ptr[#{target_type}]) -> uint"
  resolve_explicit_associated_binding(target_type, "hash", requirement_message:) do |method|
    unless method.type.params.map(&:type) == [const_pointer_to(target_type)]
      raise_sema_error("#{context} requires #{target_type}.hash(value: const_ptr[#{target_type}]) -> uint")
    end
    unless method.type.return_type == @ctx.types.fetch("uint")
      raise_sema_error("#{context} requires #{target_type}.hash(value: const_ptr[#{target_type}]) -> uint, got #{method.type.return_type}")
    end
  end
end

#resolve_explicit_instance_binding(target_type, method_name, requirement_message:) ⇒ Object



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 408

def resolve_explicit_instance_binding(target_type, method_name, requirement_message:)
  method = lookup_method(target_type, method_name)
  if method
    raise_sema_error(requirement_message) if method.type.receiver_type.nil?

    method = instantiate_function_binding_with_receiver(method, [], receiver_type: target_type) if method.type_params.any?
    yield method

    return method
  end

  if (imported_module = imported_module_with_private_method(target_type, method_name))
    raise_sema_error("#{target_type}.#{method_name} is private to module #{imported_module.name}")
  end

  nil
end

#resolve_explicit_order_binding(target_type, context:) ⇒ Object



340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 340

def resolve_explicit_order_binding(target_type, context:)
  requirement_message = "#{context} requires associated function #{target_type}.order(left: const_ptr[#{target_type}], right: const_ptr[#{target_type}]) -> int"
  resolve_explicit_associated_binding(target_type, "order", requirement_message:) do |method|
    expected_param_types = [const_pointer_to(target_type), const_pointer_to(target_type)]
    unless method.type.params.map(&:type) == expected_param_types
      raise_sema_error("#{context} requires #{target_type}.order(left: const_ptr[#{target_type}], right: const_ptr[#{target_type}]) -> int")
    end
    unless method.type.return_type == @ctx.types.fetch("int")
      raise_sema_error("#{context} requires #{target_type}.order(left: const_ptr[#{target_type}], right: const_ptr[#{target_type}]) -> int, got #{method.type.return_type}")
    end
  end
end

#resolve_foreign_call_expression(expression, scopes:) ⇒ Object



1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1151

def resolve_foreign_call_expression(expression, scopes:)
  call = expression
  return unless call.is_a?(AST::Call)

  callable_kind, callable, _receiver = resolve_callable(call.callee, scopes:)
  return unless callable_kind == :function

  callable = specialize_function_binding(
    callable,
    call.arguments,
    scopes:,
    receiver_type: callable_receiver_type_for_specialization(call.callee, scopes:),
  ) if callable.type_params.any?
  return unless foreign_function_binding?(callable)

  { call:, binding: callable }
rescue SemanticError
  nil
end

#resolve_generic_type_param_constraintsObject



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 288

def resolve_generic_type_param_constraints
  expanded_declarations.each do |decl|
    with_error_node(decl) do
      next unless decl.is_a?(AST::StructDecl) || decl.is_a?(AST::VariantDecl) || decl.is_a?(AST::InterfaceDecl)
      next if decl.type_params.empty?

      constraints = resolve_type_param_constraints(decl.type_params)
      if decl.is_a?(AST::InterfaceDecl)
        @ctx.interfaces[decl.name] = @ctx.interfaces.fetch(decl.name).with(type_param_constraints: constraints)
      else
        @ctx.types.fetch(decl.name).define_type_param_constraints(constraints)
      end
    end
  end
end

#resolve_hash_specialization(callee) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 257

def resolve_hash_specialization(callee)
  raise_sema_error("hash requires exactly one type argument") unless callee.arguments.length == 1

  type_arg = callee.arguments.first.value
  raise_sema_error("hash type argument must be a type") unless type_arg.is_a?(AST::TypeRef)

  target_type = resolve_type_ref(type_arg)
  binding = resolve_explicit_hash_binding(target_type, context: "hash[#{target_type}]")
  raise_sema_error("hash[#{target_type}] requires associated function #{target_type}.hash(value: const_ptr[#{target_type}]) -> uint") unless binding

  HashResolution.new(
    target_type:,
    binding:,
  )
end

#resolve_imported_module_const_value(import_name, value_name) ⇒ Object



425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 425

def resolve_imported_module_const_value(import_name, value_name)
  imported_module = @ctx.imports[import_name]
  return unless imported_module
  if imported_module.private_value?(value_name)
    raise_sema_error("#{import_name}.#{value_name} is private to module #{imported_module.name}")
  end

  binding = imported_module.values[value_name]
  return unless binding&.kind == :const

  binding.const_value
end

#resolve_interface_method_binding(method_decl, type_params: {}, type_param_constraints: {}) ⇒ Object



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 379

def resolve_interface_method_binding(method_decl, type_params: {}, type_param_constraints: {})
  raise_sema_error("interface method #{method_decl.name} cannot be async") if method_decl.async

  params = []
  seen = {}
  method_decl.params.each do |param|
    raise_sema_error("duplicate parameter #{param.name} in #{method_decl.name}") if seen.key?(param.name)

    seen[param.name] = true
    type = resolve_type_ref(param.type, type_params:, type_param_constraints:)
    validate_parameter_ref_type!(type, function_name: method_decl.name, parameter_name: param.name, external: false)
    validate_parameter_proc_type!(type, function_name: method_decl.name, parameter_name: param.name, external: false, foreign: false)
    params << Types::Registry.parameter(param.name, type)
  end

  return_type = method_decl.return_type ? resolve_type_ref(method_decl.return_type, type_params:, type_param_constraints:) : @ctx.types.fetch("void")
  validate_return_ref_type!(return_type, function_name: method_decl.name)
  validate_return_proc_type!(return_type, function_name: method_decl.name)

  InterfaceMethodBinding.new(
    name: method_decl.name,
    params: params.freeze,
    return_type: return_type,
    kind: method_decl.kind,
    async: method_decl.async,
    ast: method_decl,
  )
end

#resolve_interface_ref(interface_ref) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 130

def resolve_interface_ref(interface_ref)
  parts = interface_ref.parts

  interface = if parts.length == 1
                @ctx.interfaces[parts.first]
              elsif parts.length == 2 && @ctx.imports.key?(parts.first)
                imported_module = @ctx.imports.fetch(parts.first)
                raw = imported_module.interfaces[parts.last]
                if imported_module.private_interface?(parts.last)
                  raise_sema_error("#{parts.first}.#{parts.last} is private to module #{imported_module.name}")
                end
                raw
              end

  raise_sema_error("unknown interface #{interface_ref}", interface_ref) unless interface

  if interface_ref.type_arguments.any?
    raise_sema_error("interface #{interface.name} is not generic") unless interface.is_a?(GenericInterfaceBinding)

    arguments = interface_ref.type_arguments.map { |arg| resolve_type_ref(arg) }
    interface.instantiate(arguments)
  else
    raise_sema_error("generic interface #{interface.name} requires type arguments") if interface.is_a?(GenericInterfaceBinding)

    interface
  end
end

#resolve_methods_receiver_target(type_ref) ⇒ Object



1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1025

def resolve_methods_receiver_target(type_ref)
  if type_ref.is_a?(AST::TypeRef)
    generic_type = resolve_named_generic_type(type_ref.name.parts)
    if generic_type.is_a?(Types::GenericStructDefinition) || generic_type.is_a?(Types::GenericVariantDefinition)
      receiver_type_param_names = validate_methods_receiver_type_arguments!(type_ref, generic_type)
      receiver_type_params = receiver_type_param_names.to_h { |name| [name, Types::TypeVar.new(name)] }
      receiver_type_param_constraints = generic_type.respond_to?(:type_param_constraints) ? generic_type.type_param_constraints : {}
      receiver_type = resolve_type_ref(type_ref, type_params: receiver_type_params, type_param_constraints: receiver_type_param_constraints)
      return [generic_type, receiver_type, receiver_type_param_names, receiver_type_param_constraints]
    end

    begin
      receiver_type = resolve_type_ref(type_ref)
      unless receiver_type.is_a?(Types::Struct) || receiver_type.is_a?(Types::StructInstance) || receiver_type.is_a?(Types::Opaque) || receiver_type.is_a?(Types::GenericInstance) || receiver_type.is_a?(Types::Nullable) || receiver_type.is_a?(Types::StringView) || receiver_type.is_a?(Types::Primitive) || receiver_type.is_a?(Types::Variant) || receiver_type.is_a?(Types::VariantInstance) || vector_type?(receiver_type) || matrix_type?(receiver_type) || quaternion_type?(receiver_type)
        raise_sema_error("extending target #{type_ref} must be a struct, opaque, nullable/generic receiver, variant, or str")
      end

      return [receiver_type, receiver_type, [], {}]
    rescue MilkTea::SemanticError => error
      receiver_type_param_names = methods_receiver_type_argument_names!(type_ref)
      raise error if receiver_type_param_names.empty?

      receiver_type_params = receiver_type_param_names.to_h { |name| [name, Types::TypeVar.new(name)] }
      receiver_type = resolve_type_ref(type_ref, type_params: receiver_type_params)
      return [method_dispatch_receiver_type(receiver_type), receiver_type, receiver_type_param_names, {}]
    end
  end

  receiver_type = resolve_type_ref(type_ref)
  unless receiver_type.is_a?(Types::Struct) || receiver_type.is_a?(Types::StructInstance) || receiver_type.is_a?(Types::Opaque) || receiver_type.is_a?(Types::GenericInstance) || receiver_type.is_a?(Types::Nullable) || receiver_type.is_a?(Types::StringView) || receiver_type.is_a?(Types::Primitive) || receiver_type.is_a?(Types::Variant) || receiver_type.is_a?(Types::VariantInstance) || vector_type?(receiver_type) || matrix_type?(receiver_type) || quaternion_type?(receiver_type)
    raise_sema_error("extending target #{type_ref} must be a struct, opaque, nullable/generic receiver, variant, or str")
  end

  [receiver_type, receiver_type, [], {}]
end

#resolve_name_in_precheck_scopes(name, scopes) ⇒ Object



409
410
411
412
413
414
415
# File 'lib/milk_tea/core/semantic_analyzer/nullability.rb', line 409

def resolve_name_in_precheck_scopes(name, scopes)
  scopes.reverse_each do |scope|
    return scope[name] if scope.key?(name)
  end

  nil
end

#resolve_named_generic_type(parts) ⇒ Object



813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 813

def resolve_named_generic_type(parts)
  if parts.length == 1
    type = @ctx.types[parts.first]
    return type if type.is_a?(Types::GenericStructDefinition) || type.is_a?(Types::GenericVariantDefinition)
  elsif parts.length >= 2
    type = resolve_nested_type_ref(parts)
    return type if type.is_a?(Types::GenericStructDefinition) || type.is_a?(Types::GenericVariantDefinition)
    if @ctx.imports.key?(parts.first)
      type = @ctx.imports.fetch(parts.first).types[parts.last]
      return type if type.is_a?(Types::GenericStructDefinition) || type.is_a?(Types::GenericVariantDefinition)
    end
  end

  nil
end

#resolve_named_literal_type_argument(type_ref) ⇒ Object



401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 401

def resolve_named_literal_type_argument(type_ref)
  value = case type_ref.name.parts.length
          when 1
            resolve_current_module_const_value(type_ref.name.parts.first)
          when 2
            resolve_imported_module_const_value(type_ref.name.parts.first, type_ref.name.parts.last)
          end

  return unless value.is_a?(Integer) || value.is_a?(Float)

  Types::LiteralTypeArg.new(value)
end

#resolve_nested_struct_fields(parent_decl, parent_name: parent_decl.name, type_params:, type_param_constraints:, external_nested_scope: {}) ⇒ Object



803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 803

def resolve_nested_struct_fields(parent_decl, parent_name: parent_decl.name, type_params:, type_param_constraints:, external_nested_scope: {})
  parent_decl.nested_types.each do |nested|
    next unless nested.type_params.empty?
    qualified_name = "#{parent_name}.#{nested.name}"
    nested_type = @ctx.types[qualified_name]
    next unless nested_type.is_a?(Types::Struct)
    nested_type.ast_declaration = nested
    nested_scope = nested_type.nested_types.merge(external_nested_scope)
    fields = {}
    nested.fields.each do |field|
      raise_sema_error("duplicate field #{qualified_name}.#{field.name}") if fields.key?(field.name)
      begin
        field_type = resolve_type_ref(field.type, type_params:, type_param_constraints:, nested_types: nested_scope)
        validate_stored_ref_type!(field_type, "field #{qualified_name}.#{field.name}")
        fields[field.name] = field_type
      rescue SemanticError => e
        fields[field.name] = @error_type
        @structural_errors << e if @structural_errors
      end
    end
    nested_type.define_fields(fields)
    nested_events = {}
    nested.events.each do |event_decl|
      raise_sema_error("duplicate event #{qualified_name}.#{event_decl.name}") if nested_events.key?(event_decl.name)
      begin
        nested_events[event_decl.name] = resolve_event_decl_type(event_decl, type_params:, type_param_constraints:, owner_type_name: qualified_name, nested_types: nested_scope)
      rescue SemanticError => e
        collect_structural_error(e)
      end
    end
    nested_type.define_events(nested_events)
    resolve_nested_struct_fields(nested, parent_name: qualified_name, type_params:, type_param_constraints:, external_nested_scope: nested_scope)
  end
end

#resolve_nested_type_bindings(parent_decl, parent_name: parent_decl.name) ⇒ Object



838
839
840
841
842
843
844
845
846
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 838

def resolve_nested_type_bindings(parent_decl, parent_name: parent_decl.name)
  bindings = {}
  parent_decl.nested_types.each do |nested|
    qualified_name = "#{parent_name}.#{nested.name}"
    nested_type = @ctx.types[qualified_name]
    bindings[nested.name] = nested_type if nested_type
  end
  bindings
end

#resolve_nested_type_ref(parts) ⇒ Object



802
803
804
805
806
807
808
809
810
811
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 802

def resolve_nested_type_ref(parts)
  current = @ctx.types[parts.first]
  return nil unless current.is_a?(Types::Struct) || current.is_a?(Types::GenericStructDefinition)
  parts[1..].each do |part|
    nested = current.respond_to?(:nested_types) ? current.nested_types[part] : nil
    return nil unless nested
    current = nested
  end
  current
end

#resolve_non_nullable_type(type_ref, type_params: {}, type_param_constraints: {}, nested_types: nil) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 212

def resolve_non_nullable_type(type_ref, type_params: {}, type_param_constraints: {}, nested_types: nil)
  if type_ref.is_a?(AST::FunctionType)
    params = type_ref.params.map do |param|
      Types::Registry.parameter(param.name, resolve_type_ref(param.type, type_params:, type_param_constraints:))
    end
    return Types::Registry.function(nil, params:, return_type: resolve_type_ref(type_ref.return_type, type_params:, type_param_constraints:))
  end

  if type_ref.is_a?(AST::ProcType)
    params = type_ref.params.map do |param|
      Types::Registry.parameter(param.name, resolve_type_ref(param.type, type_params:, type_param_constraints:))
    end
    return Types::Registry.proc(params:, return_type: resolve_type_ref(type_ref.return_type, type_params:, type_param_constraints:))
  end

  if type_ref.is_a?(AST::DynType)
    interface = resolve_interface_ref(type_ref.interface)
    raise_sema_error("generic interface #{interface.name} requires type arguments") if interface.is_a?(GenericInterfaceBinding)
    type_arguments = interface.type_arguments || []
    type = Types::Dyn.new(interface, type_arguments)
    type = Types::Registry.nullable(type) if type_ref.nullable
    return type
  end

  if type_ref.is_a?(AST::TupleType)
    names = []
    element_types = []
    type_ref.element_types.each do |et|
      if et.is_a?(AST::Argument)
        names << et.name
        element_types << resolve_type_ref(et.value, type_params:, type_param_constraints:)
      else
        names << nil
        element_types << resolve_type_ref(et, type_params:, type_param_constraints:)
      end
    end
    has_named = names.any?
    return Types::Registry.tuple(element_types, field_names: has_named ? names : nil)
  end

  parts = type_ref.name.parts

  if type_ref.arguments.any?
    name = parts.join(".")
    arguments = type_ref.arguments.map { |argument| resolve_type_argument(argument.value, type_params:, type_param_constraints:) }

    if name != "ref" && arguments.any? { |argument| contains_ref_type?(argument) && !stored_ref_supported_type?(argument) }
      raise_sema_error("ref types cannot be nested inside #{name}", type_ref)
    end

    if name == "Task"
      validate_generic_type!(name, arguments)
      return Types::Registry.task(arguments[0])
    end

    if (generic_type = resolve_named_generic_type(parts))
      begin
        validate_generic_type_param_constraints!(generic_type, arguments, context: "type #{generic_type}", available_type_param_constraints: type_param_constraints)
        return generic_type.instantiate(arguments)
      rescue ArgumentError => error
        raise_sema_error(error.message)
      end
    end

    # Handle types with lifetime params only (no type params)
    if arguments.all? { |a| a.is_a?(Types::LifetimeRef) }
      type = @ctx.types[name]
      if type.is_a?(Types::Struct) && type.lifetime_params&.any?
        lifetime_args = arguments.select { |a| a.is_a?(Types::LifetimeRef) }.map(&:name)
        if lifetime_args.to_set == type.lifetime_params.to_set
          return type
        end
      end
    end

    validate_generic_type!(name, arguments)
    return Types::Registry.span(arguments.first) if name == "span"

    return Types::Registry.soa(arguments[0], count: arguments[1].value) if name == "SoA"

    return Types::Registry.simd(arguments[0], lane_count: arguments[1].value) if name == "simd"

    arguments = [type_ref.lifetime] + arguments if name == "ref" && type_ref.lifetime
    return Types::Registry.generic_instance(name, arguments)
  end

  if parts.length == 1 && type_ref.lifetime
    raise_sema_error("lifetime annotations are only valid on ref types, got #{type_ref.name}", type_ref)
  end

  if parts.length == 1
    return type_params.fetch(parts.first) if type_params.key?(parts.first)

    if nested_types && (type = nested_types[parts.first])
      return type
    end

    if parts.first.start_with?("@")
      raise_sema_error("unknown lifetime #{parts.first}", type_ref)
    end

    type = @ctx.types[parts.first]
    unless type
      type_names = @ctx.types.keys
      suggestion = suggest_name(parts.first, type_names)
      unless suggestion
        suggestion = import_suggestion_for_type(parts.first)
      end
      raise_sema_error("unknown type #{parts.first}", type_ref, suggestion: suggestion ? "did you mean '#{suggestion}'?" : nil)
    end
    raise_sema_error("generic type #{parts.first} requires type arguments", type_ref) if type.is_a?(Types::GenericStructDefinition) || type.is_a?(Types::GenericVariantDefinition)

    return type
  end

  if parts.length >= 2
    type = resolve_nested_type_ref(parts)
    return type if type
  end

  if parts.length == 2 && @ctx.imports.key?(parts.first)
    imported_module = @ctx.imports.fetch(parts.first)
    type = imported_module.types[parts.last]
    if imported_module.private_type?(parts.last)
      raise_sema_error("#{parts.first}.#{parts.last} is private to module #{imported_module.name}", type_ref)
    end
    raise_sema_error("unknown type #{type_ref.name}", type_ref) unless type
    raise_sema_error("generic type #{type_ref.name} requires type arguments", type_ref) if type.is_a?(Types::GenericStructDefinition) || type.is_a?(Types::GenericVariantDefinition)

    return type
  end

  if @compile_time_depth&.positive? && (ct_type = resolve_compile_time_type_ref(type_ref))
    return ct_type
  end

  raise_sema_error("unknown type #{type_ref.name}", type_ref, suggestion: import_suggestion_for_type(type_ref.name))
end

#resolve_order_specialization(callee) ⇒ Object



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 289

def resolve_order_specialization(callee)
  raise_sema_error("order requires exactly one type argument") unless callee.arguments.length == 1

  type_arg = callee.arguments.first.value
  raise_sema_error("order type argument must be a type") unless type_arg.is_a?(AST::TypeRef)

  target_type = resolve_type_ref(type_arg)
  binding = resolve_explicit_order_binding(target_type, context: "order[#{target_type}]")
  raise_sema_error("order[#{target_type}] requires associated function #{target_type}.order(left: const_ptr[#{target_type}], right: const_ptr[#{target_type}]) -> int") unless binding

  OrderResolution.new(
    target_type:,
    binding:,
  )
end

#resolve_qualified_type_name(parts) ⇒ Object



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 347

def resolve_qualified_type_name(parts)
  return nil unless parts.length >= 2

  if @ctx.imports.key?(parts.first)
    imported_module = @ctx.imports.fetch(parts.first)
    type = imported_module.types[parts.last]
    if imported_module.private_type?(parts.last)
      raise_sema_error("#{parts.first}.#{parts.last} is private to module #{imported_module.name}")
    end
    raise_sema_error("unknown type #{parts.join('.')} for struct destructure") unless type
    return type
  end

  parent_type = @ctx.types[parts.first]
  if parent_type.respond_to?(:nested_types) && parent_type.nested_types.key?(parts.last)
    return parent_type.nested_types[parts.last]
  end

  raise_sema_error("unknown type #{parts.join('.')} for struct destructure")
end

#resolve_receiver_name_for_call_inference(callee, scopes) ⇒ Object



577
578
579
580
581
582
583
584
585
586
587
588
589
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 577

def resolve_receiver_name_for_call_inference(callee, scopes)
  case callee.receiver
  when AST::Identifier
    if callee.receiver.name == "this"
      binding = lookup_value("this", scopes)
      return nil unless binding
      this_type = receiver_type_for_fields(binding.type)
      this_type&.name
    else
      callee.receiver.name
    end
  end
end

#resolve_reflection_target_argument(expression, scopes:) ⇒ Object



1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1564

def resolve_reflection_target_argument(expression, scopes:)
  if (type_expr = resolve_type_expression(expression))
    handle = struct_handle_for_type(type_expr)
    return handle if handle
  end

  handle = evaluate_compile_time_const_value(expression, scopes:)
  return handle if handle.is_a?(Types::FieldHandle) || handle.is_a?(Types::CallableHandle)

  raise_sema_error("attribute reflection expects a struct type, field handle, or callable handle")
end

#resolve_specialization_type_arguments(expression) ⇒ Object



163
164
165
166
167
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 163

def resolve_specialization_type_arguments(expression)
  expression.arguments.map do |argument|
    resolve_type_argument(argument.value, type_params: current_type_params)
  end
end

#resolve_specialized_callable_binding(expression, scopes:) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 113

def resolve_specialized_callable_binding(expression, scopes:)
  callable_kind = :function
  receiver = nil
  receiver_type = nil
  binding = case expression.callee
  when AST::Identifier
    @ctx.top_level_functions[expression.callee.name]
  when AST::MemberAccess
    if expression.callee.receiver.is_a?(AST::Identifier) && @ctx.imports.key?(expression.callee.receiver.name)
      imported_module = @ctx.imports.fetch(expression.callee.receiver.name)
      imported_function = imported_module.functions[expression.callee.member]
      if imported_function.nil? && imported_module.private_function?(expression.callee.member)
        raise_sema_error("#{expression.callee.receiver.name}.#{expression.callee.member} is private to module #{imported_module.name}")
      end

      imported_function
    elsif (type_expr = resolve_type_expression(expression.callee.receiver))
      associated_function = lookup_method(type_expr, expression.callee.member)
      if associated_function&.type&.receiver_type.nil?
        receiver_type = type_expr
        associated_function
      else
        if (imported_module = imported_module_with_private_method(type_expr, expression.callee.member))
          raise_sema_error("#{type_expr}.#{expression.callee.member} is private to module #{imported_module.name}")
        end

        nil
      end
    else
      receiver_type = infer_method_receiver_type(expression.callee.receiver, scopes:, member_name: expression.callee.member)
      method = lookup_method(receiver_type, expression.callee.member)
      if method
        callable_kind = :method
        receiver = expression.callee.receiver
        method
      else
        if (imported_module = imported_module_with_private_method(receiver_type, expression.callee.member))
          raise_sema_error("#{receiver_type}.#{expression.callee.member} is private to module #{imported_module.name}")
        end

        nil
      end
    end
  end
  return nil unless binding

  type_arguments = resolve_specialization_type_arguments(expression)
  [callable_kind, instantiate_function_binding_with_receiver(binding, type_arguments, receiver_type:), receiver]
end

#resolve_struct_handle_argument(expression, scopes:) ⇒ Object



1555
1556
1557
1558
1559
1560
1561
1562
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1555

def resolve_struct_handle_argument(expression, scopes:)
  if (type_expr = resolve_type_expression(expression))
    handle = struct_handle_for_type(type_expr)
    return handle if handle
  end

  raise_sema_error("field_of expects a struct type expression")
end

#resolve_type_aliasesObject



304
305
306
307
308
309
310
311
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 304

def resolve_type_aliases
  @ctx.ast.declarations.grep(AST::TypeAliasDecl).each do |decl|
    ensure_available_type_name!(decl.name)
    @ctx.types[decl.name] = resolve_type_ref(decl.target)
  rescue SemanticError => e
    collect_structural_error(e)
  end
end

#resolve_type_argument(argument, type_params: current_type_params, type_param_constraints: current_type_param_constraints) ⇒ Object



373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 373

def resolve_type_argument(argument, type_params: current_type_params, type_param_constraints: current_type_param_constraints)
  case argument
  when AST::TypeRef
    resolve_type_argument_ref(argument, type_params:, type_param_constraints:)
  when AST::FunctionType, AST::ProcType
    resolve_type_ref(argument, type_params:, type_param_constraints:)
  when AST::IntegerLiteral, AST::FloatLiteral
    Types::LiteralTypeArg.new(argument.value)
  else
    raise_sema_error("unsupported type argument #{argument.class.name}")
  end
end

#resolve_type_argument_ref(type_ref, type_params:, type_param_constraints:) ⇒ Object



386
387
388
389
390
391
392
393
394
395
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 386

def resolve_type_argument_ref(type_ref, type_params:, type_param_constraints:)
  return resolve_type_ref(type_ref, type_params:, type_param_constraints:) unless literal_type_argument_name_candidate?(type_ref)

  resolve_type_ref(type_ref, type_params:, type_param_constraints:)
rescue SemanticError => error
  literal_type_argument = resolve_named_literal_type_argument(type_ref)
  return literal_type_argument if literal_type_argument

  raise error
end

#resolve_type_expression(expression) ⇒ Object



975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 975

def resolve_type_expression(expression)
  case expression
  when AST::Identifier
    return current_type_params[expression.name] if current_type_params.key?(expression.name)

    @ctx.types[expression.name]
  when AST::MemberAccess
    return nil unless expression.receiver.is_a?(AST::Identifier)

    if @ctx.imports.key?(expression.receiver.name)
      imported_module = @ctx.imports.fetch(expression.receiver.name)
      if imported_module.private_type?(expression.member)
        raise_sema_error("#{expression.receiver.name}.#{expression.member} is private to module #{imported_module.name}")
      end

      return imported_module.types[expression.member]
    end

    parent_type = @ctx.types[expression.receiver.name]
    return parent_type.nested_types[expression.member] if parent_type.respond_to?(:nested_types) && parent_type.nested_types.key?(expression.member)

    nil
  when AST::Specialization
    type_ref = type_ref_from_specialization(expression)
    return nil unless type_ref

    resolve_type_ref(type_ref)
  end
end

#resolve_type_member(type, name) ⇒ Object



98
99
100
101
102
103
104
105
106
107
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 98

def resolve_type_member(type, name)
  case type
  when Types::Enum, Types::Flags
    type.member(name)
  when Types::Variant
    return type if type.arm_names.include?(name) && !type.has_payload?(name)

    nil
  end
end

#resolve_type_param_constraints(type_params) ⇒ Object



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 208

def resolve_type_param_constraints(type_params)
  type_params.each_with_object({}) do |type_param, constraints|
    if type_param.is_a?(AST::ValueTypeParam)
      ensure_non_reserved_value_type_name!(
        type_param.name,
        kind_label: "type parameter",
        line: type_param.line,
        column: type_param.column,
        length: type_param.length,
      )
      next
    end

    ensure_non_reserved_value_type_name!(
      type_param.name,
      kind_label: "type parameter",
      line: type_param.line,
      column: type_param.column,
      length: type_param.length,
    )
    next if type_param.constraints.empty?

    resolved_interfaces = []
    seen_interfaces = {}

    type_param.constraints.each do |constraint|
      case constraint.kind
      when :interface
        interface = resolve_interface_ref(constraint.interface_ref)
        raise_sema_error("duplicate interface constraint #{type_param.name} implements #{interface.name}") if seen_interfaces.key?(interface)

        seen_interfaces[interface] = true
        resolved_interfaces << interface
      else
        raise_sema_error("unsupported type parameter constraint #{constraint.kind}")
      end
    end

    constraints[type_param.name] = TypeParamConstraintBinding.new(
      interfaces: resolved_interfaces.freeze,
    )
  end
end

#resolve_type_ref(type_ref, type_params: current_type_params, type_param_constraints: current_type_param_constraints, nested_types: nil) ⇒ Object



203
204
205
206
207
208
209
210
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 203

def resolve_type_ref(type_ref, type_params: current_type_params, type_param_constraints: current_type_param_constraints, nested_types: nil)
  base = resolve_non_nullable_type(type_ref, type_params:, type_param_constraints:, nested_types:)
  return base if type_ref.is_a?(AST::FunctionType) || type_ref.is_a?(AST::ProcType) || type_ref.is_a?(AST::TupleType)

  raise_sema_error("ref types are non-null and cannot be nullable", type_ref) if type_ref.nullable && ref_type?(base)

  type_ref.nullable ? Types::Registry.nullable(base) : base
end

#resolve_variant_armsObject



624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 624

def resolve_variant_arms
  expanded_declarations.each do |decl|
    with_error_node(decl) do
      next unless decl.is_a?(AST::VariantDecl)

      variant_type = @ctx.types.fetch(decl.name)
      type_params = if variant_type.is_a?(Types::GenericVariantDefinition)
        seen = {}
        variant_type.type_params.each_with_object({}) do |name, params|
          raise_sema_error("duplicate type parameter #{decl.name}[#{name}]") if seen.key?(name)

          seen[name] = true
          params[name] = Types::TypeVar.new(name)
        end
      else
        {}
      end
      type_param_constraints = variant_type.is_a?(Types::GenericVariantDefinition) ? variant_type.type_param_constraints : {}
      seen_arms = []
      arms_hash = {}
      decl.arms.each do |arm|
        begin
          raise_sema_error("duplicate arm #{decl.name}.#{arm.name}") if seen_arms.include?(arm.name)
          unless raw_module?
            ensure_non_reserved_value_type_name!(
              arm.name,
              kind_label: "arm #{decl.name}",
              line: arm.line || decl.line,
              column: arm.column,
            )
          end

          seen_arms << arm.name
          field_types = {}
          seen_fields = []
          arm_field_errors = []
          arm.fields.each do |field|
            begin
              raise_sema_error("duplicate field #{arm.name}.#{field.name}") if seen_fields.include?(field.name)
              unless raw_module?
                ensure_non_reserved_value_type_name!(
                  field.name,
                  kind_label: "field #{decl.name}.#{arm.name}",
                  line: field.line || decl.line,
                  column: field.column,
                )
              end

              seen_fields << field.name
              field_type = resolve_type_ref(field.type, type_params:, type_param_constraints:)
              validate_stored_ref_type!(field_type, "field #{decl.name}.#{arm.name}.#{field.name}")
              unless proc_storage_supported_type?(field_type)
                raise_sema_error("field #{decl.name}.#{arm.name}.#{field.name} uses unsupported proc nesting")
              end
              field_types[field.name] = field_type
            rescue SemanticError => e
              field_types[field.name] = @error_type
              arm_field_errors << e
              @structural_errors << e if @structural_errors
            end
          end

          raise arm_field_errors.first unless arm_field_errors.empty?
          arms_hash[arm.name] = field_types
        rescue SemanticError => e
          collect_structural_error(e)
        end
      end

      variant_type.define_arms(arms_hash)
    end
  rescue SemanticError => e
    collect_structural_error(e)
  end
end

#resolved_attribute_applications_for_target(target) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 6

def resolved_attribute_applications_for_target(target)
  target_id = case target
  when Types::StructHandle then target.declaration.object_id
  when Types::FieldHandle then target.field_declaration.object_id
  when Types::CallableHandle then target.declaration.object_id
  end
  return [] unless target_id

  applications = @ctx.resolved_attribute_applications[target_id]
  return applications if applications

  @ctx.imports.each_value do |imported_module|
    applications = imported_module.attribute_applications[target_id]
    return applications if applications
  end

  []
end

#result_let_else_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


810
811
812
813
814
815
816
817
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 810

def result_let_else_type?(type)
  return false unless type.is_a?(Types::Variant)

  success_fields = type.arm("success")
  failure_fields = type.arm("failure")
  success_fields && success_fields.length == 1 && success_fields.key?("value") &&
    failure_fields && failure_fields.length == 1 && failure_fields.key?("error")
end

#run_nullability_pre_pass(binding, scopes) ⇒ Object

Runs a strict binding-ID nullability pass before statement checks. Resolution is computed with a lexical pre-check walk so shadowed names are disambiguated without relying on name fallback.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/milk_tea/core/semantic_analyzer/nullability.rb', line 56

def run_nullability_pre_pass(binding, scopes)
  return unless binding.ast.respond_to?(:body)

  @nullability_flow_result = nil
  resolution = precheck_binding_resolution(binding.ast.body, scopes)
  graph = ControlFlow::Builder.new(
    binding_resolution: ControlFlow::BindingResolution.new(
      identifier_binding_ids: resolution.identifier_binding_ids,
      declaration_binding_ids: resolution.declaration_binding_ids,
      mutating_argument_identifier_ids: resolution.mutating_argument_identifier_ids,
    ),
    strict_binding_ids: true,
  ).build(binding.ast.body)
  @nullability_flow_result = ControlFlow::NullabilityFlow.solve(graph)
end

#safe_reference_source_expression?(expression, scopes:) ⇒ Boolean

Returns:

  • (Boolean)


1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1247

def safe_reference_source_expression?(expression, scopes:)
  case expression
  when AST::Identifier
    true
  when AST::MemberAccess, AST::IndexAccess
    safe_reference_source_expression?(expression.receiver, scopes:)
  when AST::BinaryOp
    unsafe_context?
  when AST::Call
    return false unless expression.arguments.length == 1 && expression.arguments.first.name.nil?

    if read_call?(expression)
      argument_type = infer_expression(expression.arguments.first.value, scopes:)
      ref_type?(argument_type) || pointer_type?(argument_type)
    else
      false
    end
  else
    false
  end
end

#same_attribute_binding?(left, right) ⇒ Boolean

Returns:

  • (Boolean)


31
32
33
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 31

def same_attribute_binding?(left, right)
  left.name == right.name && left.module_name == right.module_name
end

#scopes_with_refinements(scopes, refinements) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 20

def scopes_with_refinements(scopes, refinements)
  return scopes if refinements.nil? || refinements.empty?

  base_scopes = scopes.last.is_a?(FlowScope) ? scopes[0...-1] : scopes
  merged_refinements = if scopes.last.is_a?(FlowScope)
    scopes.last.each_with_object({}) do |(name, binding), result|
      result[name] = if name.is_a?(String) && binding.respond_to?(:type)
        binding.type
      else
        binding
      end
    end
  else
    {}
  end
  merged_refinements = merge_refinements(merged_refinements, refinements)
  flow_scope = FlowScope.new

  merged_refinements.each do |name, refined_type|
    unless name.is_a?(String)
      flow_scope[name] = refined_type
      next
    end

    binding = lookup_value(name, base_scopes)
    next unless binding

    flow_scope[name] = binding.with_flow_type(refined_type)
  end

  return base_scopes if flow_scope.empty?

  base_scopes + [flow_scope]
end

#set_const_value(name, value) ⇒ Object



239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 239

def set_const_value(name, value)
  binding = @ctx.top_level_values.fetch(name)
  @ctx.top_level_values[name] = ValueBinding.new(
    id: binding.id,
    name: binding.name,
    storage_type: binding.storage_type,
    flow_type: binding.flow_type,
    mutable: binding.mutable,
    kind: binding.kind,
    const_value: value,
  )
  @evaluated_const_values[name] = true
end

#simd_bitwise_result(left_type, right_type) ⇒ Object



240
241
242
243
244
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 240

def simd_bitwise_result(left_type, right_type)
  return nil unless simd_type?(left_type) && simd_type?(right_type) && left_type == right_type

  left_type
end

#simd_method_kind(receiver_type, name) ⇒ Object



660
661
662
663
664
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 660

def simd_method_kind(receiver_type, name)
  return unless simd_type?(receiver_type)

  SIMD_METHOD_KINDS[name]
end

#simd_mod_result(left_type, right_type) ⇒ Object



246
247
248
249
250
251
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 246

def simd_mod_result(left_type, right_type)
  return nil unless simd_type?(left_type) && left_type.element_type.integer?
  return nil unless right_type == left_type.element_type || (simd_type?(right_type) && right_type == left_type)

  left_type
end

#simd_op_result(operator, left_type, right_type) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 224

def simd_op_result(operator, left_type, right_type)
  if simd_type?(left_type) && simd_type?(right_type) && left_type.element_type == right_type.element_type && left_type.lane_count == right_type.lane_count
    return left_type if operator == "+" || operator == "-" || operator == "*" || operator == "/"
  end

  if simd_type?(left_type) && right_type.numeric? && right_type == left_type.element_type
    return left_type if operator == "*" || operator == "/"
  end

  if left_type.numeric? && simd_type?(right_type) && left_type == right_type.element_type
    return right_type if operator == "*"
  end

  nil
end

#simd_shift_result(left_type, right_type) ⇒ Object



253
254
255
256
257
258
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 253

def simd_shift_result(left_type, right_type)
  return nil unless simd_type?(left_type) && left_type.element_type.integer?
  return nil unless right_type.is_a?(Types::Primitive) && right_type.integer?

  left_type
end

#simd_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


721
722
723
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 721

def simd_type?(type)
  type.is_a?(Types::Simd)
end

#simple_foreign_argument_expression?(expression) ⇒ Boolean

Returns:

  • (Boolean)


1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1205

def simple_foreign_argument_expression?(expression)
  case expression
  when AST::Identifier, AST::IntegerLiteral, AST::FloatLiteral, AST::StringLiteral, AST::BooleanLiteral, AST::NullLiteral
    true
  when AST::MemberAccess
    simple_foreign_argument_expression?(expression.receiver)
  else
    false
  end
end

#sized_layout_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


648
649
650
651
652
653
654
655
656
657
658
659
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 648

def sized_layout_type?(type)
  case type
  when Types::Primitive, Types::Struct, Types::StructInstance, Types::Union, Types::Enum, Types::Flags, Types::Variant, Types::Span, Types::StringView, Types::Task, Types::Event, Types::Subscription
    true
  when Types::Nullable
    true
  when Types::GenericInstance
    pointer_type?(type) || array_type?(type) || str_buffer_type?(type)
  else
    false
  end
end

#snapshot_attribute_applicationsObject



124
125
126
127
128
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 124

def snapshot_attribute_applications
  @ctx.resolved_attribute_applications.each_with_object({}) do |(target_id, applications), snapshot|
    snapshot[target_id] = applications
  end.freeze
end

#snapshot_attributesObject



116
117
118
119
120
121
122
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 116

def snapshot_attributes
  @ctx.attributes.each_with_object({}) do |(name, binding), attributes|
    next if binding.builtin

    attributes[name] = binding
  end.freeze
end

#snapshot_implemented_interfacesObject



136
137
138
139
140
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 136

def snapshot_implemented_interfaces
  @ctx.implemented_interfaces.each_with_object({}) do |(receiver_type, interfaces), snapshot|
    snapshot[receiver_type] = interfaces.dup.freeze
  end.freeze
end

#snapshot_methodsObject



130
131
132
133
134
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 130

def snapshot_methods
  @ctx.methods.each_with_object({}) do |(receiver_type, bindings), methods|
    methods[receiver_type] = bindings.dup.freeze
  end.freeze
end

#soa_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


717
718
719
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 717

def soa_type?(type)
  type.is_a?(Types::SoA)
end

#source_column(node) ⇒ Object



550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 550

def source_column(node)
  return nil unless node
  return node.column if node.column

  case node
  when AST::MemberAccess then source_column(node.receiver)
  when AST::IndexAccess then source_column(node.receiver) || source_column(node.index)
  when AST::Specialization then source_column(node.callee)
  when AST::Call then source_column(node.callee) || node.arguments.filter_map { |argument| source_column(argument.value) }.first
  when AST::Argument then source_column(node.value)
  when AST::UnaryOp then source_column(node.operand)
  when AST::BinaryOp then source_column(node.left) || source_column(node.right)
  when AST::IfExpr then source_column(node.condition) || source_column(node.then_expression) || source_column(node.else_expression)
  when AST::MatchExpr then source_column(node.expression) || node.arms.filter_map { |arm| source_column(arm.pattern) || source_column(arm.value) }.first
  when AST::AwaitExpr then source_column(node.expression)
  when AST::FormatExprPart then source_column(node.expression)
  when AST::PrefixCast then source_column(node.target_type)
  when AST::Assignment then source_column(node.target) || source_column(node.value)
  when AST::ExpressionStmt then source_column(node.expression)
  when AST::StaticAssert then source_column(node.condition)
  when AST::IfStmt then node.branches.filter_map { |branch| branch.column || source_column(branch.condition) }.first || node.else_column
  else nil
  end
end

#source_length(node) ⇒ Object



532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 532

def source_length(node)
  return nil unless node
  return node.length if node.respond_to?(:length) && node.length
  return node.name.to_s.length if node.respond_to?(:name) && node.name

  case node
  when AST::Identifier then node.name.to_s.length
  when AST::MemberAccess then node.member.to_s.length
  when AST::Assignment then source_length(node.target) || source_length(node.value)
  when AST::ExpressionStmt then source_length(node.expression)
  when AST::StaticAssert then source_length(node.condition)
  when AST::IfStmt
    node.branches.filter_map { |branch| branch.length || source_length(branch.condition) }.first
  else
    nil
  end
end

#source_line(node) ⇒ Object



511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 511

def source_line(node)
  return nil unless node
  return node.line if node.line

  case node
  when AST::MemberAccess then source_line(node.receiver)
  when AST::IndexAccess then source_line(node.receiver) || source_line(node.index)
  when AST::Specialization then source_line(node.callee)
  when AST::Call then source_line(node.callee) || node.arguments.filter_map { |argument| source_line(argument.value) }.first
  when AST::Argument then source_line(node.value)
  when AST::UnaryOp then source_line(node.operand)
  when AST::BinaryOp then source_line(node.left) || source_line(node.right)
  when AST::IfExpr then source_line(node.condition) || source_line(node.then_expression) || source_line(node.else_expression)
  when AST::MatchExpr then source_line(node.expression) || node.arms.filter_map { |arm| source_line(arm.pattern) || source_line(arm.value) }.first
  when AST::AwaitExpr then source_line(node.expression)
  when AST::FormatExprPart then source_line(node.expression)
  when AST::PrefixCast then source_line(node.target_type)
  else nil
  end
end

#span_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


576
577
578
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 576

def span_type?(type)
  type.is_a?(Types::Span)
end

#specialization_lookup_ownerObject



123
124
125
126
127
128
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 123

def specialization_lookup_owner
  return nil if @current_specialization_owner.nil?
  return nil if @current_specialization_owner.equal?(self)

  @current_specialization_owner
end

#specialize_function_binding(binding, arguments, scopes:, receiver_type: nil) ⇒ Object



169
170
171
172
173
174
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 169

def specialize_function_binding(binding, arguments, scopes:, receiver_type: nil)
  return binding if binding.type_params.empty?

  type_arguments = infer_function_type_arguments(binding, arguments, scopes:, receiver_type:)
  instantiate_function_binding(binding, type_arguments)
end

#stack_safe_pointer_source?(binding) ⇒ Boolean

Returns:

  • (Boolean)


1422
1423
1424
1425
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1422

def stack_safe_pointer_source?(binding)
  return false unless binding.type
  ref_type?(binding.type) || own_type?(binding.type) || (binding.mutable && binding.kind == :param)
end

#start_local_completion_frame(binding, scopes) ⇒ Object



101
102
103
104
105
106
107
108
109
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 101

def start_local_completion_frame(binding, scopes)
  frame = {
    function_name: binding.name,
    receiver_type: binding.type.receiver_type,
    snapshots: [],
  }
  @active_local_completion_stack << frame
  record_local_completion_snapshot(binding.ast.line, 0, scopes)
end

#statement_contains_await?(statement) ⇒ Boolean

Returns:

  • (Boolean)


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 206

def statement_contains_await?(statement)
  case statement
  when AST::ErrorBlockStmt
    (statement.header_expression && expression_contains_await?(statement.header_expression)) ||
      Array(statement.header_iterables).any? { |iterable| expression_contains_await?(iterable) } ||
      statements_contain_await?(statement.body)
  when AST::LocalDecl
    (statement.value && expression_contains_await?(statement.value)) ||
      (statement.else_body && statements_contain_await?(statement.else_body))
  when AST::Assignment
    expression_contains_await?(statement.target) || expression_contains_await?(statement.value)
  when AST::IfStmt
    statement.branches.any? { |branch| expression_contains_await?(branch.condition) || statements_contain_await?(branch.body) } ||
      (statement.else_body && statements_contain_await?(statement.else_body))
  when AST::MatchStmt
    expression_contains_await?(statement.expression) || statement.arms.any? { |arm| expression_contains_await?(arm.pattern) || statements_contain_await?(arm.body) }
  when AST::UnsafeStmt
    statements_contain_await?(statement.body)
  when AST::StaticAssert
    expression_contains_await?(statement.condition) || expression_contains_await?(statement.message)
  when AST::ForStmt
    statement.iterables.any? { |iterable| expression_contains_await?(iterable) } || statements_contain_await?(statement.body)
  when AST::WhileStmt
    expression_contains_await?(statement.condition) || statements_contain_await?(statement.body)
  when AST::ReturnStmt
    statement.value && expression_contains_await?(statement.value)
  when AST::DeferStmt
    (statement.expression && expression_contains_await?(statement.expression)) || (statement.body && statements_contain_await?(statement.body))
  when AST::ExpressionStmt
    expression_contains_await?(statement.expression)
  else
    false
  end
end

#statement_end_line(statement) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 184

def statement_end_line(statement)
  return nil unless statement

  lines = [statement.line]

  case statement
  when AST::ErrorBlockStmt
    lines.concat(statement_list_lines(statement.body))
  when AST::LocalDecl
    lines << expression_end_line(statement.value) if statement.value
    lines.concat(statement_list_lines(statement.else_body)) if statement.else_body
  when AST::IfStmt
    statement.branches.each do |branch|
      lines << expression_end_line(branch.condition)
      lines.concat(statement_list_lines(branch.body))
    end
    lines.concat(statement_list_lines(statement.else_body)) if statement.else_body
  when AST::UnsafeStmt, AST::ForStmt, AST::WhileStmt
    lines.concat(statement_list_lines(statement.body))
  when AST::MatchStmt
    statement.arms.each do |arm|
      if arm.respond_to?(:body)
        lines.concat(statement_list_lines(arm.body))
      else
        lines << expression_end_line(arm.value)
      end
    end
  when AST::DeferStmt
    lines.concat(statement_list_lines(statement.body)) if statement.body
  when AST::Assignment
    lines << expression_end_line(statement.value)
  when AST::ReturnStmt
    lines << expression_end_line(statement.value)
  when AST::ExpressionStmt
    lines << expression_end_line(statement.expression)
  when AST::StaticAssert
    lines << expression_end_line(statement.condition)
  end

  lines.compact.max
end

#statement_list_lines(statements) ⇒ Object



226
227
228
229
230
231
232
233
# File 'lib/milk_tea/core/semantic_analyzer/flow_refinement.rb', line 226

def statement_list_lines(statements)
  return [] unless statements

  statements.each_with_object([]) do |stmt, lines|
    end_line = statement_end_line(stmt)
    lines << end_line if end_line
  end
end

#statements_contain_await?(statements) ⇒ Boolean

Returns:

  • (Boolean)


241
242
243
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 241

def statements_contain_await?(statements)
  statements.any? { |statement| statement_contains_await?(statement) }
end

#static_storage_function_value?(binding) ⇒ Boolean

Returns:

  • (Boolean)


183
184
185
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 183

def static_storage_function_value?(binding)
  !binding.external && binding.type_params.empty?
end

#static_storage_member_initializer?(expression, scopes:) ⇒ Boolean

Returns:

  • (Boolean)


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 125

def static_storage_member_initializer?(expression, scopes:)
  if (type_expr = resolve_type_expression(expression.receiver))
    return true if resolve_type_member(type_expr, expression.member)
  end

  return false unless expression.receiver.is_a?(AST::Identifier)
  return false unless @ctx.imports.key?(expression.receiver.name)

  imported_module = @ctx.imports.fetch(expression.receiver.name)
  if imported_module.private_value?(expression.member) || imported_module.private_function?(expression.member) || imported_module.private_type?(expression.member)
    raise_sema_error("#{expression.receiver.name}.#{expression.member} is private to module #{imported_module.name}")
  end

  if (binding = imported_module.values[expression.member])
    return true if binding.kind == :const

    raise_sema_error("module variable initializer cannot reference mutable value #{expression.receiver.name}.#{expression.member}")
  end

  function = imported_module.functions[expression.member]
  function && static_storage_function_value?(function)
end

#stored_ref_supported_type?(type, visited = {}, allow_lifetimes: []) ⇒ Boolean

Returns:

  • (Boolean)


1135
1136
1137
1138
1139
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1135

def stored_ref_supported_type?(type, visited = {}, allow_lifetimes: [])
  visitor = StoredRefSupportedVisitor.new(allow_lifetimes:)
  visitor.visit(type)
  visitor.result?
end

#str_buffer_capacity(type) ⇒ Object



782
783
784
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 782

def str_buffer_capacity(type)
  type.arguments.first.value
end

#str_buffer_method_kind(receiver_type, name) ⇒ Object



775
776
777
778
779
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 775

def str_buffer_method_kind(receiver_type, name)
  return unless str_buffer_type?(receiver_type)

  STR_BUFFER_METHOD_KINDS[name]
end

#str_buffer_method_name(kind) ⇒ Object



781
782
783
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 781

def str_buffer_method_name(kind)
  STR_BUFFER_METHOD_NAMES.fetch(kind)
end

#str_buffer_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


746
747
748
749
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 746

def str_buffer_type?(type)
  type.is_a?(Types::GenericInstance) && type.name == "str_buffer" && type.arguments.length == 1 &&
    generic_integer_type_argument?(type.arguments.first)
end

#string_like_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


918
919
920
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 918

def string_like_type?(type)
  type == @ctx.types.fetch("str") || type == @ctx.types.fetch("cstr")
end

#string_view_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


580
581
582
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 580

def string_view_type?(type)
  type.is_a?(Types::StringView)
end

#struct_declaration_for_type(type) ⇒ Object



1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1612

def struct_declaration_for_type(type)
  return type.ast_declaration if type.respond_to?(:ast_declaration) && type.ast_declaration

  return nil unless type.respond_to?(:module_name)
  if type.module_name == @ctx.module_name
    return @ctx.ast.declarations.find do |decl|
      decl.is_a?(AST::StructDecl) && decl.name == type.name
    end
  end

  imported_module = imported_module_binding_for_name(type.module_name)
  return nil unless imported_module

  declaration = imported_module.type_declarations[type.name]
  declaration if declaration.is_a?(AST::StructDecl)
end

#struct_handle_for_type(type) ⇒ Object



1602
1603
1604
1605
1606
1607
1608
1609
1610
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1602

def struct_handle_for_type(type)
  base_type = type.is_a?(Types::StructInstance) ? type.definition : type
  return nil unless base_type.is_a?(Types::Struct) || base_type.is_a?(Types::GenericStructDefinition)

  declaration = struct_declaration_for_type(base_type)
  return nil unless declaration

  Types::StructHandle.new(base_type, declaration)
end

#struct_instance_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


843
844
845
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 843

def struct_instance_type?(type)
  type.is_a?(Types::Struct) || type.is_a?(Types::Variant)
end

#subscription_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


763
764
765
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 763

def subscription_type?(type)
  type.is_a?(Types::Subscription)
end

#substitute_type(type, substitutions) ⇒ Object



410
411
412
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 410

def substitute_type(type, substitutions)
  SubstituteTypeVisitor.new(substitutions).apply(type)
end

#substitute_value_binding(binding, substitutions) ⇒ Object



376
377
378
379
380
381
382
383
384
385
386
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 376

def substitute_value_binding(binding, substitutions)
  ValueBinding.new(
    id: binding.id,
    name: binding.name,
    storage_type: substitute_type(binding.storage_type, substitutions),
    flow_type: binding.flow_type ? substitute_type(binding.flow_type, substitutions) : nil,
    mutable: binding.mutable,
    kind: binding.kind,
    const_value: binding.const_value,
  )
end

#suggest_name(wrong, candidates, max_distance: 2) ⇒ Object



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 274

def suggest_name(wrong, candidates, max_distance: 2)
  return nil if wrong.nil? || wrong.to_s.empty? || candidates.nil? || candidates.empty?

  wrong_str = wrong.to_s
  closest = nil
  closest_dist = max_distance + 1
  candidates.each do |candidate|
    next if candidate.nil?
    cand_str = candidate.to_s
    next if cand_str.empty? || cand_str == wrong_str
    dist = levenshtein(wrong_str, cand_str)
    if dist < closest_dist
      closest_dist = dist
      closest = cand_str
    end
  end
  closest_dist <= max_distance ? closest : nil
end

#top_level_event_receiver?(receiver_expression, scopes:) ⇒ Boolean

Returns:

  • (Boolean)


726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 726

def top_level_event_receiver?(receiver_expression, scopes:)
  case receiver_expression
  when AST::Identifier
    binding = lookup_value(receiver_expression.name, scopes)
    binding&.kind == :event
  when AST::MemberAccess
    return false unless receiver_expression.receiver.is_a?(AST::Identifier) && @ctx.imports.key?(receiver_expression.receiver.name)

    imported_module = @ctx.imports.fetch(receiver_expression.receiver.name)
    binding = imported_module.values[receiver_expression.member]
    binding && binding.kind == :event
  else
    false
  end
end

#top_level_function(name) ⇒ Object



421
422
423
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 421

def top_level_function(name)
  @ctx.top_level_functions[name]
end

#try_record_generic_local_snapshot(stmt, scopes, binding) ⇒ Object

When a generic method body contains a let/var declaration, always record a completion snapshot so hover and completion can discover the local, even when full type inference is impossible. The inferred type is used when available; otherwise the explicit type annotation on the declaration is consulted. As a last resort a fallback primitive type is used so the binding is never silently dropped.



472
473
474
475
476
477
478
479
480
481
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 472

def try_record_generic_local_snapshot(stmt, scopes, binding)
  type = infer_local_initializer_type(stmt, scopes) ||
         explicit_declaration_type_for(stmt) ||
         fallback_completion_type

  decl_kind = stmt.kind == :var ? :var : :let
  vb = value_binding(name: stmt.name, type: type, mutable: stmt.kind == :var, kind: decl_kind)
  new_scopes = scopes.dup.unshift({ stmt.name => vb })
  record_local_completion_snapshot(stmt.line, stmt.column, new_scopes)
end

#type_implements_interface?(type, interface) ⇒ Boolean

Returns:

  • (Boolean)


164
165
166
167
168
169
170
171
172
173
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 164

def type_implements_interface?(type, interface)
  key = interface_implementation_key(type)
  return true if @ctx.implemented_interfaces.fetch(key, []).include?(interface)

  @ctx.imports.each_value do |module_binding|
    return true if module_binding.implemented_interfaces.fetch(key, []).include?(interface)
  end

  false
end

#type_satisfies_interface_constraint?(type, interface, available_type_param_constraints: current_type_param_constraints) ⇒ Boolean

Returns:

  • (Boolean)


175
176
177
178
179
180
181
182
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 175

def type_satisfies_interface_constraint?(type, interface, available_type_param_constraints: current_type_param_constraints)
  if type.is_a?(Types::TypeVar)
    constraint = available_type_param_constraints[type.name]
    return constraint && constraint.interfaces.include?(interface)
  end

  type_implements_interface?(type, interface)
end

#typed_null_target_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


329
330
331
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 329

def typed_null_target_type?(type)
  type == @ctx.types.fetch("cstr") || pointer_type?(type) || function_pointer_type?(type)
end

#types_compatible?(actual_type, expected_type, expression: nil, scopes: nil, external_numeric: false, external_pointer_null: false, contextual_int_to_float: false) ⇒ Boolean

Returns:

  • (Boolean)


28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 28

def types_compatible?(actual_type, expected_type, expression: nil, scopes: nil, external_numeric: false, external_pointer_null: false, contextual_int_to_float: false)
  return true if error_type?(actual_type) || error_type?(expected_type)
  return true if actual_type == expected_type
  return true if ref_types_compatible?(actual_type, expected_type)
  return true if null_assignable_to?(actual_type, expected_type)
  return true if external_pointer_null && external_typed_null_pointer_compatibility?(actual_type, expected_type)
  return true if expected_type.is_a?(Types::Nullable) && actual_type == expected_type.base
  return true if mutable_to_const_pointer_compatibility?(actual_type, expected_type)
  return true if own_to_raw_pointer_compatibility?(actual_type, expected_type)
  return true if string_literal_cstr_compatibility?(expression, expected_type)
  return true if exact_compile_time_numeric_compatibility?(actual_type, expression, expected_type, scopes:)
  return true if integer_to_char_compatibility?(actual_type, expected_type) &&
                 (!expression || !scopes || integer_constant_fits_in_char?(expression, scopes))
  return true if lossless_integer_compatibility?(actual_type, expected_type)
  return true if external_numeric && external_numeric_compatibility?(actual_type, expected_type)
  return true if contextual_int_to_float && contextual_int_to_float_compatibility?(actual_type, expected_type) &&
                 (!expression || !scopes || contextual_int_to_float_fits?(expression, expected_type, scopes))
  return true if same_external_opaque_handle_pointer_compatibility?(actual_type, expected_type)
  return true if actual_type.is_a?(Types::Function) && expected_type.is_a?(Types::Function) &&
                 !actual_type.receiver_type && !actual_type.variadic &&
                 function_type_matches_proc_type?(actual_type, expected_type)
  return true if quat_vec4_compatible?(actual_type, expected_type)

  false
end

#unsafe_context?Boolean

Returns:

  • (Boolean)


82
83
84
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 82

def unsafe_context?
  @unsafe_depth.positive?
end

#unsupported_async_await_context(expression) ⇒ Object



202
203
204
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 202

def unsupported_async_await_context(expression)
  nil
end

#validate_async_expression_support!(expression, context:) ⇒ Object



195
196
197
198
199
200
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 195

def validate_async_expression_support!(expression, context:)
  unsupported_context = unsupported_async_await_context(expression)
  return unless unsupported_context

  raise_sema_error("await in async functions is not supported inside #{unsupported_context} yet")
end

#validate_async_function_body!(statements) ⇒ Object



125
126
127
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 125

def validate_async_function_body!(statements)
  statements.each { |statement| validate_async_statement!(statement) }
end

#validate_async_statement!(statement) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 129

def validate_async_statement!(statement)
  case statement
  when AST::ErrorBlockStmt
    if statement.header_expression
      context = case statement.header_type
                when :if then "if conditions"
                when :while then "while conditions"
                end
      validate_async_expression_support!(statement.header_expression, context:) if context
    end
    if statement.header_type == :for
      Array(statement.header_iterables).each do |iterable|
        validate_async_expression_support!(iterable, context: "for iterables")
      end
    end
    statement.body.each { |s| validate_async_statement!(s) }
  when AST::ErrorStmt
    nil
  when AST::LocalDecl
    validate_async_expression_support!(statement.value, context: "local initializer") if statement.value
    statement.else_body&.each { |s| validate_async_statement!(s) }
  when AST::Assignment
    validate_async_expression_support!(statement.target, context: "assignment target")
    validate_async_expression_support!(statement.value, context: "assignment")
  when AST::ExpressionStmt
    validate_async_expression_support!(statement.expression, context: "expression statement")
  when AST::ReturnStmt
    return unless statement.value

    validate_async_expression_support!(statement.value, context: "return statement")
  when AST::IfStmt
    statement.branches.each do |branch|
      validate_async_expression_support!(branch.condition, context: "if conditions")

      branch.body.each { |s| validate_async_statement!(s) }
    end
    statement.else_body&.each { |s| validate_async_statement!(s) }
  when AST::WhileStmt
    validate_async_expression_support!(statement.condition, context: "while conditions")

    statement.body.each { |s| validate_async_statement!(s) }
  when AST::ForStmt
    statement.iterables.each do |iterable|
      validate_async_expression_support!(iterable, context: "for iterables")
    end

    statement.body.each { |s| validate_async_statement!(s) }
  when AST::MatchStmt
    validate_async_expression_support!(statement.expression, context: "match discriminants")

    statement.arms.each { |arm| arm.body.each { |s| validate_async_statement!(s) } }
  when AST::UnsafeStmt
    statement.body.each { |s| validate_async_statement!(s) }
  when AST::DeferStmt
    validate_async_expression_support!(statement.expression, context: "defer cleanup") if statement.expression
    statement.body&.each { |s| validate_async_statement!(s) }
  when AST::WhenStmt
    statement.branches.each { |branch| branch.body.each { |s| validate_async_statement!(s) } }
    statement.else_body&.each { |s| validate_async_statement!(s) }
  when AST::BreakStmt, AST::ContinueStmt, AST::StaticAssert, AST::PassStmt
    nil
  else
    raise_sema_error("async functions currently only support straight-line local declarations, assignments, expression statements, and return statements")
  end
end

#validate_attribute_target_compatibility!(target, binding) ⇒ Object



1633
1634
1635
1636
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1633

def validate_attribute_target_compatibility!(target, binding)
  target_kind = attribute_target_kind(target)
  raise_sema_error("attribute #{qualified_attribute_name(binding)} cannot target #{target_kind}") unless binding.targets.include?(target_kind)
end

#validate_consuming_foreign_expression!(expression, scopes:, root_allowed: false) ⇒ Object



1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1020

def validate_consuming_foreign_expression!(expression, scopes:, root_allowed: false)
  return unless expression
  return if expression.is_a?(AST::ErrorExpr)

  if (foreign_call = resolve_foreign_call_expression(expression, scopes:)) && foreign_call_consumes_binding?(foreign_call[:binding])
    raise_sema_error("consuming foreign calls must be top-level expression statements") unless root_allowed
  end

  case expression
  when AST::Call, AST::Specialization
    validate_consuming_foreign_expression!(expression.callee, scopes:, root_allowed: false)
    expression.arguments.each do |argument|
      validate_consuming_foreign_expression!(argument.value, scopes:, root_allowed: false)
    end
  when AST::UnaryOp
    validate_consuming_foreign_expression!(expression.operand, scopes:, root_allowed: false)
  when AST::BinaryOp
    validate_consuming_foreign_expression!(expression.left, scopes:, root_allowed: false)
    validate_consuming_foreign_expression!(expression.right, scopes:, root_allowed: false)
  when AST::IfExpr
    validate_consuming_foreign_expression!(expression.condition, scopes:, root_allowed: false)
    validate_consuming_foreign_expression!(expression.then_expression, scopes:, root_allowed: false)
    validate_consuming_foreign_expression!(expression.else_expression, scopes:, root_allowed: false)
  when AST::MatchExpr
    validate_consuming_foreign_expression!(expression.expression, scopes:, root_allowed: false)
    expression.arms.each do |arm|
      validate_consuming_foreign_expression!(arm.pattern, scopes:, root_allowed: false)
      arm_scopes = arm.binding_name ? scopes + [{ arm.binding_name => value_binding(name: arm.binding_name, type: @error_type, mutable: false, kind: :local, id: @preassigned_local_binding_ids.fetch(arm.object_id)) }] : scopes
      validate_consuming_foreign_expression!(arm.value, scopes: arm_scopes, root_allowed: false)
    end
  when AST::UnsafeExpr
    validate_consuming_foreign_expression!(expression.expression, scopes:, root_allowed: false)
  when AST::FormatString
    expression.parts.each do |part|
      next unless part.is_a?(AST::FormatExprPart)

      validate_consuming_foreign_expression!(part.expression, scopes:, root_allowed: false)
    end
  when AST::MemberAccess
    validate_consuming_foreign_expression!(expression.receiver, scopes:, root_allowed: false)
  when AST::IndexAccess
    validate_consuming_foreign_expression!(expression.receiver, scopes:, root_allowed: false)
    validate_consuming_foreign_expression!(expression.index, scopes:, root_allowed: false)
  when AST::RangeExpr
    validate_consuming_foreign_expression!(expression.start_expr, scopes:, root_allowed: false)
    validate_consuming_foreign_expression!(expression.end_expr, scopes:, root_allowed: false)
  when AST::PrefixCast
    validate_consuming_foreign_expression!(expression.expression, scopes:, root_allowed: false)
  end
end

#validate_consuming_foreign_parameter!(type, function_name:, parameter_name:) ⇒ Object



6
7
8
9
10
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 6

def validate_consuming_foreign_parameter!(type, function_name:, parameter_name:)
  if type.is_a?(Types::Nullable) || !(opaque_type?(type) || pointer_type?(type))
    raise_sema_error("consuming parameter #{parameter_name} of #{function_name} must use a non-null opaque or ptr[...] type")
  end
end

#validate_detach_argument!(expression, scopes:) ⇒ Object



716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 716

def validate_detach_argument!(expression, scopes:)
  return true if expression.is_a?(AST::IntegerLiteral) || expression.is_a?(AST::FloatLiteral) ||
    expression.is_a?(AST::StringLiteral) || expression.is_a?(AST::BooleanLiteral) ||
    expression.is_a?(AST::NullLiteral) || expression.is_a?(AST::CharLiteral) ||
    expression.is_a?(AST::FormatString)

  if expression.is_a?(AST::Identifier)
    binding = lookup_value(expression.name, scopes)
    return true unless binding && %i[let var param].include?(binding.kind)

    raise_sema_error("detach argument cannot capture local variable #{expression.name}")
  end

  true
end

#validate_detach_expression!(expression, scopes:) ⇒ Object



705
706
707
708
709
710
711
712
713
714
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 705

def validate_detach_expression!(expression, scopes:)
  case expression
  when AST::Call, AST::Specialization
    expression.arguments.each do |arg|
      validate_detach_argument!(arg.value, scopes:)
    end
  else
    raise_sema_error("detach currently only supports global function calls")
  end
end

#validate_explicit_aggregate_c_name!(decl) ⇒ Object



417
418
419
420
421
422
423
424
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 417

def validate_explicit_aggregate_c_name!(decl)
  return unless decl.c_name

  raise_sema_error("explicit C names are only allowed on external structs and unions") unless raw_module?
  return if !decl.respond_to?(:type_params) || decl.type_params.empty?

  raise_sema_error("explicit C names are not supported on generic external structs")
end

#validate_foreign_boundary_type!(public_type, boundary_type, function_name:, parameter_name:) ⇒ Object



69
70
71
72
73
74
75
76
77
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 69

def validate_foreign_boundary_type!(public_type, boundary_type, function_name:, parameter_name:)
  return if boundary_type == public_type
  return if boundary_type == @ctx.types.fetch("cstr") && public_type == @ctx.types.fetch("str")
  return if foreign_span_boundary_compatible?(public_type, boundary_type)
  return if foreign_char_pointer_buffer_boundary_compatible?(public_type, boundary_type)
  return if foreign_identity_projection_compatible?(public_type, boundary_type)

  raise_sema_error("foreign parameter #{parameter_name} of #{function_name} cannot map #{public_type} as #{boundary_type}")
end

#validate_function_type_param_constraints!(binding, substitutions) ⇒ Object



246
247
248
249
250
251
252
253
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 246

def validate_function_type_param_constraints!(binding, substitutions)
  binding.type_param_constraints.each do |name, constraints|
    actual_type = substitutions[name]
    raise_sema_error("cannot infer type argument #{name} for function #{binding.name}") unless actual_type

    validate_type_param_constraint_binding!(constraints, actual_type, context: "function #{binding.name}")
  end
end

#validate_generic_type!(name, arguments) ⇒ Object



835
836
837
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 835

def validate_generic_type!(name, arguments)
  super(name, arguments) { |msg| raise_sema_error(msg) }
end

#validate_generic_type_param_constraints!(generic_type, arguments, context:, available_type_param_constraints: current_type_param_constraints) ⇒ Object



192
193
194
195
196
197
198
199
200
201
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 192

def validate_generic_type_param_constraints!(generic_type, arguments, context:, available_type_param_constraints: current_type_param_constraints)
  return if generic_type.type_param_constraints.empty?

  generic_type.type_params.zip(arguments).each do |name, actual_type|
    constraints = generic_type.type_param_constraints[name]
    next unless constraints

    validate_type_param_constraint_binding!(constraints, actual_type, context:, available_type_param_constraints:)
  end
end

#validate_hash_operation_argument!(expression, target_type, scopes:, operation:) ⇒ Object



456
457
458
459
460
461
462
463
464
# File 'lib/milk_tea/core/semantic_analyzer/calls.rb', line 456

def validate_hash_operation_argument!(expression, target_type, scopes:, operation:)
  actual_type = infer_expression(expression, scopes:)
  expected_pointer_type = const_pointer_to(target_type)
  return if argument_types_compatible?(actual_type, expected_pointer_type, external: false, expression:, scopes:)
  return if ref_type?(actual_type) && types_compatible?(referenced_type(actual_type), target_type, expression:, scopes:)
  return if safe_reference_source_expression?(expression, scopes:) && types_compatible?(actual_type, target_type, expression:, scopes:)

  raise_sema_error("#{operation}[#{target_type}] expects a safe #{target_type} lvalue, ref[#{target_type}], ptr[#{target_type}], or const_ptr[#{target_type}], got #{actual_type}")
end

#validate_hoistable_foreign_expression!(expression, scopes:, root_hoistable: false) ⇒ Object



1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
# File 'lib/milk_tea/core/semantic_analyzer/expressions.rb', line 1071

def validate_hoistable_foreign_expression!(expression, scopes:, root_hoistable: false)
  return unless expression
  return if expression.is_a?(AST::ErrorExpr)

  if (foreign_call = resolve_foreign_call_expression(expression, scopes:)) && (message = inline_foreign_call_requires_hoisting_message(foreign_call, scopes:))
    raise_sema_error(message) unless root_hoistable
  end

  case expression
  when AST::Call, AST::Specialization
    validate_hoistable_foreign_expression!(expression.callee, scopes:, root_hoistable: false)
    expression.arguments.each do |argument|
      validate_hoistable_foreign_expression!(argument.value, scopes:, root_hoistable: false)
    end
  when AST::UnaryOp
    validate_hoistable_foreign_expression!(expression.operand, scopes:, root_hoistable: false)
  when AST::BinaryOp
    validate_hoistable_foreign_expression!(expression.left, scopes:, root_hoistable: false)
    validate_hoistable_foreign_expression!(expression.right, scopes:, root_hoistable: false)
  when AST::IfExpr
    validate_hoistable_foreign_expression!(expression.condition, scopes:, root_hoistable: false)
    validate_hoistable_foreign_expression!(expression.then_expression, scopes:, root_hoistable: false)
    validate_hoistable_foreign_expression!(expression.else_expression, scopes:, root_hoistable: false)
  when AST::MatchExpr
    validate_hoistable_foreign_expression!(expression.expression, scopes:, root_hoistable: false)
    expression.arms.each do |arm|
      validate_hoistable_foreign_expression!(arm.pattern, scopes:, root_hoistable: false)
      arm_scopes = arm.binding_name ? scopes + [{ arm.binding_name => value_binding(name: arm.binding_name, type: @error_type, mutable: false, kind: :local, id: @preassigned_local_binding_ids.fetch(arm.object_id)) }] : scopes
      validate_hoistable_foreign_expression!(arm.value, scopes: arm_scopes, root_hoistable: false)
    end
  when AST::UnsafeExpr
    validate_hoistable_foreign_expression!(expression.expression, scopes:, root_hoistable: false)
  when AST::FormatString
    expression.parts.each do |part|
      next unless part.is_a?(AST::FormatExprPart)

      validate_hoistable_foreign_expression!(part.expression, scopes:, root_hoistable: false)
    end
  when AST::MemberAccess
    validate_hoistable_foreign_expression!(expression.receiver, scopes:, root_hoistable: false)
  when AST::IndexAccess
    validate_hoistable_foreign_expression!(expression.receiver, scopes:, root_hoistable: false)
    validate_hoistable_foreign_expression!(expression.index, scopes:, root_hoistable: false)
  when AST::RangeExpr
    validate_hoistable_foreign_expression!(expression.start_expr, scopes:, root_hoistable: false)
    validate_hoistable_foreign_expression!(expression.end_expr, scopes:, root_hoistable: false)
  when AST::PrefixCast
    validate_hoistable_foreign_expression!(expression.expression, scopes:, root_hoistable: false)
  end
end

#validate_in_foreign_parameter!(public_type, boundary_type, function_name:, parameter_name:) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/milk_tea/core/semantic_analyzer/foreign_functions.rb', line 52

def validate_in_foreign_parameter!(public_type, boundary_type, function_name:, parameter_name:)
  unless const_pointer_type?(boundary_type)
    raise_sema_error("in parameter #{parameter_name} of #{function_name} must lower to const_ptr[...], got #{boundary_type || public_type}")
  end

  expected_public_type = pointee_type(boundary_type)
  return if expected_public_type == public_type
  return if expected_public_type == @ctx.types.fetch("void")
  return if foreign_identity_projection_compatible?(public_type, expected_public_type)

  raise_sema_error("in parameter #{parameter_name} of #{function_name} cannot map #{public_type} as #{boundary_type}")
end

#validate_local_proc_type!(type, local_name, initializer:) ⇒ Object



1209
1210
1211
1212
1213
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1209

def validate_local_proc_type!(type, local_name, initializer:)
  return unless contains_proc_type?(type)

  raise_sema_error("local #{local_name} uses unsupported proc nesting") unless proc_storage_supported_type?(type)
end

#validate_local_ref_type!(type, local_name) ⇒ Object



1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1195

def validate_local_ref_type!(type, local_name)
  return if ref_type?(type)
  return if (type.is_a?(Types::Struct) || type.is_a?(Types::StructInstance)) && type.fields.any? && contains_ref_type?(type)
  return if stored_ref_supported_type?(type)

  if contains_ref_type?(type)
    if callable_type?(type) || contains_callable_ref_type?(type)
      raise_sema_error("local #{local_name} cannot store ref types outside callable parameter positions")
    end

    raise_sema_error("local #{local_name} cannot store nested ref types")
  end
end

#validate_methods_receiver_type_arguments!(type_ref, generic_type) ⇒ Object



1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1075

def validate_methods_receiver_type_arguments!(type_ref, generic_type)
  names = type_ref.arguments.map do |argument|
    value = argument.value
    next unless value.is_a?(AST::TypeRef)
    next unless value.arguments.empty? && !value.nullable && value.name.parts.length == 1

    value.name.parts.first
  end

  expected_names = generic_type.type_params
  unless names == expected_names
    raise_sema_error("extending target #{type_ref} must use the receiver type parameters directly")
  end

  expected_names
end

#validate_parameter_proc_type!(type, function_name:, parameter_name:, external:, foreign:) ⇒ Object



1172
1173
1174
1175
1176
1177
1178
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1172

def validate_parameter_proc_type!(type, function_name:, parameter_name:, external:, foreign:)
  if contains_proc_type?(type)
    raise_sema_error("external function #{function_name} cannot take proc parameters") if external
    raise_sema_error("foreign function #{function_name} cannot take proc parameters") if foreign
    raise_sema_error("parameter #{parameter_name} of #{function_name} uses unsupported proc nesting") unless proc_storage_supported_type?(type)
  end
end

#validate_parameter_ref_type!(type, function_name:, parameter_name:, external:) ⇒ Object



1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1160

def validate_parameter_ref_type!(type, function_name:, parameter_name:, external:)
  if ref_type?(type)
    raise_sema_error("external function #{function_name} cannot take ref parameters") if external

    return
  end

  return if callable_param_ref_supported?(type)

  raise_sema_error("parameter #{parameter_name} of #{function_name} cannot nest ref types") if contains_ref_type?(type)
end

#validate_read_call_arguments!(arguments) ⇒ Object



1295
1296
1297
1298
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1295

def validate_read_call_arguments!(arguments)
  raise_sema_error("read does not support named arguments") if arguments.any?(&:name)
  raise_sema_error("read expects 1 argument, got #{arguments.length}") unless arguments.length == 1
end

#validate_return_proc_type!(type, function_name:) ⇒ Object



1189
1190
1191
1192
1193
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1189

def validate_return_proc_type!(type, function_name:)
  if contains_proc_type?(type)
    raise_sema_error("function #{function_name} uses unsupported proc nesting in return type") unless proc_storage_supported_type?(type)
  end
end

#validate_return_ref_type!(type, function_name:) ⇒ Object



1180
1181
1182
1183
1184
1185
1186
1187
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1180

def validate_return_ref_type!(type, function_name:)
  if contains_ref_type?(type)
    if (type.is_a?(Types::Struct) || type.is_a?(Types::StructInstance)) && type.fields.any?
      raise_sema_error("function #{function_name} cannot return non-owning struct #{type.name} (contains borrowed ref)")
    end
    raise_sema_error("function #{function_name} cannot return ref types")
  end
end

#validate_specialized_function_binding!(function_name, function_type, body_params) ⇒ Object



388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 388

def validate_specialized_function_binding!(function_name, function_type, body_params)
  function_type.params.each do |param|
    validate_specialized_function_type!(param.type, function_name:, context: "parameter #{param.name}")
    validate_specialized_function_type!(param.boundary_type, function_name:, context: "boundary parameter #{param.name}") if param.boundary_type
  end
  validate_specialized_function_type!(function_type.return_type, function_name:, context: "return type")
  validate_specialized_function_type!(function_type.receiver_type, function_name:, context: "receiver type") if function_type.receiver_type

  body_params.each do |param|
    validate_specialized_function_type!(param.type, function_name:, context: "body parameter #{param.name}")
  end
end

#validate_specialized_function_body(binding) ⇒ Object

Validates the body of a specialized (instantiated) function or method binding. The owner checker may be in collecting-errors mode, which would silently swallow body errors into @structural_errors. We temporarily disable collect mode on the owner so the caller receives the SemanticError directly.



269
270
271
272
273
274
275
276
277
278
# File 'lib/milk_tea/core/semantic_analyzer/function_binding.rb', line 269

def validate_specialized_function_body(binding)
  owner = binding.owner
  prev_collecting = owner.instance_variable_get(:@collecting_errors)
  owner.instance_variable_set(:@collecting_errors, false)
  owner.check_function(binding)
rescue SemanticError => e
  raise unless e.message.include?("cannot assign through immutable")
ensure
  owner.instance_variable_set(:@collecting_errors, prev_collecting) if owner
end

#validate_specialized_function_type!(type, function_name:, context:) ⇒ Object



401
402
403
404
405
406
407
408
# File 'lib/milk_tea/core/semantic_analyzer/generics.rb', line 401

def validate_specialized_function_type!(type, function_name:, context:)
  ValidateSpecializedTypeVisitor.new(
    function_name:,
    context:,
    on_error: ->(msg) { raise_sema_error(msg) },
    on_generic_instance: ->(name, args) { validate_generic_type!(name, args) },
  ).visit(type)
end

#validate_static_storage_call_initializer!(expression, scopes:) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 148

def validate_static_storage_call_initializer!(expression, scopes:)
  expression.arguments.each do |argument|
    validate_static_storage_initializer!(argument.value, scopes:)
  end

  callee = expression.callee
  if callee.is_a?(AST::Identifier)
    if (type_expr = resolve_type_expression(callee))
      return if type_expr.is_a?(Types::Struct) || type_expr.is_a?(Types::StringView)
    end
  end

  if callee.is_a?(AST::MemberAccess)
    if (type_expr = resolve_type_expression(callee))
      return if type_expr.is_a?(Types::Struct) || type_expr.is_a?(Types::StringView)
    end
  end

  if callee.is_a?(AST::Specialization)
    if callee.callee.is_a?(AST::Identifier)
      case callee.callee.name
      when "array", "span", "zero", "reinterpret"
        return
      end
    end

    if (type_ref = type_ref_from_specialization(callee))
      specialized_type = resolve_type_ref(type_ref)
      return if specialized_type.is_a?(Types::Struct) || result_type?(specialized_type)
    end
  end

  raise_sema_error("module variable initializer must be static-storage-safe")
end

#validate_static_storage_initializer!(expression, scopes:) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/milk_tea/core/semantic_analyzer/top_level.rb', line 69

def validate_static_storage_initializer!(expression, scopes:)
  case expression
  when AST::ErrorExpr
    return
  when AST::IntegerLiteral, AST::FloatLiteral, AST::StringLiteral, AST::BooleanLiteral, AST::NullLiteral,
       AST::SizeofExpr, AST::AlignofExpr, AST::OffsetofExpr
    return
  when AST::Identifier
    if (binding = lookup_value(expression.name, scopes))
      return if binding.kind == :const

      raise_sema_error("module variable initializer cannot reference mutable value #{expression.name}")
    end

    function = @ctx.top_level_functions[expression.name]
    return if function && static_storage_function_value?(function)

    raise_sema_error("module variable initializer must be static-storage-safe")
  when AST::MemberAccess
    return if static_storage_member_initializer?(expression, scopes:)

    raise_sema_error("module variable initializer must be static-storage-safe")
  when AST::UnaryOp
    validate_static_storage_initializer!(expression.operand, scopes:)
  when AST::BinaryOp
    validate_static_storage_initializer!(expression.left, scopes:)
    validate_static_storage_initializer!(expression.right, scopes:)
  when AST::IfExpr
    validate_static_storage_initializer!(expression.condition, scopes:)
    validate_static_storage_initializer!(expression.then_expression, scopes:)
    validate_static_storage_initializer!(expression.else_expression, scopes:)
  when AST::UnsafeExpr
    validate_static_storage_initializer!(expression.expression, scopes:)
  when AST::ExpressionList
    expression.elements.each { |element| validate_static_storage_initializer!(element, scopes:) }
  when AST::Specialization
    if expression.callee.is_a?(AST::Identifier)
      return if expression.callee.name == "zero"
    end

    raise_sema_error("module variable initializer must be static-storage-safe")
  when AST::Call
    validate_static_storage_call_initializer!(expression, scopes:)
  when AST::ProcExpr
    # Proc expressions are static-storage-safe when their body only
    # references module-level constants, functions, types, and imports.
    # The lowering handles capture detection; if a proc truly captures
    # a local from an enclosing scope (impossible at module level),
    # the sema validation above already rejects it via the
    # "cannot reference mutable value" check on the proc body.
    return
  else
    raise_sema_error("module variable initializer must be static-storage-safe")
  end
end

#validate_stored_ref_type!(type, context, allow_lifetimes: []) ⇒ Object



1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1120

def validate_stored_ref_type!(type, context, allow_lifetimes: [])
  return unless contains_ref_type?(type, allow_lifetimes:)
  return if stored_ref_supported_type?(type, allow_lifetimes:)

  if callable_type?(type) || contains_callable_ref_type?(type)
    raise_sema_error("#{context} cannot store ref types outside callable parameter positions")
  end

  if context.start_with?("field ")
    raise_sema_error("#{context} cannot store ref types; declare a lifetime on the struct and use ref[@lt, T] (e.g. struct MyStruct[@a]: field: ref[@a, SomeType])")
  end

  raise_sema_error("#{context} cannot store ref types")
end

#validate_struct_layout!(decl) ⇒ Object



408
409
410
411
412
413
414
415
# File 'lib/milk_tea/core/semantic_analyzer/type_declaration.rb', line 408

def validate_struct_layout!(decl)
  return unless decl.alignment

  raise_sema_error("align(...) requires a positive alignment") unless decl.alignment.positive?
  return if power_of_two?(decl.alignment)

  raise_sema_error("align(...) requires a power-of-two alignment, got #{decl.alignment}")
end

#validate_threaded_for_body!(body) ⇒ Object



1145
1146
1147
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1145

def validate_threaded_for_body!(body)
  body.each { |stmt| validate_threaded_for_statement!(stmt) }
end

#validate_threaded_for_statement!(stmt) ⇒ Object



1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1149

def validate_threaded_for_statement!(stmt)
  case stmt
  when AST::BreakStmt
    raise_sema_error("break is not allowed inside parallel for", line: stmt.line, column: stmt.column)
  when AST::ContinueStmt
    raise_sema_error("continue is not allowed inside parallel for", line: stmt.line, column: stmt.column)
  when AST::ReturnStmt
    raise_sema_error("return is not allowed inside parallel for", line: stmt.line, column: stmt.column)
  when AST::DeferStmt
    raise_sema_error("defer is not allowed inside parallel for", line: stmt.line, column: stmt.column)
  when AST::AwaitExpr
    raise_sema_error("await is not allowed inside parallel for", line: stmt.line, column: stmt.column)
  when AST::IfStmt
    validate_threaded_for_body!(stmt.then_body)
    stmt.else_if_clauses&.each { |clause| validate_threaded_for_body!(clause.body) }
    validate_threaded_for_body!(stmt.else_body) if stmt.else_body
  when AST::WhileStmt
    validate_threaded_for_body!(stmt.body)
  when AST::ForStmt
    raise_sema_error("nested for loops are not allowed inside parallel for", line: stmt.line, column: stmt.column) if stmt.threaded
    validate_threaded_for_body!(stmt.body)
  when AST::MatchStmt
    stmt.arms&.each { |arm| validate_threaded_for_body!(arm.body) }
  when AST::UnsafeStmt
    validate_threaded_for_body!(stmt.body) if stmt.body.is_a?(Array)
  end
end

#validate_type_param_constraint_binding!(constraints, actual_type, context:, available_type_param_constraints: current_type_param_constraints) ⇒ Object



184
185
186
187
188
189
190
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 184

def validate_type_param_constraint_binding!(constraints, actual_type, context:, available_type_param_constraints: current_type_param_constraints)
  constraints.interfaces.each do |interface|
    next if type_satisfies_interface_constraint?(actual_type, interface, available_type_param_constraints:)

    raise_sema_error("type #{actual_type} does not implement interface #{interface.name} for #{context}")
  end
end

#value_binding(name:, type:, mutable:, kind:, flow_type: nil, const_value: nil, id: nil) ⇒ Object



1362
1363
1364
1365
1366
1367
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 1362

def value_binding(name:, type:, mutable:, kind:, flow_type: nil, const_value: nil, id: nil)
  id ||= allocate_binding_id
  @binding_name_by_id[id] = name
  @binding_type_by_id[id] = flow_type == type ? type : (flow_type || type)
  ValueBinding.new(id:, name:, storage_type: type, flow_type: flow_type == type ? nil : flow_type, mutable:, kind:, const_value:)
end

#variant_match_arm_name(pattern, scrutinee_type) ⇒ Object



936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 936

def variant_match_arm_name(pattern, scrutinee_type)
  # Pattern must be `TypeName.arm_name` or `module.TypeName.arm_name`
  # For struct patterns, the pattern is Call(MemberAccess(...), args) — unwrap the callee
  callee = case pattern
           when AST::Call
             pattern.callee
           else
             pattern
           end
  return nil unless callee.is_a?(AST::MemberAccess)

  member = callee.member
  return nil unless scrutinee_type.arm_names.include?(member)

  # Verify the receiver resolves to the scrutinee variant type
  receiver_type = resolve_type_expression(callee.receiver)
  return member if receiver_type == scrutinee_type

  if scrutinee_type.is_a?(Types::VariantInstance) && receiver_type.is_a?(Types::GenericVariantDefinition)
    return member if receiver_type == scrutinee_type.definition
  end

  return nil unless scrutinee_type.is_a?(Types::VariantInstance) && receiver_type.is_a?(Types::Variant)
  return nil unless receiver_type.name == scrutinee_type.name && receiver_type.module_name == scrutinee_type.module_name

  member
end

#vector_arithmetic_result(operator, left_type, right_type) ⇒ Object



215
216
217
218
219
220
221
222
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 215

def vector_arithmetic_result(operator, left_type, right_type)
  return vector_op_result(operator, left_type, right_type) if vector_type?(left_type) || vector_type?(right_type)
  return matrix_op_result(operator, left_type, right_type) if matrix_type?(left_type) || matrix_type?(right_type)
  return quaternion_op_result(operator, left_type, right_type) if quaternion_type?(left_type) || quaternion_type?(right_type)
  return simd_op_result(operator, left_type, right_type) if simd_type?(left_type) || simd_type?(right_type)

  nil
end

#vector_op_result(operator, left_type, right_type) ⇒ Object



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 260

def vector_op_result(operator, left_type, right_type)
  if vector_type?(left_type) && vector_type?(right_type) && left_type.element_type == right_type.element_type
    return left_type if operator == "+" || operator == "-"
    return left_type if operator == "*"
  end

  if vector_type?(left_type) && right_type.numeric?
    return left_type if operator == "*" || operator == "/"
  end

  if left_type.numeric? && vector_type?(right_type) && operator == "*"
    return right_type
  end

  nil
end

#vector_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


705
706
707
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 705

def vector_type?(type)
  type.is_a?(Types::Vector)
end

#walk_assignment_target_reads_for_precheck_resolution(target, operator, scopes, identifier_ids, declaration_ids = nil) ⇒ Object



389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/milk_tea/core/semantic_analyzer/nullability.rb', line 389

def walk_assignment_target_reads_for_precheck_resolution(target, operator, scopes, identifier_ids, declaration_ids = nil)
  if operator != "=" && target.is_a?(AST::Identifier)
    if (binding_id = resolve_name_in_precheck_scopes(target.name, scopes))
      identifier_ids[target.object_id] = binding_id
    end
  end

  case target
  when AST::Identifier
    nil
  when AST::MemberAccess
    walk_expression_for_precheck_resolution(target.receiver, scopes, identifier_ids, declaration_ids)
  when AST::IndexAccess
    walk_expression_for_precheck_resolution(target.receiver, scopes, identifier_ids, declaration_ids)
    walk_expression_for_precheck_resolution(target.index, scopes, identifier_ids, declaration_ids)
  else
    walk_expression_for_precheck_resolution(target, scopes, identifier_ids, declaration_ids)
  end
end

#walk_expression_for_precheck_resolution(expression, scopes, identifier_ids, declaration_ids = nil) ⇒ Object



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/milk_tea/core/semantic_analyzer/nullability.rb', line 334

def walk_expression_for_precheck_resolution(expression, scopes, identifier_ids, declaration_ids = nil)
  case expression
  when nil
    nil
  when AST::Identifier
    if (binding_id = resolve_name_in_precheck_scopes(expression.name, scopes))
      identifier_ids[expression.object_id] = binding_id
    end
  when AST::MemberAccess
    walk_expression_for_precheck_resolution(expression.receiver, scopes, identifier_ids, declaration_ids)
  when AST::IndexAccess
    walk_expression_for_precheck_resolution(expression.receiver, scopes, identifier_ids, declaration_ids)
    walk_expression_for_precheck_resolution(expression.index, scopes, identifier_ids, declaration_ids)
  when AST::Specialization
    walk_expression_for_precheck_resolution(expression.callee, scopes, identifier_ids, declaration_ids)
  when AST::Call
    walk_expression_for_precheck_resolution(expression.callee, scopes, identifier_ids, declaration_ids)
    expression.arguments.each { |argument| walk_expression_for_precheck_resolution(argument.value, scopes, identifier_ids, declaration_ids) }
  when AST::UnaryOp
    walk_expression_for_precheck_resolution(expression.operand, scopes, identifier_ids, declaration_ids)
  when AST::BinaryOp
    walk_expression_for_precheck_resolution(expression.left, scopes, identifier_ids, declaration_ids)
    walk_expression_for_precheck_resolution(expression.right, scopes, identifier_ids, declaration_ids)
  when AST::RangeExpr
    walk_expression_for_precheck_resolution(expression.start_expr, scopes, identifier_ids, declaration_ids)
    walk_expression_for_precheck_resolution(expression.end_expr, scopes, identifier_ids, declaration_ids)
  when AST::IfExpr
    walk_expression_for_precheck_resolution(expression.condition, scopes, identifier_ids, declaration_ids)
    walk_expression_for_precheck_resolution(expression.then_expression, scopes, identifier_ids, declaration_ids)
    walk_expression_for_precheck_resolution(expression.else_expression, scopes, identifier_ids, declaration_ids)
  when AST::MatchExpr
    walk_expression_for_precheck_resolution(expression.expression, scopes, identifier_ids, declaration_ids)
    expression.arms.each do |arm|
      walk_expression_for_precheck_resolution(arm.pattern, scopes, identifier_ids, declaration_ids)
      arm_scopes = scopes
      if arm.binding_name
        binding_id = @preassigned_local_binding_ids.fetch(arm.object_id)
        arm_scopes = scopes + [{ arm.binding_name => binding_id }]
        declaration_ids[arm.object_id] = binding_id if declaration_ids
      end
      walk_expression_for_precheck_resolution(if arm.respond_to?(:value) then arm.value else arm.body end, arm_scopes, identifier_ids, declaration_ids)
    end
  when AST::UnsafeExpr
    walk_expression_for_precheck_resolution(expression.expression, scopes, identifier_ids, declaration_ids)
  when AST::AwaitExpr
    walk_expression_for_precheck_resolution(expression.expression, scopes, identifier_ids, declaration_ids)
  when AST::FormatString
    expression.parts.each do |part|
      next unless part.is_a?(AST::FormatExprPart)

      walk_expression_for_precheck_resolution(part.expression, scopes, identifier_ids, declaration_ids)
    end
  end
end

#walk_statements_for_precheck_resolution(statements, scopes, declaration_ids, identifier_ids) ⇒ Object



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/milk_tea/core/semantic_analyzer/nullability.rb', line 246

def walk_statements_for_precheck_resolution(statements, scopes, declaration_ids, identifier_ids)
  block_scopes = scopes + [{}]
  statements.each do |statement|
    case statement
    when AST::ErrorBlockStmt
      if statement.header_type == :for
        Array(statement.header_iterables).each do |iterable|
          walk_expression_for_precheck_resolution(iterable, block_scopes, identifier_ids, declaration_ids)
        end
        for_scopes = block_scopes + [{}]
        Array(statement.header_bindings).each do |binding|
          binding_id = @preassigned_local_binding_ids.fetch(binding.object_id)
          for_scopes.last[binding.name] = binding_id
          declaration_ids[binding.object_id] = binding_id
        end
        walk_statements_for_precheck_resolution(statement.body || [], for_scopes, declaration_ids, identifier_ids)
      else
        walk_expression_for_precheck_resolution(statement.header_expression, block_scopes, identifier_ids, declaration_ids) if statement.header_expression
        walk_statements_for_precheck_resolution(statement.body || [], block_scopes, declaration_ids, identifier_ids)
      end
    when AST::LocalDecl
      walk_expression_for_precheck_resolution(statement.value, block_scopes, identifier_ids, declaration_ids) if statement.value
      if statement.else_binding && (statement.else_body || statement.recovered_else)
        else_scopes = block_scopes + [{}]
        binding_id = @preassigned_local_binding_ids.fetch(statement.else_binding.object_id)
        else_scopes.last[statement.else_binding.name] = binding_id
        declaration_ids[statement.else_binding.object_id] = binding_id
        walk_statements_for_precheck_resolution(statement.else_body || [], else_scopes, declaration_ids, identifier_ids)
      else
        walk_statements_for_precheck_resolution(statement.else_body || [], block_scopes, declaration_ids, identifier_ids)
      end
      binding_id = @preassigned_local_binding_ids.fetch(statement.object_id)
      unless let_else_discard_binding_syntax?(statement)
        block_scopes.last[statement.name] = binding_id
        declaration_ids[statement.object_id] = binding_id
      end
    when AST::Assignment
      walk_expression_for_precheck_resolution(statement.value, block_scopes, identifier_ids, declaration_ids)
      walk_assignment_target_reads_for_precheck_resolution(statement.target, statement.operator, block_scopes, identifier_ids, declaration_ids)
      if statement.target.is_a?(AST::Identifier)
        if (binding_id = resolve_name_in_precheck_scopes(statement.target.name, block_scopes))
          identifier_ids[statement.target.object_id] = binding_id
        end
      end
    when AST::IfStmt
      statement.branches.each do |branch|
        walk_expression_for_precheck_resolution(branch.condition, block_scopes, identifier_ids, declaration_ids)
        walk_statements_for_precheck_resolution(branch.body || [], block_scopes, declaration_ids, identifier_ids)
      end
      walk_statements_for_precheck_resolution(statement.else_body || [], block_scopes, declaration_ids, identifier_ids)
    when AST::MatchStmt
      walk_expression_for_precheck_resolution(statement.expression, block_scopes, identifier_ids, declaration_ids)
      statement.arms.each do |arm|
        arm_scopes = block_scopes + [{}]
        if arm.binding_name
          binding_id = @preassigned_local_binding_ids.fetch(arm.object_id)
          arm_scopes.last[arm.binding_name] = binding_id
          declaration_ids[arm.object_id] = binding_id
        end
        walk_statements_for_precheck_resolution(if arm.respond_to?(:body) then (arm.body || []) else [arm.value].compact end, arm_scopes, declaration_ids, identifier_ids)
      end
    when AST::UnsafeStmt, AST::WhileStmt
      walk_expression_for_precheck_resolution(statement.condition, block_scopes, identifier_ids, declaration_ids) if statement.is_a?(AST::WhileStmt)
      walk_statements_for_precheck_resolution(statement.body || [], block_scopes, declaration_ids, identifier_ids)
    when AST::ForStmt
      statement.iterables.each do |iterable|
        walk_expression_for_precheck_resolution(iterable, block_scopes, identifier_ids, declaration_ids)
      end
      for_scopes = block_scopes + [{}]
      statement.bindings.each do |binding|
        binding_id = @preassigned_local_binding_ids.fetch(binding.object_id)
        for_scopes.last[binding.name] = binding_id
        declaration_ids[binding.object_id] = binding_id
      end
      walk_statements_for_precheck_resolution(statement.body || [], for_scopes, declaration_ids, identifier_ids)
    when AST::DeferStmt
      walk_expression_for_precheck_resolution(statement.expression, block_scopes, identifier_ids, declaration_ids) if statement.expression
      walk_statements_for_precheck_resolution(statement.body || [], block_scopes, declaration_ids, identifier_ids) if statement.body
    when AST::ExpressionStmt
      walk_expression_for_precheck_resolution(statement.expression, block_scopes, identifier_ids, declaration_ids)
    when AST::ReturnStmt
      walk_expression_for_precheck_resolution(statement.value, block_scopes, identifier_ids, declaration_ids) if statement.value
    when AST::StaticAssert
      walk_expression_for_precheck_resolution(statement.condition, block_scopes, identifier_ids, declaration_ids)
    end
  end
end

#when_chosen_body(decl) ⇒ Object



1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 1239

def when_chosen_body(decl)
  discriminant_value = evaluate_compile_time_const_value(decl.discriminant, scopes: [])
  return nil if discriminant_value.nil?

  chosen_branch = decl.branches.find do |branch|
    pattern_value = evaluate_compile_time_const_value(branch.pattern, scopes: [])
    discriminant_value == pattern_value
  end

  chosen_branch&.body || decl.else_body
end

#wider_float_type(left_type, right_type) ⇒ Object



195
196
197
# File 'lib/milk_tea/core/semantic_analyzer/type_compatibility.rb', line 195

def wider_float_type(left_type, right_type)
  left_type.float_width >= right_type.float_width ? left_type : right_type
end

#wildcard_pattern?(expression) ⇒ Boolean

Returns:

  • (Boolean)


773
774
775
# File 'lib/milk_tea/core/semantic_analyzer/statements.rb', line 773

def wildcard_pattern?(expression)
  expression.is_a?(AST::Identifier) && expression.name == "_"
end

#with_async_functionObject



41
42
43
44
45
46
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 41

def with_async_function
  @async_function_depth += 1
  yield
ensure
  @async_function_depth -= 1
end

#with_compile_timeObject



55
56
57
58
59
60
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 55

def with_compile_time
  @compile_time_depth += 1
  yield
ensure
  @compile_time_depth -= 1
end

#with_error_node(node) ⇒ Object



492
493
494
495
496
497
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 492

def with_error_node(node)
  @error_node_stack << node
  yield
ensure
  @error_node_stack.pop
end

#with_foreign_mapping_contextObject



34
35
36
37
38
39
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 34

def with_foreign_mapping_context
  @foreign_mapping_depth += 1
  yield
ensure
  @foreign_mapping_depth -= 1
end

#with_loopObject



48
49
50
51
52
53
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 48

def with_loop
  @loop_depth += 1
  yield
ensure
  @loop_depth -= 1
end

#with_loop_barrierObject



74
75
76
77
78
79
80
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 74

def with_loop_barrier
  previous_loop_depth = @loop_depth
  @loop_depth = 0
  yield
ensure
  @loop_depth = previous_loop_depth
end

#with_nested_scope(scopes) {|nested_scopes| ... } ⇒ Object

Yields:

  • (nested_scopes)


120
121
122
123
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 120

def with_nested_scope(scopes)
  nested_scopes = scopes + [{}]
  yield(nested_scopes)
end

#with_return_context(return_type, allow_return:) ⇒ Object



98
99
100
101
102
103
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 98

def with_return_context(return_type, allow_return:)
  @return_context_stack << { return_type:, allow_return: }
  yield
ensure
  @return_context_stack.pop
end

#with_scope(bindings) {|[scope]| ... } ⇒ Object

Yields:

  • ([scope])


109
110
111
112
113
114
115
116
117
118
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 109

def with_scope(bindings)
  scope = {}
  bindings.each do |binding|
    raise_sema_error("duplicate local #{binding.name}") if scope.key?(binding.name)

    scope[binding.name] = binding
  end

  yield([scope])
end

#with_type_resolution_scopes(scopes) ⇒ Object

Tracks the innermost value scopes during body checking so that resolve_type_ref can resolve a compile-time reflection type expression (e.g. field.type inside an inline for) by evaluating it against the local bindings in scope. Nil outside body checking.



66
67
68
69
70
71
72
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 66

def with_type_resolution_scopes(scopes)
  saved = @type_resolution_scopes
  @type_resolution_scopes = scopes
  yield
ensure
  @type_resolution_scopes = saved
end

#with_unsafeObject



6
7
8
9
10
11
# File 'lib/milk_tea/core/semantic_analyzer/analysis_context.rb', line 6

def with_unsafe
  @unsafe_depth += 1
  yield
ensure
  @unsafe_depth -= 1
end

#zero_initializable_type?(type, operation: "zero") ⇒ Boolean

Returns:

  • (Boolean)


668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
# File 'lib/milk_tea/core/semantic_analyzer/name_resolution.rb', line 668

def zero_initializable_type?(type, operation: "zero")
  return true if type.is_a?(Types::Primitive) && !type.void?
  return true if type.is_a?(Types::Nullable)
  return true if type.is_a?(Types::EnumBase)
  return true if span_type?(type)
  return true if string_view_type?(type)
  return true if task_type?(type)
  return true if event_type?(type)
  return true if subscription_type?(type)
  return true if type.is_a?(Types::Struct)
  return true if type.is_a?(Types::Variant)
  return true if pointer_type?(type)
  return true if type.is_a?(Types::Opaque) && !type.external
  return true if array_type?(type)
  return true if str_buffer_type?(type)
  return true if vector_type?(type)
  return true if matrix_type?(type)
  return true if quaternion_type?(type)
  return true if soa_type?(type)
  return true if simd_type?(type)
  return true if atomic_type?(type)

  raise_sema_error("#{operation} does not support type #{type}")
end