Module: Jade::Frontend::TypeChecking::Inference::FunctionCall

Extended by:
FunctionCall, Helpers
Included in:
FunctionCall
Defined in:
lib/jade/frontend/type_checking/inference/function_call.rb

Instance Method Summary collapse

Methods included from Helpers

check, generalize, instantiate, type_from_symbol, unify

Instance Method Details

#infer(node, registry, state, expected) ⇒ Object



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
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/jade/frontend/type_checking/inference/function_call.rb', line 9

def infer(node, registry, state, expected)
  node => AST::FunctionCall(callee:, args:)

  callee_state, callee_result = check(
    callee,
    registry,
    state,
    Expected.infer(state.fresh),
  )
    .then { |st, rs| [st, rs.attach_origin(node)] }

  args_state, args_acc = args
    .reduce([callee_state, Result.accumulator]) do |(state_acc, acc), arg|
      check(
        arg,
        registry,
        state_acc,
        Expected.infer(state_acc.fresh),
      )
      .then { |(new_state, result)| [new_state, acc.add(result)] }
    end

  after_callee_state, result_type = unify_callee(
    args_state,
    callee_result,
    args_acc,
    node,
    state,
  )

  after_callee_state.unify_result(
    callee_result.map { result_type },
    expected.type,
    &type_error(state, node)
  )
  .then do |st, rs|
    # When expected is authoritative and this unification failed,
    # adopt expected.type so the enclosing body-level unify doesn't
    # re-report the same mismatch.
    adopted = expected.check? && st.errors.size > after_callee_state.errors.size
    base_rs = adopted ? rs.with(type: expected.type) : rs

    callee_subst = base_rs
      .constraints
      .map { st.env.substitution.apply(it) }

    args_subst = args_acc
      .constraints
      .map { st.env.substitution.apply(it) }

    propagated = (callee_subst + args_subst)
      .select { it.type.is_a?(Type::Var) }

    # Pass 1 still needs propagation so constraints from inner calls
    # bubble up through outer constructor calls and reach the function
    # binding before generalization. Skip only the mutating dictionary
    # attachment, which would otherwise emit double dispatch code.
    next [st, base_rs.with(constraints: propagated)] if st.skip_constraints

    # Attach a resolution per callee constraint, in callee order, so codegen
    # can pass dicts positionally. Concrete constraints attach a resolved
    # Implementation; var-typed ones attach themselves as a marker meaning
    # "use the enclosing function's local dict".
    callee_errors = callee_subst
      .flat_map do |c|
        case c.type
        in Type::Var
          Constraints.attach_dictionary(c, c)
          []
        else
          Constraints.solve_at_call_site(c, registry, st.env.entry_name)
        end
      end

    # Args' constraints dispatch at their own origins (inner call sites,
    # or a QualifiedAccess/VariableReference when a polymorphic fn is
    # passed as a value). Var-typed ones attach themselves as a marker,
    # mirroring the callee path, so reference-as-value codegen can
    # resolve via the enclosing function's dict_env.
    args_errors = args_subst
      .flat_map do |c|
        case c.type
        in Type::Var
          Constraints.attach_dictionary(c, c)
          []
        else
          Constraints.solve_at_call_site(c, registry, st.env.entry_name)
        end
      end

    st
      .add_errors(callee_errors + args_errors)
      .then { [it, base_rs.with(constraints: propagated)] }
  end
end