diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bcb01a..4bfdedc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,11 @@ Rusty-Elixir before lowering, by `RustQ.Meta.Desugar`. - Pattern, comprehension, path, and mutability lowering are their own modules beside `RustQ.Meta.Lower`. +- Each Elixir module's lowering implements + `RustQ.Meta.Lower.Stdlib.Lowering`, whose `use` imports the helpers they + share, and a call goes to the lowering named after its module. Lowering + builds Rust with `RustQ.Rust.AST.Builder`, whose `expr/1` now accepts every + RustQ AST node. ## 1.0.0-rc.17 - 2026-10-25 diff --git a/lib/rustq/meta/lower.ex b/lib/rustq/meta/lower.ex index 9390030..5c0e3f9 100644 --- a/lib/rustq/meta/lower.ex +++ b/lib/rustq/meta/lower.ex @@ -24,7 +24,7 @@ defmodule RustQ.Meta.Lower do alias RustQ.Meta.Lower.Paths alias RustQ.Meta.Lower.Pattern, as: LowerPattern alias RustQ.Meta.Lower.Stdlib - alias RustQ.Meta.Lower.Stdlib.Context, as: StdlibContext + alias RustQ.Meta.Lower.Stdlib.Lowering.Context, as: StdlibContext alias RustQ.Meta.Ownership alias RustQ.Meta.RustMacro alias RustQ.Meta.Semantics @@ -33,6 +33,7 @@ defmodule RustQ.Meta.Lower do alias RustQ.Meta.Typing alias RustQ.Rust alias RustQ.Rust.AST + alias RustQ.Rust.AST.Builder, as: A alias RustQ.Rust.Identifier # A vector whose item type Rust infers, for collecting where none is known. @@ -59,6 +60,8 @@ defmodule RustQ.Meta.Lower do rust_macros: %{}, built_from: %{} ] + + @type t :: %__MODULE__{} end alias __MODULE__.Env, as: Context @@ -119,7 +122,7 @@ defmodule RustQ.Meta.Lower do return_context = %{context | position: :return} Enum.map(statements, &lower_statement(&1, statement_context)) ++ - [%AST.Return{expr: lower_expr(final, expected_type, return_context)}] + [A.return_stmt(lower_expr(final, expected_type, return_context))] end @keyed_collections [:HashMap, :HashSet, :BTreeMap, :BTreeSet] @@ -170,12 +173,12 @@ defmodule RustQ.Meta.Lower do defp lower_statement({:case, _, [expression, [do: clauses]]}, %Context{} = context) do case lower_option_if_let_statement(expression, normalize_case_clauses(clauses), context) do %AST.IfLet{} = if_let -> if_let - nil -> %AST.ExprStmt{expr: lower_case(expression, clauses, context)} + nil -> A.stmt(lower_case(expression, clauses, context)) end end defp lower_statement({:if, _, [condition, branches]}, %Context{} = context) do - %AST.ExprStmt{expr: lower_if(condition, branches, context)} + A.stmt(lower_if(condition, branches, context)) end # Run for its effects, a `cond` is a chain of `if`s; a final branch doing nothing is left out. @@ -185,7 +188,7 @@ defmodule RustQ.Meta.Lower do defp lower_statement({:with, _, clauses} = with, %Context{} = context) do case with_if_lets(clauses) do {:ok, matches, body} -> lower_statement(nested_if_lets(matches, body), context) - :error -> %AST.ExprStmt{expr: lower_with(with |> elem(2), context)} + :error -> A.stmt(lower_with(with |> elem(2), context)) end end @@ -194,7 +197,7 @@ defmodule RustQ.Meta.Lower do expression, %Context{} = context ) do - %AST.ExprStmt{expr: lower_statement_expr(expression, context)} + A.stmt(lower_statement_expr(expression, context)) end defp lower_statement({:for, _, args} = expression, %Context{} = context) when is_list(args) do @@ -203,19 +206,19 @@ defmodule RustQ.Meta.Lower do Comprehension.lower_for_loop(qualifiers, body, context) _other -> - %AST.ExprStmt{expr: Comprehension.lower_comprehension(expression, nil, context)} + A.stmt(Comprehension.lower_comprehension(expression, nil, context)) end end - defp lower_statement(:ok, %Context{}), do: %AST.ExprStmt{expr: %AST.Tuple{values: []}} + defp lower_statement(:ok, %Context{}), do: A.stmt(A.tuple([])) defp lower_statement({:__block__, _, [expression]}, context), do: lower_statement(expression, context) - defp lower_statement(nil, %Context{}), do: %AST.ExprStmt{expr: %AST.Tuple{values: []}} + defp lower_statement(nil, %Context{}), do: A.stmt(A.tuple([])) defp lower_statement(expression, %Context{} = context) do - %AST.ExprStmt{expr: lower_statement_expr(expression, context)} + A.stmt(lower_statement_expr(expression, context)) end defp cond_statement([{:->, _, [[true], body]} | _rest]), do: body @@ -267,19 +270,19 @@ defmodule RustQ.Meta.Lower do do: {:__block__, [], [expression, nested_if_lets(rest, body)]} defp lower_return({:case, _, [expression, [do: clauses]]}, %Context{} = context) do - %AST.Return{expr: lower_case(expression, clauses, context, context.return_type)} + A.return_stmt(lower_case(expression, clauses, context, context.return_type)) end defp lower_return({:if, _, [condition, branches]}, %Context{} = context) do - %AST.Return{expr: lower_if(condition, branches, context)} + A.return_stmt(lower_if(condition, branches, context)) end defp lower_return({:cond, _, [[do: clauses]]}, %Context{} = context) do - %AST.Return{expr: lower_cond(clauses, context, context.return_type)} + A.return_stmt(lower_cond(clauses, context, context.return_type)) end defp lower_return({:with, _, clauses}, %Context{} = context) do - %AST.Return{expr: lower_with(clauses, context)} + A.return_stmt(lower_with(clauses, context)) end defp lower_return( @@ -287,11 +290,11 @@ defmodule RustQ.Meta.Lower do expression, %Context{} = context ) do - %AST.Return{expr: Comprehension.lower_for_reduce_expr(expression, context)} + A.return_stmt(Comprehension.lower_for_reduce_expr(expression, context)) end defp lower_return(expression, %Context{return_type: return_type} = context), - do: %AST.Return{expr: lower_return_expr(expression, return_type, context)} + do: A.return_stmt(lower_return_expr(expression, return_type, context)) @doc "Lowers an expression in return position." def lower_return_expr(expression, return_type, %Context{} = context), @@ -308,7 +311,7 @@ defmodule RustQ.Meta.Lower do defp lower_return_expr_context(:ok, %Type{kind: :atom} = return_type, %Context{} = context), do: lower_expr(:ok, return_type, context) - defp lower_return_expr_context(:ok, _return_type, %Context{}), do: %AST.Tuple{values: []} + defp lower_return_expr_context(:ok, _return_type, %Context{}), do: A.tuple([]) # A tail `decode_as!` in a NIF returning what it decodes already is the `NifResult`. defp lower_return_expr_context( @@ -335,7 +338,7 @@ defmodule RustQ.Meta.Lower do %Type{kind: :option} = return_type, %Context{} = context ) do - %AST.Some{expr: lower_expr(expression, Type.inner(return_type), context)} + A.some(lower_expr(expression, Type.inner(return_type), context)) end defp lower_return_expr_context( @@ -344,17 +347,17 @@ defmodule RustQ.Meta.Lower do %Context{} = context ) when kind in [:result, :nif_result], - do: %AST.Ok{expr: lower_expr(value, Type.inner(return_type) || return_type, context)} + do: A.ok(lower_expr(value, Type.inner(return_type) || return_type, context)) defp lower_return_expr_context({:error, value}, %Type{kind: :nif_result}, %Context{} = context), - do: %AST.Err{expr: lower_nif_error(value, context)} + do: A.err(lower_nif_error(value, context)) defp lower_return_expr_context( {:error, value}, %Type{kind: :result} = return_type, %Context{} = context ), - do: %AST.Err{expr: lower_expr(value, Type.error(return_type), context)} + do: A.err(lower_expr(value, Type.error(return_type), context)) defp lower_return_expr_context( expression, @@ -368,7 +371,7 @@ defmodule RustQ.Meta.Lower do %Typing.Check{} -> if option_chain?(expression, context), do: lower_expr(expression, return_type, context), - else: %AST.Some{expr: lower_expr(expression, context)} + else: A.some(lower_expr(expression, context)) end end @@ -382,7 +385,7 @@ defmodule RustQ.Meta.Lower do defp lower_return_expr_context(expression, %Type{} = return_type, %Context{} = context) do if infer_propagation?(expression, return_type, context) do - %AST.Try{expr: lower_expr(expression, context)} + A.try(lower_expr(expression, context)) else lower_expr(expression, return_type, context) end @@ -398,7 +401,7 @@ defmodule RustQ.Meta.Lower do defp lower_result_return(expression, return_type, %Context{} = context) do cond do infer_propagation?(expression, return_type, context) -> - %AST.Try{expr: lower_expr(expression, context)} + A.try(lower_expr(expression, context)) plain_value?(expression, context) -> lower_plain_result(expression, return_type, context) @@ -414,10 +417,10 @@ defmodule RustQ.Meta.Lower do %AST.PathCall{path: %AST.Path{parts: [:atoms, _name]}, args: []} = atom, %Type{kind: :nif_result} ), - do: %AST.Ok{expr: atom} + do: A.ok(atom) defp wrap_atom_result(%AST.AtomValue{} = atom, %Type{kind: :nif_result}), - do: %AST.Ok{expr: atom} + do: A.ok(atom) defp wrap_atom_result(lowered, _return_type), do: lowered @@ -444,7 +447,7 @@ defmodule RustQ.Meta.Lower do collected _lowered when return_type.kind == :nif_result -> - %AST.Ok{expr: lower_expr(expression, Type.inner(return_type), context)} + A.ok(lower_expr(expression, Type.inner(return_type), context)) _lowered -> plain_result_value(expression, return_type) @@ -525,7 +528,7 @@ defmodule RustQ.Meta.Lower do %Type{} = expected_type, %Context{} = context ), - do: %AST.BlockExpr{body: lower_expected_block(expressions, context, expected_type)} + do: A.block_expr(lower_expected_block(expressions, context, expected_type)) defp lower_expected_expr_context( {tag, _value} = expression, @@ -539,7 +542,7 @@ defmodule RustQ.Meta.Lower do # `{:some, value}` and `:none` where an option is expected are the option, as patterns read # them and as `{:ok, value}` is a result. defp lower_expected_expr_context({:some, value}, %Type{kind: :option} = expected_type, context), - do: %AST.Some{expr: lower_expr(value, Type.inner(expected_type), context)} + do: A.some(lower_expr(value, Type.inner(expected_type), context)) defp lower_expected_expr_context(:none, %Type{kind: :option}, %Context{}), do: %AST.None{} @@ -549,23 +552,21 @@ defmodule RustQ.Meta.Lower do %Context{} ) when is_binary(value), - do: %AST.MethodCall{receiver: %AST.Literal{value: value}, method: :to_string, args: []} + do: A.method(A.lit(value), :to_string) # A borrowed list literal is a borrowed array, which coerces to the slice expected. defp lower_expected_expr_context({:ref, _, [values]}, %Type{} = expected_type, context) when is_list(values) do item_type = Type.slice_inner(Type.expected_value(expected_type)) - %AST.Ref{ - expr: %AST.ArrayLiteral{ - values: - Enum.map(values, fn value -> - if item_type, - do: lower_expr(value, item_type, context), - else: lower_expr(value, context) - end) - } - } + A.ref(%AST.ArrayLiteral{ + values: + Enum.map(values, fn value -> + if item_type, + do: lower_expr(value, item_type, context), + else: lower_expr(value, context) + end) + }) end defp lower_expected_expr_context( @@ -584,7 +585,7 @@ defmodule RustQ.Meta.Lower do _none -> lower_expr(expression, context) end - %AST.Ref{expr: expr} + A.ref(expr) end defp lower_expected_expr_context( @@ -623,10 +624,7 @@ defmodule RustQ.Meta.Lower do when is_list(fields) do inner = expected_type |> Type.inner() |> Type.expected_value() - %AST.PathCall{ - path: %AST.Path{parts: [:ResourceArc, :new]}, - args: [lower_expr(expression, inner, context)] - } + A.path_call([:ResourceArc, :new], [lower_expr(expression, inner, context)]) end defp lower_expected_expr_context( @@ -637,10 +635,7 @@ defmodule RustQ.Meta.Lower do when is_list(fields) do inner = expected_type |> Type.inner() |> Type.expected_value() - %AST.PathCall{ - path: %AST.Path{parts: [:ResourceArc, :new]}, - args: [lower_expr(expression, inner, context)] - } + A.path_call([:ResourceArc, :new], [lower_expr(expression, inner, context)]) end # A tagged tuple or atom where a Rust enum is expected names its variant, as in @@ -654,13 +649,13 @@ defmodule RustQ.Meta.Lower do (is_tuple(expression) and elem(expression, 0) == :{}) do case rust_enum_variant(expression, type) do {variant, [], _types} -> - %AST.Path{parts: [rust_name, rust_variant(variant)]} + A.path([rust_name, rust_variant(variant)]) {variant, payloads, types} when length(payloads) == length(types) -> - %AST.PathCall{ - path: %AST.Path{parts: [rust_name, rust_variant(variant)]}, - args: Enum.zip_with(payloads, types, &lower_expr(&1, &2, context)) - } + A.path_call( + [rust_name, rust_variant(variant)], + Enum.zip_with(payloads, types, &lower_expr(&1, &2, context)) + ) _other -> lower_checked_expr(expression, type, context) @@ -675,14 +670,14 @@ defmodule RustQ.Meta.Lower do ) when kind in [:result, :nif_result] do case Type.inner(expected_type) do - %Type{} = inner -> %AST.Ok{expr: lower_expr(value, inner, context)} - nil -> %AST.Ok{expr: lower_expr(value, context)} + %Type{} = inner -> A.ok(lower_expr(value, inner, context)) + nil -> A.ok(lower_expr(value, context)) end end # `:ok` where nothing is expected is the unit value, as in `ok(:ok)`. defp lower_expected_expr_context(:ok, %Type{kind: :unit}, %Context{}), - do: %AST.Tuple{values: []} + do: A.tuple([]) # An atom where an atom-union enum is expected names its variant. defp lower_expected_expr_context( @@ -692,7 +687,7 @@ defmodule RustQ.Meta.Lower do ) when is_atom(atom) and atom not in [nil, true, false] and is_list(variants) do if atom in variants, - do: %AST.Path{parts: [rust_name, rust_variant(atom)]}, + do: A.path([rust_name, rust_variant(atom)]), else: lower_expr(atom, context) end @@ -711,7 +706,7 @@ defmodule RustQ.Meta.Lower do ) when is_list(fields) do %AST.StructLiteral{ - path: %AST.Path{parts: parts}, + path: A.path(parts), fields: lower_named_fields(fields, expected_type, context) } end @@ -723,7 +718,7 @@ defmodule RustQ.Meta.Lower do ) when is_list(fields) do %AST.StructLiteral{ - path: %AST.Path{parts: parts}, + path: A.path(parts), fields: lower_named_fields(fields, expected_type, context) } end @@ -789,7 +784,7 @@ defmodule RustQ.Meta.Lower do generics: [target.ast] } - if expected_type.kind == :nif_result, do: decoded, else: %AST.Try{expr: decoded} + if expected_type.kind == :nif_result, do: decoded, else: A.try(decoded) end end @@ -802,16 +797,14 @@ defmodule RustQ.Meta.Lower do values: lower_expected_collection_values(values, expected_type, context) } - if slice_type?(expected_type), do: %AST.Ref{expr: array}, else: array + if slice_type?(expected_type), do: A.ref(array), else: array end defp lower_expected_expr_context(values, %Type{} = expected_type, %Context{} = context) when is_list(values) do - vec = %AST.VecLiteral{ - values: lower_expected_collection_values(values, expected_type, context) - } + vec = A.vec(lower_expected_collection_values(values, expected_type, context)) - if slice_type?(expected_type), do: %AST.Ref{expr: vec}, else: vec + if slice_type?(expected_type), do: A.ref(vec), else: vec end defp lower_expected_expr_context( @@ -821,7 +814,7 @@ defmodule RustQ.Meta.Lower do ) do case expected_option_inner(expected_type) do %Type{} = inner_expected -> - %AST.Some{expr: lower_option_some_expr(expression, inner_expected, context)} + A.some(lower_option_some_expr(expression, inner_expected, context)) nil -> lower_checked_expr({:some, [], [expression]}, expected_type, context) @@ -878,7 +871,7 @@ defmodule RustQ.Meta.Lower do ) do cond do slice_type?(Type.expected_value(expected_type)) -> - %AST.Ref{expr: expression |> lower_expr(context) |> Comprehension.collect_as(@any_vec)} + A.ref(expression |> lower_expr(context) |> Comprehension.collect_as(@any_vec)) expected_type.kind in [:option, :result] -> expression |> lower_expr(context) |> Comprehension.collect_as(expected_type.ast) @@ -910,16 +903,12 @@ defmodule RustQ.Meta.Lower do position: :expr }) - %AST.MethodCall{ - receiver: lower_expr(receiver, context), - method: method, - args: [ - %AST.Closure{ - args: [closure_arg!(arg)], - body: lower_closure_body(body, closure_return, closure_context) - } - ] - } + A.method(lower_expr(receiver, context), method, [ + A.closure( + [closure_arg!(arg)], + lower_closure_body(body, closure_return, closure_context) + ) + ]) _other -> lower_checked_expr(expression, expected_type, context) @@ -935,7 +924,7 @@ defmodule RustQ.Meta.Lower do when is_atom(name) and is_atom(ast_context) do case Map.get(context.vars, name) do %Type{ast: %AST.TypePath{parts: [:Binary]}} -> - %AST.MethodCall{receiver: lower_expr(variable, context), method: :to_vec, args: []} + A.method(lower_expr(variable, context), :to_vec) _other -> lower_checked_expr(variable, expected_type, context) @@ -982,12 +971,11 @@ defmodule RustQ.Meta.Lower do do: lower_expr(expression, inner_expected, context) defp lower_expected_tuple(values, types, %Context{} = context) do - %AST.Tuple{ - values: - values - |> Enum.zip(types) - |> Enum.map(fn {value, type} -> lower_expr(value, type, context) end) - } + A.tuple( + values + |> Enum.zip(types) + |> Enum.map(fn {value, type} -> lower_expr(value, type, context) end) + ) end # `match?(pattern, value)` is the `case` that answers whether the value matches. @@ -995,7 +983,7 @@ defmodule RustQ.Meta.Lower do # where it is borrowed or still in use, and takes it otherwise. defp lower_expr_context({:__rustq_items__, _, [list]}, %Context{} = context) do method = if borrowed_list?(list, context), do: :iter, else: :into_iter - %AST.MethodCall{receiver: lower_expr(list, context), method: method, args: []} + A.method(lower_expr(list, context), method) end # `__rustq_owned__(item, list)` is a copy of an item of `list`, as `.clone()` makes one of an @@ -1009,12 +997,12 @@ defmodule RustQ.Meta.Lower do if Collection.copy?(inner), do: lower_expr(item, context), - else: %AST.MethodCall{receiver: lower_expr(item, context), method: :clone, args: []} + else: A.method(lower_expr(item, context), :clone) end # A block of expressions is a Rust block whose value is its last expression. defp lower_expr_context({:__block__, _, [_first, _second | _rest] = expressions}, context), - do: %AST.BlockExpr{body: lower_value_block(expressions, context)} + do: A.block_expr(lower_value_block(expressions, context)) defp lower_expr_context({:match?, meta, [pattern, expression]}, %Context{} = context) do clauses = [{:->, meta, [[pattern], true]}, {:->, meta, [[{:_, meta, nil}], false]}] @@ -1022,42 +1010,33 @@ defmodule RustQ.Meta.Lower do end defp lower_expr_context({:unwrap!, _, [expression]}, %Context{} = context), - do: %AST.Try{expr: lower_expr(expression, context)} + do: A.try(lower_expr(expression, context)) defp lower_expr_context({:ok_or!, _, [option, error]}, %Context{} = context) do - %AST.Try{ - expr: %AST.MethodCall{ - receiver: lower_expr(option, context), - method: :ok_or, - args: [lower_nif_error(error, context)] - } - } + A.try(A.method(lower_expr(option, context), :ok_or, [lower_nif_error(error, context)])) end defp lower_expr_context({:|>, _, [left, right]}, %Context{} = context), do: lower_pipe(left, right, context) defp lower_expr_context({:cast, _, [expression, type]}, %Context{} = context), - do: %AST.Cast{ - expr: lower_cast_operand(expression, context), - type: lower_type_arg(type, context) - } + do: A.cast(lower_cast_operand(expression, context), lower_type_arg(type, context)) defp lower_expr_context({:decode_as!, _, [expression, type_ast]}, %Context{} = context), - do: %AST.Try{expr: decode_as_expr(expression, type_ast, context)} + do: A.try(decode_as_expr(expression, type_ast, context)) defp lower_expr_context({:decode_as, _, [expression, type_ast]}, %Context{} = context), do: decode_as_expr(expression, type_ast, context) # A borrowed list literal is a slice of an array, not of a vector built for the borrow. defp lower_expr_context({:ref, _, [values]}, %Context{} = context) when is_list(values), - do: %AST.Ref{expr: %AST.ArrayLiteral{values: Enum.map(values, &lower_expr(&1, context))}} + do: A.ref(%AST.ArrayLiteral{values: Enum.map(values, &lower_expr(&1, context))}) defp lower_expr_context({:ref, _, [expression]}, %Context{} = context), - do: %AST.Ref{expr: lower_expr(expression, context)} + do: A.ref(lower_expr(expression, context)) defp lower_expr_context({:mut_ref, _, [expression]}, %Context{} = context), - do: %AST.Ref{expr: lower_expr(expression, context), mutable: true} + do: A.mut_ref(lower_expr(expression, context)) defp lower_expr_context({:deref, _, [expression]}, %Context{} = context) do expr = @@ -1069,24 +1048,24 @@ defmodule RustQ.Meta.Lower do nil -> lower_expr(expression, context) end - %AST.UnaryOp{op: :deref, expr: expr} + A.deref(expr) end defp lower_expr_context({:tuple_field, _, [expression, index]}, %Context{} = context) when is_integer(index), - do: %AST.Field{receiver: lower_expr(expression, context), field: index} + do: A.field(lower_expr(expression, context), index) defp lower_expr_context({:some, _, [expression]}, %Context{} = context), - do: %AST.Some{expr: lower_some_arg_expr(expression, context)} + do: A.some(lower_some_arg_expr(expression, context)) defp lower_expr_context({:none, _, []}, %Context{}), do: %AST.None{} defp lower_expr_context({:ok, _, []}, %Context{}), do: %AST.Ok{} defp lower_expr_context({:ok, _, [expression]}, %Context{} = context), - do: %AST.Ok{expr: lower_expr(expression, context)} + do: A.ok(lower_expr(expression, context)) defp lower_expr_context({:err, _, [expression]}, %Context{} = context), - do: %AST.Err{expr: lower_expr(expression, context)} + do: A.err(lower_expr(expression, context)) defp lower_expr_context({:token_macro, _, [path, tokens]}, %Context{}), do: %AST.TokenMacro{path: lower_token_macro_path(path), tokens: tokens} @@ -1115,7 +1094,7 @@ defmodule RustQ.Meta.Lower do do: LowerPattern.lower_semantic_pat(pattern, context) defp lower_expr_context({:stmt!, _, [expression]}, %Context{} = context), - do: %AST.ExprStmt{expr: lower_expr(expression, context)} + do: A.stmt(lower_expr(expression, context)) defp lower_expr_context({:raw_expr!, _, [tokens]}, %Context{}), do: parse_syn(:Expr, tokens) defp lower_expr_context({:raw_pat!, _, [tokens]}, %Context{}), do: parse_syn(:Pat, tokens) @@ -1128,10 +1107,10 @@ defmodule RustQ.Meta.Lower do body: lower_semantic_arm_body(block, context) } - defp lower_expr_context({:nif_env, _, []}, %Context{}), do: %AST.Path{parts: [:env]} + defp lower_expr_context({:nif_env, _, []}, %Context{}), do: A.path([:env]) defp lower_expr_context({:badarg, _, []}, %Context{}), - do: %AST.Path{parts: [:rustler, :Error, :BadArg]} + do: A.path([:rustler, :Error, :BadArg]) defp lower_expr_context({:%{}, _, [{:|, _, [base, fields]}]}, %Context{} = context) when is_list(fields), @@ -1177,7 +1156,7 @@ defmodule RustQ.Meta.Lower do do: repeat_expression(group, expression, context) defp lower_expr_context({:index, _, [receiver, index]}, %Context{} = context), - do: %AST.Index{receiver: lower_expr(receiver, context), index: lower_expr(index, context)} + do: A.index(lower_expr(receiver, context), lower_expr(index, context)) # `container[key]` indexes; a map is indexed by a reference to its key. defp lower_expr_context({{:., _, [Access, :get]}, _, [receiver, key]}, %Context{} = context) do @@ -1185,10 +1164,10 @@ defmodule RustQ.Meta.Lower do index = if map_type?(infer_expr_type(receiver, context.vars)), - do: %AST.Ref{expr: index}, + do: A.ref(index), else: index - %AST.Index{receiver: lower_expr(receiver, context), index: index} + A.index(lower_expr(receiver, context), index) end defp lower_expr_context({:&, _, [{:/, _, [function, arity]}]}, %Context{} = context) @@ -1281,16 +1260,16 @@ defmodule RustQ.Meta.Lower do lower_remote_or_method_call(receiver, function, args, context) end - defp lower_expr_context({:__rustq_path__, _, parts}, %Context{}), do: %AST.Path{parts: parts} + defp lower_expr_context({:__rustq_path__, _, parts}, %Context{}), do: A.path(parts) defp lower_expr_context({:__aliases__, _, parts}, %Context{} = context), - do: %AST.Path{parts: Paths.mapped_alias_parts(parts, context.rust_modules)} + do: A.path(Paths.mapped_alias_parts(parts, context.rust_modules)) defp lower_expr_context({:{}, _, values}, %Context{} = context), - do: %AST.Tuple{values: Enum.map(values, &lower_expr(&1, context))} + do: A.tuple(Enum.map(values, &lower_expr(&1, context))) defp lower_expr_context({left, right}, %Context{} = context), - do: %AST.Tuple{values: [lower_expr(left, context), lower_expr(right, context)]} + do: A.tuple([lower_expr(left, context), lower_expr(right, context)]) defp lower_expr_context({name, _, args}, %Context{} = context) when is_atom(name) and is_list(args) do @@ -1303,15 +1282,15 @@ defmodule RustQ.Meta.Lower do end defp lower_expr_context(values, %Context{} = context) when is_list(values), - do: %AST.VecLiteral{values: Enum.map(values, &lower_array_value(&1, context))} + do: A.vec(Enum.map(values, &lower_array_value(&1, context))) - defp lower_expr_context(value, %Context{}) when is_binary(value), do: %AST.Literal{value: value} + defp lower_expr_context(value, %Context{}) when is_binary(value), do: A.lit(value) defp lower_expr_context(value, %Context{}) when is_integer(value) or is_float(value), - do: %AST.Literal{value: value} + do: A.lit(value) - defp lower_expr_context(true, %Context{}), do: %AST.Literal{value: true} - defp lower_expr_context(false, %Context{}), do: %AST.Literal{value: false} + defp lower_expr_context(true, %Context{}), do: A.lit(true) + defp lower_expr_context(false, %Context{}), do: A.lit(false) defp lower_expr_context(nil, %Context{}), do: %AST.None{} defp lower_expr_context(atom, %Context{}) when is_atom(atom), do: %AST.AtomValue{name: atom} @@ -1330,12 +1309,12 @@ defmodule RustQ.Meta.Lower do expression, %Context{return_type: %Type{}} = context ) do - %AST.Try{expr: Comprehension.lower_for_reduce_expr(expression, context)} + A.try(Comprehension.lower_for_reduce_expr(expression, context)) end defp lower_statement_expr(expression, %Context{return_type: %Type{} = return_type} = context) do if infer_statement_propagation?(expression, return_type, context) do - %AST.Try{expr: lower_expr(expression, context)} + A.try(lower_expr(expression, context)) else lower_expr(expression, context) end @@ -1349,7 +1328,7 @@ defmodule RustQ.Meta.Lower do defp lower_wrapper_arg_expr(expression, %Context{} = context) do if same_wrapper_propagation?(expression, context.return_type, context) do - %AST.Try{expr: lower_expr(expression, context)} + A.try(lower_expr(expression, context)) else lower_expr(expression, context) end @@ -1357,7 +1336,7 @@ defmodule RustQ.Meta.Lower do defp lower_some_arg_expr(expression, %Context{} = context) do if fallible_expression?(expression, context) and propagation_allowed?(context.return_type) do - %AST.Try{expr: lower_expr(expression, context)} + A.try(lower_expr(expression, context)) else lower_wrapper_arg_expr(expression, context) end @@ -1386,7 +1365,7 @@ defmodule RustQ.Meta.Lower do defp lower_cast_operand(expression, %Context{} = context) do if same_wrapper_propagation?(expression, context.return_type, context) do - %AST.Try{expr: lower_expr(expression, context)} + A.try(lower_expr(expression, context)) else lower_expr(expression, context) end @@ -1428,7 +1407,7 @@ defmodule RustQ.Meta.Lower do if option_propagation_in_result_context?(check, context) do lower_expr(expression, context) else - %AST.Try{expr: lower_expr(expression, context)} + A.try(lower_expr(expression, context)) end end @@ -1442,41 +1421,41 @@ defmodule RustQ.Meta.Lower do do: lower_expr(expression, context) defp lower_checked_coercion(%Typing.Check{coercion: :borrow}, expression, context), - do: %AST.Ref{expr: lower_expr(expression, context)} + do: A.ref(lower_expr(expression, context)) defp lower_checked_coercion(%Typing.Check{coercion: :mut_borrow}, expression, context), - do: %AST.Ref{expr: lower_expr(expression, context), mutable: true} + do: A.mut_ref(lower_expr(expression, context)) defp lower_checked_coercion(%Typing.Check{coercion: :propagate_borrow}, expression, context), - do: %AST.Ref{expr: %AST.Try{expr: lower_expr(expression, context)}} + do: A.ref(A.try(lower_expr(expression, context))) defp lower_checked_coercion( %Typing.Check{coercion: :propagate_mut_borrow}, expression, context ), - do: %AST.Ref{expr: %AST.Try{expr: lower_expr(expression, context)}, mutable: true} + do: A.mut_ref(A.try(lower_expr(expression, context))) defp lower_checked_coercion(%Typing.Check{coercion: :as_ref}, expression, context), do: option_as_ref_expr(lower_expr(expression, context)) defp lower_checked_coercion(%Typing.Check{coercion: :propagate_as_ref}, expression, context), - do: option_as_ref_expr(%AST.Try{expr: lower_expr(expression, context)}) + do: option_as_ref_expr(A.try(lower_expr(expression, context))) defp lower_checked_coercion(%Typing.Check{coercion: :deref}, expression, context), - do: %AST.UnaryOp{op: :deref, expr: lower_expr(expression, context)} + do: A.deref(lower_expr(expression, context)) defp lower_checked_coercion(%Typing.Check{coercion: :some}, expression, context), - do: %AST.Some{expr: lower_expr(expression, context)} + do: A.some(lower_expr(expression, context)) defp lower_checked_coercion(%Typing.Check{coercion: :to_vec}, expression, context), - do: %AST.MethodCall{receiver: lower_expr(expression, context), method: :to_vec, args: []} + do: A.method(lower_expr(expression, context), :to_vec) defp lower_checked_coercion(%Typing.Check{}, expression, context), do: lower_expr(expression, context) defp option_as_ref_expr(expr) do - %AST.MethodCall{receiver: expr, method: :as_ref, args: []} + A.method(expr, :as_ref) end defp option_propagation_in_result_context?( @@ -1520,7 +1499,7 @@ defmodule RustQ.Meta.Lower do } end) - %AST.Match{expr: match_expr, arms: arms} + A.match_expr(match_expr, arms) end defp case_scrutinee( @@ -1532,7 +1511,7 @@ defmodule RustQ.Meta.Lower do match_expr = lower_expr(expression, context) if Enum.any?(clauses, &list_case_clause?/1) do - {expression_type, %AST.MethodCall{receiver: match_expr, method: :as_slice, args: []}} + {expression_type, A.method(match_expr, :as_slice)} else {expression_type, match_expr} end @@ -1588,7 +1567,7 @@ defmodule RustQ.Meta.Lower do %AST.If{ condition: lower_condition(condition, context), then: lower_clause_body(body, context, expected_type), - else: [%AST.Return{expr: lower_cond_clauses(rest, context, expected_type)}] + else: [A.return_stmt(lower_cond_clauses(rest, context, expected_type))] } end @@ -1610,14 +1589,14 @@ defmodule RustQ.Meta.Lower do defp clause_expr(body, context, _expected_type) do case body |> block_expressions() |> lower_value_block(context) do [%AST.Return{expr: expr}] -> expr - statements -> %AST.BlockExpr{body: statements} + statements -> A.block_expr(statements) end end defp lower_expected_value_block(expressions, %Context{} = context, %Type{} = expected_type) do case lower_expected_block(expressions, context, expected_type) do [%AST.Return{expr: expr}] -> expr - statements -> %AST.BlockExpr{body: statements} + statements -> A.block_expr(statements) end end @@ -1662,28 +1641,20 @@ defmodule RustQ.Meta.Lower do match_type = declared_case_type(match_type, context) body_context = LowerPattern.context_with_match_pattern(pattern, match_type, context) - %AST.Match{ - expr: lower_expr(expression, context), - arms: [ - %AST.Arm{ - pattern: LowerPattern.lower_with_pattern(pattern, match_type, context), - body: [ - %AST.Return{ - expr: lower_with_matches(rest, body, else_clauses, body_context, expected_type) - } - ] - }, - with_fallback_arm(with_value, pattern, else_clauses, context, expected_type) - ] - } + A.match_expr(lower_expr(expression, context), [ + %AST.Arm{ + pattern: LowerPattern.lower_with_pattern(pattern, match_type, context), + body: [ + A.return_stmt(lower_with_matches(rest, body, else_clauses, body_context, expected_type)) + ] + }, + with_fallback_arm(with_value, pattern, else_clauses, context, expected_type) + ]) end # Without `else`, an unmatched result or option passes its error or `None` on, as in Elixir. defp with_fallback_arm(_value_name, {:ok, _value}, [], _context, _expected_type), - do: - passthrough_arm(%AST.PatErr{pattern: %AST.PatVar{name: :error}}, %AST.Err{ - expr: %AST.Var{name: :error} - }) + do: passthrough_arm(%AST.PatErr{pattern: %AST.PatVar{name: :error}}, A.err(A.var(:error))) defp with_fallback_arm(_value_name, {:some, _value}, [], _context, _expected_type), do: passthrough_arm(%AST.PatNone{}, %AST.None{}) @@ -1706,27 +1677,26 @@ defmodule RustQ.Meta.Lower do %AST.Arm{ pattern: %AST.PatVar{name: value_name}, body: [ - %AST.Return{expr: lower_with_else(value_name, else_clauses, context, expected_type)} + A.return_stmt(lower_with_else(value_name, else_clauses, context, expected_type)) ] } end defp passthrough_arm(pattern, expr), - do: %AST.Arm{pattern: pattern, body: [%AST.Return{expr: expr}]} + do: %AST.Arm{pattern: pattern, body: [A.return_stmt(expr)]} - defp lower_with_else(value_name, [], _context, _expected_type), do: %AST.Var{name: value_name} + defp lower_with_else(value_name, [], _context, _expected_type), do: A.var(value_name) defp lower_with_else(value_name, else_clauses, %Context{} = context, expected_type) do - %AST.Match{ - expr: %AST.Var{name: value_name}, - arms: - Enum.map(else_clauses, fn {:->, _, [[pattern], body]} -> - %AST.Arm{ - pattern: LowerPattern.lower_match_pattern(pattern, nil, context), - body: lower_clause_body(body, %{context | position: :return}, expected_type) - } - end) - } + A.match_expr( + A.var(value_name), + Enum.map(else_clauses, fn {:->, _, [[pattern], body]} -> + %AST.Arm{ + pattern: LowerPattern.lower_match_pattern(pattern, nil, context), + body: lower_clause_body(body, %{context | position: :return}, expected_type) + } + end) + ) end defp lower_assignment(target, {op, _meta, [left, right]}, expected_type, %Context{} = context) @@ -1745,10 +1715,7 @@ defmodule RustQ.Meta.Lower do end defp lower_assignment(target, expression, expected_type, %Context{} = context) do - %AST.Assign{ - target: lower_expr(target, context), - expr: lower_expr(expression, expected_type, context) - } + A.assign(lower_expr(target, context), lower_expr(expression, expected_type, context)) end defp lower_assignment_op(target, op, left, right, expected_type, %Context{} = context) do @@ -1762,10 +1729,7 @@ defmodule RustQ.Meta.Lower do expr: lower_expr(right, expected_type, context) } else - %AST.Assign{ - target: lowered_target, - expr: lower_expr({op, [], [left, right]}, expected_type, context) - } + A.assign(lowered_target, lower_expr({op, [], [left, right]}, expected_type, context)) end end @@ -1917,7 +1881,7 @@ defmodule RustQ.Meta.Lower do defp block_expr([%AST.Return{expr: expr}]), do: expr defp block_expr(statements) do - %AST.BlockExpr{body: statements} + A.block_expr(statements) end @doc "Lowers the body of a clause, expected to give `type` when known." @@ -1976,23 +1940,16 @@ defmodule RustQ.Meta.Lower do cond do no_parens? and Paths.alias_path_ast?(receiver) -> - %AST.Path{ - parts: Paths.alias_path_parts(receiver, context.rust_modules) ++ [field_or_function] - } + A.path(Paths.alias_path_parts(receiver, context.rust_modules) ++ [field_or_function]) no_parens? -> - %AST.Field{receiver: lower_expr(receiver, context), field: field_or_function} + A.field(lower_expr(receiver, context), field_or_function) Paths.super_alias_ast?(receiver) -> - %AST.PathCall{path: %AST.Path{parts: [:super, field_or_function]}, args: []} + A.path_call([:super, field_or_function]) Paths.alias_ast?(receiver) -> - %AST.PathCall{ - path: %AST.Path{ - parts: Paths.alias_parts(receiver, context.rust_modules) ++ [field_or_function] - }, - args: [] - } + A.path_call(Paths.alias_parts(receiver, context.rust_modules) ++ [field_or_function]) field_or_function == :into_iter -> receiver_type = @@ -2018,7 +1975,7 @@ defmodule RustQ.Meta.Lower do _method, %Context{} = context ), - do: %AST.Try{expr: lower_expr(receiver, context)} + do: A.try(lower_expr(receiver, context)) defp lower_dot_receiver(receiver, _method, %Context{} = context), do: lower_expr(receiver, context) @@ -2029,26 +1986,17 @@ defmodule RustQ.Meta.Lower do lower_remote_macro_call(receiver, function, args, context) Paths.super_alias_ast?(receiver) -> - %AST.PathCall{ - path: %AST.Path{parts: [:super, function]}, - args: lower_parent_call_args(function, args, context) - } + A.path_call([:super, function], lower_parent_call_args(function, args, context)) Paths.rust_constructor_alias?(receiver) -> path = Paths.alias_parts(receiver, context.rust_modules) ++ [rust_variant(function)] - %AST.PathCall{ - path: %AST.Path{parts: path}, - args: lower_path_call_args(path, function, args, context) - } + A.path_call(path, lower_path_call_args(path, function, args, context)) Paths.alias_ast?(receiver) -> path = Paths.alias_parts(receiver, context.rust_modules) ++ [function] - %AST.PathCall{ - path: %AST.Path{parts: path}, - args: lower_path_call_args(path, function, args, context) - } + A.path_call(path, lower_path_call_args(path, function, args, context)) true -> receiver_type = @@ -2056,11 +2004,11 @@ defmodule RustQ.Meta.Lower do target = Paths.callable_target_from_type(receiver_type) - %AST.MethodCall{ - receiver: lower_method_receiver(receiver, receiver_type, function, context), - method: borrowed_iteration(function, args, receiver_type), - args: lower_method_call_args(receiver_type, target, function, args, context) - } + A.method( + lower_method_receiver(receiver, receiver_type, function, context), + borrowed_iteration(function, args, receiver_type), + lower_method_call_args(receiver_type, target, function, args, context) + ) end end @@ -2070,7 +2018,7 @@ defmodule RustQ.Meta.Lower do _function, %Context{} = context ) do - %AST.Try{expr: lower_expr(receiver, context)} + A.try(lower_expr(receiver, context)) end defp lower_method_receiver(receiver, %Type{} = receiver_type, :unwrap_or, %Context{} = context) do @@ -2106,7 +2054,7 @@ defmodule RustQ.Meta.Lower do defp lower_var_or_macro_capture(name, ast_context, %Context{} = context) do case macro_var_fragment(name, context.macro_vars) do nil -> - %AST.Var{name: name} + A.var(name) :ty -> Diagnostic.lower( @@ -2353,7 +2301,7 @@ defmodule RustQ.Meta.Lower do end defp lower_pipe_call(receiver, {:cast, _, [type]}, %Context{}), - do: %AST.Cast{expr: receiver, type: RustQ.Spec.type(type).ast} + do: A.cast(receiver, RustQ.Spec.type(type).ast) defp lower_pipe_call( receiver, @@ -2361,16 +2309,12 @@ defmodule RustQ.Meta.Lower do %Context{} = context ) when operator in [:+, :-, :*, :/] do - %AST.BinaryOp{left: receiver, op: operator_op(operator), right: lower_expr(right, context)} + A.binary(receiver, operator_op(operator), lower_expr(right, context)) end defp lower_pipe_call(receiver, {name, _, args}, %Context{} = context) when is_atom(name) and is_list(args) do - %AST.MethodCall{ - receiver: receiver, - method: name, - args: Enum.map(args, &lower_expr(&1, context)) - } + A.method(receiver, name, Enum.map(args, &lower_expr(&1, context))) end defp lower_pipe_call(_receiver, other, %Context{}) do @@ -2383,28 +2327,11 @@ defmodule RustQ.Meta.Lower do end defp lower_binary_op(left, op, right, %Context{} = context) do - %AST.BinaryOp{ - left: lower_binary_operand(left, context), - op: op, - right: lower_binary_operand(right, context) - } + A.binary(lower_binary_operand(left, context), op, lower_binary_operand(right, context)) end - defp stdlib_context(%Context{} = context, expected \\ nil) do - %StdlibContext{ - lower: &lower_expr(&1, context), - lower_expected: &lower_expr(&1, &2, context), - lower_binary_operand: &lower_binary_operand(&1, context), - lower_closure: &lower_closure_args(&1, &2, context), - lower_typed_closure: &lower_closure_args(&1, &2, context, &3), - lower_closure_body: &lower_closure_body(&1, &2, context), - lower_capture: &lower_function_capture(&1, context), - closure_arg: &closure_arg!/1, - type_of: &Typing.synth(&1, typing_env(context)), - in_use?: &in_use?(&1, context), - expected: expected - } - end + defp stdlib_context(%Context{} = context, expected \\ nil), + do: %StdlibContext{env: context, expected: expected} @doc "Whether a collection is still in use after this use of it; see `RustQ.Meta.Ownership`." # A collection is in use unless this is its value's last use; see `RustQ.Meta.Ownership`. A @@ -2420,9 +2347,10 @@ defmodule RustQ.Meta.Lower do end end - defp lower_binary_operand(expression, %Context{} = context) do + @doc "`expression`, lowered as an operand: a fallible one propagates its error with `?`." + def lower_binary_operand(expression, %Context{} = context) do if fallible_expression?(expression, context) and propagation_allowed?(context.return_type) do - %AST.Try{expr: lower_expr(expression, context)} + A.try(lower_expr(expression, context)) else lower_expr(expression, context) end @@ -2451,10 +2379,10 @@ defmodule RustQ.Meta.Lower do LowerPattern.context_with_match_pattern(pattern, type, acc) end) - %AST.Closure{ - args: Enum.map(args, &closure_arg!/1), - body: lower_closure_body(body, closure_return_type(expected_type), context) - } + A.closure( + Enum.map(args, &closure_arg!/1), + lower_closure_body(body, closure_return_type(expected_type), context) + ) end defp closure_context(args, context) do @@ -2467,8 +2395,9 @@ defmodule RustQ.Meta.Lower do } end - defp lower_closure_args(args, body, %Context{} = context, expected_return_type \\ nil) - when is_list(args) do + @doc "A closure taking `args` and returning `body`, as a value of `expected_return_type` when given." + def lower_closure_args(args, body, %Context{} = context, expected_return_type \\ nil) + when is_list(args) do {args, typed} = Enum.map_reduce(args, [], &untyped_closure_arg/2) captured = @@ -2492,10 +2421,10 @@ defmodule RustQ.Meta.Lower do position: :expr } - %AST.Closure{ - args: Enum.map(args, &closure_arg!/1), - body: lower_closure_body(body, expected_return_type, context) - } + A.closure( + Enum.map(args, &closure_arg!/1), + lower_closure_body(body, expected_return_type, context) + ) end # A closure argument may carry the type of the items it is given, as an element of the list @@ -2505,21 +2434,22 @@ defmodule RustQ.Meta.Lower do defp untyped_closure_arg(arg, typed), do: {arg, typed} - defp lower_closure_body( - {:__block__, _, [_first, _second | _rest] = expressions}, - expected_return_type, - %Context{} = context - ) do + @doc "A closure's `body`, lowered to return a value of `expected_return_type` when given." + def lower_closure_body( + {:__block__, _, [_first, _second | _rest] = expressions}, + expected_return_type, + %Context{} = context + ) do body = case expected_return_type do %Type{} = type -> lower_expected_block(expressions, context, type) _none -> lower_value_block(expressions, context) end - %AST.BlockExpr{body: body} + A.block_expr(body) end - defp lower_closure_body(body, expected_return_type, %Context{} = context), + def lower_closure_body(body, expected_return_type, %Context{} = context), do: lower_expr(closure_body_expr(body), expected_return_type, context) defp lower_value_block(expressions, %Context{} = context) do @@ -2527,7 +2457,7 @@ defmodule RustQ.Meta.Lower do {statements, final} = split_final(expressions) Enum.map(statements, &lower_statement(&1, %{context | position: :statement})) ++ - [%AST.Return{expr: lower_expr(final, %{context | position: :return})}] + [A.return_stmt(lower_expr(final, %{context | position: :return}))] end defp map_type?(%Type{kind: kind} = type) when kind in [:ref, :mut_ref], @@ -2538,23 +2468,24 @@ defmodule RustQ.Meta.Lower do defp map_type?(_type), do: false - defp lower_function_capture({name, _, capture_context}, %Context{}) - when is_atom(name) and is_atom(capture_context), - do: %AST.Path{parts: [name]} + @doc "A captured named function, as the Rust path of that function." + def lower_function_capture({name, _, capture_context}, %Context{}) + when is_atom(name) and is_atom(capture_context), + do: A.path([name]) - defp lower_function_capture( - {{:., _, [{:__aliases__, _, [:Super]}, name]}, _, []}, - %Context{} - ), - do: %AST.Path{parts: [:super, name]} + def lower_function_capture( + {{:., _, [{:__aliases__, _, [:Super]}, name]}, _, []}, + %Context{} + ), + do: A.path([:super, name]) - defp lower_function_capture( - {{:., _, [{:__aliases__, _, parts}, name]}, _, []}, - %Context{} = context - ), - do: %AST.Path{parts: Paths.mapped_alias_parts(parts, context.rust_modules) ++ [name]} + def lower_function_capture( + {{:., _, [{:__aliases__, _, parts}, name]}, _, []}, + %Context{} = context + ), + do: A.path(Paths.mapped_alias_parts(parts, context.rust_modules) ++ [name]) - defp lower_function_capture(other, %Context{}) do + def lower_function_capture(other, %Context{}) do Diagnostic.lower( :unsupported_function_capture, other, @@ -2563,9 +2494,10 @@ defmodule RustQ.Meta.Lower do ) end - defp closure_arg!({name, _, context}) when is_atom(name) and is_atom(context), do: name + @doc "A closure argument, as a Rust closure argument: a name, or a binding pattern." + def closure_arg!({name, _, context}) when is_atom(name) and is_atom(context), do: name - defp closure_arg!(other), do: LowerPattern.lower_binding_pattern(other) + def closure_arg!(other), do: LowerPattern.lower_binding_pattern(other) defp closure_body_expr([expression]), do: expression defp closure_body_expr(expression), do: expression @@ -2610,7 +2542,7 @@ defmodule RustQ.Meta.Lower do defp lower_macro_call(name, args, rust_macros, %Context{} = context) do part = macro_call_part(name) - path = %AST.Path{parts: [part]} + path = A.path([part]) case Map.get(rust_macros, part) do nil -> @@ -2658,7 +2590,7 @@ defmodule RustQ.Meta.Lower do if path_parts do %AST.MacroCall{ - path: %AST.Path{parts: path_parts}, + path: A.path(path_parts), args: Enum.map(args, &lower_expr(&1, context)) } else @@ -2721,14 +2653,11 @@ defmodule RustQ.Meta.Lower do defp rustler_atom_type, do: RustQ.Spec.type(quote(do: RustQ.Type.atom())) defp parse_syn(type, tokens) do - %AST.PathCall{ - path: %AST.Path{parts: [:super, "parse_syn::<#{type}>"]}, - args: [quote_tokens(tokens)] - } + A.path_call([:super, "parse_syn::<#{type}>"], [quote_tokens(tokens)]) end defp quote_tokens(tokens) when is_binary(tokens), - do: %AST.TokenMacro{path: %AST.Path{parts: [:quote]}, tokens: tokens} + do: %AST.TokenMacro{path: A.path([:quote]), tokens: tokens} defp quote_tokens(other) do Diagnostic.lower( @@ -2749,11 +2678,11 @@ defmodule RustQ.Meta.Lower do end) if explicit do - %AST.Path{parts: explicit} + A.path(explicit) else type_ast = {{:., meta, [alias_ast, :t]}, meta, []} %AST.TypePath{parts: resolved} = RustQ.Spec.type(type_ast, context.type_aliases).ast - %AST.Path{parts: resolved} + A.path(resolved) end end @@ -2764,7 +2693,7 @@ defmodule RustQ.Meta.Lower do def enum_variant_path(path, variant, %Context{} = context) do %AST.Path{parts: parts} = lower_expr(path, context) - %AST.Path{parts: parts ++ [rust_variant(LowerPattern.semantic_atom!(variant))]} + A.path(parts ++ [rust_variant(LowerPattern.semantic_atom!(variant))]) end # The struct a type declaration of this module, or of a callable module, names `name`. @@ -2786,7 +2715,7 @@ defmodule RustQ.Meta.Lower do defp clone_captured(lowered, {name, _, ast_context}, %Context{captured: captured}) when is_atom(name) and is_atom(ast_context) do if MapSet.member?(captured, name), - do: %AST.MethodCall{receiver: lowered, method: :clone, args: []}, + do: A.method(lowered, :clone), else: lowered end @@ -2797,7 +2726,7 @@ defmodule RustQ.Meta.Lower do case infer_expr_type(base, context.vars) || Typing.synth(base, typing_env(context)) do %Type{kind: :struct, ast: %AST.TypePath{parts: parts}} = type -> %AST.StructLiteral{ - path: %AST.Path{parts: parts}, + path: A.path(parts), fields: lower_named_fields(fields, type, context), base: base |> lower_expr(type, context) |> clone_captured(base, context) } @@ -2823,8 +2752,8 @@ defmodule RustQ.Meta.Lower do end) end - defp lower_token_macro_path(atom) when is_atom(atom), do: %AST.Path{parts: [atom]} - defp lower_token_macro_path({:__aliases__, _, parts}), do: %AST.Path{parts: parts} + defp lower_token_macro_path(atom) when is_atom(atom), do: A.path([atom]) + defp lower_token_macro_path({:__aliases__, _, parts}), do: A.path(parts) defp lower_token_macro_path(other) do Diagnostic.lower( diff --git a/lib/rustq/meta/lower/collection.ex b/lib/rustq/meta/lower/collection.ex index 9fe45b9..6b90d83 100644 --- a/lib/rustq/meta/lower/collection.ex +++ b/lib/rustq/meta/lower/collection.ex @@ -9,9 +9,12 @@ defmodule RustQ.Meta.Lower.Collection do { let mut rustq_updated = scores; rustq_updated.insert(name, 1); rustq_updated } """ - alias RustQ.Meta.Lower.Stdlib.Context + import RustQ.Meta.Lower.Stdlib.Lowering, only: [lowered: 2, lowered: 3, type_of: 2, in_use?: 2] + + alias RustQ.Meta.Lower.Stdlib.Lowering.Context alias RustQ.Meta.Type alias RustQ.Rust.AST + alias RustQ.Rust.AST.Builder, as: A @maps [:HashMap, :BTreeMap] @sets [:HashSet, :BTreeSet] @@ -50,7 +53,7 @@ defmodule RustQ.Meta.Lower.Collection do @doc "The keyed collection `collection` is, by its type." @spec keyed_of(Macro.t(), Context.t()) :: keyed() | nil - def keyed_of(collection, %Context{} = context), do: keyed(context.type_of.(collection)) + def keyed_of(collection, %Context{} = context), do: keyed(type_of(collection, context)) @doc """ An update of `collection`: `statements` receives the variable holding the collection, or a @@ -59,41 +62,40 @@ defmodule RustQ.Meta.Lower.Collection do @spec updated(Macro.t(), (AST.expr() -> [AST.stmt()]), Context.t()) :: AST.expr() def updated(collection, statements, %Context{} = context) do name = :rustq_updated - variable = %AST.Var{name: name} - - %AST.BlockExpr{ - body: - [ - %AST.Let{ - pattern: %AST.PatVar{name: name}, - expr: owned(collection, context), - mutable: true - } - ] ++ - statements.(variable) ++ [%AST.Return{expr: variable}] - } + variable = A.var(name) + + A.block_expr( + [ + %AST.Let{ + pattern: %AST.PatVar{name: name}, + expr: owned(collection, context), + mutable: true + } + ] ++ + statements.(variable) ++ [A.return_stmt(variable)] + ) end @doc "`collection` to take: itself at its last use, otherwise a copy." @spec owned(Macro.t(), Context.t()) :: AST.expr() def owned(collection, %Context{} = context) do - lowered = context.lower.(collection) + lowered = lowered(collection, context) - if context.in_use?.(collection), - do: %AST.MethodCall{receiver: lowered, method: :clone, args: []}, + if in_use?(collection, context), + do: A.method(lowered, :clone), else: lowered end @doc "A key or item passed by reference, borrowed unless it already is one." @spec borrowed(Macro.t(), Type.t(), Context.t()) :: AST.expr() def borrowed(expression, %Type{} = type, %Context{} = context), - do: context.lower_expected.(expression, Type.ref(type)) + do: lowered(expression, Type.ref(type), context) @doc "A value read out of a collection: copied when that costs nothing, otherwise cloned." @spec read_out(AST.expr(), Type.t()) :: AST.expr() def read_out(option, %Type{} = type) do method = if copy?(type), do: :copied, else: :cloned - %AST.MethodCall{receiver: option, method: method, args: []} + A.method(option, method) end @doc """ @@ -112,13 +114,4 @@ defmodule RustQ.Meta.Lower.Collection do end def copy?(_type), do: false - - @doc "A call on `receiver`." - @spec call(AST.expr(), atom(), [AST.expr()]) :: AST.expr() - def call(receiver, method, args \\ []), - do: %AST.MethodCall{receiver: receiver, method: method, args: args} - - @doc "A statement calling `method` on `receiver`." - @spec statement(AST.expr(), atom(), [AST.expr()]) :: AST.stmt() - def statement(receiver, method, args), do: %AST.ExprStmt{expr: call(receiver, method, args)} end diff --git a/lib/rustq/meta/lower/comprehension.ex b/lib/rustq/meta/lower/comprehension.ex index 3512aa4..ee419fe 100644 --- a/lib/rustq/meta/lower/comprehension.ex +++ b/lib/rustq/meta/lower/comprehension.ex @@ -17,6 +17,7 @@ defmodule RustQ.Meta.Lower.Comprehension do alias RustQ.Meta.Type alias RustQ.Meta.Typing alias RustQ.Rust.AST + alias RustQ.Rust.AST.Builder, as: A @any_vec %AST.TypeVec{inner: %AST.TypeRaw{source: "_"}} @iterator_methods StdMethods.iterator() @@ -345,7 +346,7 @@ defmodule RustQ.Meta.Lower.Comprehension do body: [ %AST.IfLet{ pattern: LowerPattern.lower_match_pattern(pattern, nil, context), - expr: %AST.Var{name: :__rustq_item}, + expr: A.var(:__rustq_item), then: lower_for_loop_body( rest, @@ -366,12 +367,10 @@ defmodule RustQ.Meta.Lower.Comprehension do defp lower_for_loop_body([filter | rest], body, %Context{} = context) do [ - %AST.ExprStmt{ - expr: %AST.If{ - condition: Lower.lower_expr(filter, context), - then: lower_for_loop_body(rest, body, context) - } - } + A.stmt(%AST.If{ + condition: Lower.lower_expr(filter, context), + then: lower_for_loop_body(rest, body, context) + }) ] end @@ -384,32 +383,18 @@ defmodule RustQ.Meta.Lower.Comprehension do %Type{} = return_type, %Context{} = context ) do - acc = :__rustq_reduce + acc = A.var(:__rustq_reduce) acc_type = reduce_acc_type(initial, return_type) - %AST.BlockExpr{ - body: [ - %AST.Let{ - pattern: %AST.PatVar{name: acc}, - mutable: true, - expr: lower_reduce_initial(initial, acc_type, context) - }, - %AST.For{ - pattern: LowerPattern.lower_binding_pattern(pattern), - expr: Lower.lower_expr(expression, context), - body: [ - %AST.Assign{ - target: %AST.Var{name: acc}, - expr: %AST.Match{ - expr: %AST.Var{name: acc}, - arms: lower_for_reduce_arms(clauses, acc_type, context) - } - } - ] - }, - %AST.Return{expr: %AST.Var{name: acc}} - ] - } + A.block_expr([ + A.let_mut(acc.name, lower_reduce_initial(initial, acc_type, context)), + A.for_( + LowerPattern.lower_binding_pattern(pattern), + Lower.lower_expr(expression, context), + [A.assign(acc, A.match_expr(acc, lower_for_reduce_arms(clauses, acc_type, context)))] + ), + A.return_stmt(acc) + ]) end def lower_for_reduce_expr(other, _return_type, _context) do @@ -461,7 +446,7 @@ defmodule RustQ.Meta.Lower.Comprehension do [ %AST.Arm{ pattern: %AST.PatVar{name: carry}, - body: [%AST.Return{expr: %AST.Var{name: carry}}] + body: [A.return_stmt(A.var(carry))] } ] end diff --git a/lib/rustq/meta/lower/enum.ex b/lib/rustq/meta/lower/enum.ex index fd5610e..d21a0e1 100644 --- a/lib/rustq/meta/lower/enum.ex +++ b/lib/rustq/meta/lower/enum.ex @@ -4,18 +4,16 @@ defmodule RustQ.Meta.Lower.Enum do searching, sorting, and zipping for `RustQ.Meta.Lower.Stdlib`, and types such calls. """ + use RustQ.Meta.Lower.Stdlib.Lowering + alias RustQ.Diagnostic alias RustQ.Meta.Capture - alias RustQ.Meta.Core.Call alias RustQ.Meta.Desugar alias RustQ.Meta.Lower.Collection - alias RustQ.Meta.Lower.Stdlib - alias RustQ.Meta.Lower.Stdlib.{Context, TypeContext} alias RustQ.Meta.Type - alias RustQ.Rust.AST alias RustQ.Spec - @spec lower(Call.t(), Context.t()) :: {:ok, term()} | :unsupported + @impl true def lower(%Call{args: args} = call, %Context{} = context), do: lower_call(%{call | args: Enum.map(args, &Capture.expand/1)}, context) @@ -23,7 +21,7 @@ defmodule RustQ.Meta.Lower.Enum do do: {:ok, lower_map(collection, mapper, context)} defp lower_call(%Call{function: :sum, args: [collection]}, %Context{} = context), - do: {:ok, collection |> iterate(:question, context) |> method_chain(:sum)} + do: {:ok, collection |> iterate(:question, context) |> A.method(:sum)} defp lower_call( %Call{function: :reduce, args: [collection, initial, reducer]}, @@ -41,8 +39,8 @@ defmodule RustQ.Meta.Lower.Enum do # A stage's items are counted as they come; a list knows its length. defp lower_call(%Call{function: :count, args: [collection]}, %Context{} = context) do case stage(collection) do - {:ok, stage} -> {:ok, stage |> continued(context) |> method_chain(:count) |> cast_i64()} - :error -> {:ok, collection |> lower_collection(context) |> method_chain(:len) |> cast_i64()} + {:ok, stage} -> {:ok, stage |> continued(context) |> A.method(:count) |> cast_i64()} + :error -> {:ok, collection |> lower_collection(context) |> A.method(:len) |> cast_i64()} end end @@ -56,17 +54,13 @@ defmodule RustQ.Meta.Lower.Enum do {:ok, collection |> iterate(:question, context) - |> method_chain(:next) - |> method_chain(:is_none)} + |> A.method(:next) + |> A.method(:is_none)} defp lower_call(%Call{function: :member?, args: [collection, value]}, %Context{} = context), do: {:ok, - %AST.MethodCall{ - receiver: lower_collection(collection, context), - method: :contains, - args: [%AST.Ref{expr: context.lower.(value)}] - }} + A.method(lower_collection(collection, context), :contains, [A.ref(lowered(value, context))])} defp lower_call(%Call{function: :find, args: [collection, predicate]}, %Context{} = context) do iterator = iterate(collection, :keeps_items, context) @@ -82,67 +76,65 @@ defmodule RustQ.Meta.Lower.Enum do {:ok, iterator |> lower_filter(predicate, false, collection, context, :find) - |> method_chain(:unwrap_or, [context.lower.(default)])} + |> A.method(:unwrap_or, [lowered(default, context)])} end defp lower_call(%Call{function: :concat, args: [collections]}, %Context{} = context), do: {:ok, collections - |> context.lower.() - |> method_chain(:into_iter) - |> method_chain(:flatten) - |> method_chain(:collect)} + |> lowered(context) + |> A.method(:into_iter) + |> A.method(:flatten) + |> A.method(:collect)} defp lower_call(%Call{function: :concat, args: [left, right]}, %Context{} = context), do: {:ok, left - |> context.lower.() - |> method_chain(:into_iter) - |> method_chain(:chain, [context.lower.(right)]) - |> method_chain(:collect)} + |> lowered(context) + |> A.method(:into_iter) + |> A.method(:chain, [lowered(right, context)]) + |> A.method(:collect)} defp lower_call(%Call{function: :zip, args: [left, right]}, %Context{} = context), do: {:ok, left - |> context.lower.() - |> method_chain(:into_iter) - |> method_chain(:zip, [context.lower.(right)]) - |> method_chain(:collect)} + |> lowered(context) + |> A.method(:into_iter) + |> A.method(:zip, [lowered(right, context)]) + |> A.method(:collect)} defp lower_call(%Call{function: :unzip, args: [collection]}, %Context{} = context), do: {:ok, collection - |> context.lower.() - |> method_chain(:into_iter) - |> method_chain(:unzip)} + |> lowered(context) + |> A.method(:into_iter) + |> A.method(:unzip)} defp lower_call(%Call{function: :reverse, args: [collection]}, %Context{} = context), do: {:ok, collection |> iterate(:keeps_items, context) - |> method_chain(:rev) - |> method_chain(:collect)} + |> A.method(:rev) + |> A.method(:collect)} defp lower_call(%Call{function: :sort, args: [collection]} = call, %Context{} = context) do - case collection_inner(context.type_of.(collection)) do + case collection_inner(type_of(collection, context)) do %Type{} = inner when inner.kind in [:i8, :i16, :i32, :i64, :isize, :u8, :u16, :u32, :u64, :usize] -> name = :rustq_sorted_values - variable = %AST.Var{name: name} + variable = A.var(name) {:ok, - %AST.BlockExpr{ - body: [ - %AST.Let{pattern: %AST.PatVar{name: name}, expr: context.lower.(collection)}, - %AST.ExprStmt{expr: method_chain(variable, :sort)}, - %AST.Return{expr: variable} - ] - }} + A.block_expr([ + %AST.Let{pattern: %AST.PatVar{name: name}, expr: lowered(collection, context)}, + A.stmt(A.method(variable, :sort)), + A.return_stmt(variable) + ])} _type -> Diagnostic.lower( @@ -159,15 +151,15 @@ defmodule RustQ.Meta.Lower.Enum do %Context{} = context ) when operation in [:take, :drop] do - case Stdlib.nonnegative_count(count, context) do + case nonnegative_count(count, context) do {:ok, lowered_count} -> method = if operation == :drop, do: :skip, else: :take {:ok, collection |> iterate(:keeps_items, context) - |> method_chain(method, [lowered_count]) - |> method_chain(:collect)} + |> A.method(method, [lowered_count]) + |> A.method(:collect)} :unsupported -> Diagnostic.lower( @@ -186,7 +178,7 @@ defmodule RustQ.Meta.Lower.Enum do Collection.updated( collectable, fn collection -> - [Collection.statement(collection, :extend, [Collection.owned(enumerable, context)])] + [A.stmt(A.method(collection, :extend, [Collection.owned(enumerable, context)]))] end, context )} @@ -196,12 +188,12 @@ defmodule RustQ.Meta.Lower.Enum do {:ok, enumerable |> iterate(:keeps_items, context) - |> method_chain(:enumerate) - |> method_chain(:map, [ - %AST.Closure{ - args: [%AST.PatTuple{patterns: [%AST.PatVar{name: :index}, %AST.PatVar{name: :item}]}], - body: %AST.Tuple{values: [%AST.Var{name: :item}, %AST.Var{name: :index}]} - } + |> A.method(:enumerate) + |> A.method(:map, [ + A.closure( + [%AST.PatTuple{patterns: [%AST.PatVar{name: :index}, %AST.PatVar{name: :item}]}], + A.tuple([A.var(:item), A.var(:index)]) + ) ]) |> collect(context)} end @@ -213,7 +205,7 @@ defmodule RustQ.Meta.Lower.Enum do {:ok, enumerable |> iterate_as(iteration, context) - |> method_chain(:position, [mapper(predicate, context)])} + |> A.method(:position, [mapper(predicate, context)])} end # The function gives `some(value)` for the value to find and `nil` to go on. @@ -224,7 +216,7 @@ defmodule RustQ.Meta.Lower.Enum do {:ok, enumerable |> iterate_as(iteration, context) - |> method_chain(:find_map, [mapper(function, context)])} + |> A.method(:find_map, [mapper(function, context)])} end # `{:cont, acc}` goes on and `{:halt, acc}` stops, as `Ok` and `Err` do for `try_fold`. @@ -241,14 +233,14 @@ defmodule RustQ.Meta.Lower.Enum do ) do iteration = iteration(enumerable, :maps_items, context) [item] = typed_items([item], enumerable, iteration, context) - closure = context.lower_closure.([accumulator, item], continuation(body)) + closure = closure([accumulator, item], continuation(body), context) {:ok, enumerable |> iterate_as(iteration, context) - |> method_chain(:try_fold, [context.lower.(initial), closure]) - |> method_chain(:unwrap_or_else, [ - %AST.Closure{args: [:rustq_halted], body: %AST.Var{name: :rustq_halted}} + |> A.method(:try_fold, [lowered(initial, context), closure]) + |> A.method(:unwrap_or_else, [ + A.closure([:rustq_halted], A.var(:rustq_halted)) ])} end @@ -268,7 +260,7 @@ defmodule RustQ.Meta.Lower.Enum do enumerable |> iterate(:keeps_items, context) - |> method_chain(:min_by_key, [key_function(enumerable, key, order, context)]) + |> A.method(:min_by_key, [key_function(enumerable, key, order, context)]) |> fallback.() |> then(&{:ok, &1}) end @@ -279,7 +271,7 @@ defmodule RustQ.Meta.Lower.Enum do with {:ok, fallback} <- empty_fallback(fallback, context) do enumerable |> iterate(:keeps_items, context) - |> method_chain(function) + |> A.method(function) |> fallback.() |> then(&{:ok, &1}) end @@ -289,13 +281,13 @@ defmodule RustQ.Meta.Lower.Enum do {:ok, Collection.updated( enumerable, - fn list -> [Collection.statement(list, :dedup, [])] end, + fn list -> [A.stmt(A.method(list, :dedup, []))] end, context )} end defp lower_call(%Call{function: :split_with, args: [enumerable, predicate]}, context) do - case context.type_of.(enumerable) |> collection_inner() do + case type_of(enumerable, context) |> collection_inner() do %Type{} = inner -> iterator = iterate(enumerable, :keeps_items, context) partitioned = lower_filter(iterator, predicate, false, enumerable, context, :partition) @@ -307,18 +299,18 @@ defmodule RustQ.Meta.Lower.Enum do end defp lower_call(%Call{function: :at, args: [enumerable, index | default]} = call, context) do - case Stdlib.nonnegative_count(index, context) do + case nonnegative_count(index, context) do {:ok, index} -> found = - context.lower.(enumerable) - |> method_chain(:get, [index]) + lowered(enumerable, context) + |> A.method(:get, [index]) |> Collection.read_out( - collection_inner(context.type_of.(enumerable)) || %Type{kind: :type} + collection_inner(type_of(enumerable, context)) || %Type{kind: :type} ) case default do [] -> {:ok, found} - [default] -> {:ok, method_chain(found, :unwrap_or, [context.lower.(default)])} + [default] -> {:ok, A.method(found, :unwrap_or, [lowered(default, context)])} end :unsupported -> @@ -327,13 +319,13 @@ defmodule RustQ.Meta.Lower.Enum do end defp lower_call(%Call{function: :slice, args: [enumerable, start, amount]} = call, context) do - with {:ok, start} <- Stdlib.nonnegative_count(start, context), - {:ok, amount} <- Stdlib.nonnegative_count(amount, context) do + with {:ok, start} <- nonnegative_count(start, context), + {:ok, amount} <- nonnegative_count(amount, context) do {:ok, enumerable |> iterate(:keeps_items, context) - |> method_chain(:skip, [start]) - |> method_chain(:take, [amount]) + |> A.method(:skip, [start]) + |> A.method(:take, [amount]) |> collect(context)} else :unsupported -> unsupported_count!(call) @@ -355,25 +347,25 @@ defmodule RustQ.Meta.Lower.Enum do # `zip` takes anything iterable, so an owned list goes in as it is. rights = case iteration(right, :maps_items, context) do - :owned -> context.lower.(right) + :owned -> lowered(right, context) iteration -> iterate_as(right, iteration, context) end {:ok, lefts - |> method_chain(:zip, [rights]) - |> method_chain(:map, [context.lower_closure.([{x, y}], body)]) + |> A.method(:zip, [rights]) + |> A.method(:map, [closure([{x, y}], body, context)]) |> collect(context)} end defp lower_call(%Call{function: :chunk_every, args: [enumerable, count]} = call, context) do - case Stdlib.nonnegative_count(count, context) do + case nonnegative_count(count, context) do {:ok, count} -> {:ok, - context.lower.(enumerable) - |> method_chain(:chunks, [count]) - |> method_chain(:map, [ - %AST.Closure{args: [:chunk], body: method_chain(%AST.Var{name: :chunk}, :to_vec)} + lowered(enumerable, context) + |> A.method(:chunks, [count]) + |> A.method(:map, [ + A.closure([:chunk], A.method(A.var(:chunk), :to_vec)) ]) |> collect(context)} @@ -383,10 +375,9 @@ defmodule RustQ.Meta.Lower.Enum do end defp lower_call(%Call{function: :join, args: [enumerable | separator]}, context) do - if strings?(context.type_of.(enumerable)), + if strings?(type_of(enumerable, context)), do: - {:ok, - method_chain(context.lower.(enumerable), :join, [join_separator(separator, context)])}, + {:ok, A.method(lowered(enumerable, context), :join, [join_separator(separator, context)])}, else: :unsupported end @@ -403,7 +394,7 @@ defmodule RustQ.Meta.Lower.Enum do strings = enumerable |> iterate_as(iteration, context) - |> method_chain(:map, [mapper(function, context)]) + |> A.method(:map, [mapper(function, context)]) |> then( &%AST.MethodCall{ receiver: &1, @@ -413,20 +404,20 @@ defmodule RustQ.Meta.Lower.Enum do } ) - {:ok, method_chain(strings, :join, [join_separator(separator, context)])} + {:ok, A.method(strings, :join, [join_separator(separator, context)])} end defp lower_call(%Call{}, %Context{}), do: :unsupported - @spec synth(Call.t(), TypeContext.t()) :: {:ok, Type.t()} | :unsupported + @impl true def synth(%Call{function: :sum, args: [collection]}, %TypeContext{} = context), - do: collection |> context.type_of.() |> collection_inner_result() + do: collection |> type_of(context) |> collection_inner_result() def synth( %Call{function: :reduce, args: [_collection, initial, _reducer]}, %TypeContext{} = context ), - do: type_result(context.type_of.(initial)) + do: type_result(type_of(initial, context)) def synth(%Call{function: operation}, %TypeContext{}) when operation in [:any?, :all?], do: {:ok, Spec.type(quote(do: boolean()))} @@ -436,7 +427,7 @@ defmodule RustQ.Meta.Lower.Enum do def synth(%Call{function: operation, args: [collection, _mapper]}, %TypeContext{} = context) when operation in [:filter, :reject] do - case context.type_of.(collection) do + case type_of(collection, context) do %Type{} = type -> case Type.vec_inner(type) || Type.slice_inner(type) do %Type{} = inner -> {:ok, Type.vec(inner)} @@ -449,7 +440,7 @@ defmodule RustQ.Meta.Lower.Enum do end def synth(%Call{function: :map, args: [collection, mapper]}, %TypeContext{} = context) do - with %Type{} = inner <- collection_inner(context.type_of.(collection)), + with %Type{} = inner <- collection_inner(type_of(collection, context)), %Type{} = mapped <- mapper_type(mapper, inner, context) do {:ok, Type.vec(mapped)} else @@ -458,7 +449,7 @@ defmodule RustQ.Meta.Lower.Enum do end def synth(%Call{function: :flat_map, args: [collection, mapper]}, %TypeContext{} = context) do - with %Type{} = inner <- collection_inner(context.type_of.(collection)), + with %Type{} = inner <- collection_inner(type_of(collection, context)), %Type{} = mapped_collection <- mapper_type(mapper, inner, context), %Type{} = mapped <- collection_inner(mapped_collection) do {:ok, Type.vec(mapped)} @@ -478,7 +469,7 @@ defmodule RustQ.Meta.Lower.Enum do )} def synth(%Call{function: :find, args: [collection, _predicate]}, %TypeContext{} = context) do - case collection_inner(context.type_of.(collection)) do + case collection_inner(type_of(collection, context)) do %Type{} = inner -> {:ok, Type.option(inner)} nil -> :unsupported end @@ -488,10 +479,10 @@ defmodule RustQ.Meta.Lower.Enum do %Call{function: :find, args: [_collection, default, _predicate]}, %TypeContext{} = context ), - do: type_result(context.type_of.(default)) + do: type_result(type_of(default, context)) def synth(%Call{function: :concat, args: [collections]}, %TypeContext{} = context) do - with %Type{} = nested <- collection_inner(context.type_of.(collections)), + with %Type{} = nested <- collection_inner(type_of(collections, context)), %Type{} = inner <- collection_inner(nested) do {:ok, Type.vec(inner)} else @@ -500,15 +491,15 @@ defmodule RustQ.Meta.Lower.Enum do end def synth(%Call{function: :concat, args: [left, _right]}, %TypeContext{} = context), - do: collection_vec_result(context.type_of.(left)) + do: collection_vec_result(type_of(left, context)) def synth(%Call{function: operation, args: [collection | _rest]}, %TypeContext{} = context) when operation in [:reverse, :sort, :take, :drop], - do: collection_vec_result(context.type_of.(collection)) + do: collection_vec_result(type_of(collection, context)) def synth(%Call{function: :zip, args: [left, right]}, %TypeContext{} = context) do - with %Type{} = left_inner <- collection_inner(context.type_of.(left)), - %Type{} = right_inner <- collection_inner(context.type_of.(right)) do + with %Type{} = left_inner <- collection_inner(type_of(left, context)), + %Type{} = right_inner <- collection_inner(type_of(right, context)) do {:ok, Type.vec(Type.tuple([left_inner, right_inner]))} else _unknown -> :unsupported @@ -516,7 +507,7 @@ defmodule RustQ.Meta.Lower.Enum do end def synth(%Call{function: :unzip, args: [collection]}, %TypeContext{} = context) do - with %Type{} = pair <- collection_inner(context.type_of.(collection)), + with %Type{} = pair <- collection_inner(type_of(collection, context)), %Type{kind: :tuple, meta: %{elements: [left, right]}} <- pair do {:ok, Type.tuple([Type.vec(left), Type.vec(right)])} else @@ -526,10 +517,10 @@ defmodule RustQ.Meta.Lower.Enum do def synth(%Call{function: function, args: [collection | _]}, %TypeContext{} = context) when function in [:sort_by, :uniq, :uniq_by, :dedup, :slice, :take_while, :drop_while], - do: collection_vec_result(context.type_of.(collection)) + do: collection_vec_result(type_of(collection, context)) def synth(%Call{function: :with_index, args: [collection]}, %TypeContext{} = context) do - case collection_inner(context.type_of.(collection)) do + case collection_inner(type_of(collection, context)) do %Type{} = inner -> {:ok, Type.vec(Type.tuple([inner, usize()]))} nil -> :unsupported end @@ -540,7 +531,7 @@ defmodule RustQ.Meta.Lower.Enum do def synth(%Call{function: :find_value, args: [collection, function]}, %TypeContext{} = context) do # It gives an `Option` even when what it finds has no known type. found = - with %Type{} = inner <- collection_inner(context.type_of.(collection)), + with %Type{} = inner <- collection_inner(type_of(collection, context)), %Type{} = found <- mapper_type(function, inner, context) do found else @@ -551,11 +542,11 @@ defmodule RustQ.Meta.Lower.Enum do end def synth(%Call{function: :reduce_while, args: [_collection, initial, _reducer]}, context), - do: type_result(context.type_of.(initial)) + do: type_result(type_of(initial, context)) def synth(%Call{function: function, args: [collection | rest]}, %TypeContext{} = context) when function in [:max_by, :min_by, :max, :min] do - case {collection_inner(context.type_of.(collection)), List.last(rest)} do + case {collection_inner(type_of(collection, context)), List.last(rest)} do {nil, _fallback} -> :unsupported {inner, {:fn, _, [{:->, _, [[], nil]}]}} -> {:ok, Type.option(inner)} {inner, _default} -> {:ok, inner} @@ -563,13 +554,13 @@ defmodule RustQ.Meta.Lower.Enum do end def synth(%Call{function: :split_with, args: [collection, _predicate]}, context) do - with {:ok, list} <- collection_vec_result(context.type_of.(collection)) do + with {:ok, list} <- collection_vec_result(type_of(collection, context)) do {:ok, Type.tuple([list, list])} end end def synth(%Call{function: :at, args: [collection | rest]}, %TypeContext{} = context) do - case {collection_inner(context.type_of.(collection)), rest} do + case {collection_inner(type_of(collection, context)), rest} do {%Type{} = inner, [_index]} -> {:ok, Type.option(inner)} {%Type{} = inner, [_index, _default]} -> {:ok, inner} _unknown -> :unsupported @@ -577,7 +568,7 @@ defmodule RustQ.Meta.Lower.Enum do end def synth(%Call{function: :chunk_every, args: [collection, _count]}, context) do - with {:ok, list} <- collection_vec_result(context.type_of.(collection)) do + with {:ok, list} <- collection_vec_result(type_of(collection, context)) do {:ok, Type.vec(list)} end end @@ -594,21 +585,21 @@ defmodule RustQ.Meta.Lower.Enum do closure = if mapped_type do - context.lower_typed_closure.(args, body, mapped_type) + closure(args, body, mapped_type, context) else - context.lower_closure.(args, body) + closure(args, body, context) end collection |> iterate_as(iteration, context) - |> method_chain(:map, [closure]) + |> A.method(:map, [closure]) |> collect(context) end defp lower_map(collection, {:&, _, [{:/, _, [capture, 1]}]}, context) do collection |> iterate(:maps_items, context) - |> method_chain(:map, [context.lower_capture.(capture)]) + |> A.method(:map, [capture(capture, context)]) |> collect(context) end @@ -629,11 +620,11 @@ defmodule RustQ.Meta.Lower.Enum do ) do iteration = iteration(collection, :maps_items, context) [item] = typed_items([item], collection, iteration, context) - closure = context.lower_closure.([accumulator, item], body) + closure = closure([accumulator, item], body, context) collection |> iterate_as(iteration, context) - |> method_chain(:fold, [context.lower.(initial), closure]) + |> A.method(:fold, [lowered(initial, context), closure]) end defp lower_reduce(_collection, _initial, other, _context) do @@ -663,16 +654,16 @@ defmodule RustQ.Meta.Lower.Enum do lower_filter(iterator, mapper, true, collection, context, :collect) :flat_map -> - iterator |> method_chain(:flat_map, [mapper(mapper, context)]) |> collect(context) + iterator |> A.method(:flat_map, [mapper(mapper, context)]) |> collect(context) :each -> - method_chain(iterator, :for_each, [mapper(mapper, context)]) + A.method(iterator, :for_each, [mapper(mapper, context)]) :any? -> - method_chain(iterator, :any, [mapper(mapper, context)]) + A.method(iterator, :any, [mapper(mapper, context)]) :all? -> - method_chain(iterator, :all, [mapper(mapper, context)]) + A.method(iterator, :all, [mapper(mapper, context)]) end end @@ -699,24 +690,24 @@ defmodule RustQ.Meta.Lower.Enum do context, terminal ) do - name = context.closure_arg.(argument) - condition = context.lower_closure_body.(body, nil) - condition = if negate?, do: %AST.UnaryOp{op: :not, expr: condition}, else: condition + name = closure_arg(argument) + condition = closure_body(body, nil, context) + condition = if negate?, do: A.not_(condition), else: condition argument = - if Collection.copy?(collection_inner(context.type_of.(collection))), + if Collection.copy?(collection_inner(type_of(collection, context))), do: %AST.PatRef{pattern: %AST.PatVar{name: name}}, else: name - closure = %AST.Closure{args: [argument], body: condition} + closure = A.closure([argument], condition) {method, finish} = Map.fetch!(@filter_adapters, terminal) {iterator, kept} = kept_items(iterator, finish) - adapted = iterator |> method_chain(method, [closure]) |> kept.() + adapted = iterator |> A.method(method, [closure]) |> kept.() case finish do :collect -> collect(adapted, context) - :count -> method_chain(adapted, :count) + :count -> A.method(adapted, :count) _kept -> adapted end end @@ -734,15 +725,15 @@ defmodule RustQ.Meta.Lower.Enum do # not at all when only counted. `partition` keeps every item, so it copies them first. defp kept_items(%AST.MethodCall{method: :cloned, receiver: borrowed}, finish) when finish != :partition, - do: {borrowed, if(finish == :count, do: & &1, else: &method_chain(&1, :cloned))} + do: {borrowed, if(finish == :count, do: & &1, else: &A.method(&1, :cloned))} defp kept_items(iterator, _finish), do: {iterator, & &1} defp mapper({:fn, _, [{:->, _, [args, body]}]}, context), - do: context.lower_closure.(args, body) + do: closure(args, body, context) defp mapper({:&, _, [{:/, _, [capture, 1]}]}, context), - do: context.lower_capture.(capture) + do: capture(capture, context) defp mapper(other, _context) do Diagnostic.lower( @@ -759,7 +750,7 @@ defmodule RustQ.Meta.Lower.Enum do context ) when is_atom(name) and is_atom(ast_context), - do: context.type_with_vars.(body, %{name => inner}) + do: type_with_vars(body, %{name => inner}, context) defp mapper_type(_mapper, _inner, _context), do: nil @@ -784,7 +775,7 @@ defmodule RustQ.Meta.Lower.Enum do # A list is iterated by value, so a closure given its items may treat each as the list's # element type, as a struct update needs; filters see references and keep untyped arguments. defp typed_items([item | rest], collection, iteration, context) do - case context.type_of.(collection) do + case type_of(collection, context) do %Type{kind: :vec} = type -> inner = Type.vec_inner(type) inner = if iteration == :borrowed, do: Type.ref(inner), else: inner @@ -825,9 +816,9 @@ defmodule RustQ.Meta.Lower.Enum do # A stage's iterator, without the list it would collect: `list |> Enum.map(f) |> Enum.find(p)` # is one Rust chain, `.map(f).find(p)`, since no list between them is ever seen. defp continued(stage, context) do - case context.lower.(stage) do + case lowered(stage, context) do %AST.MethodCall{method: :collect, receiver: iterator} -> iterator - collected -> method_chain(collected, :into_iter) + collected -> A.method(collected, :into_iter) end end @@ -867,18 +858,18 @@ defmodule RustQ.Meta.Lower.Enum do case iteration do :owned -> - if borrowed_collection?(context.type_of.(collection)), - do: method_chain(receiver, :iter), - else: method_chain(receiver, :into_iter) + if borrowed_collection?(type_of(collection, context)), + do: A.method(receiver, :iter), + else: A.method(receiver, :into_iter) :borrowed -> - method_chain(receiver, :iter) + A.method(receiver, :iter) :copied -> - receiver |> method_chain(:iter) |> method_chain(:copied) + receiver |> A.method(:iter) |> A.method(:copied) :cloned -> - receiver |> method_chain(:iter) |> method_chain(:cloned) + receiver |> A.method(:iter) |> A.method(:cloned) end end @@ -889,7 +880,7 @@ defmodule RustQ.Meta.Lower.Enum do defp iteration(collection, use, context) do case stage(collection) do {:ok, _stage} -> :owned - :error -> iteration(context.type_of.(collection), collection, use, context) + :error -> iteration(type_of(collection, context), collection, use, context) end end @@ -901,7 +892,7 @@ defmodule RustQ.Meta.Lower.Enum do defp list_iteration(collection, type, use, context) do copy? = Collection.copy?(Type.vec_inner(type)) - in_use? = (use == :question and not copy?) or context.in_use?.(collection) + in_use? = (use == :question and not copy?) or in_use?(collection, context) cond do not in_use? -> :owned @@ -925,9 +916,9 @@ defmodule RustQ.Meta.Lower.Enum do defp borrowed_collection?(_type), do: false defp lower_collection(collection, context) do - case context.type_of.(collection) do - %Type{kind: :vec} = type -> context.lower_expected.(collection, type) - _unknown -> context.lower.(collection) + case type_of(collection, context) do + %Type{kind: :vec} = type -> lowered(collection, type, context) + _unknown -> lowered(collection, context) end end @@ -935,10 +926,10 @@ defmodule RustQ.Meta.Lower.Enum do %AST.MethodCall{receiver: receiver, method: :collect, args: [], generics: [type]} end - defp collect(receiver, %Context{}), do: method_chain(receiver, :collect) + defp collect(receiver, %Context{}), do: A.method(receiver, :collect) defp cast_i64(expression), - do: %AST.Cast{expr: expression, type: %AST.TypePath{parts: [:i64]}} + do: A.cast(expression, A.type(:i64)) defp continuation(body) do Macro.prewalk(body, fn @@ -953,7 +944,7 @@ defmodule RustQ.Meta.Lower.Enum do enumerable, fn list -> [ - Collection.statement(list, :sort_by_key, [key_function(enumerable, key, order, context)]) + A.stmt(A.method(list, :sort_by_key, [key_function(enumerable, key, order, context)])) ] end, context @@ -966,18 +957,18 @@ defmodule RustQ.Meta.Lower.Enum do item = Macro.var(:rustq_item, __MODULE__) arg = - case collection_inner(context.type_of.(enumerable)) do + case collection_inner(type_of(enumerable, context)) do %Type{} = inner -> {:__rustq_typed_arg__, [], [item, Type.ref(inner)]} nil -> item end %AST.Closure{body: body} = - closure = context.lower_closure.([arg], Desugar.apply_function(key, item)) + closure = closure([arg], Desugar.apply_function(key, item), context) if order == :desc, do: %{ closure - | body: %AST.PathCall{path: %AST.Path{parts: [:std, :cmp, :Reverse]}, args: [body]} + | body: A.path_call([:std, :cmp, :Reverse], [body]) }, else: closure end @@ -989,10 +980,9 @@ defmodule RustQ.Meta.Lower.Enum do defp empty_fallback({:fn, _, [{:->, _, [[], default]}]}, context) do if Macro.quoted_literal?(default) or match?({name, _, ctx} when is_atom(name) and is_atom(ctx), default) do - {:ok, &method_chain(&1, :unwrap_or, [context.lower.(default)])} + {:ok, &A.method(&1, :unwrap_or, [lowered(default, context)])} else - {:ok, - &method_chain(&1, :unwrap_or_else, [%AST.Closure{args: [], body: context.lower.(default)}])} + {:ok, &A.method(&1, :unwrap_or_else, [A.closure([], lowered(default, context))])} end end @@ -1013,8 +1003,8 @@ defmodule RustQ.Meta.Lower.Enum do defp string_type, do: RustQ.Spec.type(quote(do: String.t())) defp usize, do: RustQ.Spec.type(quote(do: RustQ.Type.usize())) - defp join_separator([], _context), do: %AST.Literal{value: ""} - defp join_separator([separator], context), do: context.lower.(separator) + defp join_separator([], _context), do: A.lit("") + defp join_separator([separator], context), do: lowered(separator, context) @spec unsupported_count!(Call.t()) :: no_return() defp unsupported_count!(%Call{function: function, source: source}) do @@ -1025,8 +1015,4 @@ defmodule RustQ.Meta.Lower.Enum do suggestion: "Use R.usize() for dynamic non-negative native indexes." ) end - - defp method_chain(receiver, method, args \\ []) do - %AST.MethodCall{receiver: receiver, method: method, args: args} - end end diff --git a/lib/rustq/meta/lower/kernel.ex b/lib/rustq/meta/lower/kernel.ex index 05c39ec..721c141 100644 --- a/lib/rustq/meta/lower/kernel.ex +++ b/lib/rustq/meta/lower/kernel.ex @@ -4,13 +4,10 @@ defmodule RustQ.Meta.Lower.Kernel do `is_nil/1` for `RustQ.Meta.Lower.Stdlib`, and types such calls. """ - import RustQ.Meta.Lower.Stdlib, only: [evaluated_constant: 3, shape_type: 1] + use RustQ.Meta.Lower.Stdlib.Lowering alias RustQ.Diagnostic - alias RustQ.Meta.Core.Call - alias RustQ.Meta.Lower.Stdlib.{Context, TypeContext} alias RustQ.Meta.Type - alias RustQ.Rust.AST alias RustQ.Spec @binary_ops %{ @@ -32,20 +29,18 @@ defmodule RustQ.Meta.Lower.Kernel do @comparison_functions [:==, :!=, :<, :<=, :>, :>=, :and, :or, :not, :is_nil] - @spec lower(Call.t(), Context.t()) :: {:ok, term()} | :unsupported + @impl true + def lower(%Call{function: function} = call, %Context{} = context) when function in [:.., :in], + do: RustQ.Meta.Lower.Range.lower(call, context) + def lower(%Call{function: :/, args: [left, right]} = call, %Context{} = context) do - if integer_type?(context.type_of.(left)) or integer_type?(context.type_of.(right)) do + if integer_type?(type_of(left, context)) or integer_type?(type_of(right, context)) do unsupported_semantics!( call, "Kernel.//2 with integer operands requires an explicit floating-point conversion" ) else - {:ok, - %AST.BinaryOp{ - left: context.lower_binary_operand.(left), - op: :div, - right: context.lower_binary_operand.(right) - }} + {:ok, A.binary(operand(left, context), :div, operand(right, context))} end end @@ -54,43 +49,35 @@ defmodule RustQ.Meta.Lower.Kernel do when function in [:==, :!=] do lower_operand = if wrapper_comparison?(left, right, context), - do: context.lower, - else: context.lower_binary_operand + do: &lowered(&1, context), + else: &operand(&1, context) {:ok, - %AST.BinaryOp{ - left: lower_operand.(left), - op: Map.fetch!(@binary_ops, function), - right: lower_operand.(right) - }} + A.binary(lower_operand.(left), Map.fetch!(@binary_ops, function), lower_operand.(right))} end def lower(%Call{function: function, args: [left, right]}, %Context{} = context) when is_map_key(@binary_ops, function) do {:ok, - %AST.BinaryOp{ - left: context.lower_binary_operand.(left), - op: Map.fetch!(@binary_ops, function), - right: context.lower_binary_operand.(right) - }} + A.binary(operand(left, context), Map.fetch!(@binary_ops, function), operand(right, context))} end def lower(%Call{function: :-, args: [expression]}, %Context{} = context), - do: {:ok, %AST.UnaryOp{op: :neg, expr: context.lower.(expression)}} + do: {:ok, A.neg(lowered(expression, context))} def lower(%Call{function: :+, args: [expression]}, %Context{} = context), - do: {:ok, context.lower.(expression)} + do: {:ok, lowered(expression, context)} def lower(%Call{function: :not, args: [expression]}, %Context{} = context), - do: {:ok, %AST.UnaryOp{op: :not, expr: context.lower.(expression)}} + do: {:ok, A.not_(lowered(expression, context))} def lower(%Call{function: :abs, args: [expression]}, %Context{} = context), - do: {:ok, method(context.lower.(expression), :abs)} + do: {:ok, A.method(lowered(expression, context), :abs)} def lower(%Call{function: function, args: [left, right]} = call, %Context{} = context) when function in [:min, :max] do - if integer_type?(context.type_of.(left)) and integer_type?(context.type_of.(right)) do - {:ok, method(context.lower.(left), function, [context.lower.(right)])} + if integer_type?(type_of(left, context)) and integer_type?(type_of(right, context)) do + {:ok, A.method(lowered(left, context), function, [lowered(right, context)])} else unsupported_semantics!( call, @@ -101,10 +88,10 @@ defmodule RustQ.Meta.Lower.Kernel do def lower(%Call{function: function, args: [value]}, %Context{} = context) when function in [:byte_size, :length], - do: {:ok, cast_i64(method(context.lower.(value), :len))} + do: {:ok, cast_i64(A.method(lowered(value, context), :len))} def lower(%Call{function: :map_size, args: [map]} = call, %Context{} = context) do - case struct_fields(shape_type(context.type_of.(map))) do + case struct_fields(shape_type(type_of(map, context))) do fields when is_list(fields) -> evaluated_constant(map, length(fields), context) @@ -115,30 +102,28 @@ defmodule RustQ.Meta.Lower.Kernel do def lower(%Call{function: :elem, args: [tuple, index]}, %Context{} = context) when is_integer(index) and index >= 0, - do: {:ok, %AST.Field{receiver: context.lower.(tuple), field: index}} + do: {:ok, A.field(lowered(tuple, context), index)} def lower(%Call{function: :put_elem, args: [tuple, index, value]} = call, %Context{} = context) when is_integer(index) and index >= 0 do - case tuple_elements(context.type_of.(tuple)) do + case tuple_elements(type_of(tuple, context)) do elements when index < length(elements) -> tuple_name = :rustq_tuple_value - tuple_var = %AST.Var{name: tuple_name} + tuple_var = A.var(tuple_name) values = elements |> Elixir.Enum.with_index() |> Elixir.Enum.map(fn - {type, ^index} -> context.lower_expected.(value, type) - {_type, field} -> %AST.Field{receiver: tuple_var, field: field} + {type, ^index} -> lowered(value, type, context) + {_type, field} -> A.field(tuple_var, field) end) {:ok, - %AST.BlockExpr{ - body: [ - %AST.Let{pattern: %AST.PatVar{name: tuple_name}, expr: context.lower.(tuple)}, - %AST.Return{expr: %AST.Tuple{values: values}} - ] - }} + A.block_expr([ + %AST.Let{pattern: %AST.PatVar{name: tuple_name}, expr: lowered(tuple, context)}, + A.return_stmt(A.tuple(values)) + ])} _unknown -> unsupported_semantics!( @@ -149,7 +134,7 @@ defmodule RustQ.Meta.Lower.Kernel do end def lower(%Call{function: :tuple_size, args: [tuple]} = call, %Context{} = context) do - case tuple_elements(shape_type(context.type_of.(tuple))) do + case tuple_elements(shape_type(type_of(tuple, context))) do elements when is_list(elements) -> evaluated_constant(tuple, length(elements), context) @@ -159,18 +144,22 @@ defmodule RustQ.Meta.Lower.Kernel do end def lower(%Call{function: :is_nil, args: [value]}, %Context{} = context), - do: {:ok, method(context.lower.(value), :is_none)} + do: {:ok, A.method(lowered(value, context), :is_none)} def lower(%Call{}, %Context{}), do: :unsupported - @spec synth(Call.t(), TypeContext.t()) :: {:ok, Type.t()} | :unsupported + @impl true + def synth(%Call{function: function} = call, %TypeContext{} = context) + when function in [:.., :in], + do: RustQ.Meta.Lower.Range.synth(call, context) + def synth(%Call{function: function}, %TypeContext{}) when function in @comparison_functions, do: {:ok, Spec.type(quote(do: boolean()))} # Arithmetic on an option or result works on the value its operand propagates. def synth(%Call{function: function, args: [left | _rest]}, %TypeContext{} = context) when function in [:+, :-, :*, :/, :div, :rem, :abs, :min, :max], - do: left |> context.type_of.() |> propagated_value() |> type_result() + do: left |> type_of(context) |> propagated_value() |> type_result() def synth(%Call{function: function}, %TypeContext{}) when function in [:byte_size, :length, :map_size, :tuple_size], @@ -178,22 +167,19 @@ defmodule RustQ.Meta.Lower.Kernel do def synth(%Call{function: :elem, args: [tuple, index]}, %TypeContext{} = context) when is_integer(index) and index >= 0 do - case tuple_elements(context.type_of.(tuple)) do + case tuple_elements(type_of(tuple, context)) do elements when index < length(elements) -> type_result(Elixir.Enum.at(elements, index)) _unknown -> :unsupported end end def synth(%Call{function: :put_elem, args: [tuple, _index, _value]}, %TypeContext{} = context), - do: type_result(context.type_of.(tuple)) + do: type_result(type_of(tuple, context)) def synth(%Call{}, %TypeContext{}), do: :unsupported - defp method(receiver, name, args \\ []), - do: %AST.MethodCall{receiver: receiver, method: name, args: args} - defp cast_i64(expression), - do: %AST.Cast{expr: expression, type: %AST.TypePath{parts: [:i64]}} + do: A.cast(expression, A.type(:i64)) defp tuple_elements(%Type{kind: :tuple, meta: %{elements: elements}}), do: elements defp tuple_elements(_type), do: nil @@ -223,8 +209,8 @@ defmodule RustQ.Meta.Lower.Kernel do defp wrapper_comparison?(left, right, %Context{} = context) do wrapper_literal?(left) or wrapper_literal?(right) or - (wrapper_kind(context.type_of.(left)) != nil and - wrapper_kind(context.type_of.(left)) == wrapper_kind(context.type_of.(right))) + (wrapper_kind(type_of(left, context)) != nil and + wrapper_kind(type_of(left, context)) == wrapper_kind(type_of(right, context))) end defp wrapper_literal?({:some, _, [_value]}), do: true diff --git a/lib/rustq/meta/lower/list.ex b/lib/rustq/meta/lower/list.ex index dbb03a4..6d2b6c0 100644 --- a/lib/rustq/meta/lower/list.ex +++ b/lib/rustq/meta/lower/list.ex @@ -4,22 +4,20 @@ defmodule RustQ.Meta.Lower.List do and types such calls. """ - alias RustQ.Meta.Core.Call - alias RustQ.Meta.Lower.Stdlib - alias RustQ.Meta.Lower.Stdlib.{Context, TypeContext} + use RustQ.Meta.Lower.Stdlib.Lowering + alias RustQ.Meta.Type - alias RustQ.Rust.AST - @spec lower(Call.t(), Context.t()) :: {:ok, term()} | :unsupported + @impl true def lower(%Call{function: function, args: [list]}, %Context{} = context) when function in [:first, :last] do terminal = if function == :first, do: :next, else: :last {:ok, list - |> context.lower.() - |> method(:into_iter) - |> method(terminal)} + |> lowered(context) + |> A.method(:into_iter) + |> A.method(terminal)} end def lower(%Call{function: function, args: [list, default]}, %Context{} = context) @@ -28,21 +26,21 @@ defmodule RustQ.Meta.Lower.List do {:ok, list - |> context.lower.() - |> method(:into_iter) - |> method(terminal) - |> method(:unwrap_or, [context.lower.(default)])} + |> lowered(context) + |> A.method(:into_iter) + |> A.method(terminal) + |> A.method(:unwrap_or, [lowered(default, context)])} end def lower(%Call{function: :wrap, args: [nil]}, %Context{}), - do: {:ok, %AST.VecLiteral{values: []}} + do: {:ok, A.vec([])} def lower(%Call{function: :wrap, args: [value]}, %Context{} = context) do - case context.type_of.(value) do + case type_of(value, context) do %Type{} = type -> if collection_inner(type), - do: {:ok, context.lower.(value)}, - else: {:ok, %AST.VecLiteral{values: [context.lower.(value)]}} + do: {:ok, lowered(value, context)}, + else: {:ok, A.vec([lowered(value, context)])} _unknown -> :unsupported @@ -50,15 +48,12 @@ defmodule RustQ.Meta.Lower.List do end def lower(%Call{function: :duplicate, args: [value, count]}, %Context{} = context) do - case Stdlib.nonnegative_count(count, context) do + case nonnegative_count(count, context) do {:ok, lowered_count} -> {:ok, - %AST.PathCall{ - path: %AST.Path{parts: [:std, :iter, :repeat]}, - args: [context.lower.(value)] - } - |> method(:take, [lowered_count]) - |> method(:collect)} + A.path_call([:std, :iter, :repeat], [lowered(value, context)]) + |> A.method(:take, [lowered_count]) + |> A.method(:collect)} :unsupported -> :unsupported @@ -66,12 +61,12 @@ defmodule RustQ.Meta.Lower.List do end def lower(%Call{function: :flatten, args: [list]}, %Context{} = context) do - depth = flatten_depth(context.type_of.(list)) + depth = flatten_depth(type_of(list, context)) if is_integer(depth) and depth > 0 do - iterator = list |> context.lower.() |> method(:into_iter) + iterator = list |> lowered(context) |> A.method(:into_iter) - {:ok, iterator |> flatten_iterator(depth) |> method(:collect)} + {:ok, iterator |> flatten_iterator(depth) |> A.method(:collect)} else :unsupported end @@ -79,10 +74,10 @@ defmodule RustQ.Meta.Lower.List do def lower(%Call{}, %Context{}), do: :unsupported - @spec synth(Call.t(), TypeContext.t()) :: {:ok, Type.t()} | :unsupported + @impl true def synth(%Call{function: function, args: [list]}, %TypeContext{} = context) when function in [:first, :last] do - case collection_inner(context.type_of.(list)) do + case collection_inner(type_of(list, context)) do %Type{} = inner -> {:ok, Type.option(inner)} nil -> :unsupported end @@ -90,10 +85,10 @@ defmodule RustQ.Meta.Lower.List do def synth(%Call{function: function, args: [_list, default]}, %TypeContext{} = context) when function in [:first, :last], - do: type_result(context.type_of.(default)) + do: type_result(type_of(default, context)) def synth(%Call{function: :wrap, args: [value]}, %TypeContext{} = context) do - case context.type_of.(value) do + case type_of(value, context) do %Type{} = type -> {:ok, if(collection_inner(type), do: Type.vec(collection_inner(type)), else: Type.vec(type))} @@ -104,14 +99,14 @@ defmodule RustQ.Meta.Lower.List do end def synth(%Call{function: :duplicate, args: [value, _count]}, %TypeContext{} = context) do - case context.type_of.(value) do + case type_of(value, context) do %Type{} = type -> {:ok, Type.vec(type)} nil -> :unsupported end end def synth(%Call{function: :flatten, args: [list]}, %TypeContext{} = context) do - case innermost_type(context.type_of.(list)) do + case innermost_type(type_of(list, context)) do %Type{} = inner -> {:ok, Type.vec(inner)} nil -> :unsupported end @@ -122,7 +117,7 @@ defmodule RustQ.Meta.Lower.List do defp flatten_iterator(iterator, 1), do: iterator defp flatten_iterator(iterator, depth) do - Elixir.Enum.reduce(1..(depth - 1), iterator, fn _, acc -> method(acc, :flatten) end) + Elixir.Enum.reduce(1..(depth - 1), iterator, fn _, acc -> A.method(acc, :flatten) end) end defp flatten_depth(type), do: do_flatten_depth(type, 0) @@ -150,7 +145,4 @@ defmodule RustQ.Meta.Lower.List do defp type_result(%Type{} = type), do: {:ok, type} defp type_result(_type), do: :unsupported - - defp method(receiver, name, args \\ []), - do: %AST.MethodCall{receiver: receiver, method: name, args: args} end diff --git a/lib/rustq/meta/lower/map.ex b/lib/rustq/meta/lower/map.ex index 50e3200..eb5820e 100644 --- a/lib/rustq/meta/lower/map.ex +++ b/lib/rustq/meta/lower/map.ex @@ -9,15 +9,13 @@ defmodule RustQ.Meta.Lower.Map do `fetch!`, `has_key?`, `put`, and `replace!` read and write its fields. """ + use RustQ.Meta.Lower.Stdlib.Lowering + alias RustQ.Diagnostic - alias RustQ.Meta.Core.Call alias RustQ.Meta.Lower.Collection - alias RustQ.Meta.Lower.Stdlib - alias RustQ.Meta.Lower.Stdlib.{Context, TypeContext} alias RustQ.Meta.Type - alias RustQ.Rust.AST - @spec lower(Call.t(), Context.t()) :: {:ok, term()} | :unsupported + @impl true def lower(%Call{function: :new, args: []}, %Context{} = context), do: lower_new([], context) @@ -30,11 +28,11 @@ defmodule RustQ.Meta.Lower.Map do # `Map.new/1,2` is a comprehension; see `RustQ.Meta.Desugar`. defp lower_new([], _context), - do: {:ok, %AST.PathCall{path: %AST.Path{parts: [:HashMap, :new]}, args: []}} + do: {:ok, A.path_call([:HashMap, :new])} defp lower_keyed(%Call{function: :get, args: [map, key]}, key_type, value_type, context) do lookup = - Collection.call(context.lower.(map), :get, [Collection.borrowed(key, key_type, context)]) + A.method(lowered(map, context), :get, [Collection.borrowed(key, key_type, context)]) {:ok, Collection.read_out(lookup, value_type)} end @@ -46,27 +44,24 @@ defmodule RustQ.Meta.Lower.Map do context ) do lookup = - Collection.call(context.lower.(map), :get, [Collection.borrowed(key, key_type, context)]) + A.method(lowered(map, context), :get, [Collection.borrowed(key, key_type, context)]) {:ok, lookup |> Collection.read_out(value_type) - |> Collection.call(:unwrap_or, [context.lower_expected.(default, value_type)])} + |> A.method(:unwrap_or, [lowered(default, value_type, context)])} end # A missing key raises in Elixir; indexing panics, which Rustler raises. defp lower_keyed(%Call{function: :fetch!, args: [map, key]}, key_type, value_type, context) do - value = %AST.Index{ - receiver: context.lower.(map), - index: Collection.borrowed(key, key_type, context) - } + value = A.index(lowered(map, context), Collection.borrowed(key, key_type, context)) - {:ok, if(Collection.copy?(value_type), do: value, else: Collection.call(value, :clone))} + {:ok, if(Collection.copy?(value_type), do: value, else: A.method(value, :clone))} end defp lower_keyed(%Call{function: :has_key?, args: [map, key]}, key_type, _value_type, context) do {:ok, - Collection.call(context.lower.(map), :contains_key, [ + A.method(lowered(map, context), :contains_key, [ Collection.borrowed(key, key_type, context) ])} end @@ -77,10 +72,12 @@ defmodule RustQ.Meta.Lower.Map do map, fn map -> [ - Collection.statement(map, :insert, [ - context.lower_expected.(key, key_type), - context.lower_expected.(value, value_type) - ]) + A.stmt( + A.method(map, :insert, [ + lowered(key, key_type, context), + lowered(value, value_type, context) + ]) + ) ] end, context @@ -92,7 +89,7 @@ defmodule RustQ.Meta.Lower.Map do Collection.updated( map, fn map -> - [Collection.statement(map, :remove, [Collection.borrowed(key, key_type, context)])] + [A.stmt(A.method(map, :remove, [Collection.borrowed(key, key_type, context)]))] end, context )} @@ -103,7 +100,7 @@ defmodule RustQ.Meta.Lower.Map do Collection.updated( map, fn map -> - [Collection.statement(map, :extend, [Collection.owned(other, context)])] + [A.stmt(A.method(map, :extend, [Collection.owned(other, context)]))] end, context )} @@ -116,8 +113,8 @@ defmodule RustQ.Meta.Lower.Map do value_type, context ) do - key_name = %AST.Var{name: :rustq_key} - value_name = %AST.Var{name: :rustq_value} + key_name = A.var(:rustq_key) + value_name = A.var(:rustq_value) {:ok, Collection.updated( @@ -125,17 +122,17 @@ defmodule RustQ.Meta.Lower.Map do fn map -> updated = map - |> Collection.call(:remove, [%AST.Ref{expr: key_name}]) - |> Collection.call(:map, [update_function(fun, value_type, context)]) - |> Collection.call(:unwrap_or, [context.lower_expected.(default, value_type)]) + |> A.method(:remove, [A.ref(key_name)]) + |> A.method(:map, [update_function(fun, value_type, context)]) + |> A.method(:unwrap_or, [lowered(default, value_type, context)]) [ %AST.Let{ pattern: %AST.PatVar{name: :rustq_key}, - expr: context.lower_expected.(key, key_type) + expr: lowered(key, key_type, context) }, %AST.Let{pattern: %AST.PatVar{name: :rustq_value}, expr: updated}, - Collection.statement(map, :insert, [key_name, value_name]) + A.stmt(A.method(map, :insert, [key_name, value_name])) ] end, context @@ -147,10 +144,10 @@ defmodule RustQ.Meta.Lower.Map do {method, type} = if function == :keys, do: {:keys, key_type}, else: {:values, value_type} {:ok, - context.lower.(map) - |> Collection.call(method) + lowered(map, context) + |> A.method(method) |> Collection.read_out(type) - |> Collection.call(:collect) + |> A.method(:collect) |> collected(Type.vec(type))} end @@ -158,18 +155,18 @@ defmodule RustQ.Meta.Lower.Map do {:ok, map |> Collection.owned(context) - |> Collection.call(:into_iter) - |> Collection.call(:collect) + |> A.method(:into_iter) + |> A.method(:collect) |> collected(Type.vec(Type.tuple([key_type, value_type])))} end defp lower_keyed(%Call{}, _key_type, _value_type, _context), do: :unsupported defp update_function({:fn, _, [{:->, _, [[argument], body]}]}, value_type, context), - do: context.lower_closure.([{:__rustq_typed_arg__, [], [argument, value_type]}], body) + do: closure([{:__rustq_typed_arg__, [], [argument, value_type]}], body, context) defp update_function({:&, _, [{:/, _, [function, 1]}]}, _value_type, context), - do: context.lower_capture.(function) + do: capture(function, context) defp update_function(other, _value_type, _context) do Diagnostic.lower( @@ -189,13 +186,13 @@ defmodule RustQ.Meta.Lower.Map do defp lower_fields(%Call{function: :get, args: [map, key, default]} = call, %Context{} = context) when is_atom(key), - do: lower_get(call, map, key, context.lower.(default), true, context) + do: lower_get(call, map, key, lowered(default, context), true, context) defp lower_fields(%Call{function: :fetch!, args: [map, key]} = call, %Context{} = context) when is_atom(key) do - with %Type{} = map_type <- map_type(context.type_of.(map)), + with %Type{} = map_type <- map_type(type_of(map, context)), %Type{} <- Type.field_type(map_type, key) do - {:ok, %AST.Field{receiver: context.lower.(map), field: key}} + {:ok, A.field(lowered(map, context), key)} else _unknown -> unsupported_field!(call) end @@ -203,11 +200,11 @@ defmodule RustQ.Meta.Lower.Map do defp lower_fields(%Call{function: :has_key?, args: [map, key]} = call, %Context{} = context) when is_atom(key) do - case map_type(Stdlib.shape_type(context.type_of.(map))) do + case map_type(shape_type(type_of(map, context))) do %Type{} = type -> present? = match?(%Type{}, Type.field_type(type, key)) - Stdlib.evaluated_constant(map, present?, context) + evaluated_constant(map, present?, context) nil -> unsupported_field!(call) @@ -219,26 +216,21 @@ defmodule RustQ.Meta.Lower.Map do %Context{} = context ) when function in [:put, :replace!] and is_atom(key) do - with %Type{} = map_type <- map_type(context.type_of.(map)), + with %Type{} = map_type <- map_type(type_of(map, context)), %Type{} = field_type <- Type.field_type(map_type, key) do name = :rustq_map_value - variable = %AST.Var{name: name} + variable = A.var(name) {:ok, - %AST.BlockExpr{ - body: [ - %AST.Let{ - pattern: %AST.PatVar{name: name}, - expr: context.lower.(map), - mutable: true - }, - %AST.Assign{ - target: %AST.Field{receiver: variable, field: key}, - expr: context.lower_expected.(value, field_type) - }, - %AST.Return{expr: variable} - ] - }} + A.block_expr([ + %AST.Let{ + pattern: %AST.PatVar{name: name}, + expr: lowered(map, context), + mutable: true + }, + A.assign(A.field(variable, key), lowered(value, field_type, context)), + A.return_stmt(variable) + ])} else _unknown -> unsupported_field!(call) end @@ -246,11 +238,11 @@ defmodule RustQ.Meta.Lower.Map do defp lower_fields(%Call{}, %Context{}), do: :unsupported - @spec synth(Call.t(), TypeContext.t()) :: {:ok, Type.t()} | :unsupported + @impl true def synth(%Call{function: :new}, %TypeContext{}), do: :unsupported def synth(%Call{args: [map | _]} = call, %TypeContext{} = context) do - case Collection.keyed(context.type_of.(map)) do + case Collection.keyed(type_of(map, context)) do {:map, key, value} -> synth_keyed(call, map, key, value, context) _fields -> synth_fields(call, context) end @@ -270,7 +262,7 @@ defmodule RustQ.Meta.Lower.Map do defp synth_keyed(%Call{function: function}, map, _key_type, _value, context) when function in [:put, :delete, :merge, :update] do - case context.type_of.(map) do + case type_of(map, context) do %Type{kind: kind} = type when kind in [:ref, :mut_ref] -> type_result(Type.ref_inner(type)) type -> type_result(type) end @@ -288,7 +280,7 @@ defmodule RustQ.Meta.Lower.Map do defp synth_fields(%Call{function: function, args: [map, key | _rest]}, %TypeContext{} = context) when function in [:get, :fetch!] and is_atom(key) do - with %Type{} = map_type <- map_type(context.type_of.(map)), + with %Type{} = map_type <- map_type(type_of(map, context)), %Type{} = field_type <- Type.field_type(map_type, key) do {:ok, field_type} else @@ -304,12 +296,12 @@ defmodule RustQ.Meta.Lower.Map do %TypeContext{} = context ) when function in [:put, :replace!], - do: type_result(context.type_of.(map)) + do: type_result(type_of(map, context)) defp synth_fields(%Call{}, %TypeContext{}), do: :unsupported defp lower_get(call, map, key, default, evaluate_default?, context) do - case map_type(context.type_of.(map)) do + case map_type(type_of(map, context)) do %Type{} = type -> lower_typed_get(type, map, key, default, evaluate_default?, context) nil -> unsupported_field!(call) end @@ -326,26 +318,22 @@ defmodule RustQ.Meta.Lower.Map do name = :rustq_map_get_value {:ok, - %AST.BlockExpr{ - body: [ - %AST.Let{pattern: %AST.PatVar{name: name}, expr: context.lower.(map)}, - %AST.Let{pattern: %AST.PatWildcard{}, expr: default}, - %AST.Return{expr: %AST.Field{receiver: %AST.Var{name: name}, field: key}} - ] - }} + A.block_expr([ + %AST.Let{pattern: %AST.PatVar{name: name}, expr: lowered(map, context)}, + %AST.Let{pattern: %AST.PatWildcard{}, expr: default}, + A.return_stmt(A.field(A.var(name), key)) + ])} end defp lower_present_get(map, key, _default, false, context), - do: {:ok, %AST.Field{receiver: context.lower.(map), field: key}} + do: {:ok, A.field(lowered(map, context), key)} defp lower_missing_get(map, default, context) do {:ok, - %AST.BlockExpr{ - body: [ - %AST.Let{pattern: %AST.PatWildcard{}, expr: context.lower.(map)}, - %AST.Return{expr: default} - ] - }} + A.block_expr([ + %AST.Let{pattern: %AST.PatWildcard{}, expr: lowered(map, context)}, + A.return_stmt(default) + ])} end defp map_type(%Type{kind: :alias, meta: %{target: %Type{} = target}}), do: map_type(target) diff --git a/lib/rustq/meta/lower/map_set.ex b/lib/rustq/meta/lower/map_set.ex index aef32f7..b32ac02 100644 --- a/lib/rustq/meta/lower/map_set.ex +++ b/lib/rustq/meta/lower/map_set.ex @@ -6,15 +6,14 @@ defmodule RustQ.Meta.Lower.MapSet do see `RustQ.Meta.Lower.Collection`. """ - alias RustQ.Meta.Core.Call + use RustQ.Meta.Lower.Stdlib.Lowering + alias RustQ.Meta.Lower.Collection - alias RustQ.Meta.Lower.Stdlib.{Context, TypeContext} alias RustQ.Meta.Type - alias RustQ.Rust.AST - @spec lower(Call.t(), Context.t()) :: {:ok, term()} | :unsupported + @impl true def lower(%Call{function: :new, args: []}, %Context{}), - do: {:ok, %AST.PathCall{path: %AST.Path{parts: [:HashSet, :new]}, args: []}} + do: {:ok, A.path_call([:HashSet, :new])} def lower(%Call{args: [set | _]} = call, %Context{} = context) do case Collection.keyed_of(set, context) do @@ -30,7 +29,7 @@ defmodule RustQ.Meta.Lower.MapSet do Collection.updated( set, fn set -> - [Collection.statement(set, :insert, [context.lower_expected.(value, item)])] + [A.stmt(A.method(set, :insert, [lowered(value, item, context)]))] end, context )} @@ -41,15 +40,14 @@ defmodule RustQ.Meta.Lower.MapSet do Collection.updated( set, fn set -> - [Collection.statement(set, :remove, [Collection.borrowed(value, item, context)])] + [A.stmt(A.method(set, :remove, [Collection.borrowed(value, item, context)]))] end, context )} end defp lower_set(%Call{function: :member?, args: [set, value]}, item, context) do - {:ok, - Collection.call(context.lower.(set), :contains, [Collection.borrowed(value, item, context)])} + {:ok, A.method(lowered(set, context), :contains, [Collection.borrowed(value, item, context)])} end defp lower_set(%Call{function: :union, args: [set, other]}, _item, context) do @@ -57,7 +55,7 @@ defmodule RustQ.Meta.Lower.MapSet do Collection.updated( set, fn set -> - [Collection.statement(set, :extend, [Collection.owned(other, context)])] + [A.stmt(A.method(set, :extend, [Collection.owned(other, context)]))] end, context )} @@ -65,19 +63,15 @@ defmodule RustQ.Meta.Lower.MapSet do defp lower_set(%Call{function: function, args: [set, other]}, _item, context) when function in [:difference, :intersection] do - kept = %AST.MethodCall{ - receiver: context.lower.(other), - method: :contains, - args: [%AST.Var{name: :rustq_item}] - } + kept = A.method(lowered(other, context), :contains, [A.var(:rustq_item)]) - kept = if function == :difference, do: %AST.UnaryOp{op: :not, expr: kept}, else: kept + kept = if function == :difference, do: A.not_(kept), else: kept {:ok, Collection.updated( set, fn set -> - [Collection.statement(set, :retain, [%AST.Closure{args: [:rustq_item], body: kept}])] + [A.stmt(A.method(set, :retain, [A.closure([:rustq_item], kept)]))] end, context )} @@ -87,34 +81,30 @@ defmodule RustQ.Meta.Lower.MapSet do when function in [:subset?, :disjoint?] do method = if function == :subset?, do: :is_subset, else: :is_disjoint - {:ok, Collection.call(context.lower.(set), method, [%AST.Ref{expr: context.lower.(other)}])} + {:ok, A.method(lowered(set, context), method, [A.ref(lowered(other, context))])} end defp lower_set(%Call{function: :size, args: [set]}, _item, context) do - {:ok, - %AST.Cast{ - expr: Collection.call(context.lower.(set), :len), - type: %AST.TypePath{parts: [:i64]} - }} + {:ok, A.cast(A.method(lowered(set, context), :len), A.type(:i64))} end defp lower_set(%Call{function: :to_list, args: [set]}, item, context) do collected = set |> Collection.owned(context) - |> Collection.call(:into_iter) - |> Collection.call(:collect) + |> A.method(:into_iter) + |> A.method(:collect) {:ok, %{collected | generics: [Type.vec(item).ast]}} end defp lower_set(%Call{}, _item, _context), do: :unsupported - @spec synth(Call.t(), TypeContext.t()) :: {:ok, Type.t()} | :unsupported + @impl true def synth(%Call{function: :new}, %TypeContext{}), do: :unsupported def synth(%Call{function: function, args: [set | _]}, %TypeContext{} = context) do - case {function, Collection.keyed(context.type_of.(set))} do + case {function, Collection.keyed(type_of(set, context))} do {_function, nil} -> :unsupported @@ -128,7 +118,7 @@ defmodule RustQ.Meta.Lower.MapSet do {:ok, Type.vec(item)} {function, _set} when function in [:put, :delete, :union, :difference, :intersection] -> - owned(context.type_of.(set)) + owned(type_of(set, context)) _other -> :unsupported diff --git a/lib/rustq/meta/lower/mutability.ex b/lib/rustq/meta/lower/mutability.ex index 7b5294d..62488e5 100644 --- a/lib/rustq/meta/lower/mutability.ex +++ b/lib/rustq/meta/lower/mutability.ex @@ -8,6 +8,7 @@ defmodule RustQ.Meta.Lower.Mutability do alias RustQ.Meta.StdMethods alias RustQ.Rust.AST + alias RustQ.Rust.AST.Builder, as: A alias RustQ.Rust.AST.Walk @mutating_methods StdMethods.mutating() @@ -33,7 +34,7 @@ defmodule RustQ.Meta.Lower.Mutability do {[_ | _] = updates, [%AST.Return{expr: %AST.Var{name: :rustq_updated}}]} -> case rename_var(updates, :rustq_updated, name) do [update] -> update - updates -> %AST.ExprStmt{expr: %AST.BlockExpr{body: updates}} + updates -> A.stmt(A.block_expr(updates)) end _other -> @@ -43,7 +44,7 @@ defmodule RustQ.Meta.Lower.Mutability do def updated_in_place(statement), do: statement - defp rename_var(%AST.Var{name: from}, from, to), do: %AST.Var{name: to} + defp rename_var(%AST.Var{name: from}, from, to), do: A.var(to) defp rename_var(%module{} = node, from, to) do fields = diff --git a/lib/rustq/meta/lower/pattern.ex b/lib/rustq/meta/lower/pattern.ex index 46f3d59..3d88b63 100644 --- a/lib/rustq/meta/lower/pattern.ex +++ b/lib/rustq/meta/lower/pattern.ex @@ -18,6 +18,7 @@ defmodule RustQ.Meta.Lower.Pattern do alias RustQ.Meta.Type alias RustQ.Meta.Typing alias RustQ.Rust.AST + alias RustQ.Rust.AST.Builder, as: A alias RustQ.Rust.AST.Walk @doc "Whether `case` clauses match `{:ok, _}`/`{:error, _}` or `{:some, _}`/`nil`." @@ -347,7 +348,7 @@ defmodule RustQ.Meta.Lower.Pattern do do: {:__rustq_path__, [], Paths.mapped_alias_parts(parts, context.rust_modules)} def lower_match_pattern({:__rustq_path__, _, parts}, _case_type), - do: %AST.PatPath{path: %AST.Path{parts: parts}} + do: %AST.PatPath{path: A.path(parts)} def lower_match_pattern(:ok, %Type{kind: kind}) when kind in [:result, :nif_result], do: %AST.PatOk{pattern: %AST.PatTuple{patterns: []}} @@ -420,7 +421,7 @@ defmodule RustQ.Meta.Lower.Pattern do ) when is_list(fields) do %AST.PatStruct{ - path: %AST.Path{parts: [rust_name]}, + path: A.path([rust_name]), fields: lower_struct_pattern_fields(fields, type_fields) } end @@ -431,7 +432,7 @@ defmodule RustQ.Meta.Lower.Pattern do ) when is_list(fields) do %AST.PatStruct{ - path: %AST.Path{parts: [rust_name]}, + path: A.path([rust_name]), fields: lower_struct_pattern_fields(fields, type_fields) } end @@ -443,10 +444,10 @@ defmodule RustQ.Meta.Lower.Pattern do module = List.last(modules) %AST.PatPathTuple{ - path: %AST.Path{parts: [rust_name, module]}, + path: A.path([rust_name, module]), patterns: [ %AST.PatStruct{ - path: %AST.Path{parts: [module]}, + path: A.path([module]), fields: Enum.map(fields, fn {name, pattern} -> {name, lower_match_pattern(pattern, nil)} end) } @@ -474,11 +475,11 @@ defmodule RustQ.Meta.Lower.Pattern do when is_atom(pattern) or is_tuple(pattern) do case Lower.rust_enum_variant(pattern, type) do {variant, [], _types} -> - %AST.PatPath{path: %AST.Path{parts: [rust_name, Lower.rust_variant(variant)]}} + %AST.PatPath{path: A.path([rust_name, Lower.rust_variant(variant)])} {variant, payloads, types} when length(payloads) == length(types) -> %AST.PatPathTuple{ - path: %AST.Path{parts: [rust_name, Lower.rust_variant(variant)]}, + path: A.path([rust_name, Lower.rust_variant(variant)]), patterns: Enum.zip_with(payloads, types, &lower_tuple_element_pattern/2) } @@ -488,7 +489,7 @@ defmodule RustQ.Meta.Lower.Pattern do end def lower_match_pattern(atom, %Type{kind: :enum, rust: rust_name}) when is_atom(atom), - do: %AST.PatPath{path: %AST.Path{parts: [rust_name, Lower.rust_variant(atom)]}} + do: %AST.PatPath{path: A.path([rust_name, Lower.rust_variant(atom)])} def lower_match_pattern(atom, %Type{kind: :atom}) when is_atom(atom), do: %AST.PatAtomGuard{name: atom} diff --git a/lib/rustq/meta/lower/range.ex b/lib/rustq/meta/lower/range.ex index 22e43e7..867f306 100644 --- a/lib/rustq/meta/lower/range.ex +++ b/lib/rustq/meta/lower/range.ex @@ -3,19 +3,18 @@ defmodule RustQ.Meta.Lower.Range do Lowers ranges and `in` over them for `RustQ.Meta.Lower.Stdlib`, and types such calls. """ + use RustQ.Meta.Lower.Stdlib.Lowering + alias RustQ.Diagnostic - alias RustQ.Meta.Core.Call - alias RustQ.Meta.Lower.Stdlib.{Context, TypeContext} - alias RustQ.Rust.AST alias RustQ.Spec - @spec lower(Call.t(), Context.t()) :: {:ok, term()} | :unsupported + @impl true def lower(%Call{function: :.., args: [start, stop]} = call, %Context{} = context) do if ascending_literal_range?(start, stop) do {:ok, %AST.Range{ - start: context.lower.(start), - stop: context.lower.(stop), + start: lowered(start, context), + stop: lowered(stop, context), inclusive: true }} else @@ -30,22 +29,17 @@ defmodule RustQ.Meta.Lower.Range do def lower(%Call{function: :in, args: [value, collection]}, %Context{} = context) do receiver = - case context.lower.(collection) do - %AST.Range{} = range -> %AST.BlockExpr{body: [%AST.Return{expr: range}]} + case lowered(collection, context) do + %AST.Range{} = range -> A.block_expr([A.return_stmt(range)]) expression -> expression end - {:ok, - %AST.MethodCall{ - receiver: receiver, - method: :contains, - args: [%AST.Ref{expr: context.lower.(value)}] - }} + {:ok, A.method(receiver, :contains, [A.ref(lowered(value, context))])} end def lower(%Call{}, %Context{}), do: :unsupported - @spec synth(Call.t(), TypeContext.t()) :: {:ok, term()} | :unsupported + @impl true def synth(%Call{function: :in}, %TypeContext{}), do: {:ok, Spec.type(quote(do: boolean()))} diff --git a/lib/rustq/meta/lower/stdlib.ex b/lib/rustq/meta/lower/stdlib.ex index 75e0f37..ee4aea6 100644 --- a/lib/rustq/meta/lower/stdlib.ex +++ b/lib/rustq/meta/lower/stdlib.ex @@ -1,75 +1,15 @@ defmodule RustQ.Meta.Lower.Stdlib do @moduledoc """ - Lowers calls to Elixir's standard library (`Kernel`, `Enum`, `List`, `Map`, `MapSet`, - `String`, `Tuple`, and `Range`) to Rust that keeps their semantics, dispatching to one lowering module - per Elixir module. Calls whose semantics Rust cannot preserve are rejected. + Lowers calls to Elixir's standard library to Rust that keeps their semantics: a call to an + Elixir module goes to the module lowering it, named after it (`RustQ.Meta.Lower.Enum` for + `Enum`), which implements `RustQ.Meta.Lower.Stdlib.Lowering`. Calls whose semantics Rust + cannot preserve are rejected. """ alias RustQ.Diagnostic alias RustQ.Meta.Core.Call - alias RustQ.Meta.Type - alias RustQ.Rust.AST - - defmodule Context do - @moduledoc """ - The callbacks a standard-library lowering uses to lower its arguments and closures, and - the type the call is expected to produce. - """ - - defstruct [ - :lower, - :lower_expected, - :lower_binary_operand, - :lower_closure, - :lower_typed_closure, - :lower_closure_body, - :lower_capture, - :closure_arg, - :type_of, - :in_use?, - :expected - ] - - @type t :: %__MODULE__{ - lower: (Macro.t() -> term()), - lower_expected: (Macro.t(), term() -> term()), - lower_binary_operand: (Macro.t() -> term()), - lower_closure: ([Macro.t()], Macro.t() -> term()), - lower_typed_closure: ([Macro.t()], Macro.t(), Type.t() | nil -> term()), - lower_closure_body: (Macro.t(), term() -> term()), - lower_capture: (Macro.t() -> term()), - closure_arg: (Macro.t() -> atom()), - type_of: (Macro.t() -> term()), - in_use?: (Macro.t() -> boolean()), - expected: Type.t() | nil - } - end - - @spec shape_type(Type.t() | nil) :: Type.t() | nil - def shape_type(%Type{kind: kind} = type) when kind in [:nif_result, :result], - do: Type.inner(type) - - def shape_type(type), do: type - - @spec evaluated_constant(Macro.t(), term(), Context.t()) :: {:ok, AST.expr()} - def evaluated_constant(argument, value, context) do - evaluated = - case context.type_of.(argument) do - %Type{kind: kind} = type when kind in [:nif_result, :result] -> - context.lower_expected.(argument, Type.inner(type)) - - _type -> - context.lower.(argument) - end - - {:ok, - %AST.BlockExpr{ - body: [ - %AST.Let{pattern: %AST.PatWildcard{}, expr: evaluated}, - %AST.Return{expr: %AST.Literal{value: value}} - ] - }} - end + alias RustQ.Meta.Lower.Stdlib.Lowering + alias RustQ.Meta.Lower.Stdlib.Lowering.{Context, TypeContext} @spec lower(Macro.t(), Context.t()) :: {:ok, term()} | :unsupported def lower(ast, %Context{} = context) do @@ -95,17 +35,6 @@ defmodule RustQ.Meta.Lower.Stdlib do end end - defmodule TypeContext do - @moduledoc "The callbacks a standard-library lowering uses to type its arguments." - - defstruct [:type_of, :type_with_vars] - - @type t :: %__MODULE__{ - type_of: (Macro.t() -> term()), - type_with_vars: (Macro.t(), map() -> term()) - } - end - @spec synth(Macro.t(), TypeContext.t()) :: {:ok, term()} | :unsupported def synth(ast, %TypeContext{} = context) do with {:ok, %Call{} = call} <- Call.normalize(ast) do @@ -113,80 +42,39 @@ defmodule RustQ.Meta.Lower.Stdlib do end end - @spec nonnegative_count(Macro.t(), Context.t()) :: {:ok, AST.expr()} | :unsupported - def nonnegative_count(count, %Context{} = context) when is_integer(count) and count >= 0, - do: {:ok, context.lower.(count)} - - def nonnegative_count(count, %Context{} = context) do - case context.type_of.(count) do - %Type{kind: :usize} -> - {:ok, context.lower.(count)} - - %Type{kind: kind} when kind in [:u8, :u16, :u32, :u64] -> - {:ok, %AST.Cast{expr: context.lower.(count), type: %AST.TypePath{parts: [:usize]}}} - - _type -> - :unsupported + defp lower_call(%Call{} = call, context) do + case lowering(call.module) do + nil -> :unsupported + lowering -> lowering.lower(call, context) end end - defp lower_call(%Call{module: Kernel, function: function} = call, context) - when function in [:.., :in], - do: RustQ.Meta.Lower.Range.lower(call, context) - - defp lower_call(%Call{module: Kernel} = call, context), - do: RustQ.Meta.Lower.Kernel.lower(call, context) - - defp lower_call(%Call{module: Enum} = call, context), - do: RustQ.Meta.Lower.Enum.lower(call, context) - - defp lower_call(%Call{module: List} = call, context), - do: RustQ.Meta.Lower.List.lower(call, context) - - defp lower_call(%Call{module: Map} = call, context), - do: RustQ.Meta.Lower.Map.lower(call, context) - - defp lower_call(%Call{module: MapSet} = call, context), - do: RustQ.Meta.Lower.MapSet.lower(call, context) - - defp lower_call(%Call{module: String} = call, context), - do: RustQ.Meta.Lower.String.lower(call, context) - - defp lower_call(%Call{module: Tuple} = call, context), - do: RustQ.Meta.Lower.Tuple.lower(call, context) - - defp lower_call(%Call{module: Range} = call, context), - do: RustQ.Meta.Lower.Range.lower(call, context) - - defp lower_call(%Call{}, _context), do: :unsupported - - defp synth_call(%Call{module: Kernel, function: function} = call, context) - when function in [:.., :in], - do: RustQ.Meta.Lower.Range.synth(call, context) - - defp synth_call(%Call{module: Kernel} = call, context), - do: RustQ.Meta.Lower.Kernel.synth(call, context) - - defp synth_call(%Call{module: Enum} = call, context), - do: RustQ.Meta.Lower.Enum.synth(call, context) - - defp synth_call(%Call{module: List} = call, context), - do: RustQ.Meta.Lower.List.synth(call, context) - - defp synth_call(%Call{module: Map} = call, context), - do: RustQ.Meta.Lower.Map.synth(call, context) - - defp synth_call(%Call{module: MapSet} = call, context), - do: RustQ.Meta.Lower.MapSet.synth(call, context) - - defp synth_call(%Call{module: String} = call, context), - do: RustQ.Meta.Lower.String.synth(call, context) + defp synth_call(%Call{} = call, context) do + case lowering(call.module) do + nil -> :unsupported + lowering -> lowering.synth(call, context) + end + end - defp synth_call(%Call{module: Tuple} = call, context), - do: RustQ.Meta.Lower.Tuple.synth(call, context) + # The module named after an Elixir module that lowers it, such as `RustQ.Meta.Lower.Enum` + # for `Enum`. Only Elixir's own modules are looked up, so an alias a body names never is. + # Each module's answer is kept, since a body names the same few modules many times. + defp lowering(module) do + case :persistent_term.get({__MODULE__, module}, :unknown) do + :unknown -> + lowering = find_lowering(module) + :persistent_term.put({__MODULE__, module}, lowering) + lowering + + lowering -> + lowering + end + end - defp synth_call(%Call{module: Range} = call, context), - do: RustQ.Meta.Lower.Range.synth(call, context) + defp find_lowering(module) do + lowering = Module.concat(RustQ.Meta.Lower, module) - defp synth_call(%Call{}, _context), do: :unsupported + if Application.get_application(module) == :elixir and Lowering.lowering?(lowering), + do: lowering + end end diff --git a/lib/rustq/meta/lower/stdlib/lowering.ex b/lib/rustq/meta/lower/stdlib/lowering.ex new file mode 100644 index 0000000..4df0cd2 --- /dev/null +++ b/lib/rustq/meta/lower/stdlib/lowering.ex @@ -0,0 +1,168 @@ +defmodule RustQ.Meta.Lower.Stdlib.Lowering do + @moduledoc """ + The behaviour of a module lowering one Elixir standard-library module, and the helpers such + a module lowers and types its arguments with. + + `RustQ.Meta.Lower.Stdlib` dispatches a call to `Enum` to `RustQ.Meta.Lower.Enum`, a call to + `Map` to `RustQ.Meta.Lower.Map`, and so on: the module named after the Elixir module, when + it implements this behaviour. `use` this module to implement it and import the helpers: + + defmodule RustQ.Meta.Lower.List do + use RustQ.Meta.Lower.Stdlib.Lowering + + @impl true + def lower(%Call{function: :first, args: [list]}, %Context{} = context), + do: {:ok, list |> lowered(context) |> A.method(:into_iter) |> A.method(:next)} + end + + A lowering returns `:unsupported` for a call it does not lower, and `RustQ.Meta.Lower.Stdlib` + reports it. + """ + + alias RustQ.Meta.Core.Call + alias RustQ.Meta.Lower + alias RustQ.Meta.Type + alias RustQ.Meta.Typing + alias RustQ.Rust.AST + alias RustQ.Rust.AST.Builder, as: A + + defmodule Context do + @moduledoc """ + What a standard-library lowering knows: the lowering environment of the call, and the + type the call is expected to produce. + """ + + defstruct [:env, :expected] + + @type t :: %__MODULE__{env: Lower.Env.t(), expected: Type.t() | nil} + end + + defmodule TypeContext do + @moduledoc "What a standard-library lowering knows when it types a call." + + defstruct [:env] + + @type t :: %__MODULE__{env: Typing.Env.t()} + end + + @doc "Lowers `call`, or returns `:unsupported` for a call this module does not lower." + @callback lower(Call.t(), Context.t()) :: {:ok, AST.expr()} | :unsupported + + @doc "Types `call`, or returns `:unsupported` for a call this module does not type." + @callback synth(Call.t(), TypeContext.t()) :: {:ok, Type.t()} | :unsupported + + defmacro __using__(_opts) do + quote do + @behaviour unquote(__MODULE__) + + import unquote(__MODULE__), except: [lowering?: 1] + + alias RustQ.Meta.Core.Call + alias unquote(__MODULE__).{Context, TypeContext} + alias RustQ.Rust.AST + alias RustQ.Rust.AST.Builder, as: A + end + end + + @doc "Whether `module` lowers an Elixir standard-library module." + @spec lowering?(module()) :: boolean() + def lowering?(module) do + # `ensure_compiled` waits for a lowering RustQ itself is still compiling. + match?({:module, _}, Code.ensure_compiled(module)) and + __MODULE__ in List.flatten(Keyword.get_values(module.module_info(:attributes), :behaviour)) + end + + @doc "`expression`, lowered." + @spec lowered(Macro.t(), Context.t()) :: AST.expr() + def lowered(expression, %Context{env: env}), do: Lower.lower_expr(expression, env) + + @doc "`expression`, lowered to produce a value of `expected`." + @spec lowered(Macro.t(), Type.t() | nil, Context.t()) :: AST.expr() + def lowered(expression, expected, %Context{env: env}), + do: Lower.lower_expr(expression, expected, env) + + @doc "`expression`, lowered as an operand: a fallible one propagates its error with `?`." + @spec operand(Macro.t(), Context.t()) :: AST.expr() + def operand(expression, %Context{env: env}), do: Lower.lower_binary_operand(expression, env) + + @doc "A closure taking `args` and returning `body`, lowered." + @spec closure([Macro.t()], Macro.t(), Context.t()) :: AST.expr() + def closure(args, body, %Context{env: env}), do: Lower.lower_closure_args(args, body, env) + + @doc "A closure taking `args` and returning `body` as a value of `returns`, lowered." + @spec closure([Macro.t()], Macro.t(), Type.t() | nil, Context.t()) :: AST.expr() + def closure(args, body, returns, %Context{env: env}), + do: Lower.lower_closure_args(args, body, env, returns) + + @doc "A closure's `body`, lowered to return a value of `returns`." + @spec closure_body(Macro.t(), Type.t() | nil, Context.t()) :: AST.expr() + def closure_body(body, returns, %Context{env: env}), + do: Lower.lower_closure_body(body, returns, env) + + @doc "A closure argument, as a Rust closure argument." + @spec closure_arg(Macro.t()) :: atom() | AST.pat() + defdelegate closure_arg(arg), to: Lower, as: :closure_arg! + + @doc "A captured named function, as the Rust path of that function." + @spec capture(Macro.t(), Context.t()) :: AST.expr() + def capture(function, %Context{env: env}), do: Lower.lower_function_capture(function, env) + + @doc "The type of `expression`, or nil when it is not known." + @spec type_of(Macro.t(), Context.t() | TypeContext.t()) :: Type.t() | nil + def type_of(expression, %Context{env: env}), do: Typing.synth(expression, Lower.typing_env(env)) + def type_of(expression, %TypeContext{env: env}), do: Typing.synth(expression, env) + + @doc "The type of `expression` where `vars` are bound to the given types." + @spec type_with_vars(Macro.t(), %{optional(atom()) => Type.t()}, TypeContext.t()) :: + Type.t() | nil + def type_with_vars(expression, vars, %TypeContext{env: env}), + do: Typing.synth(expression, %{env | vars: Map.merge(env.vars, vars)}) + + @doc "Whether `collection` is still in use after this use of it; see `RustQ.Meta.Ownership`." + @spec in_use?(Macro.t(), Context.t()) :: boolean() + def in_use?(collection, %Context{env: env}), do: Lower.in_use?(collection, env) + + @doc "The type a call shapes its value as: a fallible call's success type, and others' own." + @spec shape_type(Type.t() | nil) :: Type.t() | nil + def shape_type(%Type{kind: kind} = type) when kind in [:nif_result, :result], + do: Type.inner(type) + + def shape_type(type), do: type + + @doc "`argument` evaluated for its effects, then the constant `value`." + @spec evaluated_constant(Macro.t(), term(), Context.t()) :: {:ok, AST.expr()} + def evaluated_constant(argument, value, %Context{} = context) do + evaluated = + case type_of(argument, context) do + %Type{kind: kind} = type when kind in [:nif_result, :result] -> + lowered(argument, Type.inner(type), context) + + _type -> + lowered(argument, context) + end + + {:ok, + A.block_expr([ + %AST.Let{pattern: %AST.PatWildcard{}, expr: evaluated}, + A.return_stmt(A.lit(value)) + ])} + end + + @doc "`count` as a `usize`, when it is a literal or unsigned count that cannot be negative." + @spec nonnegative_count(Macro.t(), Context.t()) :: {:ok, AST.expr()} | :unsupported + def nonnegative_count(count, %Context{} = context) when is_integer(count) and count >= 0, + do: {:ok, lowered(count, context)} + + def nonnegative_count(count, %Context{} = context) do + case type_of(count, context) do + %Type{kind: :usize} -> + {:ok, lowered(count, context)} + + %Type{kind: kind} when kind in [:u8, :u16, :u32, :u64] -> + {:ok, A.cast(lowered(count, context), A.type(:usize))} + + _type -> + :unsupported + end + end +end diff --git a/lib/rustq/meta/lower/string.ex b/lib/rustq/meta/lower/string.ex index c294770..6725fb0 100644 --- a/lib/rustq/meta/lower/string.ex +++ b/lib/rustq/meta/lower/string.ex @@ -4,11 +4,9 @@ defmodule RustQ.Meta.Lower.String do `String.replace/3` and `String.trim/1` for `RustQ.Meta.Lower.Stdlib`, and types such calls. """ - alias RustQ.Meta.Core.Call - alias RustQ.Meta.Lower.Stdlib - alias RustQ.Meta.Lower.Stdlib.{Context, TypeContext} + use RustQ.Meta.Lower.Stdlib.Lowering + alias RustQ.Meta.Type - alias RustQ.Rust.AST alias RustQ.Spec @predicate_methods %{ @@ -17,24 +15,22 @@ defmodule RustQ.Meta.Lower.String do contains?: :contains } - @spec lower(Call.t(), Context.t()) :: {:ok, term()} | :unsupported + @impl true def lower(%Call{function: function, args: [string, pattern]}, %Context{} = context) when is_map_key(@predicate_methods, function) do {:ok, - %AST.MethodCall{ - receiver: context.lower.(string), - method: Map.fetch!(@predicate_methods, function), - args: [%AST.Ref{expr: context.lower.(pattern)}] - }} + A.method(lowered(string, context), Map.fetch!(@predicate_methods, function), [ + A.ref(lowered(pattern, context)) + ])} end def lower(%Call{function: :trim, args: [string]}, %Context{} = context), do: {:ok, string - |> context.lower.() - |> method(:trim) - |> method(:to_string)} + |> lowered(context) + |> A.method(:trim) + |> A.method(:to_string)} def lower( %Call{function: :replace, args: [string, pattern, replacement]}, @@ -42,30 +38,30 @@ defmodule RustQ.Meta.Lower.String do ), do: {:ok, - method(context.lower.(string), :replace, [ + A.method(lowered(string, context), :replace, [ string_slice(pattern, context), string_slice(replacement, context) ])} def lower(%Call{function: :duplicate, args: [string, count]} = call, %Context{} = context) do - case Stdlib.nonnegative_count(count, context) do - {:ok, lowered_count} -> {:ok, method(context.lower.(string), :repeat, [lowered_count])} + case nonnegative_count(count, context) do + {:ok, lowered_count} -> {:ok, A.method(lowered(string, context), :repeat, [lowered_count])} :unsupported -> unsupported_count!(call) end end def lower(%Call{function: :valid?, args: [string]}, %Context{} = context) do - case context.type_of.(string) do + case type_of(string, context) do %Type{} = type when type.kind == :binary -> - bytes = %AST.MethodCall{receiver: context.lower.(string), method: :as_slice, args: []} + bytes = A.method(lowered(string, context), :as_slice) {:ok, - %AST.PathCall{path: %AST.Path{parts: [:std, :str, :from_utf8]}, args: [bytes]} + A.path_call([:std, :str, :from_utf8], [bytes]) |> then(&%AST.MethodCall{receiver: &1, method: :is_ok, args: []})} %Type{} = type -> if Type.category(type) == :string, - do: {:ok, %AST.Literal{value: true}}, + do: {:ok, A.lit(true)}, else: :unsupported _unknown -> @@ -75,7 +71,7 @@ defmodule RustQ.Meta.Lower.String do def lower(%Call{}, %Context{}), do: :unsupported - @spec synth(Call.t(), TypeContext.t()) :: {:ok, term()} | :unsupported + @impl true def synth(%Call{function: function}, %TypeContext{}) when function in [:starts_with?, :ends_with?, :contains?, :valid?], do: {:ok, Spec.type(quote(do: boolean()))} @@ -86,11 +82,8 @@ defmodule RustQ.Meta.Lower.String do def synth(%Call{}, %TypeContext{}), do: :unsupported - defp string_slice(value, context) when is_binary(value), do: context.lower.(value) - defp string_slice(value, context), do: method(context.lower.(value), :as_str) - - defp method(receiver, name, args \\ []), - do: %AST.MethodCall{receiver: receiver, method: name, args: args} + defp string_slice(value, context) when is_binary(value), do: lowered(value, context) + defp string_slice(value, context), do: A.method(lowered(value, context), :as_str) @spec unsupported_count!(Call.t()) :: no_return() defp unsupported_count!(%Call{source: source}) do diff --git a/lib/rustq/meta/lower/tuple.ex b/lib/rustq/meta/lower/tuple.ex index 3d6646e..1e34da7 100644 --- a/lib/rustq/meta/lower/tuple.ex +++ b/lib/rustq/meta/lower/tuple.ex @@ -3,33 +3,30 @@ defmodule RustQ.Meta.Lower.Tuple do Lowers `Tuple.to_list/1` for `RustQ.Meta.Lower.Stdlib`, and types such calls. """ + use RustQ.Meta.Lower.Stdlib.Lowering + alias RustQ.Diagnostic - alias RustQ.Meta.Core.Call - alias RustQ.Meta.Lower.Stdlib.{Context, TypeContext} alias RustQ.Meta.Type - alias RustQ.Rust.AST - @spec lower(Call.t(), Context.t()) :: {:ok, term()} | :unsupported + @impl true def lower(%Call{function: :to_list, args: [tuple]} = call, %Context{} = context) do - with elements when is_list(elements) <- tuple_elements(context.type_of.(tuple)), + with elements when is_list(elements) <- tuple_elements(type_of(tuple, context)), true <- homogeneous?(elements) do name = :rustq_tuple_value - variable = %AST.Var{name: name} + variable = A.var(name) values = elements |> Elixir.Enum.with_index() |> Elixir.Enum.map(fn {_type, index} -> - %AST.Field{receiver: variable, field: index} + A.field(variable, index) end) {:ok, - %AST.BlockExpr{ - body: [ - %AST.Let{pattern: %AST.PatVar{name: name}, expr: context.lower.(tuple)}, - %AST.Return{expr: %AST.VecLiteral{values: values}} - ] - }} + A.block_expr([ + %AST.Let{pattern: %AST.PatVar{name: name}, expr: lowered(tuple, context)}, + A.return_stmt(A.vec(values)) + ])} else _unknown -> unsupported_tuple!(call) end @@ -37,9 +34,9 @@ defmodule RustQ.Meta.Lower.Tuple do def lower(%Call{}, %Context{}), do: :unsupported - @spec synth(Call.t(), TypeContext.t()) :: {:ok, Type.t()} | :unsupported + @impl true def synth(%Call{function: :to_list, args: [tuple]}, %TypeContext{} = context) do - with [first | _rest] = elements <- tuple_elements(context.type_of.(tuple)), + with [first | _rest] = elements <- tuple_elements(type_of(tuple, context)), true <- homogeneous?(elements) do {:ok, Type.vec(first)} else diff --git a/lib/rustq/meta/typing.ex b/lib/rustq/meta/typing.ex index 7c39b5c..6615e41 100644 --- a/lib/rustq/meta/typing.ex +++ b/lib/rustq/meta/typing.ex @@ -11,7 +11,7 @@ defmodule RustQ.Meta.Typing do alias RustQ.Meta.Lower.Collection alias RustQ.Meta.Lower.Paths alias RustQ.Meta.Lower.Stdlib - alias RustQ.Meta.Lower.Stdlib.TypeContext, as: StdlibTypeContext + alias RustQ.Meta.Lower.Stdlib.Lowering.TypeContext, as: StdlibTypeContext alias RustQ.Meta.Pattern alias RustQ.Meta.StandardPointer alias RustQ.Meta.StdMethods @@ -344,12 +344,7 @@ defmodule RustQ.Meta.Typing do if Enum.all?(types, &match?(%Type{}, &1)), do: Type.tuple(types) end - defp stdlib_type_context(%Env{} = env) do - %StdlibTypeContext{ - type_of: &synth(&1, env), - type_with_vars: fn ast, vars -> synth(ast, %{env | vars: Map.merge(env.vars, vars)}) end - } - end + defp stdlib_type_context(%Env{} = env), do: %StdlibTypeContext{env: env} defp default_inference_callbacks do %{ diff --git a/lib/rustq/rust/ast/builder.ex b/lib/rustq/rust/ast/builder.ex index 8889c3c..de8f964 100644 --- a/lib/rustq/rust/ast/builder.ex +++ b/lib/rustq/rust/ast/builder.ex @@ -66,7 +66,6 @@ defmodule RustQ.Rust.AST.Builder do MacroItem, MacroItemCall, MacroRepeat, - MacroRepeatExpr, MacroRule, MacroRules, MacroVar, @@ -95,6 +94,8 @@ defmodule RustQ.Rust.AST.Builder do VecLiteral } + @ast_modules AST.__rustq_ast_modules__() + defmacro block(do: body) do quote do Builder.flatten(unquote(block_values(body))) @@ -406,53 +407,8 @@ defmodule RustQ.Rust.AST.Builder do def pat(name) when is_atom(name), do: PatternBuilder.var(name) def wildcard, do: PatternBuilder.wildcard() - def expr(%{__struct__: module} = value) - when module in [ - Use, - AST.Attribute, - AST.Module, - AST.Const, - AST.Static, - AST.TypeAlias, - AST.MacroItem, - AST.MacroItemCall, - AST.Impl, - AST.Var, - AST.Path, - AST.Field, - AST.Index, - AST.Range, - AST.Cast, - AST.UnaryOp, - AST.PathCall, - AST.MethodCall, - AST.LocalCall, - AST.StructLiteral, - AST.Ref, - AST.Try, - AST.Tuple, - AST.VecLiteral, - AST.ArrayLiteral, - MacroRepeatExpr, - AST.Closure, - AST.Literal, - AST.ByteString, - AST.EscapeExpr, - AST.TokenMacro, - AST.MacroCall, - AST.AtomValue, - AST.None, - AST.Some, - AST.Ok, - AST.Err, - AST.NifRaiseAtom, - AST.BlockExpr, - AST.UnsafeBlock, - AST.Match, - AST.If, - AST.BinaryOp - ], - do: value + # Any RustQ AST node is already an expression, statement, or pattern in its own right. + def expr(%{__struct__: module} = value) when module in @ast_modules, do: value def expr(value) when is_boolean(value), do: lit(value) def expr(name) when is_atom(name), do: var(name)