Skip to content

Commit bf24edc

Browse files
committed
Merge pull request #1663 from petermm/elixir-1.18-ci
CI: Add & Improve Elixir 1.18 support These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 49def72 + 6d00d65 commit bf24edc

File tree

8 files changed

+1449
-5
lines changed

8 files changed

+1449
-5
lines changed

.github/workflows/build-and-test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ jobs:
192192
rebar3_version: "3.24.0"
193193

194194
- otp: "27"
195-
elixir_version: "1.17"
195+
elixir_version: "1.18"
196196
rebar3_version: "3.24.0"
197197

198198
# Old versions of OTP/Elixir

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Introduce ports to represent native processes and added support for external ports and encoded ports in external terms
1717
- Added `atomvm:get_creation/0`, equivalent to `erts_internal:get_creation/0`
1818
- Added menuconfig option for enabling USE_USB_SERIAL, eg. serial over USB for certain ESP32-S2 boards etc.
19-
- Partial support for `erlang:fun_info/2`
19+
- Partial support for `erlang:fun_info/2` and `erlang:fun_info/1`
2020
- Added support for `registered_name` in `erlang:process_info/2` and `Process.info/2`
2121
- Added `net:gethostname/0` on platforms with gethostname(3).
2222
- Added `socket:getopt/2`
@@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4646
- Added `init:get_argument/1`, `init:get_plain_arguments/0` and `init:notify_when_started/1`
4747
- Added `application:get_env/2`
4848
- Added CodeQL analysis to esp32, stm32, pico, and wasm workflows
49+
- Added Function.ex and Protocol.ex improving Elixir 1.18 support
4950

5051
### Changed
5152

libs/estdlib/src/erlang.erl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
float_to_binary/2,
7272
float_to_list/1,
7373
float_to_list/2,
74+
fun_info/1,
7475
fun_info/2,
7576
integer_to_binary/1,
7677
integer_to_binary/2,
@@ -830,6 +831,21 @@ float_to_list(_Float) ->
830831
float_to_list(_Float, _Options) ->
831832
erlang:nif_error(undefined).
832833

