Module: MilkTea::Lowering::Assertions

Included in:
MilkTea::Lowerer
Defined in:
lib/milk_tea/core/lowering/assertions.rb

Constant Summary collapse

ASSERT_CALL_NAMES =
%w[assert expect expect_eq expect_ne].freeze

Instance Method Summary collapse

Instance Method Details

#assert_like_call_kind(expression) ⇒ Object



55
56
57
58
59
60
61
# File 'lib/milk_tea/core/lowering/assertions.rb', line 55

def assert_like_call_kind(expression)
  return nil unless expression.is_a?(AST::Call)
  return nil unless expression.callee.is_a?(AST::Identifier)
  return nil unless ASSERT_CALL_NAMES.include?(expression.callee.name)

  expression.callee.name
end

#binary_expression(operator, left, right, line:, column:) ⇒ Object



91
92
93
# File 'lib/milk_tea/core/lowering/assertions.rb', line 91

def binary_expression(operator, left, right, line:, column:)
  AST::BinaryOp.new(operator:, left:, right:, line:, column:)
end

#default_assert_message(kind, line) ⇒ Object



63
64
65
66
67
68
69
70
71
# File 'lib/milk_tea/core/lowering/assertions.rb', line 63

def default_assert_message(kind, line)
  path = @ctx.current_analysis_path.to_s
  case kind
  when "assert" then "assertion failed at #{path}:#{line}"
  when "expect" then "expectation failed at #{path}:#{line}"
  when "expect_eq" then "expect_eq failed: values are not equal at #{path}:#{line}"
  when "expect_ne" then "expect_ne failed: values are equal at #{path}:#{line}"
  end
end

#lower_assert_like_statement(statement, env:) ⇒ Object

Lowers an assert/expect/expect_eq/expect_ne expression statement into an IR::IfStmt whose then-branch aborts via fatal. The message is only evaluated on the failing path so unused messages cost nothing. Returns nil when the statement is not one of the assertion calls so the caller can fall through to ordinary expression-statement lowering.



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/lowering/assertions.rb', line 13

def lower_assert_like_statement(statement, env:)
  kind = assert_like_call_kind(statement.expression)
  return nil unless kind

  arguments = statement.expression.arguments
  line = statement.expression.line
  column = statement.expression.column
  default_message = default_assert_message(kind, statement.line)

  condition_ast = nil
  message = nil
  case kind
  when "assert", "expect"
    condition_ast = unary_expression("not", arguments.fetch(0).value, line:, column:)
    message = arguments.length > 1 ? arguments.fetch(1).value : string_literal(default_message, line:, column:)
  when "expect_eq"
    condition_ast = binary_expression("!=", arguments.fetch(0).value, arguments.fetch(1).value, line:, column:)
    message = arguments.length > 2 ? arguments.fetch(2).value : string_literal(default_message, line:, column:)
  when "expect_ne"
    condition_ast = binary_expression("==", arguments.fetch(0).value, arguments.fetch(1).value, line:, column:)
    message = arguments.length > 2 ? arguments.fetch(2).value : string_literal(default_message, line:, column:)
  end

  # Hoist inline-proc/foreign-temporary setup out of the condition so the
  # failure check itself stays a plain `if (!cond)` in C.
  setup, prepared_condition, cleanups = prepare_expression_with_cleanups(
    condition_ast,
    env:,
    expected_type: @ctx.types.fetch("bool"),
  )

  [
    *setup,
    IR::IfStmt.new(
      condition: lower_expression(prepared_condition, env:, expected_type: @ctx.types.fetch("bool")),
      then_body: [lower_fatal_expression_statement(message, line:, column:, env:)],
      else_body: [],
    ),
    *cleanups.flat_map(&:itself),
  ]
end

#lower_fatal_expression_statement(message, line:, column:, env:) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/milk_tea/core/lowering/assertions.rb', line 73

def lower_fatal_expression_statement(message, line:, column:, env:)
  fatal_call = AST::Call.new(
    callee: AST::Identifier.new(name: "fatal", line:, column:),
    arguments: [AST::Argument.new(name: nil, value: message, line:, column:)],
    line:,
    column:,
  )
  IR::ExpressionStmt.new(
    expression: lower_expression(fatal_call, env:, expected_type: @ctx.types.fetch("void")),
    line:,
    path: @ctx.current_analysis_path,
  )
end

#string_literal(value, line:, column:) ⇒ Object



95
96
97
# File 'lib/milk_tea/core/lowering/assertions.rb', line 95

def string_literal(value, line:, column:)
  AST::StringLiteral.new(lexeme: value.inspect, value:, cstring: false, line:, column:)
end

#unary_expression(operator, operand, line:, column:) ⇒ Object



87
88
89
# File 'lib/milk_tea/core/lowering/assertions.rb', line 87

def unary_expression(operator, operand, line:, column:)
  AST::UnaryOp.new(operator:, operand:, line:, column:)
end