834+
%%-----------------------------------------------------------------------------
835+
%% @param Fun Function to get information about
836+
%% @returns Requested information about the function as a list of tuples.
837+
%% @doc Returns information about the function `Fun' in unspecified order.
838+
%% @end
839+
%%-----------------------------------------------------------------------------
840+
fun_info(Fun) ->
841+
Items = [module, name, arity, type, env],
842+
lists:map(
843+
fun(Item) ->
844+
erlang:fun_info(Fun, Item)
845+
end,
846+
Items
847+
).
848+
833849
%%-----------------------------------------------------------------------------
834850
%% @param Fun Function to get information about
835851
%% @param Info A list of atoms specifying the information to return.

libs/exavmlib/lib/BadArityError.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ defmodule BadArityError do
3030
fun = exception.function
3131
args = exception.args
3232
insp = Enum.map_join(args, ", ", &inspect/1)
33-
# TODO: enable as soon as :erlang.fun_info and Function.info are implemented
34-
# {:arity, arity} = Function.info(fun, :arity)
35-
# "#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
33+
34+
{:arity, arity} = Function.info(fun, :arity)
35+
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
3636
"#{inspect(fun)} called with #{count(length(args), insp)}"
3737
end
3838

libs/exavmlib/lib/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ set(ELIXIR_MODULES
4040
Enumerable.MapSet
4141
Enumerable.Range
4242
Exception
43+
Function
4344
IO
4445
List
4546
Map
@@ -48,6 +49,7 @@ set(ELIXIR_MODULES
4849
Keyword
4950
Kernel
5051
Process
52+
Protocol
5153
Protocol.UndefinedError
5254
Range
5355
System

libs/exavmlib/lib/Function.ex

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: 2021 The Elixir Team
3+
# SPDX-FileCopyrightText: 2012 Plataformatec
4+
5+
defmodule Function do
6+
@moduledoc """
7+
A set of functions for working with functions.
8+
9+
Anonymous functions are typically created by using `fn`:
10+
11+
iex> add = fn a, b -> a + b end
12+
iex> add.(1, 2)
13+
3
14+
15+
Anonymous functions can also have multiple clauses. All clauses
16+
should expect the same number of arguments:
17+
18+
iex> negate = fn
19+
...> true -> false
20+
...> false -> true
21+
...> end
22+
iex> negate.(false)
23+
true
24+
25+
## The capture operator
26+
27+
It is also possible to capture public module functions and pass them
28+
around as if they were anonymous functions by using the capture
29+
operator `&/1`:
30+
31+
iex> add = &Kernel.+/2
32+
iex> add.(1, 2)
33+
3
34+
35+
iex> length = &String.length/1
36+
iex> length.("hello")
37+
5
38+
39+
To capture a definition within the current module, you can skip the
40+
module prefix, such as `&my_fun/2`. In those cases, the captured
41+
function can be public (`def`) or private (`defp`).
42+
43+
The capture operator can also be used to create anonymous functions
44+
that expect at least one argument:
45+
46+
iex> add = &(&1 + &2)
47+
iex> add.(1, 2)
48+
3
49+
50+
In such cases, using the capture operator is no different than using `fn`.
51+
52+
## Internal and external functions
53+
54+
We say that functions that point to definitions residing in modules, such
55+
as `&String.length/1`, are **external** functions. All other functions are
56+
**local** and they are always bound to the file or module that defined them.
57+
58+
Besides the functions in this module to work with functions, `Kernel` also
59+
has an `apply/2` function that invokes a function with a dynamic number of
60+
arguments, as well as `is_function/1` and `is_function/2`, to check
61+
respectively if a given value is a function or a function of a given arity.
62+
"""
63+
64+
@type information ::
65+
:arity
66+
| :env
67+
| :index
68+
| :module
69+
| :name
70+
| :new_index
71+
| :new_uniq
72+
| :pid
73+
| :type
74+
| :uniq
75+
76+
@doc """
77+
Captures the given function.
78+
79+
Inlined by the compiler.
80+
81+
## Examples
82+
83+
iex> Function.capture(String, :length, 1)
84+
&String.length/1
85+
86+
"""
87+
@doc since: "1.7.0"
88+
@spec capture(module, atom, arity) :: fun
89+
def capture(module, function_name, arity) do
90+
:erlang.make_fun(module, function_name, arity)
91+
end
92+
93+
@doc """
94+
Returns a keyword list with information about a function.
95+
96+
The returned keys (with the corresponding possible values) for
97+
all types of functions (local and external) are the following:
98+
99+
* `:type` - `:local` (for anonymous functions) or `:external` (for
100+
named functions).
101+
102+
* `:module` - an atom which is the module where the function is defined when
103+
anonymous or the module which the function refers to when it's a named function.
104+
105+
* `:arity` - (integer) the number of arguments the function is to be called with.
106+
107+
* `:name` - (atom) the name of the function.
108+
109+
* `:env` - a list of the environment or free variables. For named
110+
functions, the returned list is always empty.
111+
112+
When `fun` is an anonymous function (that is, the type is `:local`), the following
113+
additional keys are returned:
114+
115+
* `:pid` - PID of the process that originally created the function.
116+
117+
* `:index` - (integer) an index into the module function table.
118+
119+
* `:new_index` - (integer) an index into the module function table.
120+
121+
* `:new_uniq` - (binary) a unique value for this function. It's
122+
calculated from the compiled code for the entire module.
123+
124+
* `:uniq` - (integer) a unique value for this function. This integer is
125+
calculated from the compiled code for the entire module.
126+
127+
**Note**: this function must be used only for debugging purposes.
128+
129+
Inlined by the compiler.
130+
131+
## Examples
132+
133+
iex> fun = fn x -> x end
134+
iex> info = Function.info(fun)
135+
iex> Keyword.get(info, :arity)
136+
1
137+
iex> Keyword.get(info, :type)
138+
:local
139+
140+
iex> fun = &String.length/1
141+
iex> info = Function.info(fun)
142+
iex> Keyword.get(info, :type)
143+
:external
144+
iex> Keyword.get(info, :name)
145+
:length
146+
147+
"""
148+
@doc since: "1.7.0"
149+
@spec info(fun) :: [{information, term}]
150+
def info(fun), do: :erlang.fun_info(fun)
151+
152+
@doc """
153+
Returns a specific information about the function.
154+
155+
The returned information is a two-element tuple in the shape of
156+
`{info, value}`.
157+
158+
For any function, the information asked for can be any of the atoms
159+
`:module`, `:name`, `:arity`, `:env`, or `:type`.
160+
161+
For anonymous functions, there is also information about any of the
162+
atoms `:index`, `:new_index`, `:new_uniq`, `:uniq`, and `:pid`.
163+
For a named function, the value of any of these items is always the
164+
atom `:undefined`.
165+
166+
For more information on each of the possible returned values, see
167+
`info/1`.
168+
169+
Inlined by the compiler.
170+
171+
## Examples
172+
173+
iex> f = fn x -> x end
174+
iex> Function.info(f, :arity)
175+
{:arity, 1}
176+
iex> Function.info(f, :type)
177+
{:type, :local}
178+
179+
iex> fun = &String.length/1
180+
iex> Function.info(fun, :name)
181+
{:name, :length}
182+
iex> Function.info(fun, :pid)
183+
{:pid, :undefined}
184+
185+
"""
186+
@doc since: "1.7.0"
187+
@spec info(fun, item) :: {item, term} when item: information
188+
def info(fun, item), do: :erlang.fun_info(fun, item)
189+
190+
@doc """
191+
Returns its input `value`. This function can be passed as an anonymous function
192+
to transformation functions.
193+
194+
## Examples
195+
196+
iex> Function.identity("Hello world!")
197+
"Hello world!"
198+
199+
iex> ~c"abcdaabccc" |> Enum.sort() |> Enum.chunk_by(&Function.identity/1)
200+
[~c"aaa", ~c"bb", ~c"cccc", ~c"d"]
201+
202+
iex> Enum.group_by(~c"abracadabra", &Function.identity/1)
203+
%{97 => ~c"aaaaa", 98 => ~c"bb", 99 => ~c"c", 100 => ~c"d", 114 => ~c"rr"}
204+
205+
iex> Enum.map([1, 2, 3, 4], &Function.identity/1)
206+
[1, 2, 3, 4]
207+
208+
"""
209+
@doc since: "1.10.0"
210+
@spec identity(value) :: value when value: var
211+
def identity(value), do: value
212+
end

0 commit comments

Comments
 (0)