Compare commits

...
533 Commits
Author SHA1 Message Date
José Valim 71c335ac26 Release v1.9.0 2019-06-24 11:20:18 +02:00
Julius Putra Tanu Setiaji 02f5d57871 Fix typespec of Macro.Env.t (#9155) 2019-06-24 07:15:11 +02:00
José Valim 6c16486b4a Clarify the relationship with config/releases.exs, closes #9153 2019-06-21 18:59:33 +02:00
José Valim bfa5d6d23c Do not pass Meta to Erlang AST, closes #9152
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-06-21 16:57:49 +02:00
José Valim c30b6d675b Rename :end metadata to less ambiguous :closing
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-06-16 00:02:26 +02:00
José Valim b59937b80f Add missing @doc since annotation
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-06-14 07:57:47 +02:00
Eksperimental 5b0f17130f Place Version.Requirement module under Basic Types in docs.exs (#9139)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-06-14 07:57:46 +02:00
José Valim 2548965a1e Ensure started/loaded apps do not leak between Mix tests, closes #9137 2019-06-13 13:49:59 +02:00
Fernando Tapia Rico 09c01da205 Fix bad naming on release script for Windows (#9135)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-06-12 19:32:08 +02:00
Andrea Leopardi 9ed78dea24 Improve a comment in env.*.eex for releases (#9134)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-06-12 19:32:06 +02:00
Justin Schneck e5888e7b93 Add RELEASE_BOOT_SCRIPT and RELEASE_BOOT_SCRIPT_CLEAN (#9132)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-06-12 09:35:09 +02:00
Jonatan Männchen 13af842c66 IEx: Sort Types in t helper (#9131) 2019-06-11 23:50:56 +02:00
José Valim a211223810 Keep struct fields ordered in types
Closes https://github.com/elixir-lang/ex_doc/issues/1016
2019-06-11 13:50:07 +02:00
Chris Wögi 8bc3c826b1 Fix redirection to null on windows (#9130)
Concerning generated `bin/release.bat` from `mix release`.
2019-06-11 13:25:54 +02:00
José Valim 7a3d6ec928 Remove timestamps from release, closes #9127 2019-06-11 10:48:30 +02:00
José Valim 9b2e7892ca Do not crash formatter on false positive sigils, closes #9123
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-06-10 11:19:09 +02:00
Tristan Sloughter 04794d5dfd --paths= in rebar3 bare compile fixes subcommand splitting on comma bug (#9120)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-06-08 16:00:21 +02:00
José Valim 36c2787fc6 Immediately shutdown the lexical tracker
Otherwise we may have a race condition if the code
is passing __ENV__ to an eval function which may keep
the lexical tracker if it is alive by the time it is
checked.
2019-06-07 12:02:41 +02:00
Justin Schneck 2bafa0b50b Add preferred_cli_target (#9118) 2019-06-07 08:21:43 +02:00
José Valim c953de0036 Enforce atom keys for config 2019-06-04 15:21:39 +02:00
José Valim aad7aa4d22 Release v1.9.0-rc.0 2019-06-04 13:21:07 +02:00
Tobiasz Małecki ebe23614f7 Remove redundant "a" from Inspect.Opts moduledoc (#9114) 2019-06-04 12:00:44 +02:00
José Valim d8d6ab48c8 Prepare v1.9 for release 2019-06-03 17:36:28 +02:00
José Valim e1b68261b3 Raise on recursive macros (#9111)
Closes #9101.
2019-06-03 17:34:15 +02:00
Fernando Tapia Rico 462dc57156 Clarify syntax for custom Module attributes (#9109) 2019-06-03 12:59:26 +02:00
Wojtek Mach 209b826e51 Mention custom scripts in mix release "One-off commands" docs (#9105) 2019-06-03 07:16:29 -03:00
Fernando Tapia Rico 5b100cc1b3 Handle multiple versions of the same app during releases (#9102) 2019-06-03 11:40:55 +02:00
José Valim 4428e56ba2 Clarify inspect docs and link to printable boolean functions, closes #9108 2019-06-02 18:03:45 +02:00
Leandro Cesquini Pereira 9310da3d2f Fix doc (#9107)
daemon_iex is a command, not a "comment".
2019-06-01 16:36:30 +02:00
Leandro Cesquini Pereira 3ecc0b4ddc Typo: Window server -> Windows service (#9106) 2019-06-01 16:30:53 +02:00
José Valim 7e39eeaa4c Improve error message from wrong test invocation 2019-06-01 11:01:14 +02:00
Chulki Lee 9f03c38d24 Use SPDX full name of Apache License 2.0 (#9104)
https://spdx.org/licenses/Apache-2.0.html

- Full name: Apache License 2.0
- Short identifier: Apache-2.0
2019-05-31 21:58:54 +02:00
José Valim ce41a70b2e Update registered process lists, closes #9103 2019-05-31 19:24:17 +02:00
José Valim 4ab1d5a6f2 Review CHANGELOG and docs for upcoming RC (#9100) 2019-05-31 09:19:40 +02:00
José Valim 78ce6793e3 Store relative file in module definition (#9099)
Prior to this patch, we would always compute the relative
path to the current working directory (CWD), but this meant
consolidated protocols would always get a full path since
they are always outside of their current working directory.
We address this by also storing the relative path in the
definition.

Closes #9095
2019-05-31 09:01:02 +02:00
José Valim 7e1d1650b3 Support unary functions in String.replace/4 (#9073)
We also deprecate the :insert_replaced option since the
anonymous functions is strictly superior.

Closes #9023.
2019-05-30 11:09:27 +02:00
Aleksei Matiushkin bb0e8a8850 Inspect.Opts.custom_options (#9096) 2019-05-30 10:10:48 +02:00
Ryan Bigg 679f978e35 Add 'a' to {invalid_clauses, Name} error (#9090) 2019-05-29 07:42:00 +02:00
Eksperimental d4f8d419e4 Use https instead of http when servers are redirecting (#9094)
The servers are automatically redirecting from http:// to https://
2019-05-29 07:37:54 +02:00
José Valim c4468a95de Rewrite start_supervised already started into duplicate_child_name
Closes #9089.
2019-05-28 23:03:05 +02:00
José Valim 5aa31f4a0b Run 21.3.8 on Travis due to regression on 21.3.0 (#9088) 2019-05-28 17:55:50 +02:00
José Valim 3cdcc78f22 Assert runtime config is also sset during eval 2019-05-28 16:52:35 +02:00
José Valim b8b7e5af01 Start elixir and load config provider app holder 2019-05-28 16:01:54 +02:00
José Valim b65af04517 Load the project config just once 2019-05-28 15:06:31 +02:00
Leandro Cesquini Pereira 0e85ec4d5e Raise an error if the umbrella app's dir name and mix.exs app name doesn't match (#9072)
Closes #9038.
2019-05-28 15:02:28 +02:00
Eksperimental 786a3d2adf Review of README.md (#9086) 2019-05-28 08:10:00 +02:00
Fernando Tapia Rico a6e7b8a4fd Remove duplicated typespec on t:GenServer.server/0 (#9085)
`{:via, module, term}` is covered by the type `name`
2019-05-27 16:14:10 +02:00
Qqwy / Wiebe-Marten cddb455495 Improve documentation of GenServer.server() type. (#9082) 2019-05-27 15:56:34 +02:00
Qqwy / Wiebe-Marten ecda40872f Improves module documentation of DynamicSupervisor (#9080)
Fixes #9075
2019-05-27 11:56:23 +02:00
Eksperimental 7d0b2a2417 Add SECURITY.md (Security Policy) (#9078) 2019-05-27 09:38:15 +02:00
Eksperimental c99eb7a7b4 Fix grammar mistake in Compatibility page (#9077) 2019-05-27 08:17:03 +02:00
José Valim dd17800b86 Do not expand ~ when not followed by a path separator 2019-05-27 00:16:39 +02:00
Eksperimental 064c8ce56d Fix casing in Unicode Syntax (#9074) 2019-05-27 00:02:30 +02:00
José Valim 43a4c850c4 Don't discard Logger messages from other nodes
It is important to leave a trail of what executed
in both systems.
2019-05-24 22:21:08 +02:00
Tobiasz Małecki 34ce5a5c08 Fix typo in Config.Provider moduledoc (#9070) 2019-05-24 19:09:24 +02:00
Andrea Leopardi 291d4ad7cc Add guards to the Stream module (#9069)
Before we hadn't added guards to functions in the Stream module
because the error message for invoking a function with the wrong
number of arguments was better than the FunctionClauseError. Now
that FunctionClauseErrors are better and display the failed
clauses and everything, having guards in Stream functions means
that we can fail fast in many cases instead of waiting to fail
until the stream is realized.
2019-05-24 16:20:38 +02:00
Eksperimental daf47ddbc8 Remove Dialyzer errors in Application module (#9062)
We were getting the "Type specification ... is a supertype of the success typing" error.
2019-05-24 15:30:12 +02:00
Fernando Tapia Rico b325677a97 Support paths with white spaces in Windows releases (#9065) 2019-05-23 09:39:50 +02:00
José Valim bf7ace56c9 Add a note about the scope of the syntax reference
Closes #9067.
2019-05-22 19:28:55 +02:00
Fernando Tapia Rico 2490cc8e6a Add possible Windows errors to File.cd!/1 tests 2019-05-21 18:47:32 +02:00
Fernando Tapia Rico 030e0c7b53 Do not explicitly recompile regexes, fallback to binary matching
Regexes used to be precompiled to binary values during code
compilation. These binary values may be incompatible with
different operating systems and OTP releases. That's why
an explicit `Regex.recompile/1` or `Regex.recompile!/1` was
required.

Regexes now fallback to binary matching if the precompiled
binary is not compatible with the runtime version.
2019-05-21 10:41:19 +02:00
melpon 5cdb1ea744 Fix hiding Mix format errors with stdin (#9059) 2019-05-21 09:35:24 +02:00
Daniil Fedotov 145b7019ae Make regexes fall back to binary matching on incompatible runtime version (#9040)
Regexes are precompiled to binary values during code compilation.
These binary values may be incompatible between different PCRE versions
and OS endianness. This makes code less portable to achieve slight
performance improvement.

PCRE version and endianness are parts of Regex structure and may be
checked in runtime.

This commit adds this check and makes regex execution fall back 
to binary matching if versions are incompatible.

This slightly reduces performance (around 5% for simple regexes) for
compatible versions because there is an additional version read.
Also for incompatible versions it's as fast as binary matching.
2019-05-21 09:34:55 +02:00
Eksperimental e974743475 Fix specs in EEx.Tokenizer (#9061)
Spec for t:token/0 was missing on element in the tuple

this created a series of Dialyzer errors when running `make dialyze`

/elixir/lib/eex/lib/eex/compiler.ex:44: The pattern <[{'expr', _line@1, _mark@1, _chars@1, _} | _rest@1], _buffer@1, _scope@1, _state@1> can never match the type <[{'text',binary() | maybe_improper_list(any(),binary() | [])} | {'end_expr',non_neg_integer(),[any()],binary() | maybe_improper_list(any(),binary() | [])} | {'expr',non_neg_integer(),[any()],binary() | maybe_improper_list(any(),binary() | [])} | {'middle_expr',non_neg_integer(),[any()],binary() | maybe_improper_list(any(),binary() | [])} | {'start_expr',non_neg_integer(),[any()],binary() | maybe_improper_list(any(),binary() | [])}],_,[],#{'engine':=_, 'file':=_, 'line':=non_neg_integer(), 'quoted':=[], 'start_line':='nil'}>
2019-05-21 09:26:33 +02:00
José Valim f778fbfd3b Run CI on Erlang/OTP 22 (#9058) 2019-05-20 13:11:42 +02:00
Eksperimental 1e6d4a8ea7 Fix overlapping domains in Dialyzer (#9046) 2019-05-19 15:20:41 +02:00
kw7oe f44adfbeb0 Improve heredoc error message (#9048) 2019-05-19 10:10:11 +02:00
José Valim 76a7aebc8e Start the VM in interactive mode for rpc/eval/remote (#9056)
This speeds up boot time and decreases memory use.
2019-05-19 10:04:14 +02:00
José Valim 2d1fe10e3d Update CHANGELOG 2019-05-18 22:17:19 +02:00
José Valim c72162ab52 Include apps/APP prefix in test locations when running from umbrella root 2019-05-18 22:04:26 +02:00
Mike Binns 852efbaf60 Resolve full umbrella path name in mix test arg (#9050) 2019-05-18 21:48:00 +02:00
José Valim d67240a39a Fix skipping message assertion 2019-05-18 21:28:33 +02:00
José Valim 08b59f510b Warn when executables are missing, close #9037 2019-05-18 20:53:13 +02:00
Justin Schneck a957faa3c5 Try loading app from otp_root before :code.lib_dir (#9054) 2019-05-18 20:18:19 +02:00
Justin Schneck a697f8fd83 Include erts paths for release script (#9055) 2019-05-18 19:58:55 +02:00
Justin Schneck b9eed39237 Base erts lib dir off erts source path (#9052) 2019-05-18 19:58:19 +02:00
Andrea Leopardi 39c6eb64fe Mention default return in List.keyfind/4 docs
[ci skip]
2019-05-18 19:02:46 +02:00
Aleksei Magusev 7bdffe148f Remove unnecessary is_reference/1 check 2019-05-17 02:06:07 +02:00
José Valim 10fd316439 Delegate Erlang/OTP version check to Elixir 2019-05-15 17:23:26 +02:00
mguimas d63a7a46a7 Support default value when reading inexistent module attribute (#9041)
The private function Module.put_attribute/4 has been renamed to
Module.__put_attribute__/4 for consistency.
2019-05-15 09:28:35 +02:00
Eksperimental c8e700bec1 Improve List.duplicate/2 (#9044) 2019-05-15 09:17:10 +02:00
Eksperimental 341a2d1da8 Improve List.flatten/1,2 (#9045) 2019-05-15 09:16:31 +02:00
Eksperimental 30ab4ad968 Revert #8777 (#9042)
Revert "Remove Integer.to_string/1 and Integer.to_charlist/1 (#8777)"

This reverts commit 2622fd6b0a.
2019-05-15 09:15:55 +02:00
Eksperimental 3038401b09 Improve List.delete/2 (#9043) 2019-05-15 09:14:38 +02:00
José Valim a5b45d6896 Use proper heading for checksums 2019-05-15 00:14:40 +02:00
José Valim 161f5f9a3b Add Erlang/OTP 22 compatible versions 2019-05-14 14:50:44 +02:00
José Valim 921738899b Escape Unicode chars in String, closes #9039 2019-05-11 09:23:48 +02:00
Victor Rodrigues 7e6bac3450 Do not allow defmodule with special atoms (#9032)
Fixes #9030
2019-05-10 20:47:36 +02:00
José Valim f41d758541 Use backslash on Windows (#9035) 2019-05-10 17:42:02 +02:00
José Valim 0229358f88 Ensure third element in location tuple is either nil or an integer 2019-05-09 10:59:13 +02:00
José Valim 81e5a57d51 Nest all terminator metadata under :end key 2019-05-09 10:28:18 +02:00
José Valim dc154f5db7 Allow developers to choose between sname and name in releases (#9024)
We default to --sname which provides safer security defaults.
2019-05-08 11:14:28 +02:00
José Valim 2d3a9f80bd Improve coverage and cleanup archive_test 2019-05-08 11:11:55 +02:00
Mike Binns 0bede3b971 Improved archive.uninstall to find any version of specified file (#9025)
Added logic to search for `archive-name-*` if `archive-name` is not found when running `mix archive.uninstall archive-name`.
For example, `mix archive.uninstall ironman` will find `ironman-0.4.2` archive.
Still checks for exact match first, so only modifies cases where legacy would report "no archives found".
2019-05-08 11:05:03 +02:00
Eksperimental 2bce89045e Add Kernel.in/2 to bug fixes in CHANGELOG.md (#9027) 2019-05-08 09:16:37 +02:00
Samar Dhwoj Acharya a9f27336ea Fix typo on techniques section of release mix task (#9026) 2019-05-08 09:14:15 +02:00
José Valim 01175743b7 Only keep last expression from doctest 2019-05-07 18:12:12 +02:00
Hans 8d2e15c3af Display the actual doctest code when doctset fails (#8903) 2019-05-07 18:00:21 +02:00
Mitchell Henke 5f6113ba90 Translate process crash on node in Logger (#9020) 2019-05-07 10:20:50 +02:00
José Valim 7cd6ce4a32 Also provide nice error message on null bytes 2019-05-06 16:37:54 +02:00
Wojtek Mach 6fae20977e Document Inspect.inspect/2 (#9015) 2019-05-06 16:36:46 +02:00
Andrea Leopardi ad4a11da74 Use parens in IO.puts/1 calls around the codebase (#9018)
There were instances of IO.puts/1 calls without parens throughout the
 whole codebase (in docs, code, tests). I removed most of them where it
made sense to do so, so that users will find documentation and other
things consistent with how the formatter behaves.
2019-05-06 10:30:42 +02:00
Tony Han e38cd472d9 Fix a syntax error in the docs for try/1 (#9017)
[ci skip]
2019-05-06 10:02:30 +02:00
José Valim 6ac1b99e0c Rely on bootstrap only during compilation 2019-05-04 16:43:18 +02:00
Matt Miller d9a64c43c2 Update documentation for less confusing read (#9014) 2019-05-04 12:18:29 +02:00
Wojtek Mach 9a75975769 Add inspect_fun to Inspect.Opts (#8980)
Let's see some use cases. First we configure IEx to use our custom
function:

    iex> IEx.configure(inspect: [inspect_fun: &Pretty.inspect/2])
    :ok

1. Pretty-printing integers

    iex> :math.pow(2, 32) |> trunc()
    4_294_967_296

2. Inspect tuples as records

    iex> {:foo, :bar}
    {:foo, :bar}

    iex> {:person, "Alice", 30}
    #person(name: "Alice", age: 30)

3. Overwrite struct's Inspect implementation

    iex> URI.parse("https://elixir-lang.org")
    #URI<https://elixir-lang.org>

---

Code for the `Pretty.inspect/2` function:

    defmodule Pretty do
      def inspect(integer, opts) when is_integer(integer) do
        PrettyIntegers.inspect(integer, opts)
      end

      def inspect(tuple, opts) when is_tuple(tuple) do
        records = %{
          {:person, 2} => [:name, :age]
        }

        PrettyTuples.inspect(tuple, records, opts)
      end

      def inspect(%URI{} = uri, _opts) do
        Inspect.Algebra.concat(["#URI<", URI.to_string(uri), ">"])
      end

      def inspect(term, opts) do
        Inspect.inspect(term, opts)
      end
    end

    defmodule PrettyIntegers do
      def inspect(term, %Inspect.Opts{base: :decimal}) do
        pretty_decimal(term, "_")
      end

      def inspect(term, %Inspect.Opts{base: base}) do
        Integer.to_string(term, base_to_value(base))
        |> prepend_prefix(base)
      end

      def pretty_decimal(n, separator) when n < 0 do
        "-" <> pretty_decimal(abs(n), separator)
      end

      def pretty_decimal(n, separator) when n >= 1000 do
        left = div(n, 1_000)
        right = rem(n, 1_000)
        right_str = :io_lib.format("~3..0B", [right]) |> IO.iodata_to_binary
        pretty_decimal(left, separator) <> separator <> right_str
      end

      def pretty_decimal(n, _) do
        Integer.to_string(n)
      end

      defp base_to_value(base) do
        case base do
          :binary  -> 2
          :octal   -> 8
          :hex     -> 16
        end
      end

      defp prepend_prefix(value, base) do
        prefix = case base do
          :binary -> "0b"
          :octal  -> "0o"
          :hex    -> "0x"
        end
        prefix <> value
      end
    end

    defmodule PrettyTuples do
      import Inspect.Algebra

      def inspect({}, _records, opts), do: inspect_tuple([], opts)

      def inspect(tuple, records, opts) do
        list = Tuple.to_list(tuple)
        [record_name | values] = list

        case record_fields(records, record_name, tuple) do
          {:ok, fields} ->
            inspect_record(record_name, fields, values, opts)

          _ ->
            inspect_tuple(list, opts)
        end
      end

      defp record_fields(records, record_name, tuple) do
        fields_size = tuple_size(tuple) - 1
        Map.fetch(records, {record_name, fields_size})
      end

      defp inspect_tuple(list, opts) do
        inspect("{", list, "}", &to_doc/2, :flex, opts)
      end

      defp inspect_record(record_name, fields, values, opts) do
        kwlist = Enum.zip(fields, values)
        inspect("##{record_name}(", kwlist, ")", &Inspect.List.keyword/2, :strict, opts)
      end

      defp inspect(open, list, close, fun, break, opts) do
        open = color(open, :tuple, opts)
        sep = color(",", :tuple, opts)
        close = color(close, :tuple, opts)
        container_opts = [separator: sep, break: break]
        container_doc(open, list, close, opts, fun, container_opts)
      end
    end
2019-05-03 17:42:58 +02:00
Matthijs Kuiper 8a7817ecdb Add docs example for Path.dirname/1 when path is a filename (#9013) 2019-05-03 16:49:52 +02:00
Khaja Minhajuddin 5b08744a41 Fix typo (#9011) 2019-05-02 22:53:13 +02:00
José Valim 141915625d Consider @file attribute on undefined locals, closes #9005 2019-05-02 20:20:21 +02:00
José Valim 7f41fa903b Only linify if keep was given 2019-05-02 18:57:44 +02:00
José Valim 91f9321fcb Improve docs and use relative paths 2019-05-02 17:03:02 +02:00
José Valim 59fcce0b92 Unify error handling and compiler deadlock checking (#9009) 2019-05-02 16:27:13 +02:00
José Valim bd54f4381a Set ticktime and autohost on IEx --remsh (#8997) 2019-05-02 09:56:01 +02:00
Billy Ceskavich a7b19052e8 Add defmodule/2 to default locals_without_parens list (#9007) 2019-05-02 09:54:54 +02:00
Wojtek Mach 9931dcf9c3 Allow multiple ex_unit filter excludes (#9003) 2019-05-01 10:48:41 +02:00
José Valim fe999119cd Update kernel.ex 2019-05-01 09:33:02 +02:00
Paulo Daniel Gonzalez 422614ca57 Update docs to make them easier to read in HTML format (#9004)
[ci skip]
2019-04-30 20:47:36 +02:00
José Valim 119b03b472 Update Library Guidelines.md 2019-04-30 18:36:15 +02:00
José Valim 89216bbe06 Do not raise on deadlock on ensure_compiled 2019-04-30 12:52:56 +02:00
Johanna Larsson 1f702b359c Fix some typos in Mix.Tasks.Release (#9002)
[ci skip]
2019-04-30 11:11:26 +02:00
Johanna Larsson 0933ed55b8 Add Registry.select/2 to changelog (#9001) 2019-04-30 10:39:57 +02:00
Tommaso Pavese 4925c210c5 Improve the documentation and example for the Config.Provider module. (#9000) 2019-04-30 10:33:05 +02:00
Andrea Leopardi b3fb4bd7f7 Deprecate Enumerable keys in Map.{take|drop|split} (#8999)
"Keyword.{take|drop|split}" don't offer support for an Enumerable of
keys to take/drop/split based on. However, the "Map" counterparts do.
With this commit, we hard-deprecate support for Enumerables of keys in
"Map.{take|drop|split}". Only lists of keys are allowed.
2019-04-30 09:58:07 +02:00
José Valim 944ddf79f1 More comments on release size 2019-04-30 09:46:38 +02:00
José Valim 1cd8c92d3a Use $RELEASE_NODE as the base for --remsh/--rpc (#8998)
This guarantees that custom hostnames and similar
propagate properly.
2019-04-29 23:52:09 +02:00
Tomáš Janoušek 8d079862dc Fix columns in tokenize of dot_call_op (#8995)
handle_dot already gets the column of `(` from strip_dot_space.
2019-04-29 23:34:08 +02:00
José Valim e425c8aa65 Improve release text 2019-04-29 22:34:30 +02:00
José Valim bd76632553 Add a section on Why Releases? 2019-04-29 22:30:43 +02:00
Samar Dhwoj Acharya e466c357e0 Fix typo OPT -> OTP on elixir application.ex (#8996) 2019-04-29 21:53:06 +02:00
Gary Rennie 8dcc5caf8f Fix typo of configuration in release.ex 2019-04-29 17:49:21 +02:00
Andrea Leopardi a27c8da434 Add missing guards to functions in Keyword (#8992) 2019-04-29 10:16:18 +02:00
Wojtek Mach 9a41024e12 Use proper files in "mix release.init" docs (#8991)
[ci skip]
2019-04-29 10:03:07 +02:00
José Valim 8b23bbca3a Trim whitespace 2019-04-28 12:31:33 +02:00
José Valim 321a671589 Streamline customization and configuration docs 2019-04-28 12:30:03 +02:00
José Valim b8a2bcb276 Use --force in release.init as we use it everywhere (except in mix release) 2019-04-27 23:00:28 +02:00
José Valim f10a6ea618 Clarify root node only expansion in Macro 2019-04-27 10:34:20 +02:00
José Valim 5b2a230bb9 Replace bin/start by release/RELEASE_VSN/env (#8988) 2019-04-27 09:48:03 +02:00
José Valim ccca6b95f4 More release fixes 2019-04-26 19:12:05 +02:00
José Valim 546814bc48 No need for brackets 2019-04-26 18:24:22 +02:00
José Valim 4a6c72b91a Improve to the release docs 2019-04-26 18:23:56 +02:00
Wojtek Mach ae94e3de6e Add impl true to Mix task run callbacks (#8984) 2019-04-26 08:44:32 +02:00
Marco Milanesi 27c1e5cc19 Be more descriptive in trim functions (#8983) 2019-04-26 08:44:17 +02:00
Eric Meadows-Jönsson 0abf5b437a Document time complexity (#8981) 2019-04-25 20:49:57 +02:00
José Valim 07937fcbfd Remove complexity mention in intersperse
The overall complexity rules is detailed in the module docs.
2019-04-25 12:01:19 +02:00
José Valim 37f84c936e Update CHANGELOG 2019-04-24 10:46:09 +02:00
José Valim 1f5ad68302 Use :persistent_term for Logger config (#8977)
Closes #8592.
2019-04-24 09:28:31 +02:00
José Valim 44fae5cc7a Support :force_do_end_blocks in formatter (#8978) 2019-04-24 09:27:59 +02:00
José Valim c50a8308cf Update EEx tokenizer tests 2019-04-24 08:21:40 +02:00
José Valim cf5d080656 Consistently trim on multiple lines, closes #8792 2019-04-24 07:59:32 +02:00
José Valim 8a644181c6 Halt sibling compilation on errors
Closes #8768.
2019-04-24 07:36:21 +02:00
Frank Hunleth 649339aa95 Fix ETS leak in Registry.register
This fixes an issue where a registry's PIDPartition table would gain an
entry each time after the first call to Registry.register on a unique
registry.

We also guarantee unregistered works generally with "tricky keys".

Closes #8611.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-04-23 12:34:51 +02:00
José Valim 56538588eb Do not suggest override: true for from_umbrella conflicts
Closes #8922.
Closes #8924.
2019-04-23 11:54:46 +02:00
José Valim b3ac0608aa Raise on bad EEx state, closes #8790 (#8976) 2019-04-23 11:54:34 +02:00
José Valim e3242cdb7d Allow vm.args to be set with env var (#8974) 2019-04-22 20:58:12 +02:00
José Valim 5f6a1d44a1 Add templating support to releases (#8967) 2019-04-22 17:54:48 +02:00
Johanna Larsson e261b42413 Add Registry.select/2 (#8963) 2019-04-22 17:43:07 +02:00
José Valim 0be3097b22 Use Mix.Config instead of Config on import 2019-04-22 14:16:45 +02:00
José Valim 459319fb75 Improve warning for duplicate behaviours, closes #8965 2019-04-19 12:52:24 +02:00
José Valim 59832404af Add :steps to releases (#8966) 2019-04-19 12:21:00 +02:00
José Valim 3d4f8f9d05 Update release docs 2019-04-19 11:02:50 +02:00
Qqwy / Wiebe-Marten 6d5e49c120 Updates documentation of capture operator with reference to Function.capture/3 (#8964) 2019-04-18 20:33:50 +02:00
José Valim 92c76c7e7a Improve docs and since annotations 2019-04-18 20:32:43 +02:00
José Valim eedef76f40 Update CHANGELOG 2019-04-18 19:26:12 +02:00
Fernando Tapia Rico 075339a342 Optimize &super/arity and &super(&1) (#8962)
The previous implementation was generating an unnecessary
intermediate function when using `&super/arity`.

In addition, capture expressions using `super` with sequential
value placeholders (`&n`) were not optimized like other functions.
For example, the capture expression `&foo(&1, &2, &3)` is
optimized to `&foo/3` because the value placeholders (`&n`) are
sequential `&1, &2, &3`.
2019-04-18 19:22:04 +02:00
José Valim 8faa40ebff Add config providers (#8957) 2019-04-18 16:34:50 +02:00
Nikola Jichev b007282566 Fix GenServer.code_change/3 @spec (#8959)
As we can see at http://erlang.org/doc/man/gen_server.html#Module:code_change-3,
the function returns "{:ok, new_state} | {:error, reason}" while the
old version can either be old version or "{:down, current version}".
2019-04-16 17:18:08 +02:00
Trevor Brown 5cedd202e7 Clarify that elixirc_options in supports all the command line options (#8958)
The documentation listed three options that `elixirc_options` supported, but the
code supports all the available options. I replaced the three options listed in
the docs with a sentence saying all the command line options are supported by
`elixirc_options`.
2019-04-15 16:05:57 +02:00
Arjan Scherpenisse cef19c9365 Implement static_atom_encoder tokenizer option (#8956) 2019-04-14 19:44:17 +02:00
Szymon Madeja 714eeb4922 Clarify assert/2 documentation (#8955)
The documentation for assert/2 suggests that it behaves differently than
assert/1, which is not the case.
2019-04-13 22:23:26 +02:00
Glauber Campinho 382d363254 Check that the first letter of alias is upcase (#8947)
The current approach discards the __aliases__ tuple and
do a simple check that allows some strange behavior like:

```elixir
iex(1)> alias :lists, as: :"Elixir.foobar"
:lists
iex(2)> :"Elixir.foobar"
:"Elixir.foobar"
iex(3)> alias :"Elixir.foobar", as: Baz
:"Elixir.foobar"
iex(3)> Baz
:lists
```
2019-04-13 16:54:04 +02:00
José Valim 3e39053c53 Preserve order given in :applications 2019-04-13 11:12:09 +02:00
Andrea Leopardi 93449f1e79 Improve typespecs for Enum.*reduce functions (#8951) 2019-04-11 18:54:39 +02:00
José Valim 391e4f6c56 Improve invalid interpolation error message 2019-04-11 12:53:09 +02:00
José Valim b68ef52b94 No longer keep a field for tracking type in __ENV__ vars 2019-04-11 00:42:48 +02:00
Wojtek Mach 3cf1c830dc Fix a typo in the docs for "mix release" (#8950)
[ci skip]
2019-04-10 16:28:41 +02:00
Niels Bom 551204f2fa Define what a "switch" is (#8944) 2019-04-09 09:20:21 +02:00
José Valim 0602b1f64f Emphasize not converting IO data to binary 2019-04-09 09:19:56 +02:00
Eksperimental 333ebbe13b Fix bug in Kernel.in/2 when right is [] (#8946)
Bug was introduced in 08829b3709
2019-04-09 08:37:29 +02:00
Andrea Leopardi 666a0bca8d Add a section about iodata to the IO docs (#8943)
[ci skip]
2019-04-08 17:03:10 +02:00
José Valim f3206c03bc Do not block the main ParallelCompiler process 2019-04-08 14:55:59 +02:00
eksperimental 08829b3709 Improvents to Kernel.in/2
1) Keep order of elements in Kernel.in/2:

The order of elements was not being kept (not doing as it was said in the docs).
Now it is guaranteed that the elements are compared in the same order they were given.

previously:

    iex> quote do 1 in [1, 2, 3] end |> Macro.expand(Map.put(__ENV__, :context, :guard)) |> Macro.to_string()
    ":erlang.orelse(:erlang.\"=:=\"(1, 2), :erlang.orelse(:erlang.\"=:=\"(1, 3), :erlang.\"=:=\"(1, 1)))"

now:

    iex>  quote do 1 in [1, 2, 3] end |> Macro.expand(Map.put(__ENV__, :context, :guard)) |> Macro.to_string()
    ":erlang.orelse(:erlang.orelse(:erlang.\"=:=\"(1, 1), :erlang.\"=:=\"(1, 2)), :erlang.\"=:=\"(1, 3))"

2) Error message improved from: invalid args for operator "in"...
to: invalid right argument for operator "in"...

3) More tests added to check for generated code

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-04-07 11:36:38 +02:00
José Valim cc57b6c64b Further improve Enum docs 2019-04-07 10:56:58 +02:00
Andrea Leopardi 352ca946cd Simplify wording in docs of some Enum functions
[ci skip]
2019-04-06 23:27:46 +02:00
Kevin Peter 92f71b5825 Mention support for negative start_index in Enum.slice/3 (#8941)
[ci skip]
2019-04-06 23:22:53 +02:00
José Valim bcac90e3a6 Ensure the first equal entry is returned by min/max
Closes #8936
Closes #8935
2019-04-05 08:35:47 +02:00
José Valim d10d4ed86e Do not depend on basedir 2019-04-04 20:07:32 +02:00
José Valim 6d58085d77 Refactor and document XDG support 2019-04-04 19:09:51 +02:00
Yiming Chen 4e229a2004 Fix doc example for Protocol.derive/3 (#8934)
- `Sample` was not a defined protocol in this example
- running this example in an IEx session would cause `ArgumentError`
2019-04-04 18:56:40 +02:00
Jonatan Männchen afaf8892ef Follow XDG base dir specification in Mix (#8933) 2019-04-04 18:54:50 +02:00
José Valim e436fa7c08 No longer generate config for mix new (#8932)
The fact we explicitly told developers to configure their
application in the config file and access it with
`Application.get_env/2`, probably led many to rely on
the application environment when better options are
available.

Furthermore, `mix new` is mostly used for libraries,
where config is even less important, as configuration
from libraries is not transitive.

Developers that need the config, can simply add it back
by calling:

    echo "use Mix.Config" > config/config.exs

This commit removes config for new apps and deprecate
the `--config` flag, which cannot perform deep merges.
Note we do keep the config for umbrellas, as umbrellas
are about applications and not libraries (plus we need
the umbrella children to share and point to an existing
config).

Closes #8815.
Closes #8811.
2019-04-04 14:11:11 +02:00
José Valim 579c235a6f Allow to dry run the Elixir CLI command 2019-04-02 23:19:24 +02:00
Charlotte 34d7494da0 Fix dialyzer warnings related to Version.Requirement's opaque type (#8929)
Co-Authored-By: umamaistempo <11341349+umamaistempo@users.noreply.github.com>
2019-04-02 18:26:13 +02:00
Wojtek Mach eca7e27ad5 Fix a typo in the docs for the Code module (#8926)
[ci skip]
2019-04-01 14:17:11 +02:00
José Valim e809326d81 Allow Code.ensure_compiled/2 to not raise on deadlocks
This is important when you may want to check if a module
is available but if that module is running a similar check
on you, you don't want the compiler to fail.
2019-04-01 13:04:00 +02:00
José Valim 51d1c4de4d Pass env compiler options to v3_core 2019-04-01 09:48:32 +02:00
José Valim 0a9fa19b50 Move protocol consolidation tests to its own file 2019-03-31 11:14:49 +02:00
José Valim 4286138123 Move DebugInfo test into its own sync case 2019-03-31 11:10:52 +02:00
José Valim 22bcddfa1b Ensure debug_info is kept in protocols, closes #8923 2019-03-31 10:57:55 +02:00
Milton Mazzarri 326d02fdc1 Clarify Keyword.pop/3 docs on duplicated keys (#8919)
The description from `Keyword.pop/3` mentioned that it will return
**all** the values associated with `key` in the keyword list, but this
wasn't happening, at least when you have duplicate keys in the keyword
list `Keyword.pop/3` was returning just the first match, even one of
examples showed that behavior.
2019-03-31 10:06:20 +02:00
Fernando Tapia Rico ae6666f0d6 Ensure the struct name matches the given module
This change fixes inconsistencies when building structs: if the
struct was built using the map syntax the `:__struct__` key
was overridden by the given module name; however, if the struct
was built using `struct/1,2` the `:__struct__` key was kept.

For example:

    defmodule X do
      defstruct [:x]
    end

    defmodule Y do
      defdelegate __struct__, to: X
      defdelegate __struct__(args), to: X
    end

    iex> %Y{}
    %Y{x: nil}

    iex> struct(Y)
    %X{x: nil}

Closes #8800
2019-03-31 09:52:58 +02:00
Fernando Tapia Rico 930ecc789b Make Version.Requirement public (#8920)
The `Version.Requirement` struct is mentioned in several places
of the `Version` documentation, used as an argument of
`Version.compile_requirement/1` and `Version.match?/3`, and
returned as a result of `Version.parse_requirement/1` and
`Version.parse_requirement!/1`.
2019-03-30 20:34:06 +01:00
yunsong 3a595ffddf fix Code.Typespec.typespec_to_quoted conversion for variable (#8918) 2019-03-30 08:53:19 +01:00
Eksperimental 3f661b0166 Improve specs for List.first/1 and List.last/1 when list is empty (#8915) 2019-03-28 09:31:04 +01:00
Eksperimental 5a7e6c0778 List.to_string/1 improve error message and docs (#8913) 2019-03-28 09:30:40 +01:00
Eksperimental e4997797f3 Simplify Kernel.in/2 (#8911)
It uses the &expand/1 function, instead of relying on Macro.expand/2
2019-03-27 21:44:43 +01:00
José Valim 9a8944f470 URI works on bytes 2019-03-27 02:20:53 +01:00
José Valim 93fb75d793 Optimize Unicode graphemes
We only check for Pictographic once we find a ZWJ.
2019-03-27 01:46:34 +01:00
José Valim 3673849e36 Use System.no_halt/1 instead of Process.sleep/1 in mix run
Otherwise :init.get_status will stay in
{:starting, :started} forever.
2019-03-27 00:17:21 +01:00
Fernando Tapia Rico 966d2213a9 Fix grammar in Module 2019-03-26 17:51:27 +01:00
José Valim a6da9e59b7 Document defines? does not include overridable, closes #8896 2019-03-26 13:26:06 +01:00
Edison Yap 8d28d77301 Raise when retrieving capitalized module attribute (#8908) 2019-03-24 15:55:27 +01:00
Fernando Tapia Rico 861ba87db4 Ensure guard checks when expanding string interpolations (#8905)
During the bitstring expansion, guard checks were avoided due to
a special case for inlining binaries during interpolation. These
changes ensure guards are checked.
2019-03-22 11:26:02 +01:00
José Valim 7aaf02a28d Update CHANGELOG 2019-03-22 01:00:07 +01:00
Eric Meadows-Jönsson cee233cff6 Improve error message if dependency name is misspelled (#8904) 2019-03-21 10:18:53 +01:00
Tobias Pfeiffer fa64e8c78c Add struct examples to put_in/2, get_in/2 and get_and_update_in/2 (#8899) 2019-03-18 10:19:58 +01:00
Jake Becker fb8b8c9c8b Ensure Erlang-based Mix compilers (erlang, leex, yecc) set valid position on diagnostics (#8900)
The Mix.Task.Compiler.Diagnostic spec allows position to be nil, but the yecc compiler was sometimes setting it to :none which violates the spec.
2019-03-18 00:26:04 +01:00
Tobias Pfeiffer 88944ca89e Add OTP 21.3 to the build matrix (#8898) 2019-03-18 00:24:56 +01:00
Guillermo Iguaran ecf36adfc2 Add support for a default value in System.get_env/1 (#3798) 2019-03-17 11:02:58 +01:00
Fernando Tapia Rico 94c2ffa3a4 Warn if underscored type variable is used more than once (#8894)
The meaning of underscored variables in type specifications 
seems to be the same as regular underscored variables: values 
that are not meant to be used. This behavior can be seen in the
`singleton_typevar` error, in which underscored type variables
are ignored.
2019-03-15 09:37:10 +01:00
Eksperimental eb228896f7 Mention list of reserved and unreserved chars in docs (#8881) 2019-03-15 09:33:58 +01:00
José Valim a3c5820d12 Properly track files instead of modules in mix integration with lexical tracker 2019-03-15 00:06:41 +01:00
José Valim 914e32a741 Pass lexical_tracker on each_file callback 2019-03-14 23:22:34 +01:00
José Valim fb01dcffd5 Only spawn processes if a file has not yet been required 2019-03-14 22:32:13 +01:00
José Valim 018df4fbfd Simplify acquiring lock on Code.require_file 2019-03-14 21:55:46 +01:00
José Valim 88338bb99b Take nil lexical_tracker into account 2019-03-14 21:22:59 +01:00
José Valim 2fa6577eee Run the formatter 2019-03-14 20:53:58 +01:00
José Valim 874d022b2b Simplify source handling in compilers/elixir 2019-03-14 20:42:02 +01:00
José Valim b80ee75e2d Pass lexical tracker forward if available, closes #8893 2019-03-14 20:42:02 +01:00
slepher 12dbdbec86 Handle underscore variables (_, _foo) in typespecs (#8891)
Type variables starting with an underscore (_foo) should not raise compile errors.

Underscore variables (_) cannot be used as type variables.

Fixes #8888
2019-03-14 20:22:35 +01:00
José Valim d48b16cf54 Quote :: to avoid ambiguity (#8890) 2019-03-14 17:08:04 +01:00
José Valim ee007da29e Clarify touch behaviour in some OSes, closes #8875 2019-03-14 10:28:22 +01:00
Eksperimental f401c954e0 Force System.build_info()[:revision] to be 7 char long (#8879)
Use "git rev-parse --short=7 HEAD" in System.build_info/0 since Git is not always consistent with the length of their short hashes (for example, repos with large histories will have longer hashes) and users can override the size of short hashes. This change attempts to make it more consistent.
2019-03-12 16:55:35 -07:00
Eksperimental 352673d78b Improve System.build_info/0 spec (#8883)
Be specific about map fields.
2019-03-12 23:26:07 +01:00
eksperimental a304aac97b Fix alias surrounded by backticks not being linked
The command to find the offending lines is:

$ ag '<code [^<>]*class=\"inline\">([A-Z][A-Za-z0-9_]*\.?)+(?<!\.)</code>(?!</a>)' doc/
2019-03-12 23:25:33 +01:00
eksperimental 4cb3c123f3 Fix function/arity surrounded by backticks not being linked
The command to find the offending lines is:

$ ag '<code [^<>]*class=\"inline\">[a-z_][A-Za-z0-9_]*[?!]?/\d+</code>(?!</a>)' doc/
2019-03-12 23:25:33 +01:00
Eksperimental 3c001a569d Improve System.build_info/0 documentation (#8884) 2019-03-12 23:24:55 +01:00
Eksperimental 84f9128825 Replace use of "item(s)" with "element(s)" in Enum module (#8885) 2019-03-12 23:24:22 +01:00
Eksperimental 180bf41c25 Add an example that actually decodes to URI.query_decoder/2 (#8880) 2019-03-12 22:10:19 +01:00
José Valim da3c426478 Add a note that Erlang has to be installed before compiling from source
Closes #8877
2019-03-12 13:15:40 +01:00
Fernando Tapia Rico 0903ad17bc Ensure inspect returns valid ~r// expressions (#8872)
Inspecting regular expressions that contain backslashes (\)
followed by slashes (/) result in invalid ~r// expressions:

    iex(1)> Regex.compile("a\\/b")
    {:ok, ~r/a\\/b/}
    iex(2)> Regex.match?(~r/a\\/b/, "a/b")
    ** (SyntaxError) iex:2: syntax error before: ','
2019-03-11 13:57:43 +01:00
Caleb McQuillin 4043bd8de6 Fix small typo for ceil behavior docs (#8874) 2019-03-11 09:22:33 +01:00
Fernando Tapia Rico a7019ac90a Use inspect/1 with values returned by __struct__/* (#8873)
__struct__/0,1 is not meant to return an AST. Using
Macro.to_string/1 might lead to incorrect error messages:

    defmodule MyStruct do
      def __struct__, do: {:ok, :one, :two}
      def __struct__(_), do: {:ok, :one, :two}
    end

    iex> %MyStruct{}
    ** (CompileError) iex:2: expected MyStruct.__struct__/1 to
       return a map with a :__struct__ key that holds the name
       of the struct (atom), got: ok
2019-03-08 23:24:45 +01:00
Fernando Tapia Rico 7cac527c9e Add guard to Macro.to_string/2 (#8870) 2019-03-08 22:51:11 +01:00
Fernando Tapia Rico 9cfd989f4b Use :elixir_aliases.inspect/1 with module aliases (#8869)
The `Module` variable in these cases is guaranteed to be a module
alias.
2019-03-08 22:49:55 +01:00
Christophe De Troyer d269a7bfcd Update documentation of Logger (#8866)
Add some information about the precedence of the logger levels.
2019-03-08 22:45:37 +01:00
Tom Nicklin c8e054a7f2 More examples for Kernel.function_exported?/3 (#8865) 2019-03-07 18:06:11 +01:00
Fernando Tapia Rico b88cd49d8d Validate __struct__ key in map returned by __struct__/0,1 (#8864) 2019-03-07 12:12:30 +01:00
José Valim e90ebaecff Improve error on unknown backends 2019-03-05 14:16:33 +01:00
Eksperimental 35a7750827 Improve documentation in test task cmd line options (#8858) 2019-03-04 13:59:00 +01:00
Niels Bom cd80616924 Tiny docs change falsy value (#8859) 2019-03-04 13:25:30 +01:00
Eksperimental 4c3967339c Use in instead of on (#8854) 2019-03-04 10:29:28 +01:00
Eksperimental 4f645c3289 Improve clarification on string() type replacements in Typespecs page (#8857) 2019-03-04 02:25:30 -06:00
Eksperimental af47fb24a8 Avoid hyphen at EOL (#8853)
Avoid splitting words that contain a hyphen into two lines,
since a space is added when docs are rendered in IEx and ExDoc.

Command used to detect this bug:

$ ag -- "\w-\n"
2019-03-04 02:22:12 -06:00
José Valim 243cbb5453 Update Syntax Reference.md 2019-03-04 09:20:53 +01:00
Eksperimental f036f6592c Improvements in the documentation (#8856) 2019-03-04 02:18:50 -06:00
Eksperimental 23b2a09079 Improve error message on invalid @doc (#8852) 2019-03-03 18:22:15 -08:00
SpaceEEC 33d484231b Ensure :name is given in Registry.start_link/1 (#8850) 2019-03-03 17:07:01 -08:00
Eksperimental 8d00745217 Replace "(un)defined" with "(un)set" for system env (#8851) 2019-03-03 23:35:34 +01:00
Niels Bom 1a9a6bb806 Clarify Enum.all? and Enum.any? for empty lists (#8849) 2019-03-03 23:28:28 +01:00
Niels Bom ce92d83e3e Add docs for truthy/falsy values (#8848)
In a number of places in the docs (`!/`, `&&/2`, `Enum.all?`) the
concept of a truthy/falsy value is mentioned and briefly explained.
Let's have single place for that explanation and point to it
from those other places.
2019-03-03 13:19:03 -08:00
Andrea Leopardi 3680e85355 Add System.fetch_env/1 and System.fetch_env!/1 (#8846)
This commit adds the System.fetch_env/1 and System.fetch_env!/1 
functions as per the discussion in 
https://groups.google.com/forum/#!topic/elixir-lang-core/QGxEibK4y7Q.

I chose to keep using the same underlying implementation for fetch_env/1 
as the one for get_env/1 because the number of lines would have been the 
same but we would have added one function call.
2019-03-01 12:32:31 -08:00
Carlo Colombo fc039770b4 Enable Access.at/1 to handle negative index (#8843)
Enabling negative index as Enum.at/2 to keep it consistent.
2019-03-01 10:03:10 -08:00
José Valim 954f276a96 Do not change super signature, pass info as metadata 2019-03-01 08:58:53 -08:00
José Valim af9fea4aad Supervisor.start_link/2 with a list of children cannot return ignore
Closes #8845.
2019-03-01 06:45:19 -08:00
Andrea Leopardi 0558e7c92a Slightly improve Macro.generate_arguments/2 (#8842)
I added a bodyless clause so that arguments are clearly named in the 
documentation. I also added a is_atom/1 guard to the first function 
clause so that we consistently check that the context is an atom.
2019-03-01 06:33:15 +01:00
Tobiasz Małecki 9e387ce198 Add typespec for Macro.generate_arguments/2 (#8840) 2019-03-01 00:06:46 +01:00
Fernando Tapia Rico cae8c328bd Avoid Dialyzer warnings on Inspect (#8841)
Closes #8749.
2019-02-28 23:45:03 +01:00
Andrea Leopardi 12953fe8da Remove StreamData-related stuff from the formatter configuration (#8833)
We're not including StreamData (https://github.com/whatyouhide/stream_data) in the standard library anymore. It doesn't make sense that we have StreamData functions in the default locals without parens of the formatter. Instead, we solve the problem on the StreamData side with https://github.com/whatyouhide/stream_data/pull/124.
2019-02-28 23:34:39 +01:00
Andrew Kappen ea445b1300 Allow atime, mtime, ctime to have integer() types (#8839) 2019-02-27 17:44:30 -08:00
José Valim 28b5df2be2 Check for timeout to blame clauses, closes #8837 2019-02-27 09:45:11 -08:00
Fernando Tapia Rico 0a81b27861 Raise if a macro is invoked before its definition (#8822) 2019-02-25 22:48:21 +01:00
Wojtek Mach b819b9f093 Add ~U sigil for UTC date times (#8824) 2019-02-25 15:01:12 +01:00
José Valim cb2d914174 Do not consider modules that are no longer cover compiled (#8828)
The main issue is that Cover no longer considers protocols to
be cover compiled at the end of suite, which was causing protocols
to be taken into account when considering Total but they were not
listed individually.

By only considering cover compiled modules when computing the results,
it automatically removes protocols and it should also apply for future cases.
The umbrella --cover test was improved to be more assertive and also
list protocols.

Closes #8825
2019-02-25 14:43:43 +01:00
Saúl Cabrera f93b09629c Add typespecs to IEx public functions (#8827) 2019-02-25 10:03:52 +01:00
Wojtek Mach ff25707c73 Set DateTime.from_unix precision to microseconds for units not divisible by 10 (#8823)
Before:

    DateTime.from_unix!(143_256_036_886_856, 1024).microsecond
    {320312, 3}

After:

    DateTime.from_unix!(143_256_036_886_856, 1024).microsecond
    {320312, 6}
2019-02-24 16:04:43 +01:00
Fernando Tapia Rico 34e0fcd923 Do not allow to override macros as functions (#8816)
And vice-versa.

Overriding a macro as a function (and vice-versa) might produce
undesired effects.

For example, it's not clear if the following code should or
should not raise:

    defmodule Foo do
      def foo, do: bar()
      defmacro bar, do: :ok
      defoverridable bar: 0
      def bar, do: :ok
    end

On top of that, `super` does not currently work for such cases.
2019-02-23 09:08:16 +01:00
Page Bobby McConnell Grayson 6c88543e7f Fix uncommented ... in periodic task genserver documentation (#8820) 2019-02-23 09:07:50 +01:00
Gustavo Saiani 8c27da88c7 Fix typos in Kernel.SpecialForms (#8819) 2019-02-22 21:16:00 +01:00
Adam Zapaśnik 74cbc8f0ea Raise error when doc attribute is true (#8814) 2019-02-21 12:39:28 +01:00
José Valim 7afbdae53c Improve error message when deps.loadpaths/loadpaths/compile has not run
Closes #8803.
2019-02-20 10:39:07 +01:00
José Valim 4f819651eb Use proper var_context in with optimization 2019-02-19 23:20:34 +01:00
Colin Jones 4982cb52ed Update docs to consistently use "keyword list" (#8808) 2019-02-19 09:38:53 +01:00
Fernando Tapia Rico 6595283c35 Improve defoverridable test suite (#8802) 2019-02-18 07:15:22 +01:00
m. simon borg 8add228591 Fix documentation typos (#8805)
Fixed a few typos in the documentation section of `terminate/2`
2019-02-18 07:14:49 +01:00
Rauan Assis 4dda125e78 Adding missing List functions tests (#8804) 2019-02-18 07:14:23 +01:00
Fernando Tapia Rico 1aeb445b40 Store overridable information more efficiently (#8799)
This is part of an ongoing effort to forbid overriding macros
with functions and vice-versa. New checks will require the
overridable information to be stored more efficiently.
2019-02-16 16:05:54 +01:00
Eksperimental a14fe3184a Improvements to the URI module (#8779) 2019-02-16 11:19:40 +01:00
Aaron Renner a190c58680 Allow integer time units for DateTime.from_unix!/2 (#8801) 2019-02-15 19:57:04 +01:00
Gustavo Saiani c1bc409cc7 Improve Kernel.SpecialForms docs (#8798) 2019-02-15 14:09:40 +01:00
José Valim 819114b850 Specify that the formatter behaviour for operators has been generalized 2019-02-14 18:47:23 +01:00
Robb Shecter ddaa3f6ccb Focus on defguard in the "Guards" page (#8795)
The current doc first describes in detail the _deprecated_ way to create a custom guard. Only after that's complete is there a small note saying that this isn't recommended, and instead to use `defguard`. This commit changes that order around to lead with the current recommended practice.
2019-02-14 08:53:44 +01:00
Sebastian Abondano 58c86e1fcf Improve Task.Supervisor.async_nolink/3 doc (#8794) 2019-02-13 23:05:11 +01:00
Sebastian Abondano d4f7f5e5be Fix typo in Task.Supervisor.async_nolink/3 doc (#8793)
[ci skip]
2019-02-13 17:22:52 +01:00
José Valim 2791c8ccf5 Escape protocols docs accordingly 2019-02-13 10:58:28 +01:00
José Valim bf3f4b5d6d Consolidate Protocols information in the protocol module 2019-02-13 10:52:47 +01:00
Eric Meadows-Jönsson 6e7e9a994a Remove project_plugins from dependency rebar.config (#8791)
This will avoid fetching unnecessary plugins when using rebar3
dependencies.
2019-02-12 00:41:30 +01:00
Nathan Long b1c5e250ba Clearer documentation for GenServer timeouts (#8776)
I thought it was unclear what would happen if a timeout of `0`
were set but messages were already waiting.
2019-02-11 15:06:19 +01:00
Marin Atanasov Nikolov 4b22ead1ca Typo fixes in Kernel.def docstring (#8789) 2019-02-11 12:31:54 +01:00
Fernando Tapia Rico 17c19eaf89 Do not overwrite existent ERTS files in release (#8787)
Releases can include the Erlang Runtime System (ERTS), copying
it by default from the ERTS installation of the machine. If
the ERTS files were previously copied, the files are overwritten.
However, some package managers install ERTS files without writing
permissions, and once those files are copied they cannot be
overwritten.
2019-02-11 11:45:59 +01:00
José Valim a6120c459c Move precompilation section to the bottom, expand use cases 2019-02-10 09:35:29 +01:00
Christopher Eyre e80b06574c Added regex documentation for named character classes (#8785) 2019-02-10 09:29:35 +01:00
Łukasz Jan Niemier 352dd7f78c Fix module inspect on case insensitive filesystems (#8782)
Earlier this outputted error messages when loaded module that existed
with different casing, ex.:

    iex> i Datetime

Caused errors to be shown. Now this checks if there is module file
defines module with the same name as the provided atom to prevent such
cases.
2019-02-09 11:10:07 +01:00
Tobiasz Małecki 0432af273c Fix grammar issue in Mix.Tasks.Compile.Xref moduledoc (#8784)
[ci skip]
2019-02-09 10:41:28 +01:00
Leandro Bighetti bf616f77e9 Fix typo in DynamicSupervisor docs (#8783)
[ci skip]
2019-02-08 21:32:50 +01:00
Gustavo Saiani c59b341cf3 Fix typos in Kernel.SpecialForm docs (#8781)
[ci skip]
2019-02-08 12:23:21 +01:00
Eksperimental 7432e7c2f7 Use as_boolean/1 type in Enum.split_with/2 (#8778) 2019-02-07 09:32:23 +01:00
Eksperimental 2622fd6b0a Remove Integer.to_string/1 and Integer.to_charlist/1 (#8777)
They are not needed, since they are covered by their /2 version.
2019-02-07 09:29:10 +01:00
Eksperimental f41d8104ac Reword docs about Collectable.into/1 (#8775)
Be explicit that we are talking about Collectable.into/1
and Enumerable.reduce/3
2019-02-07 09:27:37 +01:00
Eksperimental 99d919e0c8 Use "code point" instead of "codepoint" (#8774)
That's the term it is used in the Unicode standard.
2019-02-06 11:21:03 +01:00
Tobiasz Małecki bd3a67fd6e Improve Kernel.SpecialForms.quote/2 doc examples (#8771) 2019-02-06 09:57:16 +01:00
Tobiasz Małecki a5550b8e83 Change "vm" to uppercase in IEx.Helpers (#8772)
VM is written in uppercase in other places.
2019-02-06 09:36:39 +01:00
Tobiasz Małecki c1026f77f7 Fix link to IEx.pry/0 in IEx.Server moduledoc (#8773) 2019-02-06 09:31:57 +01:00
Tobiasz Małecki bf4b16d823 Add missing typespecs in File.Stat module (#8769) 2019-02-05 10:13:17 +01:00
Tobiasz Małecki e0b7efdcd8 Fix typo in syntax reference page (#8770) 2019-02-05 01:51:16 +01:00
Evgeny Golyshev e18d46f600 Add missing arguments to man pages (#8764)
* Add missing arguments to man pages elixir(1) and iex(1)

* Extend AUTHOR section in man pages
2019-02-04 21:44:15 +01:00
Eksperimental 083974aacf ExUnit: Improve timeout error message and info (#8766) 2019-02-04 10:01:56 +01:00
Tobiasz Małecki cb3b243702 Change invalid uppercase letter in "ERlang" (#8765) 2019-02-03 23:57:52 +01:00
Eksperimental 53cf664b00 Sort alphabetically args in "mix new" task (#8763) 2019-02-03 22:56:03 +01:00
Eksperimental 3d0208d420 User "true or false" instead of the other way around (#8762)
While "false or true" is alphabetically ordered, the most common
way it is used in the English language is "true or false" and this
is the only one reference in Elixir core that we say "false or true".
2019-02-03 22:54:45 +01:00
Eksperimental 4e54c3c35e Improve List.ascii_printable?/2 spec (#8761) 2019-02-03 22:53:49 +01:00
José Valim a5395fc374 Add eval and version commands to releases (#8758)
We also split start | daemon iex | elixir into start and start_iex,
daemon and daemon_iex.
2019-02-02 21:27:28 +01:00
Tobiasz Małecki c95506bb44 Improve doc example in Kernel.Utils (#8759) 2019-02-02 19:55:02 +01:00
José Valim ae23447a6f Run the formatter 2019-02-02 17:00:43 +01:00
José Valim d423deafdb Rename remote.boot to start_clean.boot
This will pave the way for the upcoming eval script.
2019-02-02 16:51:14 +01:00
Eksperimental b38942a482 Check for POSIX compliant shell scripts in CI (#8750) 2019-02-02 12:53:41 +01:00
José Valim ceaa0450ce Shellcheck bin/elixirc too 2019-02-02 00:05:51 +01:00
José Valim 4b855ceed5 Updates to Makefile and man 2019-02-01 23:44:40 +01:00
José Valim 7cfe2bae27 Fix CI for releases (#8755) 2019-02-01 17:55:53 +01:00
Gustavo Saiani 3eb370aeb6 Fix typos in Kernel.SpecialForm docs (#8756) 2019-02-01 14:58:15 +01:00
Shunsuke Kirino 4f450ab1b1 Optimize performance of dynamic dispatching for non-consolidated protocols (#8754) 2019-02-01 13:58:46 +01:00
José Valim d3aebdb6a5 Shellcheck improvements to bin/iex 2019-02-01 10:39:09 +01:00
Jason Axelson 2b9c5266bd Document the $callers tracking that is new in Elixir 1.8 (#8753)
The majority of the text was taken from the release notes:
https://elixir-lang.org/blog/2019/01/14/elixir-v1-8-0-released/
2019-02-01 10:17:16 +01:00
Eksperimental 673ee3e027 Reverse order of OTP releases in .travis.yml (#8751)
The rationale is that the last version checks for
reproducible build and will also do other linting checks in the future
while the rest of the OTP releases don't.
So if it's going to fail, we don't need to wait until the end of the CI suite.
2019-02-01 10:06:26 +01:00
Joel C 1d34fb44df Improve docs for Kernel.unless (#8748) 2019-01-31 19:21:37 +01:00
José Valim 1cea9fa90c Avoid :erlang.apply/2 MFA indirection 2019-01-31 14:38:34 +01:00
José Valim 5e2087cbb8 Take into account spawned processes when checking for deadlocks
Elixir v1.6 introduced Kernel.ParallelCompiler.async/1, which
allows a file to spawn other processes to speed up compilation.
However, the compiler implementation assumed that if the number
of waiting processes were equal to the number of queued files,
it was a deadlock. However, with the introduction of async,
this is not necessarily true, as the async entries can be waiting
too. So this commit changes the compiler to track all spawned
entries.

Closes #8744.
2019-01-31 13:24:38 +01:00
José Valim 71b5044987 Improve docs for binwrite, closes #8746 2019-01-31 10:27:50 +01:00
José Valim b92c925bb0 Add remsh/1 to test output 2019-01-30 23:07:03 +01:00
José Valim b194e8b24e Small improvements for releases 2019-01-30 22:40:13 +01:00
José Valim 322ce6d8c2 Bring back autocomplete for remote nodes, closes #8743 2019-01-30 22:40:13 +01:00
Wojtek Mach a87d39ed05 Include field names in generated type for records (#8742)
For this module:

    defmodule M do
      import Record
      @type t :: record(:r, foo: atom(), bar: integer())
      defrecord :r, [:foo, :bar]
    end

`@type t` is defined as the following:

Before this patch:

    @type t() :: {:r, atom(), integer()}

After this patch:

    @type t() :: {:r, foo :: atom(), bar :: integer()}

This is similar to loading record definition in Erlang shell:

    1> rr(file).
    [file_descriptor,file_info]
    2> rl(file_descriptor).
    -record(file_descriptor,{module :: module(),data :: term()}).
    ok
2019-01-30 19:42:39 +01:00
Eksperimental bacda57b33 Make bin/elixir POSIX compliant (#8736)
We use Shellcheck as a linter
https://github.com/koalaman/shellcheck
2019-01-30 19:42:01 +01:00
Saúl Cabrera a444533db1 Add function specs to Mix public interface (#8745) 2019-01-30 19:33:56 +01:00
Tobiasz Małecki 033fad32d5 Fix typos in Mix.Tasks.Release (#8741) 2019-01-30 11:28:29 +01:00
José Valim 1eb080a1a8 More docs for releases 2019-01-30 09:29:28 +01:00
Tobiasz Małecki be309f1888 Fix typos in Mix.Release (#8739) 2019-01-30 08:48:14 +01:00
José Valim 0136b907c5 Avoid exporting and setting script var at once 2019-01-29 23:42:12 +01:00
José Valim 92f048f7cf Avoid races on windows on timer test
We check for >= 5000 (us) instead of >= 10000 (us)
because the resolution on Windows system is not high
enough and we would get a difference of 9000 from
time to time. So a value halfway is good enough.
2019-01-29 23:30:24 +01:00
Tobiasz Małecki 271e9737a8 Fix typo (#8737) 2019-01-29 23:10:28 +01:00
José Valim c6b0db6a4a Wait until all log is flushed, closes #8738 2019-01-29 23:02:25 +01:00
José Valim ef624e5613 Update CHANGELOG 2019-01-29 20:28:43 +01:00
José Valim 9de51f88ca Revert "Include optional dependencies in extra_applications (#8263)"
Unfortunately adding optional dependencies doesn't work for umbrella
apps where each app has a different optional dependency. For example,
Ecto 3.0 has both jason and poison as optional deps. Imagine the two
umbrella children below:

    foo
      * ecto
      * jason

    bar
      * ecto
      * poison
      * jason

Because ecto is shared with both, Ecto will include both poison and
jason, which makes`foo` fail to boot when running in isolation.

Closes #7930.
2019-01-29 19:54:11 +01:00
José Valim 0eff63b349 Fix OS assertion on Elixir script 2019-01-29 18:33:04 +01:00
José Valim 04c431c699 Organize iex test for l helper 2019-01-29 18:08:41 +01:00
José Valim 47ef3d0abc Add missing parens to elixir.bat 2019-01-29 17:21:22 +01:00
José Valim acf2c4e9f2 Add Windows build badge 2019-01-29 17:15:13 +01:00
José Valim e08a2b2642 More fixes on Windows with werl 2019-01-29 15:40:17 +01:00
José Valim 50cab0b962 Add RELEASE_NODE, RELEASE_COOKIE and fixes 2019-01-29 15:30:51 +01:00
Andrea Leopardi cccc35de4d Add missing "`" to Mix.Release docs (#8732)
[ci skip]
2019-01-29 14:50:44 +01:00
Nico 42e686ee03 add missing "`" to Mix.Release docs 2019-01-29 08:24:51 -05:00
Gustavo Saiani 653bf090a1 Fix typos in Kernel.SpecialForms doc (#8731) 2019-01-29 14:03:31 +01:00
José Valim ca7d95f005 Clarify include_erts option 2019-01-29 09:04:16 +01:00
José Valim 3078f1a87b Wait until connected 2019-01-29 08:26:28 +01:00
Tobiasz Małecki a266f78de4 Fix IEx.pry crash when IEx (IEx.Broker) isn't running (#8730) 2019-01-29 08:12:12 +01:00
José Valim 5af83fa1b9 Add docs to Kernel.CLI entry 2019-01-28 22:30:26 +01:00
José Valim dc030376d6 Basic mix release support (#8677)
See #8612.
2019-01-28 21:42:07 +01:00
Eksperimental c9991ecb5c IEx h: sort results by arity (#8727)
The results were not sorted, when calling:
h Module.function_name

Example:

```
iex)> h :erlang.float_to_binary
                           :erlang.float_to_binary/2

  @spec float_to_binary(float, options) :: binary()
        when float: float(),
             options: [option],
             option:
               {:decimals, decimals :: 0..253}
               | {:scientific, decimals :: 0..249}
               | :compact

Module was compiled without docs. Showing only specs.

                           :erlang.float_to_binary/1

  @spec float_to_binary(float) :: binary() when float: float()

Module was compiled without docs. Showing only specs.
```

Now the results are sorted by arity.
2019-01-28 15:22:50 +01:00
Eksperimental 4e138aeed4 Make .travis.yml valid (#8726) 2019-01-28 11:01:00 +01:00
Eksperimental 3d5b3417a2 CI feature: Reproducible build (#8701)
See https://github.com/elixir-lang/elixir/issues/8689 for more information
2019-01-28 10:24:14 +01:00
Tobiasz Małecki 774ead32fb IEx docs examples improvement (#8725) 2019-01-27 19:34:13 +01:00
Tobiasz Małecki 89ae8631d4 IEx.Helpers docs examples improvement (#8724) 2019-01-27 18:07:43 +01:00
Tobiasz Małecki e883e32f41 Mix.Tasks.Compile.App moduledoc examples improvement (#8722) 2019-01-27 18:02:05 +01:00
Tobiasz Małecki ea9b343219 Add typespec for Registry.unregister_match/4 (#8721) 2019-01-26 17:23:26 +01:00
Tobiasz Małecki bcbdc87e2d Added typespec for Port.info/1 (#8720) 2019-01-26 17:23:01 +01:00
Tobiasz Małecki 17918dd5c5 Added missing specs to new functions in System module (#8718) 2019-01-26 17:22:36 +01:00
Tobiasz Małecki dd1e96ea68 Added missing specs for Node get_cookie/0 and set_cookie/2 (#8719) 2019-01-26 17:22:04 +01:00
Fernando Tapia Rico 25c61e39b4 Use default args in Calendar.ISO.*_to_string/* (#8707) 2019-01-26 09:21:34 +01:00
Cody Fuller 2b3227f131 Add note about generating epub docs (#8711) 2019-01-26 09:18:15 +01:00
Tobiasz Małecki 4247f8e2f1 Mix.Compilers.Erlang.compile/6 example improvement (#8712) 2019-01-26 09:14:29 +01:00
Tobiasz Małecki c6a821791d Mix.Shell.Process docs examples formatting (#8715) 2019-01-26 00:17:08 +01:00
Wojtek Mach 77a8f397a0 Do not compile Elixir with --warnings-as-errors (#8716)
When working on Elixir, it's useful to sometimes let warnings slip. On CI we run with the flags.
2019-01-26 00:16:36 +01:00
Tobiasz Małecki 69ed1d4213 Mix.Task moduledoc eample formatting (#8713) 2019-01-26 00:09:56 +01:00
Tobiasz Małecki 533a2bd091 Mix.Tasks.Test moduledoc example formatting (#8714) 2019-01-26 00:09:34 +01:00
Tobiasz Małecki 678cceb963 Add spec for Mix.Project.consolidation_path/1 (#8710) 2019-01-26 00:05:39 +01:00
Tobiasz Małecki 38e369766d Improve the docs for ExUnit.Case (#8709) 2019-01-25 18:27:47 +01:00
Tobiasz Małecki 3bd3f88d57 ExUnit.Callbacks moduledoc example improvement (#8708) 2019-01-25 14:55:02 +01:00
Fernando Tapia Rico 0c89a94c65 Add missing @doc :since to Calendar.ISO functions (#8703) 2019-01-25 10:20:26 +01:00
José Valim dee400cc0f Do not rely on timezone database for DateTime.now
The FakeTimeZoneDatabase doesn't have all timezone entries,
which means it would be just a matter of time for this test
to start failing.

Closes #8702.
2019-01-25 10:11:30 +01:00
Tobiasz Małecki eca0389878 Add missing doc and spec to Calendar.ISO.time_to_string/5 (#8676) 2019-01-25 08:40:51 +01:00
Devon Estes 2859ec9027 Raise error when attempting to run single line tests on multiple files (#8682)
Right now the behavior when trying to use the shorthand for running a
single test for multiple files is confusing. We're now explicitly
disallowing this by raising an error when this happens.
2019-01-25 08:40:24 +01:00
José Valim 0ccf798fc2 Fix rounding for subnormal floats (#8687)
Closes #8685
2019-01-25 08:39:41 +01:00
José Valim 5b3f27c929 Support SOURCE_DATE_EPOCH for reproducible builds (#8694) 2019-01-25 08:31:37 +01:00
Tobiasz Małecki 54d2b58f3e ExUnit.Assertions docs examples improvement (#8698) 2019-01-25 08:23:16 +01:00
Tobiasz Małecki a4e41d059c ExUnit.DocTest moduledoc examples improvement (#8697) 2019-01-25 08:22:25 +01:00
Tobiasz Małecki 22b8238c50 ExUnit.CaseTemplate moduledoc example improvement (#8696) 2019-01-24 22:40:06 +01:00
Tobiasz Małecki d9f0488fc1 ExUnit.CaptureIO documentation examples improvement (#8695) 2019-01-24 22:39:38 +01:00
Tobiasz Małecki a128b0ac07 ExUnit.CaptureLog moduledoc improvement (#8693) 2019-01-24 22:19:54 +01:00
Joe Yates 346f7240fe Explicity test EEx options in error messages (#8692) 2019-01-24 22:19:34 +01:00
José Valim 3fd6cf5e91 Do not rely on map ordering when sorting specs, see #8689 2019-01-24 21:54:05 +01:00
Joe Yates 051d3b40eb Ensure we're testing the actual assignment metadata (#8691)
The existing test was not actually testing anything, as
the default argument to Keyword.get/3, 0, matched one of the
expected values.
2019-01-24 21:05:02 +01:00
José Valim fee525f65e Do not execute :rand.uniform/1 at the time the docs are defined
Closes #8689
2019-01-24 17:33:04 +01:00
Bernhard M. Wiedemann ae67b56bff Make tests pass in 2020 (#8688)
Using only 2038 to keep 32-bit UNIX systems happy.
2019-01-24 14:50:15 +01:00
José Valim 7a1ae92b42 Improve docs for Access
A lot of confusion in the previous docs was caused by
trying to explain both bracket-based and dot-based
syntaxes in the same place. This made sense at the time
because those syntaxes were not described elsewhere.
But now we can link to other resources and focus the
docs specifically in the Access module.
2019-01-24 12:53:55 +01:00
Wojtek Mach 5820888bc0 Standardize on iodata variable name, as this is what docs use 2019-01-23 23:25:41 +01:00
Wojtek Mach 3c0794b62e Convert private is_iodata macro to a private guard 2019-01-23 23:25:41 +01:00
Tobiasz Małecki 1fc6652ed6 Fix specs for Mix.Project umbrella?/1 and apps_paths/1 (#8683) 2019-01-23 23:15:05 +01:00
José Valim f0e58a2e87 Remove confusing comment about dynamically generated cases 2019-01-23 13:35:57 +01:00
Devon Estes 4da4ccdb8c Add ability to filter by more than one line number (#8673) 2019-01-23 13:34:42 +01:00
Tobiasz Małecki 2afc16b623 EEx.SmartEngine moduledoc examples improvement (#8681) 2019-01-22 23:02:44 +01:00
Tobiasz Małecki 4d5f903cc3 EEx examples improvement (#8680) 2019-01-22 22:19:02 +01:00
Tobiasz Małecki f865289130 Logger documentation improvement (#8679) 2019-01-22 21:13:08 +01:00
Tobiasz Małecki e333434019 Agent moduledoc improvement (#8674) 2019-01-22 17:44:10 +01:00
Eksperimental 2d0a481557 Make documentation accurate in File.cp* functions (#8627) 2019-01-22 17:27:57 +01:00
José Valim 8594944c2e Add version to umbrella projects too
This will be useful when assembling releases.
2019-01-22 16:23:32 +01:00
Eksperimental 5bd907d126 Use hexadecimal notation in code points in tokenizer error msg (#8669) 2019-01-22 13:39:40 +01:00
Eksperimental 2b6b1d51e6 Improve documentation and info on charlists (#8645) 2019-01-22 09:01:09 +01:00
Eksperimental 3beaf09dfe Correct arity in List test (#8670) 2019-01-22 08:58:37 +01:00
Eksperimental b60c8b82be Correct verb tenses (#8667) 2019-01-22 08:56:38 +01:00
Eksperimental 2f943d84cd Name args in Range.disjoint?/2 (#8666) 2019-01-22 08:55:56 +01:00
Fernando Tapia Rico 4f9c6e6c01 Optimize generated code for catch-all else clauses (#8661)
Elixir's with is expanded into a series of nested cases. For
example, this code:

    with {:ok, a} <- fun_a(),
         {:ok, b} <- fun_b() do
      wa = fun_w(a)
      wb = fun_w(b)
    else
      error -> {:c, error}
    end

would be expanded to something similar to this:

    case fun_a() do
      {:ok, a} ->
        case fun_b() do
          {:ok, b} ->
            wa = fun_w(a)
            wb = fun_w(b)

          var1 ->
            case var1 do
              error -> {:c, error}
              var2 -> error({with_clause, var2})
            end
        end

      var1 ->
        case var1 do
          error -> {:c, error}
          var2 -> error({with_clause, var2})
        end
    end

The generated code can be optimized for else clauses that
only contain a single catch-all clause (a variable that would
bind everything). Applying this optimization, the code of the
example above would be:

    case fun_a() do
      {:ok, a} ->
        case fun_b() do
          {:ok, b} ->
            wa = fun_w(a)
            wb = fun_w(b)

          error ->
            {:c, error}
        end

      error ->
        {:c, error}
    end
2019-01-22 08:53:24 +01:00
Eksperimental e81f8c065c Simplify guards in List.ascii_printable?/2 (#8656) 2019-01-22 08:50:48 +01:00
Fernando Tapia Rico 13179d2958 Augment doctest errors with misplaced opaque types (#8653) 2019-01-22 08:48:10 +01:00
Tobiasz Małecki 054814fd3e Mix.Config.import_config/1 doc improvement (#8665) 2019-01-21 21:58:32 +01:00
Tobiasz Małecki 106f1497ec Format code sample in Mix.Project.in_project/4 doc (#8664) 2019-01-21 21:39:55 +01:00
Wojtek Mach 736a1ac906 Update Module.spec_to_callback to keep existing specs and copy docs (#8663) 2019-01-21 19:49:44 +01:00
Eksperimental f1eca78ca4 Improve docs in Module functions and raise errors (#8635) 2019-01-21 18:12:51 +01:00
Wojtek Mach 985bfe53b4 Hide functions accidentally made public (#8662) 2019-01-21 14:13:13 +01:00
Eksperimental 861cf504f0 Use "operating system" instead of OS (#8658) 2019-01-20 21:15:20 +01:00
Eksperimental 6fceb8caaa Add test to List.ascii_printable?/2 (#8655)
Add test that checks improper lists with limit
2019-01-20 19:23:09 +01:00
Eksperimental 5a217ee941 Fix indentation in doctests (#8654) 2019-01-20 19:17:01 +01:00
Fernando Tapia Rico 5cf587f9eb Remove unneeded IO capture in IEx tests (#8652) 2019-01-20 18:48:42 +01:00
José Valim 9d77f5d8f6 Move code and macros to after processes and applications 2019-01-20 14:53:09 +01:00
farmio 209e6cb5a6 Fix a typo in the Library Guidelines page (#8651)
[ci skip]
2019-01-20 14:45:38 +01:00
Tyler Witt a3a0e25cf7 Update Kernel rounding documentation (#8648)
Updates Kernel.round/1 to describe what happens on half.
2019-01-20 09:08:41 +01:00
Saúl Cabrera 8d48a4399f Fix typo in Task docs (#8650) 2019-01-20 09:08:21 +01:00
Ali Farhadi 69cd698535 Fix docs for DynamicSupervisor.start_child/2 (#8647)
[ci skip]
2019-01-19 12:25:18 +01:00
José Valim 0ce62a2add Rely on IO.warn whenever possible 2019-01-18 14:59:20 +01:00
Fernando Tapia Rico 7af2e48156 Minor grammar fix in ExUnit.CaptureIO (#8644)
[ci skip]
2019-01-17 19:33:45 +01:00
José Valim bf6022f225 Ensure receive and send are done in a single read line operation, closes #8640 2019-01-17 18:01:47 +01:00
José Valim 17e86fd7eb Improve capture_io signature and docs 2019-01-17 17:59:17 +01:00
Gustavo Saiani 2d9681cd6b Improve IEx.Introspection.h/1 error message (#8643) 2019-01-17 13:48:13 +01:00
José Valim b8c68e6df3 Clarify the need for stop_supervised/1 2019-01-17 13:44:36 +01:00
José Valim f5ff567ce5 Avoid dialyzer warnings on formatter, closes #8631 2019-01-17 10:52:37 +01:00
Roman Smirnov 4bdc4e4050 Fix parsing Markdown links for h function (fix #8629) (#8641) 2019-01-16 22:39:14 +01:00
Eksperimental 0da99435c3 Use ID and not id (#8639)
The command used to detect these entries:
ag -s "\b(?<![/:{_\[.])ids?(?![}:_\]])(?!\(\))(?!enti)(?!ea)(?!iom)(?!x)"  --elixir
2019-01-16 19:14:39 +01:00
Eksperimental 2142465fdc Add missing colon and backticks (#8638) 2019-01-16 16:04:52 +01:00
Eksperimental ac2ee78e91 Use PID instead of pid (#8628) 2019-01-16 15:39:39 +01:00
Eksperimental 57a15ec541 Add note to File.stat/2 and File.lstat/2 on optimization (#8632)
The note was taken from the :file.read_file_info/1 Erlang documentation.
2019-01-16 09:42:23 +01:00
Eksperimental d605600d3f Sort Module.__info__/1 options alphabetically (#8637) 2019-01-16 09:38:02 +01:00
Eksperimental 8dc218edc3 Mention Code.fetch_docs/1 and ExDoc in Module (#8634) 2019-01-16 09:31:28 +01:00
Eksperimental f451f9f63d Replace use of "item(s)" with "element(s)" in List module (#8633)
The usage of items and elements was mixed,
but Erlang refers to them as Elements,
and Enumerable also refer to them as elements,
so I took the liberty to standardize its usage.
2019-01-16 09:28:50 +01:00
Eksperimental 8b1dfe2d50 Standardize non-doctest example in Agent moduledoc (#8630) 2019-01-16 09:27:57 +01:00
Derek Kraan 0f1d094bd9 Use consistent variable naming in the docs (#8625) 2019-01-15 19:57:24 +01:00
José Valim 786740a790 Write to tmp instead of root 2019-01-15 13:24:29 +01:00
Yurii Skrynnykov b711d08463 Do not halt the system on malformed .iex.exs (#8619) 2019-01-15 11:09:14 +01:00
Aleks Tkachenko 0a48866077 Fix typo in GenServer docs (#8623) 2019-01-15 10:59:09 +01:00
José Valim c5a3943e71 Address bootstrap issue 2019-01-15 00:29:05 +01:00
José Valim d54c3183d2 Format compatibilty table so it reads better online 2019-01-14 23:58:37 +01:00
José Valim a6e9de56db Allow __MODULE__.Something in a nested defmodule
Defining __MODULE__.Something in a nested defmodule would fail
to compile. This commit address this:

    defmodule Foo do
      defmodule __MODULE__.Bar do
        __MODULE__ == Elixir.Foo.Bar
      end
    end

Since `__MODULE__` expands to a fully qualified name `Elixir.Foo`,
the nested module must then be `Elixir.Foo.Bar`. This commit also
fixes an alias leakage that would happen under such scenarios.

Another possible interpretation would be for it to return `Foo.Foo.Bar`.
However, that would mean we would not be able to access the module
right below its definition using `__MODULE__.Bar`, which means its
`Foo.Foo.Bar` would be the incorrect choice.
2019-01-14 23:58:37 +01:00
Nikita Avvakumov 1f2140994c Fix typo in DynamicSupervisor.init/1 docs (#8622) 2019-01-14 22:19:50 +01:00
José Valim 085cb22352 Only perform behaviour checking if blaming 2019-01-13 13:29:52 +01:00
José Valim d7d49e1f58 Consistently forbid upcasting in calendar conversion functions 2019-01-12 09:47:55 +01:00
John Jacob 35b29c1644 Update typespec of drop to allow negative integer (#8614)
Drop allows negative integers for dropping from the end.
2019-01-11 22:29:51 +01:00
Fernando Tapia Rico 0a058028c2 Fix typo in compile.xref (#8609)
[ci skip]
2019-01-09 17:26:59 +01:00
Eksperimental e8acfa6802 Update usage of Unicode characters in atoms, variables and friends (#8603)
Update the Syntax Reference as well as List.to_atom/1
and List.to_existing_atom/1
2019-01-09 13:28:30 +01:00
Bryan Paxton 61a12f4a53 Improve coverage of Version.Parser (#8607)
Added assertions to exists test to cover lines 409, 412, and 511
2019-01-09 13:13:44 +01:00
José Valim a5468c2faa Update CHANGELOG 2019-01-08 20:46:45 +01:00
José Valim 427c2aa3ec Revamp Elixir CLI (#8595)
This commit adds 6 new options to elixir CLI to aid releases:

  * `--pipe-to PIPEDIR LOGDIR` - invokes run_erl with daemon pipe and logs (only on Unix-like systems)

  * `--rpc-eval NODE COMMAND` - evaluates the given expression on the given node

  * `--boot FILE`, `--boot-var VAR VALUE`, `--erl-config FILE` and `--vm-args FILE` - which are equivalent to Erlang's `-boot`, `-boot_var`, `-config` and `-args_file`

We have also rewritten the CLI to only pass Elixir options to Kernel.CLI.
2019-01-08 20:44:43 +01:00
Petr Stepchenko 01fd5a53f3 Add File.rename!/2 (#8606) 2019-01-08 20:25:03 +01:00
José Valim 3e673ac134 Ensure we support counters in bitstring size vars 2019-01-07 22:31:31 +01:00
Fernando Tapia Rico b2c698d295 Provide better errors with invalid dates in Calendar.ISO (#8601) 2019-01-07 21:58:31 +01:00
José Valim 5648b4e438 Update Writing Documentation.md (#8599) 2019-01-07 21:58:11 +01:00
Andrea Leopardi aa24627547 Use proper formatting in the docs for System.pid/0
[ci skip]
2019-01-07 15:08:56 +01:00
Fernando Tapia Rico 144e0427c7 Handle non-printable args in StringIO gracefully (#8600)
Non-printable arguments were killing the StringIO process, which
was killing the Logger handler associated to it. That was
causing some errors when users were running their test suites
with `capture_log: true`, because the proxy (Logger handler) was
not there when ExUnit.CaptureLog was trying to remove it.
2019-01-06 21:02:49 +01:00
José Valim 00e488d282 Remove remsh/1 from assertion 2019-01-05 23:02:24 +01:00
José Valim ea4cedb796 Remove remaining remsh autocomplete logic
After a batch of tests remsh autocompletion
works just fine without such setup.
2019-01-05 22:29:11 +01:00
José Valim 5c96513dea Remove unecessary IEx module 2019-01-05 17:46:45 +01:00
Fernando Tapia Rico 7ad213b289 Error on undefined variables in bitstring segments (#8598)
Binary/bitstring matching allows to dynamically define the
`size` of the binary in certain conditions:

  * if the `size` variable is defined prior to the pattern
    match:

        iex> size = 8
        iex> <<a::size(size), rest::binary>> = "hello"
        iex> a
        104

  * if the `size` variable is matched within the same
    binary/bitstring match, prior to its use:

        iex> <<name_size::size(8), name::binary-size(name_size), _rest::binary>> = <<5, "Frank the Walrus">>
        iex> name
        "Frank"

Other cases are considered illegal patterns, for example:

    {name_size, <<name::binary-size(name_size), _rest::binary>>} = {5, "Frank the Walrus"}

This commit raises a `CompileError: undefined variable ...` for such cases.
2019-01-05 17:46:33 +01:00
José Valim 6377b324ee Use a descentralized mode computation for Logger (#8567)
Previous logger versions would always compute the mode on the Logger
handler which meant that, if any other Logger handler blocked, either because
it was waiting on IO, the system is overloaded or due to long computations, a
burst of messages could make the Logger message queue grow quite large
until it notices it should change its mode.

This commit changes the computation to use a counter which is offset by
the current message length queue. This makes the following script go from
tens of thousands messages in the Logger inbox to only hundreds (assuming
a `sync_threshold` of 100) when writing a rate limited stdio with
`mix run foo.exs | pv -p -L 1k 1>/dev/null`:

```elixir
require Logger

# monitor memory
# :observer.start()

defmodule Loop do
  def run() do
    Logger.info(inspect(:crypto.strong_rand_bytes(1024)))
    Process.sleep(10)
    run()
  end
end

for _i <- 1..100 do
  spawn(fn ->
    Loop.run()
  end)
end

defmodule OtherLoop do
  def run() do
    IO.inspect(:stderr, Process.info(Process.whereis(Logger), :message_queue_len), [])
    IO.inspect(:stderr, :ets.tab2list(Logger.Config), [])
    Process.sleep(100)
    run()
  end
end

OtherLoop.run()
```
2019-01-05 17:31:54 +01:00
José Valim a356e60d45 Refactor Kernel.CLI tests and fix faulty assertions 2019-01-05 13:18:28 +01:00
José Valim 9a87eb5f98 Add System.restart/0 and System.pid/0 (#8597) 2019-01-05 13:17:59 +01:00
José Valim 091886d176 Handle unquote in remote call, closes #8588 2019-01-05 09:17:23 +01:00
José Valim 52ff4ce544 Group tests and run the formatter 2019-01-04 14:45:52 +01:00
José Valim 92b41acc88 Properly handle closing brackets in do in EEx 2019-01-04 14:35:36 +01:00
Benjamin Milde 3d5d42afd4 Improve eex tokenizer further (#8594) 2019-01-04 14:30:23 +01:00
Benjamin Milde 74ca2ac5dc Allow more complex mixed expressions in EEx (#8591)
Closes #8590.
2019-01-04 09:07:02 +01:00
José Valim 145412e6c9 Document as_boolean/1, closes #8593 2019-01-04 09:05:46 +01:00
José Valim 1d7545a180 Remove needless comment 2019-01-03 13:16:35 +01:00
José Valim fe11f867b5 Move Calendar to its group 2019-01-02 16:03:41 +01:00
José Valim ccc3691cb0 Also run CI on Erlang/OTP 21.2 (#8587) 2019-01-02 15:59:54 +01:00
José Valim a14e2eb316 Suggest quotes where appropriate (#8561) 2019-01-02 14:40:22 +01:00
Gustavo Saiani c6c788f1e1 Add verb to IO.ANSI.format/2 doc (#8586)
[CI skip]
2019-01-02 13:42:01 +01:00
Wojtek Mach ca40d1594c Show docs for callback with multiple clauses (#8553)
* Merge callback's multiple clauses in elixir_erl.erl
* Simpler callbacks deduplication
2019-01-02 13:12:01 +01:00
Wojtek Mach ae6ae8ccb9 Module.__info__/1 docs and specs improvements (#8580) 2019-01-02 13:09:47 +01:00
Bruce Park 8aeeb86dfe Improve Protocol.UndefinedError message... (#8579)
when passing the module name of a struct

Addresses https://github.com/elixir-lang/elixir/issues/8532
2019-01-02 13:08:37 +01:00
Devon Estes ffa3c8bc4b Add documentation about ExUnit.Test.time (#8583)
* Add documentation about ExUnit.Test.time

Now we're specifying what that integer actually represents -
microseconds!

* Update lib/ex_unit/lib/ex_unit.ex

Co-Authored-By: devonestes <devon.c.estes@gmail.com>
2019-01-01 19:12:57 +01:00
José Valim c1e81f171f Update enum.ex 2019-01-01 16:21:01 +01:00
José Valim 316b969feb Clear up MIX_ENV env variable before tests, closes #8584 2018-12-31 19:22:23 +01:00
Eksperimental 05363f77f8 Improve documentation for Enum.take/2 (#8581)
It was confusing to say in the summary that:
"Takes the first `amount` items from the `enumerable`."
when it can be from the end as well, depending on the sign of `amount`.
2018-12-31 13:38:30 +01:00
José Valim 963c3eb61d No longer wrap doctest errors in custom exception
They end-up hiding more information than showing.

Closes #8547
2018-12-31 12:17:44 +01:00
Wojtek Mach 759a2ba133 Update System.build_info/0 docs (#8578) 2018-12-29 12:09:00 +01:00
Jon Anderson e3dacf07b3 Add additional guards for Keywords.merge/3 (#8574) 2018-12-29 11:12:30 +01:00
Eksperimental a0f451bc0e Add missing deprecation related to mix compile.erlang (#8577) 2018-12-29 11:09:45 +01:00
Eksperimental 61b36e34fc Improve spec and documentation for System.build_info/0 (#8575)
- Give more detailed spec
- Correct mention to keyword list when it is a map that it returns.
- Compile a list of keys returned and what each one is
- Add example
2018-12-29 11:08:18 +01:00
José Valim 53855cec8e More docs to child specs 2018-12-28 21:12:28 +01:00
Wojtek Mach 3d7b40b001 Make KeyError.message/2 private (#8573)
It was introduced accidentally in #7803 (no docs, no specs)
2018-12-27 23:44:49 +01:00
José Valim 1ba9859e9e Improve docs for supervised proceesses 2018-12-27 23:03:16 +01:00
José Valim b5f6e8e4b2 Default to required map keys
This aligns the behaviour with atoms keys and maps
for state without forcing everyone to pick one or
the other.

For clarity, developers can always use required/optional.
2018-12-27 17:14:21 +01:00
nico piderman a15b07a283 User defined types with the name record fail to compile in 1.8.0-rc.0 (#8569)
Closes https://github.com/elixir-lang/elixir/issues/8564

This is my first attempt to contribute code to elixir, so please forgive me if this PR is completely naive.

Besides adding the `is_list/1` guards, I have updated the error message to specify that record specifications must use an atom `literal` as the name, to make it clear that the type `atom` is also not valid.
2018-12-27 17:06:13 +01:00
José Valim 0e15f809bb Avoid race conditions on behaviour checks, closes #8568 2018-12-27 16:29:33 +01:00
José Valim 1995202862 Streamline supervisor docs 2018-12-27 14:54:14 +01:00
José Valim 6c066f13fd Undeprecate Mix.Config.read!/2 2018-12-25 21:55:18 +01:00
José Valim 1c7ee571c2 Further improvements to Mix.Config docs 2018-12-25 18:08:32 +01:00
José Valim dd531317c4 Improve docs for Mix.Config 2018-12-25 17:53:49 +01:00
José Valim bfaee8e7b5 Improve release instructions 2018-12-24 15:49:54 +01:00
Sfusato ce9d7488c8 Add IEx warning when using --remsh with 'dumb' terminal (#8563)
Closes #8562
2018-12-24 09:46:47 +01:00
José Valim 69630a36d4 Halt IEx when it receives EOF from stdin (#8560)
Prior to this patch IEx would just hang as stdin
was dead. This aligns the behaviour with the `erl`
shell.
2018-12-23 19:34:11 +01:00
José Valim f045475934 Remove undocumented environment variable 2018-12-23 19:00:41 +01:00
José Valim ad4a363232 Swap argument order to avoid failures on OTP master 2018-12-22 10:04:28 +01:00
José Valim 7123901baa Improve OptionParser docs a bit further 2018-12-22 09:10:15 +01:00
Eksperimental dc7a35c3a0 Rephrase useWerl option in elixir.bat (#8559) 2018-12-22 09:06:05 +01:00
José Valim 579a3e197b Provide a quick introduction to OptionParser in mdocs 2018-12-22 00:36:01 +01:00
Eksperimental d19701dc91 Correct use of repetitive "otherwise" in Map.get/3 (#8556) 2018-12-21 22:00:10 +01:00
José Valim a81259aba8 Add a note about Hex and requirements, closes #8555 2018-12-21 19:27:14 +01:00
Eksperimental dfe34380ca Add Supported Erlang/OTP versions for v1.9 (#8552) 2018-12-21 18:10:59 +01:00
Eksperimental ec50dcead3 Add Supported Erlang/OTP versions for v1.8 (#8549) 2018-12-21 18:01:14 +01:00
Wojtek Mach 784e076a61 Improve defprotocol/2 docs (#8548)
* `__protocol__` doesn't accept `:name`
* Elaborate on atoms that `__protocol__` accepts
* Change code example to doctest and fix it
2018-12-21 17:42:43 +01:00
José Valim 5b349504f4 Move simple_one_for_one deprecations to v1.10 2018-12-21 13:49:12 +01:00
José Valim 0c1df34645 Standardize more TODOs 2018-12-21 13:42:54 +01:00
José Valim d85ed42765 Update removal and deprecation annotations
In particular, all deprecated code would be removed on v2.0,
so we don't need to annotate dperecated code with a TODO to
remove it on v2.0.
2018-12-21 13:27:06 +01:00
José Valim 93cf82f141 Start v1.9 2018-12-21 13:04:21 +01:00
Fernando Tapia Rico e15b957a0d Remove deprecated Mix.Dep.loaded/1 (#8539) 2018-12-21 12:56:07 +01:00
Fernando Tapia Rico 450261295f Deprecate %{key => value} in typespecs (#8537)
%{required(foo) => bar} and %{optional(foo) => bar} should be used.
2018-12-21 12:55:57 +01:00
338 changed files with 14392 additions and 5514 deletions
+18 -12
View File
@@ -5,12 +5,15 @@ env:
global:
- ELIXIR_ASSERT_TIMEOUT=2000
matrix:
- OTP_RELEASE=OTP-20.0
- OTP_RELEASE=OTP-20.1
- OTP_RELEASE=OTP-20.2
- OTP_RELEASE=OTP-20.3
- OTP_RELEASE=OTP-21.0
- OTP_RELEASE=OTP-22.0 CHECK_REPRODUCIBLE=true CHECK_POSIX_COMPLIANT=true
- OTP_RELEASE=OTP-21.3.8
- OTP_RELEASE=OTP-21.2
- OTP_RELEASE=OTP-21.1
- OTP_RELEASE=OTP-21.0
- OTP_RELEASE=OTP-20.3
- OTP_RELEASE=OTP-20.2
- OTP_RELEASE=OTP-20.1
- OTP_RELEASE=OTP-20.0
- OTP_RELEASE=maint
- OTP_RELEASE=master
@@ -28,14 +31,17 @@ install:
- PATH=$(pwd)/otp/bin:$PATH
script:
- make compile
- rm -rf .git
- ELIXIRC_OPTS="--warnings-as-errors" ERLC_OPTS="+warning_as_errors" make compile
- make test
- dialyzer -pa lib/elixir/ebin --build_plt --output_plt elixir.plt --apps lib/elixir/ebin/elixir.beam lib/elixir/ebin/Elixir.Kernel.beam
notifications:
recipients:
- jose.valim@gmail.com
- eric.meadows.jonsson@gmail.com
- lexmag@me.com
- an.leopardi@gmail.com
# Check for reproducible builds only in the latest OTP release
- if [ -n "$CHECK_REPRODUCIBLE" ]; then make check_reproducible; fi
# Check for POSIX compliant shell scripts
- if [ -n "$CHECK_POSIX_COMPLIANT" ]; then
shellcheck -e SC2039,2086 bin/elixir && echo "bin/elixir is POSIX compliant";
shellcheck bin/elixirc && echo "bin/elixirc is POSIX compliant";
shellcheck bin/iex && echo "bin/iex is POSIX compliant";
fi
+152 -68
View File
@@ -1,101 +1,185 @@
# Changelog for Elixir v1.8
# Changelog for Elixir v1.9
## v1.8.0-dev
## Releases
The main feature in Elixir v1.9 is the addition of releases. A release is a self-contained directory that consists of your application code, all of its dependencies, plus the whole Erlang Virtual Machine (VM) and runtime. Once a release is assembled, it can be packaged and deployed to a target as long as the target runs on the same operating system (OS) distribution and version as the machine running the `mix release` command.
You can start a new project and assemble a release for it in three easy steps:
$ mix new my_app
$ cd my_app
$ MIX_ENV=prod mix release
A release will be assembled in `_build/prod/rel/my_app`. Inside the release, there will be a `bin/my_app` file which is the entry point to your system. It supports multiple commands, such as:
* `bin/my_app start`, `bin/my_app start_iex`, `bin/my_app restart`, and `bin/my_app stop` - for general management of the release
* `bin/my_app rpc COMMAND` and `bin/my_app remote` - for running commands on the running system or to connect to the running system
* `bin/my_app eval COMMAND` - to start a fresh system that runs a single command and then shuts down
* `bin/my_app daemon` and `bin/my_app daemon_iex` - to start the system as a daemon on Unix-like systems
* `bin/my_app install` - to install the system as a service on Windows machines
### Why releases?
Releases allow developers to precompile and package all of their code and the runtime into a single unit. The benefits of releases are:
* Code preloading. The VM has two mechanisms for loading code: interactive and embedded. By default, it runs in the interactive mode which dynamically loads modules when they are used for the first time. The first time your application calls `Enum.map/2`, the VM will find the `Enum` module and load it. There’s a downside. When you start a new server in production, it may need to load many other modules, causing the first requests to have an unusual spike in response time. Releases run in embedded mode, which loads all available modules upfront, guaranteeing your system is ready to handle requests after booting.
* Configuration and customization. Releases give developers fine grained control over system configuration and the VM flags used to start the system.
* Self-contained. A release does not require the source code to be included in your production artifacts. All of the code is precompiled and packaged. Releases do not even require Erlang or Elixir in your servers, as they include the Erlang VM and its runtime by default. Furthermore, both Erlang and Elixir standard libraries are stripped to bring only the parts you are actually using.
* Multiple releases. You can assemble different releases with different configuration per application or even with different applications altogether.
### Hooks and Configuration
Releases also provide built-in hooks for configuring almost every need of the production system:
* `config/config.exs` (and `config/prod.exs`) - provides build-time application configuration, which is executed when the release is assembled
* `config/releases.exs` - provides runtime application configuration. It is executed every time the release boots and is further extensible via config providers
* `rel/vm.args.eex` - a template file that is copied into every release and provides static configuration of the Erlang Virtual Machine and other runtime flags
* `rel/env.sh.eex` and `rel/env.bat.eex` - template files that are copied into every release and executed on every command to set up environment variables, including ones specific to the VM, and the general environment
We have written extensive documentation on releases, so we recommend checking it out for more information.
## Configuration overhaul
A new `Config` module has been added to Elixir. The previous configuration API, `Mix.Config`, was part of the Mix build tool. But since releases provide runtime configuration and Mix is not included in releases, we ported the `Mix.Config` API to Elixir. In other words, `use Mix.Config` has been soft-deprecated in favor of `import Config`.
Another important change related to configuration is that `mix new` will no longer generate a `config/config.exs` file. [Relying on configuration is undesired for most libraries](https://hexdocs.pm/elixir/library-guidelines.html#avoid-application-configuration) and the generated config files pushed library authors in the wrong direction. Furthermore, `mix new --umbrella` will no longer generate a configuration for each child app, instead all configuration should be declared in the umbrella root. That's how it has always behaved, we are now making it explicit.
## Other enhancements
There are many other enhancements. The Elixir CLI got a handful of new options in order to best support releases. `Logger` now computes its sync/async/discard thresholds in a decentralized fashion, reducing contention. `EEx` templates support more complex expressions than before. Finally, there is a new `~U` sigil for working with UTC DateTimes as well as new functions in the `File`, `Registry`, and `System` modules.
## v1.9.0 (2019-06-24)
### 1. Enhancements
#### EEx
* [EEx] Optimize the default template engine to compile and execute more efficiently
* [EEx] Allow more complex mixed expressions when tokenizing
#### Elixir
* [Calendar] Add `Calendar.TimeZoneDatabase` and a `Calendar.UTCOnlyTimeZoneDatabase` implementation
* [Calendar] Add callbacks `day_of_year/3`, `quarter_of_year/3`, `year_of_era/1`, and `day_of_era/3`
* [Code.Formatter] Preserve user's choice of new line after most operators
* [Date] Add `Date.day_of_year/1`, `Date.quarter_of_year/1`, `Date.year_of_era/1`, and `Date.day_of_era/1`
* [DateTime] Add `DateTime.from_naive/3`, `DateTime.now/1`, and `DateTime.shift_zone/3`
* [File] Allow `:raw` option in `File.exists?/2`, `File.regular?/2`, and `File.dir?/2`
* [File] Allow POSIX time as an integer in `File.touch/2` and `File.touch!/2`
* [Inspect] Allow `Inspect` protocol to be derivable with the `:only`/`:except` options
* [Kernel] Do not propagate counters to variables in quote inside another quote
* [Kernel] Warn on ambiguous use of `::` and `|` in typespecs
* [Kernel] Add `:delegate_to` `@doc` metadata tag when using `defdelegate`
* [Kernel] Improve compile-time building of ranges via the `..` operator
* [Kernel] Compile charlist interpolation more efficiently
* [List] Add `List.myers_difference/3` and `List.improper?/1`
* [Macro] Add `Macro.struct!/2` for proper struct resolution during compile time
* [Map] Optimize and merge nested maps `put` and `merge` operations
* [Range] Add `Range.disjoint?/2`
* [Registry] Allow associating a value on `:via` tuple
* [String] Add `String.bag_distance/2`
* [Access] Allow `Access.at/1` to handle negative index
* [CLI] Add support for `--boot`, `--boot-var`, `--erl-config`, `--pipe-to`, `--rpc-eval`, and `--vm-args` options
* [Code] Add `static_atom_encoder` option to `Code.string_to_quoted/2`
* [Code] Support `:force_do_end_blocks` on `Code.format_string!/2` and `Code.format_file!/2`
* [Code] Do not raise on deadlocks on `Code.ensure_compiled/1`
* [Config] Add `Config`, `Config.Reader`, and `Config.Provider` modules for working with configuration
* [File] Add `File.rename!/2`
* [Inspect] Add `:inspect_fun` and `:custom_options` to `Inspect.Opts`
* [Kernel] Add `~U` sigil for UTC date times
* [Kernel] Optimize `&super/arity` and `&super(&1)`
* [Kernel] Optimize generated code for `with` with a catch-all clause
* [Kernel] Validate `__struct__` key in map returned by `__struct__/0,1`
* [Module] Add `Module.get_attribute/3`
* [Protocol] Improve `Protocol.UndefinedError` messages to also include the type that was attempted to dispatch on
* [Protocol] Optimize performance of dynamic dispatching for non-consolidated protocols
* [Record] Include field names in generated type for records
* [Regex] Automatically recompile regexes
* [Registry] Add `Registry.select/2`
* [System] Add `System.restart/0`, `System.pid/0` and `System.no_halt/1`
* [System] Add `System.get_env/2`, `System.fetch_env/1`, and `System.fetch_env!/1`
* [System] Support `SOURCE_DATE_EPOCH` for reproducible builds
#### ExUnit
* [ExUnit] Add `ExUnit.after_suite/1` callback
* [ExUnit.Assertions] Show last N messages (instead of first N) from mailbox on `assert_receive` fail
* [ExUnit] Allow multiple `:exclude` on configuration/CLI
* [ExUnit.DocTest] No longer wrap doctest errors in custom exceptions. They ended-up hiding more information than showing
* [ExUnit.DocTest] Display the actual doctest code when doctest fails
#### IEx
* [IEx.Helpers] Add `port/1` and `port/2`
* [IEx.Server] Expose `IEx.Server.run/1` for custom IEx sessions with the ability to broker pry sessions
#### Mix
* [Mix] Add `Mix.target/0` and `Mix.target/1` to control dependency management per target
* [Mix.Project] Add `:depth` and `:parents` options to `deps_paths/1`
* [mix archive.install] Add a timeout when installing archives
* [mix compile] Include optional dependencies in `:extra_applications`
* [mix escript.install] Add a timeout when installing escripts
* [mix format] Warn when the same file may be formatted by multiple `.formatter.exs`
* [mix test] Allow setting the maximum number of failures via `--max-failures`
* [mix test] Print a message instead of raising on unmatched tests inside umbrella projects
### 2. Bug fixes
#### Elixir
* [Calendar] Allow printing dates with more than 9999 years
* [Exception] Exclude deprecated functions in "did you mean?" hints
* [Float] Handle subnormal floats in `Float.ratio/1`
* [Kernel] Remove `Guard test tuple_size(...) can never succeed` dialyzer warning on try
* [Kernel] Expand operands in `size*unit` bitstring modifier instead of expecting `size` and `unit` to be literal integers
* [Kernel] Do not deadlock on circular struct dependencies in typespecs
* [Kernel] Raise proper error message when passing flags to the Erlang compiler that Elixir cannot handle
* [NaiveDateTime] Do not accept leap seconds in builder and parsing functions
* [String] Fix ZWJ handling in Unicode grapheme clusters
* [IEx.CLI] Copy ticktime from remote node on IEx `--remsh`
* [IEx.CLI] Automatically add a host on node given to `--remsh`
#### Logger
* [Logger] Allow Logger backends to be dynamically removed when an application is shutting down
* [Logger] Use a decentralized mode computation for Logger which allows overloads to be detected more quickly
* [Logger] Use `persistent_term` to store configuration whenever available for performance
#### Mix
* [mix compile.app] Respect the `:only` option between umbrella siblings
* [mix compile.protocols] Reconsolidate protocols if local dependencies are stale
* [mix deps] Properly mark dependencies with different `:system_env` as diverged
* [mix new] Use `--module` value when setting up filenames
* [Mix] Follow XDG base dir specification in Mix for temporary and configuration files
* [Mix.Generator] Add `copy_file/3`, `copy_template/4`, and `overwite?/2`
* [Mix.Project] Add `preferred_cli_target` that works like `preferred_cli_env`
* [mix archive.uninstall] Allow `mix archive.uninstall APP` to uninstall any installed version of APP
* [mix new] No longer generate a `config/` directory for mix new
* [mix release] Add support for releases
* [mix release.init] Add templates for release configuration
* [mix test] Allow running tests for a given umbrella app from the umbrella root with `mix test apps/APP/test`. Test failures also include the `apps/APP` prefix in the test location
### 2. Bug fixes
#### EEx
* [EEx] Consistently trim newlines when you have a single EEx expression per line on multiple lines
#### Elixir
* [Code] Quote `::` in `Code.format_string!/1` to avoid ambiguity
* [Code] Do not crash formatter on false positive sigils
* [Enum] Ensure the first equal entry is returned by `Enum.min/2` and `Enum.max/2`
* [Kernel] Improve error message when string interpolation is used in a guard
* [Kernel] Properly merge and handle docs for callbacks with multiple clauses
* [Kernel] Guarantee reproducible builds on modules with dozens of specs
* [Kernel] Resolve `__MODULE__` accordingly in nested `defmodule` to avoid double nesting
* [Kernel] Type variables starting with an underscore (`_foo`) should not raise compile error
* [Kernel] Keep order of elements when macro `in/2` is expanded with a literal list on the right-hand side
* [Kernel] Print proper location on undefined function error from dynamically generated functions
* [System] Make sure `:init.get_status/0` is set to `{:started, :started}` once the system starts
* [Path] Do not expand `~` in `Path.expand/2` when not followed by a path separator
* [Protocol] Ensure `debug_info` is kept in protocols
* [Regex] Ensure inspect returns valid `~r//` expressions when they are manually compiled with backslashes
* [Registry] Fix ETS leak in `Registry.register/2` for already registered calls in unique registries while the process is still alive
#### ExUnit
* [ExUnit] Raise error if attempting to run single line tests on multiple files
* [ExUnit] Return proper error on duplicate child IDs on `start_supervised`
#### IEx
* [IEx] Automatically shut down IEx if we receive EOF
#### Logger
* [Logger] Don't discard Logger messages from other nodes as to leave a trail on both systems
#### Mix
* [mix compile] Ensure Erlang-based Mix compilers (erlang, leex, yecc) set valid position on diagnostics
* [mix compile] Ensure compilation halts in an umbrella project if one of the siblings fail to compile
* [mix deps] Raise an error if the umbrella app's dir name and `mix.exs` app name don't match
* [mix deps.compile] Fix subcommand splitting bug in rebar3
* [mix test] Do not consider modules that are no longer cover compiled when computing coverage report, which could lead to flawed reports
### 3. Soft-deprecations (no warnings emitted)
#### Mix
* [Mix.Config] `Mix.Config` has been deprecated in favor of the `Config` module that now ships as part of Elixir itself. Reading configuration files should now be done by the `Config.Reader` module
### 4. Hard-deprecations
#### Elixir
* [Enum] Passing a non-empty list to `Enum.into/2` was inconsistent with maps and is deprecated in favor of `Kernel.++/2` or `Keyword.merge/2`
* [Inspect.Algebra] `surround/3` is deprecated in favor of `Inspect.Algebra.concat/2` and `Inspect.Algebra.nest/2`
* [Inspect.Algebra] `surround_many/6` is deprecated in favor of `container_doc/6`
* [Kernel] Passing a non-empty list as `:into` in `for` comprehensions was inconsistent with maps and is deprecated in favor of `Kernel.++/2` or `Keyword.merge/2`
* [Kernel.ParallelCompiler] `files/2` is deprecated in favor of `compile/2`
* [Kernel.ParallelCompiler] `files_to_path/2` is deprecated in favor of `compile_to_path/2`
* [Kernel.ParallelRequire] `files/2` is deprecated in favor of `Kernel.ParallelCompiler.require/2`
* [System] `:seconds`, `:milliseconds`, etc. as time units is deprecated in favor of `:second`, `:millisecond`, etc.
* [System] `System.cwd/0` and `System.cwd!/0` are deprecated in favor of `File.cwd/0` and `File.cwd!/0`
* [CLI] Deprecate `--detached` option, use `--erl "-detached"` instead
* [Map] Deprecate Enumerable keys in `Map.drop/2`, `Map.split/2`, and `Map.take/2`
* [String] The `:insert_replaced` option in `String.replace/4` has been deprecated. Instead you may pass a function as a replacement or use `:binary.replace/4` if you need to support earlier Elixir versions
#### Mix
* [mix compile.erlang] Returning `{:ok, contents}` or `:error` as the callback in `Mix.Compilers.Erlang.compile/6` is deprecated in favor of returning `{:ok, contents, warnings}` or `{:error, errors, warnings}`
* [Mix.Project] Deprecate `Mix.Project.load_paths/1` in favor of `Mix.Project.compile_path/1`
## v1.7
## v1.8
The CHANGELOG for v1.7 releases can be found [in the v1.7 branch](https://github.com/elixir-lang/elixir/blob/v1.7/CHANGELOG.md).
The CHANGELOG for v1.8 releases can be found [in the v1.8 branch](https://github.com/elixir-lang/elixir/blob/v1.8/CHANGELOG.md).
+7 -3
View File
@@ -39,11 +39,11 @@ If you participate in or contribute to the Elixir ecosystem in any way, you are
Explicit enforcement of the Code of Conduct applies to the official mediums operated by the Elixir project:
* The official GitHub projects and code reviews.
* The [official GitHub projects][1] and code reviews.
* The official elixir-lang mailing lists.
* The #elixir-lang IRC channel on Freenode.
* The **[#elixir-lang][2]** IRC channel on [Freenode][3].
Other Elixir activities (such as conferences, meetups, and other unofficial forums) are encouraged to adopt this Code of Conduct. Such groups must provide their own contact information.
Other Elixir activities (such as conferences, meetups, and unofficial forums) are encouraged to adopt this Code of Conduct. Such groups must provide their own contact information.
Project maintainers may remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct.
@@ -54,3 +54,7 @@ Instances of abusive, harassing, or otherwise unacceptable behavior may be repor
## Acknowledgements
This document was based on the Code of Conduct from the Go project with parts derived from Django's Code of Conduct, Rust's Code of Conduct and the Contributor Covenant.
[1]: https://github.com/elixir-lang/
[2]: https://webchat.freenode.net/?channels=#elixir-lang
[3]: https://www.freenode.net
+1 -1
View File
@@ -1,6 +1,6 @@
### Precheck
* Do not use the issues tracker for help or support (try Elixir Forum, Stack Overflow, IRC, etc.)
* Do not use the issue tracker for help or support (try Elixir Forum, Stack Overflow, IRC, etc.)
* For proposing a new feature, please start a discussion on the Elixir Core mailing list: https://groups.google.com/group/elixir-lang-core
* For bugs, do a quick search and make sure the bug has not yet been reported
* Please disclose security vulnerabilities privately at elixir-security@googlegroups.com
+42 -5
View File
@@ -1,9 +1,9 @@
PREFIX ?= /usr/local
SHARE_PREFIX ?= $(PREFIX)/share
MAN_PREFIX ?= $(SHARE_PREFIX)/man
CANONICAL := master/ # master/ or vMAJOR.MINOR/
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict --warnings-as-errors
ERLC := erlc -I lib/elixir/include +warnings_as_errors
CANONICAL := v1.9/ # master/ or vMAJOR.MINOR/
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC := erlc -I lib/elixir/include $(ERLC_OPTS)
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
GENERATE_APP := $(CURDIR)/lib/elixir/generate_app.escript
VERSION := $(strip $(shell cat VERSION))
@@ -16,8 +16,10 @@ INSTALL_DATA = $(INSTALL) -m644
INSTALL_PROGRAM = $(INSTALL) -m755
GIT_REVISION = $(strip $(shell git rev-parse HEAD 2> /dev/null ))
GIT_TAG = $(strip $(shell head="$(call GIT_REVISION)"; git tag --points-at $$head 2> /dev/null | tail -1) )
SOURCE_DATE_EPOCH_PATH = lib/elixir/tmp/ebin_reproducible
SOURCE_DATE_EPOCH_FILE = $(SOURCE_DATE_EPOCH_PATH)/SOURCE_DATE_EPOCH
.PHONY: install compile erlang elixir unicode app build_plt clean_plt dialyze test clean clean_residual_files install_man clean_man docs Docs.zip Precompiled.zip zips
.PHONY: install compile erlang elixir unicode app build_plt clean_plt dialyze test check_reproducible clean clean_residual_files install_man clean_man docs Docs.zip Precompiled.zip zips
.NOTPARALLEL: compile
#==> Functions
@@ -46,6 +48,18 @@ test_$(1): compile $(1)
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
endef
define WRITE_SOURCE_DATE_EPOCH
$(shell mkdir -p $(SOURCE_DATE_EPOCH_PATH) && bin/elixir -e \
'IO.puts System.build_info()[:date] \
|> DateTime.from_iso8601() \
|> elem(1) \
|> DateTime.to_unix()' > $(SOURCE_DATE_EPOCH_FILE))
endef
define READ_SOURCE_DATE_EPOCH
$(strip $(shell cat $(SOURCE_DATE_EPOCH_FILE)))
endef
#==> Compilation tasks
APP := lib/elixir/ebin/elixir.app
@@ -113,6 +127,29 @@ install: compile
done
$(MAKE) install_man
check_reproducible: compile
$(Q) echo "==> Checking for reproducible builds..."
$(Q) rm -rf lib/*/tmp/ebin_reproducible/
$(call WRITE_SOURCE_DATE_EPOCH)
$(Q) mkdir -p lib/elixir/tmp/ebin_reproducible/ \
lib/eex/tmp/ebin_reproducible/ \
lib/iex/tmp/ebin_reproducible/ \
lib/logger/tmp/ebin_reproducible/ \
lib/mix/tmp/ebin_reproducible/
$(Q) mv lib/elixir/ebin/* lib/elixir/tmp/ebin_reproducible/
$(Q) mv lib/eex/ebin/* lib/eex/tmp/ebin_reproducible/
$(Q) mv lib/iex/ebin/* lib/iex/tmp/ebin_reproducible/
$(Q) mv lib/logger/ebin/* lib/logger/tmp/ebin_reproducible/
$(Q) mv lib/mix/ebin/* lib/mix/tmp/ebin_reproducible/
SOURCE_DATE_EPOCH=$(call READ_SOURCE_DATE_EPOCH) $(MAKE) compile
$(Q) echo "Diffing..."
$(Q) diff -r lib/elixir/ebin/ lib/elixir/tmp/ebin_reproducible/
$(Q) diff -r lib/eex/ebin/ lib/eex/tmp/ebin_reproducible/
$(Q) diff -r lib/iex/ebin/ lib/iex/tmp/ebin_reproducible/
$(Q) diff -r lib/logger/ebin/ lib/logger/tmp/ebin_reproducible/
$(Q) diff -r lib/mix/ebin/ lib/mix/tmp/ebin_reproducible/
$(Q) echo "Builds are reproducible"
clean:
rm -rf ebin
rm -rf lib/*/ebin
@@ -190,7 +227,7 @@ Precompiled.zip: build_man compile
zips: Precompiled.zip Docs.zip
@ echo ""
@ echo "## Checksums"
@ echo "### Checksums"
@ echo ""
@ shasum -a 1 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA1:"
@ shasum -a 512 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA512:"
+2 -2
View File
@@ -11,7 +11,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@@ -27,7 +27,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
+54 -36
View File
@@ -2,6 +2,7 @@
=========
[![Travis build](https://secure.travis-ci.org/elixir-lang/elixir.svg?branch=master
"Build Status")](https://travis-ci.org/elixir-lang/elixir)
[![Windows build](https://ci.appveyor.com/api/projects/status/macwuxq7aiiv61g1?svg=true)](https://ci.appveyor.com/project/josevalim/elixir)
Elixir is a dynamic, functional language designed for building scalable
and maintainable applications.
@@ -9,13 +10,28 @@ and maintainable applications.
For more about Elixir, installation and documentation,
[check Elixir's website](https://elixir-lang.org/).
## Announcements
## Policies
New releases are announced in the [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
New releases are announced in the [announcement mailing list][8].
You can subscribe by sending an email to elixir-lang-ann+subscribe@googlegroups.com and replying to the confirmation email.
All security releases [will be tagged with `[security]`][10]. For more information, please read our [Security Policy][9].
All interactions in our official communication channels follow our [Code of Conduct][1].
## Bug reports
For reporting bugs, [visit our issue tracker][2] and follow the steps
for reporting a new issue. **Please disclose security vulnerabilities
privately at elixir-security@googlegroups.com**.
## Compiling from source
To run Elixir from source, clone this repository to your machine, compile and test it:
For the many different ways to install Elixir,
[see our installation instructions on the website](https://elixir-lang.org/install.html).
To compile from source, you can follow the steps below.
First, [install Erlang](https://elixir-lang.org/install.html#installing-erlang). Then clone this repository to your machine, compile and test it:
```sh
git clone https://github.com/elixir-lang/elixir.git
@@ -31,10 +47,9 @@ If Elixir fails to build (specifically when pulling in a new version via
`git`), be sure to remove any previous build artifacts by running
`make clean`, then `make test`.
If tests pass, you are ready to move on to the [Getting Started guide][1]
or to try Interactive Elixir by running `bin/iex` in your terminal.
If tests pass, you can use Interactive Elixir by running `bin/iex` in your terminal.
However, if tests fail, it is likely you have an outdated Erlang/OTP version
However, if tests fail, it is likely that you have an outdated Erlang/OTP version
(Elixir requires Erlang/OTP 20.0 or later). You can check your Erlang/OTP version
by calling `erl` in the command line. You will see some information as follows:
@@ -43,12 +58,6 @@ by calling `erl` in the command line. You will see some information as follows:
If you have properly set up your dependencies and tests still fail,
you may want to open up a bug report, as explained next.
## Bug reports
For reporting bugs, [visit our issues tracker][2] and follow the steps
for reporting a new issue. **Please disclose security vulnerabilities
privately at elixir-security@googlegroups.com**.
## Proposing new features
For proposing new features, please start a discussion in the
@@ -56,17 +65,14 @@ For proposing new features, please start a discussion in the
to argue and explain why a feature is useful and how it will impact the
codebase and the community.
Once a proposal is accepted, it will be added to [the issues tracker][2].
The issues tracker focuses on *actionable items* and it holds a list of
Once a proposal is accepted, it will be added to [the issue tracker][2].
The issue tracker focuses on *actionable items* and it holds a list of
upcoming enhancements and pending bugs. All entries in the tracker are
tagged for clarity and to ease collaboration.
Features and bug fixes that have already been merged and will be included
in the next release are marked as "closed" in the issues tracker and are
added to the [CHANGELOG](CHANGELOG.md).
Finally, remember all interactions in our official spaces follow our
[Code of Conduct][7].
in the next release are marked as "closed" in the issue tracker and are
added to the [changelog][7].
## Contributing
@@ -74,20 +80,20 @@ We welcome everyone to contribute to Elixir. To do so, there are a few
things you need to know about the code. First, Elixir code is divided
in applications inside the `lib` folder:
* `elixir` - Contains Elixir's kernel and stdlib
* `elixir` - Elixir's kernel and standard library
* `eex` - Template engine that allows you to embed Elixir
* `eex` - EEx is the template engine that allows you to embed Elixir
* `ex_unit` - Simple test framework that ships with Elixir
* `ex_unit` - ExUnit is a simple test framework that ships with Elixir
* `iex` - IEx, Elixir's interactive shell
* `iex` - IEx stands for Interactive Elixir: Elixir's interactive shell
* `logger` - The built-in logger
* `logger` - Logger is the built-in logger
* `mix` - Elixir's build tool
* `mix` - Mix is Elixir's build tool
You can run all tests in the root directory with `make test` and you can
also run tests for a specific framework `make test_#{NAME}`, for example,
also run tests for a specific framework `make test_#{APPLICATION}`, for example,
`make test_ex_unit`. If you just changed something in the Elixir's standard
library, you can run only that portion through `make test_stdlib`.
@@ -120,7 +126,7 @@ make clean_elixir compile
Similarly, if you can't get Elixir to compile or the tests to pass after
updating an existing checkout, run `make clean compile`. You can check
[the official build status on Travis-CI](https://travis-ci.org/elixir-lang/elixir).
More tasks can be found by reading the [Makefile](./Makefile).
More tasks can be found by reading the [Makefile](Makefile).
With tests running and passing, you are ready to contribute to Elixir and
[send a pull request](https://help.github.com/articles/using-pull-requests/).
@@ -152,7 +158,8 @@ another team member can merge it.
When the review finishes, your pull request will be squashed and merged
into the repository. If you have carefully organized your commits and
believe they should be merged without squashing, leave a comment.
believe they should be merged without squashing, please mention it in
a comment.
## Building documentation
@@ -163,7 +170,13 @@ to be installed and built alongside Elixir:
# After cloning and compiling Elixir, in its parent directory:
git clone git://github.com/elixir-lang/ex_doc.git
cd ex_doc && ../elixir/bin/mix do deps.get, compile
cd ../elixir && make docs
```
Now go back to Elixir's root directory and run:
```sh
make docs # to generate HTML pages
make docs DOCS_FORMAT=epub # to generate EPUB documents
```
This will produce documentation sets for `elixir`, `mix`, etc. under
@@ -172,25 +185,30 @@ the `doc` directory. If you are planning to contribute documentation,
## Development links
* [Elixir Getting Started guide][1]
* [Elixir Documentation][6]
* [Elixir Core Mailing list (development)][3]
* [Issues tracker][2]
* [Code of Conduct][7]
* [Announcement mailing list][8]
* [Code of Conduct][1]
* [Issue tracker][2]
* [Changelog][7]
* [Security Policy][9]
* **[#elixir-lang][4]** on [Freenode][5] IRC
[1]: https://elixir-lang.org/getting-started/introduction.html
[1]: CODE_OF_CONDUCT.md
[2]: https://github.com/elixir-lang/elixir/issues
[3]: https://groups.google.com/group/elixir-lang-core
[4]: https://webchat.freenode.net/?channels=#elixir-lang
[5]: http://www.freenode.net
[5]: https://www.freenode.net
[6]: https://elixir-lang.org/docs.html
[7]: CODE_OF_CONDUCT.md
[7]: CHANGELOG.md
[8]: https://groups.google.com/group/elixir-lang-ann
[9]: SECURITY.md
[10]: https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date
## License
"Elixir" and the Elixir logo are copyright (c) 2012 Plataformatec.
Elixir source code is released under Apache 2 License.
Elixir source code is released under Apache License 2.0.
Check [NOTICE](NOTICE) and [LICENSE](LICENSE) files for more information.
+3 -3
View File
@@ -20,9 +20,9 @@
9. Publish new zips with `make zips`, upload `Precompiled.zip` and `Docs.zip` to GitHub Releases, and include SHAs+CHANGELOG
10. Add the release to `elixir.csv` and `_data/elixir-versions.yml` files in `elixir-lang/elixir-lang.github.com`
10. Add the release to `elixir.csv` (all releases) and `_data/elixir-versions.yml` (except for RCs) files in `elixir-lang/elixir-lang.github.com`
11. Send an e-mail to elixir-lang-ann@googlegroups.com with title "Elixir vVERSION released". The body should be a link to the Release page on GitHub. If it is a security release, prefix the title with the `[security]` tag
11. Send an e-mail to elixir-lang-ann@googlegroups.com with title "Elixir vVERSION released". The body should be a link to the Release page on GitHub and the checksums. If it is a security release, prefix the title with the `[security]` tag
## Creating a new vMAJOR.MINOR branch
@@ -30,7 +30,7 @@
1. Set `CANONICAL=` in /Makefile
2. Update tables in "Compatibility and Deprecations"
2. Update tables in /SECURITY.md and "Compatibility and Deprecations"
3. Commit "Prepare vMAJOR.MINOR for release"
+23
View File
@@ -0,0 +1,23 @@
# Security Policy
## Supported versions
Elixir applies bug fixes only to the latest minor branch. Security patches are available for the last 5 minor branches:
| Elixir version | Support
| -------------- | ------------------------------
| 1.9 | Bug fixes and security patches
| 1.8 | Security patches only
| 1.7 | Security patches only
| 1.6 | Security patches only
| 1.5 | Security patches only
## Announcements
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). You can subscribe by sending an email to elixir-lang-ann+subscribe@googlegroups.com and replying to the confirmation email.
All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
## Reporting a vulnerability
Please disclose security vulnerabilities privately at elixir-security@googlegroups.com
+1 -1
View File
@@ -1 +1 @@
1.8.0-dev
1.9.0
+169 -63
View File
@@ -1,31 +1,56 @@
#!/bin/sh
set -e
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "Usage: `basename $0` [options] [.exs file] [data]
echo "Usage: $(basename "$0") [options] [.exs file] [data]
-e COMMAND Evaluates the given command (*)
-r FILE Requires the given files/patterns (*)
-S SCRIPT   Finds and executes the given script in PATH
-pr FILE Requires the given files/patterns in parallel (*)
-pa PATH Prepends the given path to Erlang code path (*)
-pz PATH Appends the given path to Erlang code path (*)
## General options
--app APP Starts the given app and its dependencies (*)
--cookie COOKIE Sets a cookie for this distributed node
--detached Starts the Erlang VM detached from console
--erl SWITCHES Switches to be passed down to Erlang (*)
--help, -h Prints this message and exits
--hidden Makes a hidden node
--logger-otp-reports BOOL Enables or disables OTP reporting
--logger-sasl-reports BOOL Enables or disables SASL reporting
--name NAME Makes and assigns a name to the distributed node
--no-halt Does not halt the Erlang VM after execution
--sname NAME Makes and assigns a short name to the distributed node
--version, -v Prints Elixir version and exits
--werl Uses Erlang's Windows shell GUI (Windows only)
-e \"COMMAND\" Evaluates the given command (*)
-h, --help Prints this message and exits
-r \"FILE\" Requires the given files/patterns (*)
-S SCRIPT   Finds and executes the given script in \$PATH
-pr \"FILE\" Requires the given files/patterns in parallel (*)
-pa \"PATH\" Prepends the given path to Erlang code path (*)
-pz \"PATH\" Appends the given path to Erlang code path (*)
-v, --version Prints Elixir version and exits
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the Erlang runtime using ELIXIR_ERL_OPTIONS or --erl" >&2
--app APP Starts the given app and its dependencies (*)
--erl \"SWITCHES\" Switches to be passed down to Erlang (*)
--eval \"COMMAND\" Evaluates the given command, same as -e (*)
--logger-otp-reports BOOL Enables or disables OTP reporting
--logger-sasl-reports BOOL Enables or disables SASL reporting
--no-halt Does not halt the Erlang VM after execution
--werl Uses Erlang's Windows shell GUI (Windows only)
Options given after the .exs file or -- are passed down to the executed code.
Options can be passed to the Erlang runtime using \$ELIXIR_ERL_OPTIONS or --erl.
## Distribution options
The following options are related to node distribution.
--cookie COOKIE Sets a cookie for this distributed node
--hidden Makes a hidden node
--name NAME Makes and assigns a name to the distributed node
--rpc-eval NODE \"COMMAND\" Evaluates the given command on the given remote node (*)
--sname NAME Makes and assigns a short name to the distributed node
## Release options
The following options are generally used under releases.
--boot \"FILE\" Uses the given FILE.boot to start the system
--boot-var VAR \"VALUE\" Makes \$VAR available as VALUE to FILE.boot (*)
--erl-config \"FILE\" Loads configuration in FILE.config written in Erlang (*)
--pipe-to \"PIPEDIR\" \"LOGDIR\" Starts the Erlang VM as a named PIPEDIR and LOGDIR
--vm-args \"FILE\" Passes the contents in file as arguments to the VM
--pipe-to starts Elixir detached from console (Unix-like only).
It will attempt to create PIPEDIR and LOGDIR if they don't exist.
See run_erl to learn more. To reattach, run: to_erl PIPEDIR.
** Options marked with (*) can be given more than once." >&2
exit 1
fi
@@ -35,70 +60,142 @@ readlink_f () {
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
echo "$(pwd -P)/$filename"
fi
}
MODE="elixir"
ERL_EXEC="erl"
# Stores static erlang arguments and --erl (which is passed as is)
ERL=""
I=1
while [ $I -le $# ]; do
# Stores erl arguments preserving spaces/quotes (mimics an array)
erl () {
eval "E${E}=\$1"
E=$((E + 1))
}
# Checks if a string starts with prefix. Usage: starts_with "$STRING" "$PREFIX"
starts_with () {
case $1 in
"$2"*) true;;
*) false;;
esac
}
ERL_EXEC="erl"
MODE="elixir"
I=1
E=0
LENGTH=$#
set -- "$@" -extra
while [ $I -le $LENGTH ]; do
S=1
eval "PEEK=\${$I}"
case "$PEEK" in
case "$1" in
+iex)
set -- "$@" "$1"
MODE="iex"
;;
+elixirc)
set -- "$@" "$1"
MODE="elixirc"
;;
-v|--compile|--no-halt)
-v|--no-halt)
set -- "$@" "$1"
;;
-e|-r|-pr|-pa|-pz|--remsh|--app)
-e|-r|-pr|-pa|-pz|--app|--eval|--remsh|--dot-iex)
S=2
set -- "$@" "$1" "$2"
;;
--detached|--hidden)
ERL="$ERL `echo $PEEK | cut -c 2-`"
--rpc-eval)
S=3
set -- "$@" "$1" "$2" "$3"
;;
--cookie)
I=$(expr $I + 1)
eval "VAL=\${$I}"
ERL="$ERL -setcookie "$VAL""
--detached)
echo "warning: the --detached option is deprecated" >&2
ERL="$ERL -detached"
;;
--sname|--name)
I=$(expr $I + 1)
eval "VAL=\${$I}"
ERL="$ERL `echo $PEEK | cut -c 2-` "$VAL""
--hidden)
ERL="$ERL -hidden"
;;
--logger-otp-reports)
I=$(expr $I + 1)
eval "VAL=\${$I}"
if [ "$VAL" = 'true' ] || [ "$VAL" = 'false' ]; then
ERL="$ERL -logger handle_otp_reports "$VAL""
S=2
if [ "$2" = 'true' ] || [ "$2" = 'false' ]; then
ERL="$ERL -logger handle_otp_reports $2"
fi
;;
--logger-sasl-reports)
I=$(expr $I + 1)
eval "VAL=\${$I}"
if [ "$VAL" = 'true' ] || [ "$VAL" = 'false' ]; then
ERL="$ERL -logger handle_sasl_reports "$VAL""
S=2
if [ "$2" = 'true' ] || [ "$2" = 'false' ]; then
ERL="$ERL -logger handle_sasl_reports $2"
fi
;;
--erl)
I=$(expr $I + 1)
eval "VAL=\${$I}"
ERL="$ERL "$VAL""
S=2
ERL="$ERL $2"
;;
--cookie)
S=2
erl "-setcookie"
erl "$2"
;;
--sname|--name)
S=2
erl "$(echo "$1" | cut -c 2-)"
erl "$2"
;;
--erl-config)
S=2
erl "-config"
erl "$2"
;;
--vm-args)
S=2
erl "-args_file"
erl "$2"
;;
--boot)
S=2
erl "-boot"
erl "$2"
;;
--boot-var)
S=3
erl "-boot_var"
erl "$2"
erl "$3"
;;
--pipe-to)
S=3
RUN_ERL_PIPE="$2"
RUN_ERL_LOG="$3"
if [ "$(starts_with "$RUN_ERL_PIPE" "-")" ]; then
echo "--pipe-to : PIPEDIR cannot be a switch" >&2 && exit 1
elif [ "$(starts_with "$RUN_ERL_LOG" "-")" ]; then
echo "--pipe-to : LOGDIR cannot be a switch" >&2 && exit 1
fi
;;
--werl)
USE_WERL=true
if [ "$OS" = "Windows_NT" ]; then ERL_EXEC="werl"; fi
;;
*)
while [ $I -le $LENGTH ]; do
I=$((I + 1))
set -- "$@" "$1"
shift
done
break
;;
esac
I=$(expr $I + $S)
I=$((I + S))
shift $S
done
I=$((E - 1))
while [ $I -ge 0 ]; do
eval "VAL=\$E$I"
set -- "$VAL" "$@"
I=$((I - 1))
done
SELF=$(readlink_f "$0")
@@ -107,17 +204,26 @@ SCRIPT_PATH=$(dirname "$SELF")
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "$SCRIPT_PATH"); fi
if [ "$MODE" != "iex" ]; then ERL="-noshell -s elixir start_cli $ERL"; fi
# Check for terminal support
if [ "$OS" != "Windows_NT" ]; then
if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi
fi
if [ "$OS" = "Windows_NT" ] && [ $USE_WERL ]; then
ERL_EXEC="werl"
ERTS_BIN=
set -- "$ERTS_BIN$ERL_EXEC" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL "$@"
if [ -n "$RUN_ERL_PIPE" ]; then
ESCAPED=""
for PART in "$@"; do
ESCAPED="$ESCAPED $(echo "$PART" | sed 's/[^a-zA-Z0-9_\-\/]/\\&/g')"
done
mkdir -p "$RUN_ERL_PIPE"
mkdir -p "$RUN_ERL_LOG"
ERL_EXEC="run_erl"
set -- "$ERTS_BIN$ERL_EXEC" -daemon "$RUN_ERL_PIPE/" "$RUN_ERL_LOG/" "$ESCAPED"
fi
if [ -z "$ERL_PATH" ]; then
ERL_PATH="$ERL_EXEC"
fi
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
if [ -n "$ELIXIR_CLI_DRY_RUN" ]; then
echo "$@"
else
exec "$@"
fi
+118 -68
View File
@@ -1,5 +1,5 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
setlocal
setlocal enabledelayedexpansion
if ""%1""=="""" goto documentation
if /I ""%1""==""--help"" goto documentation
if /I ""%1""==""-h"" goto documentation
@@ -10,105 +10,155 @@ goto parseopts
:documentation
echo Usage: %~nx0 [options] [.exs file] [data]
echo.
echo -e COMMAND Evaluates the given command (*)
echo -r FILE Requires the given files/patterns (*)
echo -S SCRIPT Finds and executes the given script in PATH
echo -pr FILE Requires the given files/patterns in parallel (*)
echo -pa PATH Prepends the given path to Erlang code path (*)
echo -pz PATH Appends the given path to Erlang code path (*)
echo ## General options
echo.
echo --app APP Starts the given app and its dependencies (*)
echo --cookie COOKIE Sets a cookie for this distributed node
echo --detached Starts the Erlang VM detached from console
echo --erl SWITCHES Switches to be passed down to Erlang (*)
echo --help, -h Prints this message and exits
echo --hidden Makes a hidden node
echo --logger-otp-reports BOOL Enables or disables OTP reporting
echo --logger-sasl-reports BOOL Enables or disables SASL reporting
echo --name NAME Makes and assigns a name to the distributed node
echo --no-halt Does not halt the Erlang VM after execution
echo --sname NAME Makes and assigns a short name to the distributed node
echo --version, -v Prints Elixir version and exits
echo --werl Uses Erlang's Windows shell GUI
echo -e "COMMAND" Evaluates the given command (*)
echo -h, --help Prints this message and exits
echo -r "FILE" Requires the given files/patterns (*)
echo -S SCRIPT Finds and executes the given script in $PATH
echo -pr "FILE" Requires the given files/patterns in parallel (*)
echo -pa "PATH" Prepends the given path to Erlang code path (*)
echo -pz "PATH" Appends the given path to Erlang code path (*)
echo -v, --version Prints Elixir version and exits
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code
echo ** Options can be passed to the Erlang runtime using ELIXIR_ERL_OPTIONS or --erl
echo --app APP Starts the given app and its dependencies (*)
echo --erl "SWITCHES" Switches to be passed down to Erlang (*)
echo --eval "COMMAND" Evaluates the given command, same as -e (*)
echo --logger-otp-reports BOOL Enables or disables OTP reporting
echo --logger-sasl-reports BOOL Enables or disables SASL reporting
echo --no-halt Does not halt the Erlang VM after execution
echo --werl Uses Erlang's Windows shell GUI (Windows only)
echo.
echo Options given after the .exs file or -- are passed down to the executed code.
echo Options can be passed to the Erlang runtime using $ELIXIR_ERL_OPTIONS or --erl.
echo.
echo ## Distribution options
echo.
echo The following options are related to node distribution.
echo.
echo --cookie COOKIE Sets a cookie for this distributed node
echo --hidden Makes a hidden node
echo --name NAME Makes and assigns a name to the distributed node
echo --rpc-eval NODE "COMMAND" Evaluates the given command on the given remote node (*)
echo --sname NAME Makes and assigns a short name to the distributed node
echo.
echo ## Release options
echo.
echo The following options are generally used under releases.
echo.
echo --boot "FILE" Uses the given FILE.boot to start the system
echo --boot-var VAR "VALUE" Makes $VAR available as VALUE to FILE.boot (*)
echo --erl-config "FILE" Loads configuration in FILE.config written in Erlang (*)
echo --vm-args "FILE" Passes the contents in file as arguments to the VM
echo.
echo --pipe-to is not supported on Windows. If set, Elixir won't boot.
echo.
echo ** Options marked with (*) can be given more than once.
goto end
:parseopts
rem Parameters for Elixir
set parsElixir=
rem Parameters for Erlang
set parsErlang=
rem Make sure we keep a copy of all parameters
set allPars=%*
rem Get the original path name from the batch file
set originPath=%~dp0
rem Optional parameters before the "-extra" parameter
set beforeExtra=
rem Option which determines whether or not to use werl vs erl
set useWerl=0
rem Option which determines whether the loop is over
set endLoop=0
rem Designates which mode / Elixir component to run as
set runMode="elixir"
rem Designates the path to the current script
set SCRIPT_PATH=%~dp0
rem Designates the path to the ERTS system
set ERTS_BIN=
rem Recursive loop called for each parameter that parses the cmd line parameters
:startloop
set par="%1"
shift
if "%par%"=="" (
rem if no parameters defined
set "par=%~1"
if "!par!"=="" (
rem skip if no parameter
goto expand_erl_libs
)
if "%par%"=="""" (
rem if no parameters defined - special case for parameter that is already quoted
goto expand_erl_libs
shift
set par="!par:"=\"!"
if !endLoop! == 1 (
set parsElixir=!parsElixir! !par!
goto startloop
)
rem ******* EXECUTION OPTIONS **********************
if "%par%"==""--werl"" (set useWerl=1)
if "%par%"==""+iex"" (set runMode="iex")
if !par!=="--werl" (set useWerl=1 && goto startloop)
if !par!=="+iex" (set parsElixir=!parsElixir! +iex && set runMode="iex" && goto startloop)
if !par!=="+elixirc" (set parsElixir=!parsElixir! +elixirc && set runMode="elixirc" && goto startloop)
rem ******* EVAL PARAMETERS ************************
if ""==!par:-e=! (
set "VAR=%~1"
set parsElixir=!parsElixir! -e "!VAR:"=\"!"
shift
goto startloop
)
if ""==!par:--eval=! (
set "VAR=%~1"
set parsElixir=!parsElixir! --eval "!VAR:"=\"!"
shift
goto startloop
)
if ""==!par:--rpc-eval=! (
set "VAR=%~2"
set parsElixir=!parsElixir! --rpc-eval %1 "!VAR:"=\"!"
shift
shift
goto startloop
)
rem ******* ELIXIR PARAMETERS **********************
rem Note: we don't have to do anything with options that don't take an argument
if """"=="%par:-e=%" (shift)
if """"=="%par:-r=%" (shift)
if """"=="%par:-pr=%" (shift)
if """"=="%par:-pa=%" (shift)
if """"=="%par:-pz=%" (shift)
if """"=="%par:--app=%" (shift)
if """"=="%par:--remsh=%" (shift)
if ""==!par:-r=! (set "parsElixir=!parsElixir! -r %1" && shift && goto startloop)
if ""==!par:-pr=! (set "parsElixir=!parsElixir! -pr %1" && shift && goto startloop)
if ""==!par:-pa=! (set "parsElixir=!parsElixir! -pa %1" && shift && goto startloop)
if ""==!par:-pz=! (set "parsElixir=!parsElixir! -pz %1" && shift && goto startloop)
if ""==!par:-v=! (set "parsElixir=!parsElixir! -v" && goto startloop)
if ""==!par:--app=! (set "parsElixir=!parsElixir! --app %1" && shift && goto startloop)
if ""==!par:--no-halt=! (set "parsElixir=!parsElixir! --no-halt" && goto startloop)
if ""==!par:--remsh=! (set "parsElixir=!parsElixir! --remsh %1" && shift && goto startloop)
if ""==!par:--dot-iex=! (set "parsElixir=!parsElixir! --dot-iex %1" && shift && goto startloop)
rem ******* ERLANG PARAMETERS **********************
if """"=="%par:--detached=%" (set parsErlang=%parsErlang% -detached)
if """"=="%par:--hidden=%" (set parsErlang=%parsErlang% -hidden)
if """"=="%par:--cookie=%" (set parsErlang=%parsErlang% -setcookie %1 && shift)
if """"=="%par:--sname=%" (set parsErlang=%parsErlang% -sname %1 && shift)
if """"=="%par:--name=%" (set parsErlang=%parsErlang% -name %1 && shift)
if """"=="%par:--logger-otp-reports=%" (set parsErlang=%parsErlang% -logger handle_otp_reports %1 && shift)
if """"=="%par:--logger-sasl-reports=%" (set parsErlang=%parsErlang% -logger handle_sasl_reports %1 && shift)
if """"=="%par:--erl=%" (set "beforeExtra=%beforeExtra% %~1" && shift)
goto:startloop
if ""==!par:--boot=! (set "parsErlang=!parsErlang! -boot %1" && shift && goto startloop)
if ""==!par:--boot-var=! (set "parsErlang=!parsErlang! -boot_var %1 %2" && shift && shift && goto startloop)
if ""==!par:--cookie=! (set "parsErlang=!parsErlang! -setcookie %1" && shift && goto startloop)
if ""==!par:--hidden=! (set "parsErlang=!parsErlang! -hidden" && goto startloop)
if ""==!par:--detached=! (set "parsErlang=!parsErlang! -detached" && echo warning: the --detached option is deprecated && goto startloop)
if ""==!par:--erl-config=! (set "parsErlang=!parsErlang! -config %1" && shift && goto startloop)
if ""==!par:--logger-otp-reports=! (set "parsErlang=!parsErlang! -logger handle_otp_reports %1" && shift && goto startloop)
if ""==!par:--logger-sasl-reports=! (set "parsErlang=!parsErlang! -logger handle_sasl_reports %1" && shift && goto startloop)
if ""==!par:--name=! (set "parsErlang=!parsErlang! -name %1" && shift && goto startloop)
if ""==!par:--sname=! (set "parsErlang=!parsErlang! -sname %1" && shift && goto startloop)
if ""==!par:--vm-args=! (set "parsErlang=!parsErlang! -args_file %1" && shift && goto startloop)
if ""==!par:--erl=! (set "beforeExtra=!beforeExtra! %~1" && shift && goto startloop)
if ""==!par:--pipe-to=! (echo --pipe-to : Option is not supported on Windows && goto end)
set endLoop=1
set parsElixir=!parsElixir! !par!
goto startloop
rem ******* assume all pre-params are parsed ********************
:expand_erl_libs
rem ******* expand all ebin paths as Windows does not support the ..\*\ebin wildcard ********************
setlocal enabledelayedexpansion
rem expand all ebin paths as Windows does not support the ..\*\ebin wildcard
set ext_libs=
for /d %%d in ("%originPath%..\lib\*.") do (
for /d %%d in ("!SCRIPT_PATH!..\lib\*.") do (
set ext_libs=!ext_libs! -pa "%%~fd\ebin"
)
setlocal disabledelayedexpansion
:run
if not %runMode% == "iex" (
set beforeExtra=-noshell -s elixir start_cli %beforeExtra%
if not !runMode! == "iex" (
set beforeExtra=-noshell -s elixir start_cli !beforeExtra!
)
if %useWerl% equ 1 (
start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
if defined useWerl (
start !ERTS_BIN!werl.exe !ext_libs! !ELIXIR_ERL_OPTIONS! !parsErlang! !beforeExtra! -extra !parsElixir!
) else (
erl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
!ERTS_BIN!erl.exe !ext_libs! !ELIXIR_ERL_OPTIONS! !parsErlang! !beforeExtra! -extra !parsElixir!
)
:end
endlocal
endlocal
+9 -7
View File
@@ -1,20 +1,22 @@
#!/bin/sh
set -e
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
echo "Usage: $(basename "$0") [elixir switches] [compiler switches] [.ex files]
-h, --help Prints this message and exits
-o The directory to output compiled files
-v, --version Prints Elixir version and exits
--help, -h Prints this message and exits
--ignore-module-conflict Does not emit warnings if a module was previously defined
--no-debug-info Does not attach debug info to compiled modules
--no-docs Does not attach documentation to compiled modules
--verbose Prints compilation status
--version, -v Prints Elixir version and exits
--warnings-as-errors Treats warnings as errors and return non-zero exit code
** Options given after -- are passed down to the executed code
** Options can be passed to the Erlang runtime using ELIXIR_ERL_OPTIONS
** Options can be passed to the Erlang compiler using ERL_COMPILER_OPTIONS" >&2
Options given after -- are passed down to the executed code.
Options can be passed to the Erlang runtime using \$ELIXIR_ERL_OPTIONS.
Options can be passed to the Erlang compiler using \$ERL_COMPILER_OPTIONS." >&2
exit 1
fi
@@ -24,7 +26,7 @@ readlink_f () {
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
echo "$(pwd -P)/$filename"
fi
}
+9 -28
View File
@@ -1,35 +1,16 @@
#!/bin/sh
if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
echo "Usage: `basename $0` [options] [.exs file] [data]
set -e
-e COMMAND Evaluates the given command (*)
-r FILE Requires the given files/patterns (*)
-S SCRIPT   Finds and executes the given script in PATH
-pr FILE Requires the given files/patterns in parallel (*)
-pa PATH Prepends the given path to Erlang code path (*)
-pz PATH Appends the given path to Erlang code path (*)
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "Usage: $(basename "$0") [options] [.exs file] [data]
--app APP Starts the given app and its dependencies (*)
--cookie COOKIE Sets a cookie for this distributed node
--detached Starts the Erlang VM detached from console
--erl SWITCHES Switches to be passed down to Erlang (*)
--help, -h Prints this message and exits
--hidden Makes a hidden node
--logger-otp-reports BOOL Enables or disables OTP reporting
--logger-sasl-reports BOOL Enables or disables SASL reporting
--name NAME Makes and assigns a name to the distributed node
--no-halt Does not halt the Erlang VM after execution
--sname NAME Makes and assigns a short name to the distributed node
--version, -v Prints IEx version and exits
--werl Uses Erlang's Windows shell GUI (Windows only)
The following options are exclusive to IEx:
--dot-iex PATH Overrides default .iex.exs file and uses path instead;
path can be empty, then no file will be loaded
--remsh NAME Connects to a node using a remote shell
--dot-iex \"PATH\" Overrides default .iex.exs file and uses path instead;
path can be empty, then no file will be loaded
--remsh NAME Connects to a node using a remote shell
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the VM using ELIXIR_ERL_OPTIONS or --erl" >&2
It accepts all other options listed by \"elixir --help\"." >&2
exit 1
fi
@@ -39,7 +20,7 @@ readlink_f () {
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
echo "$(pwd -P)/$filename"
fi
}
+9 -28
View File
@@ -1,4 +1,4 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
setlocal
if /I ""%1""==""--help"" goto documentation
if /I ""%1""==""-h"" goto documentation
@@ -9,38 +9,19 @@ goto run
:documentation
echo Usage: %~nx0 [options] [.exs file] [data]
echo.
echo -e COMMAND Evaluates the given command (*)
echo -r FILE Requires the given files/patterns (*)
echo -S SCRIPT Finds and executes the given script in PATH
echo -pr FILE Requires the given files/patterns in parallel (*)
echo -pa PATH Prepends the given path to Erlang code path (*)
echo -pz PATH Appends the given path to Erlang code path (*)
echo The following options are exclusive to IEx:
echo.
echo --app APP Starts the given app and its dependencies (*)
echo --cookie COOKIE Sets a cookie for this distributed node
echo --detached Starts the Erlang VM detached from console
echo --erl SWITCHES Switches to be passed down to Erlang (*)
echo --help, -h Prints this message and exits
echo --hidden Makes a hidden node
echo --logger-otp-reports BOOL Enables or disables OTP reporting
echo --logger-sasl-reports BOOL Enables or disables SASL reporting
echo --name NAME Makes and assigns a name to the distributed node
echo --no-halt Does not halt the Erlang VM after execution
echo --sname NAME Makes and assigns a short name to the distributed node
echo --version, -v Prints IEx version and exits
echo --werl Uses Erlang's Windows shell GUI (Windows only)
echo --dot-iex "PATH" Overrides default .iex.exs file and uses path instead;
echo path can be empty, then no file will be loaded
echo --remsh NAME Connects to a node using a remote shell
echo --werl Uses Erlang's Windows shell GUI (Windows only)
echo.
echo --dot-iex PATH Overrides default .iex.exs file and uses path instead;
echo path can be empty, then no file will be loaded
echo --remsh NAME Connects to a node using a remote shell
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code
echo ** Options can be passed to the Erlang VM using ELIXIR_ERL_OPTIONS or --erl
echo Set the IEX_WITH_WERL environment variable to always use werl.
echo It accepts all other options listed by "elixir --help".
goto end
:run
@if defined IEX_WITH_WERL (@set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
if defined IEX_WITH_WERL (@set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
call "%~dp0\elixir.bat" --no-halt --erl "-noshell -user Elixir.IEx.CLI" +iex %__ELIXIR_IEX_FLAGS% %*
:end
endlocal
+8 -5
View File
@@ -43,7 +43,8 @@ defmodule EEx do
* `:file` - the file to be used in the template. Defaults to the given
file the template is read from or to "nofile" when compiling from a string.
* `:engine` - the EEx engine to be used for compilation.
* `:trim` - trims whitespace left/right of quotation tags
* `:trim` - trims whitespace left/right of quotation tags. If a quotation
tag appears on its own in a given line, line endings are also removed.
## Engine
@@ -105,7 +106,7 @@ defmodule EEx do
iex> defmodule Sample do
...> require EEx
...> EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
...> EEx.function_from_string(:def, :sample, "<%= a + b %>", [:a, :b])
...> end
iex> Sample.sample(1, 2)
"3"
@@ -141,11 +142,12 @@ defmodule EEx do
# sample.ex
defmodule Sample do
require EEx
EEx.function_from_file :def, :sample, "sample.eex", [:a, :b]
EEx.function_from_file(:def, :sample, "sample.eex", [:a, :b])
end
# iex
Sample.sample(1, 2) #=> "3"
Sample.sample(1, 2)
#=> "3"
"""
defmacro function_from_file(kind, name, file, args \\ [], options \\ []) do
@@ -207,7 +209,8 @@ defmodule EEx do
foo <%= bar %>
# iex
EEx.eval_file "sample.eex", [bar: "baz"] #=> "foo baz"
EEx.eval_file("sample.eex", bar: "baz")
#=> "foo baz"
"""
@spec eval_file(String.t(), keyword, keyword) :: any
+34 -24
View File
@@ -41,35 +41,40 @@ defmodule EEx.Compiler do
generate_buffer(rest, buffer, scope, state)
end
defp generate_buffer([{:expr, line, mark, chars} | rest], buffer, scope, state) do
defp generate_buffer([{:expr, line, mark, chars, _} | rest], buffer, scope, state) do
expr = Code.string_to_quoted!(chars, line: line, file: state.file)
buffer = state.engine.handle_expr(buffer, IO.chardata_to_string(mark), expr)
generate_buffer(rest, buffer, scope, state)
end
defp generate_buffer([{:start_expr, start_line, mark, chars} | rest], buffer, scope, state) do
{contents, line, rest} = look_ahead_text(rest, start_line, chars)
defp generate_buffer([{:start_expr, start_line, mark, chars, _} | rest], buffer, scope, state) do
{contents, line, rest} = look_ahead_middle(rest, start_line, chars)
{contents, rest} =
generate_buffer(rest, state.engine.handle_begin(buffer), [contents | scope], %{
state
| quoted: [],
line: line,
start_line: start_line
})
generate_buffer(
rest,
state.engine.handle_begin(buffer),
[contents | scope],
%{state | quoted: [], line: line, start_line: start_line}
)
buffer = state.engine.handle_expr(buffer, IO.chardata_to_string(mark), contents)
generate_buffer(rest, buffer, scope, state)
end
defp generate_buffer([{:middle_expr, line, '', chars} | rest], buffer, [current | scope], state) do
defp generate_buffer(
[{:middle_expr, line, '', chars, _} | rest],
buffer,
[current | scope],
state
) do
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
state = %{state | line: line}
generate_buffer(rest, state.engine.handle_begin(buffer), [wrapped | scope], state)
end
defp generate_buffer(
[{:middle_expr, line, modifier, chars} | t],
[{:middle_expr, line, modifier, chars, trimmed?} | t],
buffer,
[_ | _] = scope,
state
@@ -78,38 +83,43 @@ defmodule EEx.Compiler do
"unexpected beginning of EEx tag \"<%#{modifier}\" on \"<%#{modifier}#{chars}%>\", " <>
"please remove \"#{modifier}\" accordingly"
:elixir_errors.warn(line, state.file, message)
generate_buffer([{:middle_expr, line, '', chars} | t], buffer, scope, state)
:elixir_errors.erl_warn(line, state.file, message)
generate_buffer([{:middle_expr, line, '', chars, trimmed?} | t], buffer, scope, state)
# TODO: Make this an error on Elixir v2.0 since it accidentally worked previously.
# raise EEx.SyntaxError, message: message, file: state.file, line: line
end
defp generate_buffer([{:middle_expr, line, _, chars} | _], _buffer, [], state) do
defp generate_buffer([{:middle_expr, line, _, chars, _} | _], _buffer, [], state) do
raise EEx.SyntaxError,
message: "unexpected middle of expression <%#{chars}%>",
file: state.file,
line: line
end
defp generate_buffer([{:end_expr, line, '', chars} | rest], buffer, [current | _], state) do
defp generate_buffer([{:end_expr, line, '', chars, _} | rest], buffer, [current | _], state) do
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
tuples = Code.string_to_quoted!(wrapped, line: state.start_line, file: state.file)
buffer = insert_quoted(tuples, state.quoted)
{buffer, rest}
end
defp generate_buffer([{:end_expr, line, modifier, chars} | t], buffer, [_ | _] = scope, state) do
defp generate_buffer(
[{:end_expr, line, modifier, chars, trimmed?} | t],
buffer,
[_ | _] = scope,
state
) do
message =
"unexpected beginning of EEx tag \"<%#{modifier}\" on end of " <>
"expression \"<%#{modifier}#{chars}%>\", please remove \"#{modifier}\" accordingly"
:elixir_errors.warn(line, state.file, message)
generate_buffer([{:end_expr, line, '', chars} | t], buffer, scope, state)
:elixir_errors.erl_warn(line, state.file, message)
generate_buffer([{:end_expr, line, '', chars, trimmed?} | t], buffer, scope, state)
# TODO: Make this an error on Elixir v2.0 since it accidentally worked previously.
# raise EEx.SyntaxError, message: message, file: state.file, line: line
end
defp generate_buffer([{:end_expr, line, _, chars} | _], _buffer, [], state) do
defp generate_buffer([{:end_expr, line, _, chars, _} | _], _buffer, [], state) do
raise EEx.SyntaxError,
message: "unexpected end of expression <%#{chars}%>",
file: state.file,
@@ -139,10 +149,10 @@ defmodule EEx.Compiler do
{count, new_state}
end
# Look text ahead on expressions
# Look middle expressions that immediatelly follow a start_expr
defp look_ahead_text(
[{:text, text}, {:middle_expr, line, _, chars} | rest] = tokens,
defp look_ahead_middle(
[{:text, text}, {:middle_expr, line, _, chars, _} | rest] = tokens,
start,
contents
) do
@@ -153,11 +163,11 @@ defmodule EEx.Compiler do
end
end
defp look_ahead_text([{:middle_expr, line, _, chars} | rest], _start, contents) do
defp look_ahead_middle([{:middle_expr, line, _, chars, _} | rest], _start, contents) do
{contents ++ chars, line, rest}
end
defp look_ahead_text(tokens, start, contents) do
defp look_ahead_middle(tokens, start, contents) do
{contents, start, tokens}
end
+10 -1
View File
@@ -127,7 +127,7 @@ defmodule EEx.Engine do
end
@doc false
# TODO: Raise on 2.0
# TODO: Raise on v2.0
@spec fetch_assign!(Access.t(), Access.key()) :: term | nil
def fetch_assign!(assigns, key) do
case Access.fetch(assigns, key) do
@@ -158,6 +158,7 @@ defmodule EEx.Engine do
@doc false
def handle_begin(state) do
check_state!(state)
%{state | binary: [], dynamic: []}
end
@@ -168,6 +169,7 @@ defmodule EEx.Engine do
@doc false
def handle_body(state) do
check_state!(state)
%{binary: binary, dynamic: dynamic} = state
binary = {:<<>>, [], Enum.reverse(binary)}
dynamic = [binary | dynamic]
@@ -207,4 +209,11 @@ defmodule EEx.Engine do
raise EEx.SyntaxError,
"unsupported EEx syntax <%#{marker} %> (the syntax is valid but not supported by the current EEx engine)"
end
defp check_state!(%{binary: _, dynamic: _, vars_count: _}), do: :ok
defp check_state!(state) do
raise "unexpected EEx.Engine state: #{inspect(state)}. " <>
"This typically means a bug or an outdated EEx.Engine or tool"
end
end
+3 -2
View File
@@ -24,11 +24,12 @@ defmodule EEx.SmartEngine do
# sample.ex
defmodule Sample do
require EEx
EEx.function_from_file :def, :sample, "sample.eex", [:assigns]
EEx.function_from_file(:def, :sample, "sample.eex", [:assigns])
end
# iex
Sample.sample(a: 1, b: 2) #=> "3"
Sample.sample(a: 1, b: 2)
#=> "3"
"""
+46 -35
View File
@@ -4,9 +4,13 @@ defmodule EEx.Tokenizer do
@type content :: IO.chardata()
@type line :: non_neg_integer
@type marker :: '=' | '/' | '|' | ''
@type trimmed? :: boolean
@type token ::
{:text, content}
| {:expr | :start_expr | :middle_expr | :end_expr, line, marker, content}
| {:expr | :start_expr | :middle_expr | :end_expr, line, marker, content, trimmed?}
@spaces [?\s, ?\t]
@closing_brackets ')]}'
@doc """
Tokenizes the given charlist or binary.
@@ -14,10 +18,10 @@ defmodule EEx.Tokenizer do
It returns {:ok, list} with the following tokens:
* `{:text, content}`
* `{:expr, line, marker, content}`
* `{:start_expr, line, marker, content}`
* `{:middle_expr, line, marker, content}`
* `{:end_expr, line, marker, content}`
* `{:expr, line, marker, content, trimmed?}`
* `{:start_expr, line, marker, content, trimmed?}`
* `{:middle_expr, line, marker, content, trimmed?}`
* `{:end_expr, line, marker, content, trimmed?}`
Or `{:error, line, error}` in case of errors.
"""
@@ -44,7 +48,7 @@ defmodule EEx.Tokenizer do
error
{:ok, _, new_line, rest} ->
{rest, new_line, buffer} = trim_if_needed(rest, new_line, opts, buffer, acc)
{_, rest, new_line, buffer} = trim_if_needed(rest, new_line, opts, buffer, acc)
tokenize(rest, new_line, opts, buffer, acc)
end
end
@@ -58,9 +62,9 @@ defmodule EEx.Tokenizer do
{:ok, expr, new_line, rest} ->
token = token_name(expr)
{rest, new_line, buffer} = trim_if_needed(rest, new_line, opts, buffer, acc)
{trimmed?, rest, new_line, buffer} = trim_if_needed(rest, new_line, opts, buffer, acc)
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
final = {token, line, marker, Enum.reverse(expr), trimmed?}
tokenize(rest, new_line, opts, [], [final | acc])
end
end
@@ -110,25 +114,28 @@ defmodule EEx.Tokenizer do
#
# Start tokens finish with "do" and "fn ->"
# Middle tokens are marked with "->" or keywords
# End tokens contain only the end word and optionally ")"
# End tokens contain only the end word and optionally
# combinations of ")", "]" and "}".
defp token_name([h | t]) when h in [?\s, ?\t, ?)] do
defp token_name([h | t]) when h in @spaces do
token_name(t)
end
defp token_name('od' ++ [h | _]) when h in [?\s, ?\t, ?)] do
:start_expr
defp token_name('od' ++ [h | rest]) when h in @spaces or h in @closing_brackets do
case tokenize_rest(rest) do
{:ok, [{:end, _} | _]} -> :middle_expr
_ -> :start_expr
end
end
defp token_name('>-' ++ rest) do
rest = Enum.reverse(rest)
case tokenize_rest(rest) do
{:ok, [{:end, _} | _]} ->
:middle_expr
# Tokenize the remaining passing check_terminators as
# false, which relax the tokenizer to not error on
# unmatched pairs. Then, we check if there is a "fn"
# token and, if so, it is not followed by an "end"
# token. If this is the case, we are on a start expr.
case :elixir_tokenizer.tokenize(rest, 1, file: "eex", check_terminators: false) do
# Check if there is a "fn" token and, if so, it is not
# followed by an "end" token. If this is the case, we
# are on a start expr.
{:ok, tokens} ->
tokens = Enum.reverse(tokens)
fn_index = fn_index(tokens)
@@ -148,10 +155,19 @@ defmodule EEx.Tokenizer do
defp token_name('retfa' ++ t), do: check_spaces(t, :middle_expr)
defp token_name('hctac' ++ t), do: check_spaces(t, :middle_expr)
defp token_name('eucser' ++ t), do: check_spaces(t, :middle_expr)
defp token_name('dne' ++ t), do: check_spaces(t, :end_expr)
defp token_name(_) do
:expr
defp token_name(rest) do
case Enum.drop_while(rest, &(&1 in @spaces or &1 in @closing_brackets)) do
'dne' ++ t -> check_spaces(t, :end_expr)
_ -> :expr
end
end
# Tokenize the remaining passing check_terminators as false,
# which relax the tokenizer to not error on unmatched pairs.
# If the tokens start with an "end" we have a middle expr.
defp tokenize_rest(rest) do
:elixir_tokenizer.tokenize(Enum.reverse(rest), 1, file: "eex", check_terminators: false)
end
defp fn_index(tokens) do
@@ -167,7 +183,7 @@ defmodule EEx.Tokenizer do
end
defp check_spaces(string, token) do
if Enum.all?(string, &(&1 in [?\s, ?\t])) do
if Enum.all?(string, &(&1 in @spaces)) do
token
else
:expr
@@ -189,24 +205,19 @@ defmodule EEx.Tokenizer do
# only itself and whitespace, trim the whitespace around it,
# including the line break following it if there is one.
defp trim_if_needed(rest, line, opts, buffer, acc) do
original = {rest, line, buffer}
if opts[:trim] do
case {trim_left(buffer, acc), trim_right(rest, line)} do
{{true, new_buffer}, {true, new_rest, new_line}} ->
{new_rest, new_line, new_buffer}
_ ->
original
end
with true <- opts[:trim],
{true, new_buffer} <- trim_left(buffer, acc),
{true, new_rest, new_line} <- trim_right(rest, line) do
{true, new_rest, new_line, new_buffer}
else
original
_ -> {false, rest, line, buffer}
end
end
defp trim_left(buffer, acc) do
case {trim_whitespace(buffer), acc} do
{[?\n | _] = trimmed_buffer, _} -> {true, trimmed_buffer}
{[], [{_, _, _, _, true} | _]} -> {true, []}
{[], []} -> {true, []}
_ -> {false, buffer}
end
@@ -221,7 +232,7 @@ defmodule EEx.Tokenizer do
end
end
defp trim_whitespace([h | t]) when h == ?\s or h == ?\t do
defp trim_whitespace([h | t]) when h in @spaces do
trim_whitespace(t)
end
+9 -6
View File
@@ -29,16 +29,19 @@ defmodule EEx.SmartEngineTest do
assert_eval("1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>")
end
test "preserves line numbers" do
result = EEx.compile_string("<%= @hello %>", engine: EEx.SmartEngine)
test "preserves line numbers in assignments" do
result = EEx.compile_string("foo\n<%= @hello %>", engine: EEx.SmartEngine)
Macro.prewalk(result, fn
{_left, meta, _right} ->
assert Keyword.get(meta, :line, 0) in [0, 1]
{_left, meta, [_, :hello]} ->
assert Keyword.get(meta, :line) == 2
send(self(), :found)
_ ->
:ok
node ->
node
end)
assert_received :found
end
defp assert_eval(expected, actual, binding \\ []) do
+52 -23
View File
@@ -13,23 +13,28 @@ defmodule EEx.TokenizerTest do
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '', ' bar '}]}
assert T.tokenize('foo <% bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, '', ' bar ', false}]}
end
test "strings with embedded equals code" do
assert T.tokenize('foo <%= bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '=', ' bar '}]}
assert T.tokenize('foo <%= bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, '=', ' bar ', false}]}
end
test "strings with embedded slash code" do
assert T.tokenize('foo <%/ bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '/', ' bar '}]}
assert T.tokenize('foo <%/ bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, '/', ' bar ', false}]}
end
test "strings with embedded pipe code" do
assert T.tokenize('foo <%| bar %>', 1) == {:ok, [{:text, 'foo '}, {:expr, 1, '|', ' bar '}]}
assert T.tokenize('foo <%| bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, '|', ' bar ', false}]}
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1) == {:ok, [{:text, 'foo\n'}, {:expr, 2, '=', ' bar '}]}
assert T.tokenize('foo\n<%= bar %>', 1) ==
{:ok, [{:text, 'foo\n'}, {:expr, 2, '=', ' bar ', false}]}
end
test "strings with more than one line and expression with more than one line" do
@@ -42,9 +47,9 @@ defmodule EEx.TokenizerTest do
exprs = [
{:text, 'foo '},
{:expr, 1, '=', ' bar\n\nbaz '},
{:expr, 1, '=', ' bar\n\nbaz ', false},
{:text, '\n'},
{:expr, 4, '', ' foo '},
{:expr, 4, '', ' foo ', false},
{:text, '\n'}
]
@@ -63,9 +68,9 @@ defmodule EEx.TokenizerTest do
test "quotation with interpolation" do
exprs = [
{:text, 'a <% b '},
{:expr, 1, '=', ' c '},
{:expr, 1, '=', ' c ', false},
{:text, ' '},
{:expr, 1, '=', ' d '},
{:expr, 1, '=', ' d ', false},
{:text, ' e %> f'}
]
@@ -99,9 +104,9 @@ defmodule EEx.TokenizerTest do
test "strings with embedded do end" do
exprs = [
{:text, 'foo '},
{:start_expr, 1, '', ' if true do '},
{:start_expr, 1, '', ' if true do ', false},
{:text, 'bar'},
{:end_expr, 1, '', ' end '}
{:end_expr, 1, '', ' end ', false}
]
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == {:ok, exprs}
@@ -110,26 +115,50 @@ defmodule EEx.TokenizerTest do
test "strings with embedded -> end" do
exprs = [
{:text, 'foo '},
{:start_expr, 1, '', ' cond do '},
{:middle_expr, 1, '', ' false -> '},
{:start_expr, 1, '', ' cond do ', false},
{:middle_expr, 1, '', ' false -> ', false},
{:text, 'bar'},
{:middle_expr, 1, '', ' true -> '},
{:middle_expr, 1, '', ' true -> ', false},
{:text, 'baz'},
{:end_expr, 1, '', ' end '}
{:end_expr, 1, '', ' end ', false}
]
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) ==
{:ok, exprs}
end
test "strings with multiple callbacks" do
exprs = [
{:start_expr, 1, '=', ' a fn -> ', false},
{:text, 'foo'},
{:middle_expr, 1, '', ' end, fn -> ', false},
{:text, 'bar'},
{:end_expr, 1, '', ' end ', false}
]
assert T.tokenize('<%= a fn -> %>foo<% end, fn -> %>bar<% end %>', 1) == {:ok, exprs}
end
test "strings with callback followed by do block" do
exprs = [
{:start_expr, 1, '=', ' a fn -> ', false},
{:text, 'foo'},
{:middle_expr, 1, '', ' end do ', false},
{:text, 'bar'},
{:end_expr, 1, '', ' end ', false}
]
assert T.tokenize('<%= a fn -> %>foo<% end do %>bar<% end %>', 1) == {:ok, exprs}
end
test "strings with embedded keywords blocks" do
exprs = [
{:text, 'foo '},
{:start_expr, 1, '', ' if true do '},
{:start_expr, 1, '', ' if true do ', false},
{:text, 'bar'},
{:middle_expr, 1, '', ' else '},
{:middle_expr, 1, '', ' else ', false},
{:text, 'baz'},
{:end_expr, 1, '', ' end '}
{:end_expr, 1, '', ' end ', false}
]
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == {:ok, exprs}
@@ -139,11 +168,11 @@ defmodule EEx.TokenizerTest do
template = '\t<%= if true do %> \n TRUE \n <% else %>\n FALSE \n <% end %> '
exprs = [
{:start_expr, 1, '=', ' if true do '},
{:start_expr, 1, '=', ' if true do ', true},
{:text, ' TRUE \n'},
{:middle_expr, 3, '', ' else '},
{:middle_expr, 3, '', ' else ', true},
{:text, ' FALSE \n'},
{:end_expr, 5, '', ' end '}
{:end_expr, 5, '', ' end ', true}
]
assert T.tokenize(template, 1, trim: true) == {:ok, exprs}
@@ -160,7 +189,7 @@ defmodule EEx.TokenizerTest do
test "trim mode with CRLF" do
exprs = [
{:text, '0\r\n'},
{:expr, 2, '=', ' 12 '},
{:expr, 2, '=', ' 12 ', true},
{:text, '34'}
]
@@ -170,7 +199,7 @@ defmodule EEx.TokenizerTest do
test "trim mode set to false" do
exprs = [
{:text, ' '},
{:expr, 1, '=', ' 12 '},
{:expr, 1, '=', ' 12 ', false},
{:text, ' \n'}
]
+122 -1
View File
@@ -84,6 +84,18 @@ defmodule EExTest do
assert_eval(expected, string, [], trim: true)
end
test "trim mode with multiple lines" do
string = """
<%= "First line" %>
<%= "Second line" %>
<%= "Third line" %>
<%= "Fourth line" %>
"""
expected = "First lineSecond lineThird lineFourth line"
assert_eval(expected, string, [], trim: true)
end
test "embedded code" do
assert_eval("foo bar", "foo <%= :bar %>")
end
@@ -100,6 +112,12 @@ defmodule EExTest do
assert_eval("foo ", "foo <%= if false do %>bar<% end %>")
end
test "embedded code with do preceeded by bracket" do
assert_eval("foo bar", "foo <%= if {true}do %>bar<% end %>")
assert_eval("foo bar", "foo <%= if (true)do %>bar<% end %>")
assert_eval("foo bar", "foo <%= if [true]do %>bar<% end %>")
end
test "embedded code with do end and expression" do
assert_eval("foo bar", "foo <%= if true do %><%= :bar %><% end %>")
end
@@ -132,7 +150,27 @@ defmodule EExTest do
)
end
test "embedded code with parentheses after end in end token" do
test "embedded code with end followed by bracket" do
assert_eval(
" 101 102 103 ",
"<%= Enum.map([1, 2, 3], fn x -> %> <%= 100 + x %> <% end) %>"
)
assert_eval(
" 101 102 103 ",
"<%= apply Enum, :map, [[1, 2, 3], fn x -> %> <%= 100 + x %> <% end] %>"
)
assert_eval(
" 101 102 103 ",
"<%= #{__MODULE__}.tuple_map {[1, 2, 3], fn x -> %> <%= 100 + x %> <% end} %>"
)
assert_eval(
" 101 102 103 ",
"<%= apply(Enum, :map, [[1, 2, 3], fn x -> %> <%= 100 + x %> <% end]) %>"
)
assert_eval(
" 101 102 103 ",
"<%= Enum.map([1, 2, 3], (fn x -> %> <%= 100 + x %> <% end) ) %>"
@@ -217,6 +255,20 @@ defmodule EExTest do
end
end
describe "error messages" do
test "honor line numbers" do
assert_raise EEx.SyntaxError, "nofile:99: missing token '%>'", fn ->
EEx.compile_string("foo <%= bar", line: 99)
end
end
test "honor file names" do
assert_raise EEx.SyntaxError, "my_file.eex:1: missing token '%>'", fn ->
EEx.compile_string("foo <%= bar", file: "my_file.eex")
end
end
end
describe "environment" do
test "respects line numbers" do
expected = """
@@ -342,6 +394,52 @@ defmodule EExTest do
assert_eval(expected, string)
end
test "inside multiple functions" do
expected = """
A 1
B 2
A 3
"""
string = """
<%= #{__MODULE__}.switching_map [1, 2, 3], fn x -> %>
A <%= x %>
<% end, fn x -> %>
B <%= x %>
<% end %>
"""
assert_eval(expected, string)
end
test "inside callback and do block" do
expected = """
A 1
B 2
A 3
"""
string = """
<% require #{__MODULE__} %>
<%= #{__MODULE__}.switching_macro [1, 2, 3], fn x -> %>
A <%= x %>
<% end do %>
B <%= x %>
<% end %>
"""
assert_eval(expected, string)
end
test "inside cond" do
expected = """
foo
@@ -527,4 +625,27 @@ defmodule EExTest do
defp assert_normalized_newline_equal(expected, actual) do
assert String.replace(expected, "\r\n", "\n") == String.replace(actual, "\r\n", "\n")
end
def tuple_map({list, callback}) do
Enum.map(list, callback)
end
def switching_map(list, a, b) do
list
|> Enum.with_index()
|> Enum.map(fn
{element, index} when rem(index, 2) == 0 -> a.(element)
{element, index} when rem(index, 2) == 1 -> b.(element)
end)
end
defmacro switching_macro(list, a, do: block) do
quote do
b = fn var!(x) ->
unquote(block)
end
unquote(__MODULE__).switching_map(unquote(list), unquote(a), b)
end
end
end
+13 -9
View File
@@ -12,13 +12,13 @@
Atom,
Base,
Bitwise,
Calendar,
Date,
DateTime,
Exception,
Float,
Function,
Integer,
Module,
NaiveDateTime,
Record,
Regex,
@@ -26,7 +26,8 @@
Time,
Tuple,
URI,
Version
Version,
Version.Requirement
],
"Collections & Enumerables": [
Access,
@@ -53,20 +54,17 @@
System
],
"Calendar": [
Calendar,
Calendar.ISO,
Calendar.TimeZoneDatabase,
Calendar.UTCOnlyTimeZoneDatabase
],
"Modules & Code": [
Code,
Kernel.ParallelCompiler,
Macro,
Macro.Env,
Module
],
"Processes & Applications": [
Agent,
Application,
Config,
Config.Provider,
Config.Reader,
DynamicSupervisor,
GenServer,
Node,
@@ -86,6 +84,12 @@
Protocol,
String.Chars
],
"Code & Macros": [
Code,
Kernel.ParallelCompiler,
Macro,
Macro.Env
],
Deprecated: [
Behaviour,
Dict,
+51 -95
View File
@@ -2,46 +2,12 @@ defmodule Access do
@moduledoc """
Key-based access to data structures.
Elixir supports three main key-value constructs: keywords,
maps, and structs. It also supports two mechanisms to access those keys:
by brackets (via `data[key]`) and by dot-syntax (via `data.field`).
The `Access` module defines a behaviour for dynamically accessing
keys of any type in a data structure via the `data[key]` syntax.
In the next section we will briefly recap the key-value constructs and then
discuss the access mechanisms.
## Key-value constructs
Elixir provides three main key-value constructs, summarized below:
* keyword lists - they are lists of two-element tuples where
the first element is an atom. Commonly written in the
`[key: value]` syntax, they support only atom keys. Keyword
lists are used almost exclusively to pass options to functions
and macros. They keep the user ordering and allow duplicate
keys. See the `Keyword` module.
* maps - they are the "go to" key-value data structure in Elixir.
They are capable of supporting billions of keys of any type. They are
written using the `%{key => value}` syntax and also support the
`%{key: value}` syntax when the keys are atoms. They do not
have any specified ordering and do not allow duplicate keys.
See the `Map` module.
* structs - they are named maps with a pre-determined set of keys.
They are defined with `defstruct/1` and written using the
`%StructName{key: value}` syntax.
## Key-based accessors
Elixir provides two mechanisms to access data structures by key,
described next.
### Bracket-based access
The `data[key]` syntax is used to access data structures with a
dynamic number of keys, such as keywords and maps. The key can
be of any type. The bracket-based access syntax returns `nil`
if the key does not exist:
`Access` supports keyword lists (`Keyword`) and maps (`Map`) out
of the box. The key can be of any type and it returns `nil` if
the key does not exist:
iex> keywords = [a: 1, b: 2]
iex> keywords[:a]
@@ -59,62 +25,41 @@ defmodule Access do
This syntax is very convenient as it can be nested arbitrarily:
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["john"][:age], 28)
%{"john" => %{age: 28}, "meg" => %{age: 23}}
Furthermore, the bracket-based access syntax transparently ignores
`nil` values. When trying to access anything on a `nil` value, `nil`
is returned:
iex> keywords = [a: 1, b: 2]
iex> keywords[:c][:unknown]
nil
This works because accessing anything on a `nil` value, returns
`nil` itself:
iex> nil[:a]
nil
Internally, `data[key]` translates to `Access.get(term, key, nil)`.
Developers interested in implementing their own key-value data
structures can implement the `Access` behaviour to provide the
bracket-based access syntax. `Access` requires the key comparison
to be implemented using the `===/2` operator.
The access syntax can also be used with the `Kernel.put_in/2`,
`Kernel.update_in/2` and `Kernel.get_and_update_in/2` macros
to allow values to be set in nested data structures:
### Dot-based syntax
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["john"][:age], 28)
%{"john" => %{age: 28}, "meg" => %{age: 23}}
The `data.field` syntax is used exclusively to access atom fields
in maps and structs. If the accessed field does not exist, an error is
raised. This is a deliberate decision: since all of the
fields in a struct are pre-determined, structs support only the
dot-based syntax and not the access one.
Imagine a struct named `User` with a `:name` field. The following would raise:
user = %User{name: "John"}
user[:name]
# ** (UndefinedFunctionError) undefined function User.fetch/2 (User does not implement the Access behaviour)
Instead we should use the `user.name` syntax to access fields:
user.name
#=> "John"
Differently from `user[:name]`, `user.name` is not extensible via
a behaviour and is restricted only to structs and atom keys in maps.
### Summing up
The bracket-based syntax, `user[:name]`, is used by dynamic structures,
is extensible and returns nil on missing keys.
The dot-based syntax, `user.name`, is used exclusively to access atom
keys in maps and structs, and it raises on missing keys.
> Attention! While the access syntax is allowed in maps via
> `map[key]`, if your map is made of predefined atom keys,
> you should prefer to access those atom keys with `map.key`
> instead of `map[key]`, as `map.key` will raise if the key
> is missing. This is important because, if a map has a predefined
> set of keys and a key is missing, it is most likely a bug
> in your software or a typo on the key name. For this reason,
> because structs are predefined in nature, they only allow
> the `struct.key` syntax and they do not allow the `struct[key]`
> access syntax. See the `Map` module for more information.
## Nested data structures
Both key-based access syntaxes can be used with the nested update
functions and macros in `Kernel`, such as `Kernel.get_in/2`, `Kernel.put_in/3`,
`Kernel.update_in/3`, `Kernel.pop_in/2`, and `Kernel.get_and_update_in/3`.
functions and macros in `Kernel`, such as `Kernel.get_in/2`,
`Kernel.put_in/3`, `Kernel.update_in/3`, `Kernel.pop_in/2`, and
`Kernel.get_and_update_in/3`.
For example, to update a map inside another map:
@@ -126,9 +71,9 @@ defmodule Access do
structures, like tuples and lists. These functions can be used
in all the `Access`-related functions and macros in `Kernel`.
For instance, given a user map with the `:name` and `:languages` keys,
here is how to deeply traverse the map and convert all language names
to uppercase:
For instance, given a user map with the `:name` and `:languages`
keys, here is how to deeply traverse the map and convert all
language names to uppercase:
iex> languages = [
...> %{name: "elixir", type: :functional},
@@ -144,8 +89,8 @@ defmodule Access do
]
}
See the functions `key/1`, `key!/1`, `elem/1`, and `all/0` for some of the
available accessors.
See the functions `key/1`, `key!/1`, `elem/1`, and `all/0` for
some of the available accessors.
"""
@type container :: keyword | struct | map
@@ -665,10 +610,16 @@ defmodule Access do
iex> list = [%{name: "john"}, %{name: "mary"}]
iex> get_in(list, [Access.at(1), :name])
"mary"
iex> get_in(list, [Access.at(-1), :name])
"mary"
iex> get_and_update_in(list, [Access.at(0), :name], fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{"john", [%{name: "JOHN"}, %{name: "mary"}]}
iex> get_and_update_in(list, [Access.at(-1), :name], fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{"mary", [%{name: "john"}, %{name: "MARY"}]}
`at/1` can also be used to pop elements out of a list or
a key inside of a list:
@@ -689,19 +640,14 @@ defmodule Access do
...> end)
{nil, [%{name: "john"}, %{name: "mary"}]}
An error is raised for negative indexes:
iex> get_in([], [Access.at(-1)])
** (FunctionClauseError) no function clause matching in Access.at/1
An error is raised if the accessed structure is not a list:
iex> get_in(%{}, [Access.at(1)])
** (RuntimeError) Access.at/1 expected a list, got: %{}
"""
@spec at(non_neg_integer) :: access_fun(data :: list, get_value :: term)
def at(index) when is_integer(index) and index >= 0 do
@spec at(integer) :: access_fun(data :: list, get_value :: term)
def at(index) when is_integer(index) do
fn op, data, next -> at(op, data, index, next) end
end
@@ -724,7 +670,17 @@ defmodule Access do
end
end
defp get_and_update_at([head | rest], index, next, updates) do
defp get_and_update_at(list, index, next, updates) when index < 0 do
list_length = length(list)
if list_length + index >= 0 do
get_and_update_at(list, list_length + index, next, updates)
else
{nil, list}
end
end
defp get_and_update_at([head | rest], index, next, updates) when index > 0 do
get_and_update_at(rest, index - 1, next, [head | updates])
end
+57 -10
View File
@@ -16,8 +16,8 @@ defmodule Agent do
defmodule Counter do
use Agent
def start_link do
Agent.start_link(fn -> 0 end, name: __MODULE__)
def start_link(initial_value) do
Agent.start_link(fn -> initial_value end, name: __MODULE__)
end
def value do
@@ -31,12 +31,20 @@ defmodule Agent do
Usage would be:
Counter.start_link
Counter.start_link(0)
#=> {:ok, #PID<0.123.0>}
Counter.value #=> 0
Counter.increment #=> :ok
Counter.increment #=> :ok
Counter.value #=> 2
Counter.value()
#=> 0
Counter.increment()
#=> :ok
Counter.increment()
#=> :ok
Counter.value()
#=> 2
Thanks to the agent server process, the counter can be safely incremented
concurrently.
@@ -71,9 +79,48 @@ defmodule Agent do
than in the server can lead to race conditions if multiple clients are trying
to update the same state to different values.
Finally note that `use Agent` defines a `child_spec/1` function, allowing the
defined module to be put under a supervision tree. The generated
`child_spec/1` can be customized with the following options:
## How to supervise
An `Agent` is most commonly started under a supervision tree.
When we invoke `use Agent`, it automatically defines a `child_spec/1`
function that allows us to start the agent directly under a supervisor.
To start an agent under a supervisor with an initial counter of 0,
one may do:
children = [
{Counter, 0}
]
Supervisor.start_link(children, strategy: :one_for_all)
While one could also simply pass the `Counter` as a child to the supervisor,
such as:
children = [
Counter # Same as {Counter, []}
]
Supervisor.start_link(children, strategy: :one_for_all)
The definition above wouldn't work for this particular example,
as it would attempt to start the counter with an initial value
of an empty list. However, this may be a viable option in your
own agents. A common approach is to use a keyword list, as that
would allow setting the initial value and giving a name to the
counter process, for example:
def start_link(opts) do
{initial_value, opts} = Keyword.pop(opts, :initial_value, 0)
Agent.start_link(fn -> initial_value end, opts)
end
and then you can use `Counter`, `{Counter, name: :my_counter}` or
even `{Counter, initial_value: 0, name: :my_counter}` as a child
specification.
`use Agent` also accepts a list of options which configures the
child specification and therefore how it runs under a supervisor.
The generated `child_spec/1` can be customized with the following options:
* `:id` - the child specification identifier, defaults to the current module
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
+46 -12
View File
@@ -198,8 +198,8 @@ defmodule Application do
will shut down every application in the opposite order they had been started.
By default, a SIGTERM from the operating system will automatically translate to
`System.stop/0`. You can also have more explicit control over OS signals via the
`:os.set_signal/2` function.
`System.stop/0`. You can also have more explicit control over operating system
signals via the `:os.set_signal/2` function.
## Tooling
@@ -333,13 +333,6 @@ defmodule Application do
end
end
@type app :: atom
@type key :: atom
@type value :: term
@type state :: term
@type start_type :: :normal | {:takeover, node} | {:failover, node}
@type restart_type :: :permanent | :transient | :temporary
@application_keys [
:description,
:id,
@@ -354,6 +347,16 @@ defmodule Application do
:start_phases
]
application_key_specs = Enum.reduce(@application_keys, &{:|, [], [&1, &2]})
@type app :: atom
@type key :: atom
@type application_key :: unquote(application_key_specs)
@type value :: term
@type state :: term
@type start_type :: :normal | {:takeover, node} | {:failover, node}
@type restart_type :: :permanent | :transient | :temporary
@doc """
Returns the spec for `app`.
@@ -364,7 +367,7 @@ defmodule Application do
Note the environment is not returned as it can be accessed via
`fetch_env/2`. Returns `nil` if the application is not loaded.
"""
@spec spec(app) :: [{key, value}] | nil
@spec spec(app) :: [{application_key, value}] | nil
def spec(app) when is_atom(app) do
case :application.get_all_key(app) do
{:ok, info} -> :lists.keydelete(:env, 1, info)
@@ -379,7 +382,7 @@ defmodule Application do
specification parameter does not exist, this function
will raise. Returns `nil` if the application is not loaded.
"""
@spec spec(app, key) :: value | nil
@spec spec(app, application_key) :: value | nil
def spec(app, key) when is_atom(app) and key in @application_keys do
case :application.get_key(app, key) do
{:ok, value} -> value
@@ -523,10 +526,41 @@ defmodule Application do
:application.set_env(app, key, value, opts)
end
@doc """
Puts the environment for multiple apps at the same time.
The given config should not:
* have the same application listed more than once
* have the same key inside the same application listed more than once
If those conditions are not met, the behaviour is undefined
(on Erlang/OTP 21 and earlier) or will raise (on Erlang/OTP 22
and later).
It receives the same options as `put_env/4`. Returns `:ok`.
"""
@spec put_all_env([{app, [{key, value}]}], timeout: timeout, persistent: boolean) :: :ok
def put_all_env(config, opts \\ []) when is_list(config) and is_list(opts) do
# TODO: Remove function exported? check when we require Erlang/OTP 22+
if function_exported?(:application, :set_env, 2) do
:application.set_env(config, opts)
else
for app_keyword <- config,
{app, keyword} = app_keyword,
key_value <- keyword,
{key, value} = key_value do
:application.set_env(app, key, value, opts)
end
:ok
end
end
@doc """
Deletes the `key` from the given `app` environment.
See `put_env/4` for a description of the options.
It receives the same options as `put_env/4`. Returns `:ok`.
"""
@spec delete_env(app, key, timeout: timeout, persistent: boolean) :: :ok
def delete_env(app, key, opts \\ []) when is_atom(app) do
-1
View File
@@ -37,7 +37,6 @@ defmodule Atom do
:erlang.atom_to_list(atom)
end
# TODO: Remove by 2.0
@doc false
@deprecated "Use Atom.to_charlist/1 instead"
@spec to_char_list(atom) :: charlist
+3 -3
View File
@@ -28,7 +28,7 @@ defmodule Behaviour do
do_defcallback(:defmacro, split_spec(spec, quote(do: Macro.t())))
end
defp split_spec({:when, _, [{:::, _, [spec, return]}, guard]}, _default) do
defp split_spec({:when, _, [{:"::", _, [spec, return]}, guard]}, _default) do
{spec, return, guard}
end
@@ -36,7 +36,7 @@ defmodule Behaviour do
{spec, default, guard}
end
defp split_spec({:::, _, [spec, return]}, _default) do
defp split_spec({:"::", _, [spec, return]}, _default) do
{spec, return, []}
end
@@ -56,7 +56,7 @@ defmodule Behaviour do
defp do_callback(kind, name, args, return, guards) do
fun = fn
{:::, _, [left, right]} ->
{:"::", _, [left, right]} ->
ensure_not_default(left)
ensure_not_default(right)
left
+6 -5
View File
@@ -343,7 +343,7 @@ defmodule Date do
def to_iso8601(%{calendar: Calendar.ISO} = date, format) when format in [:basic, :extended] do
%{year: year, month: month, day: day} = date
Calendar.ISO.date_to_iso8601(year, month, day, format)
Calendar.ISO.date_to_string(year, month, day, format)
end
def to_iso8601(%{calendar: _} = date, format) when format in [:basic, :extended] do
@@ -745,10 +745,11 @@ defmodule Date do
## Examples
iex> Date.day_of_era(~D[0001-01-01])
{1, 1}
iex> Date.day_of_era(~D[0000-12-31])
{1, 0}
iex> Date.day_of_era(~D[0001-01-01])
{1, 1}
iex> Date.day_of_era(~D[0000-12-31])
{1, 0}
"""
@doc since: "1.8.0"
+68 -42
View File
@@ -97,17 +97,17 @@ defmodule DateTime do
iex> {:ok, datetime} = DateTime.from_unix(1_464_096_368)
iex> datetime
#DateTime<2016-05-24 13:26:08Z>
~U[2016-05-24 13:26:08Z]
iex> {:ok, datetime} = DateTime.from_unix(1_432_560_368_868_569, :microsecond)
iex> datetime
#DateTime<2015-05-25 13:26:08.868569Z>
~U[2015-05-25 13:26:08.868569Z]
The unit can also be an integer as in `t:System.time_unit/0`:
iex> {:ok, datetime} = DateTime.from_unix(143_256_036_886_856, 1024)
iex> datetime
#DateTime<6403-03-17 07:05:22.320Z>
~U[6403-03-17 07:05:22.320312Z]
Negative Unix times are supported, up to -62167219200 seconds,
which is equivalent to "0000-01-01T00:00:00Z" or 0 Gregorian seconds.
@@ -152,17 +152,20 @@ defmodule DateTime do
# An easy way to get the Unix epoch is passing 0 to this function
iex> DateTime.from_unix!(0)
#DateTime<1970-01-01 00:00:00Z>
~U[1970-01-01 00:00:00Z]
iex> DateTime.from_unix!(1_464_096_368)
#DateTime<2016-05-24 13:26:08Z>
~U[2016-05-24 13:26:08Z]
iex> DateTime.from_unix!(1_432_560_368_868_569, :microsecond)
#DateTime<2015-05-25 13:26:08.868569Z>
~U[2015-05-25 13:26:08.868569Z]
iex> DateTime.from_unix!(143_256_036_886_856, 1024)
~U[6403-03-17 07:05:22.320312Z]
"""
@spec from_unix!(integer, :native | System.time_unit(), Calendar.calendar()) :: t
def from_unix!(integer, unit \\ :second, calendar \\ Calendar.ISO) when is_atom(unit) do
def from_unix!(integer, unit \\ :second, calendar \\ Calendar.ISO) do
case from_unix(integer, unit, calendar) do
{:ok, datetime} ->
datetime
@@ -183,9 +186,8 @@ defmodule DateTime do
## Examples
iex> {:ok, datetime} = DateTime.from_naive(~N[2016-05-24 13:26:08.003], "Etc/UTC")
iex> datetime
#DateTime<2016-05-24 13:26:08.003Z>
iex> DateTime.from_naive(~N[2016-05-24 13:26:08.003], "Etc/UTC")
{:ok, ~U[2016-05-24 13:26:08.003Z]}
When the datetime is ambiguous - for instance during changing from summer
to winter time - the two possible valid datetimes are returned. First the one
@@ -227,7 +229,7 @@ defmodule DateTime do
iex> cph_datetime = DateTime.from_naive!(~N[2018-08-24 10:00:00], "Europe/Copenhagen", FakeTimeZoneDatabase)
iex> {:ok, utc_datetime} = DateTime.from_naive(cph_datetime, "Etc/UTC", FakeTimeZoneDatabase)
iex> utc_datetime
#DateTime<2018-08-24 10:00:00Z>
~U[2018-08-24 10:00:00Z]
If instead you want a `DateTime` for the same point time in a different time zone see the
`DateTime.shift_zone/3` function which would convert 2018-08-24 10:00:00 in Copenhagen
@@ -356,7 +358,7 @@ defmodule DateTime do
## Examples
iex> DateTime.from_naive!(~N[2016-05-24 13:26:08.003], "Etc/UTC")
#DateTime<2016-05-24 13:26:08.003Z>
~U[2016-05-24 13:26:08.003Z]
iex> DateTime.from_naive!(~N[2018-05-24 13:26:08.003], "Europe/Copenhagen", FakeTimeZoneDatabase)
#DateTime<2018-05-24 13:26:08.003+02:00 CEST Europe/Copenhagen>
@@ -480,9 +482,11 @@ defmodule DateTime do
## Examples
iex> {:ok, datetime} = DateTime.now("Europe/Copenhagen", FakeTimeZoneDatabase)
iex> {:ok, datetime} = DateTime.now("Etc/UTC")
iex> datetime.time_zone
"Europe/Copenhagen"
"Etc/UTC"
iex> DateTime.now("Europe/Copenhagen")
{:error, :utc_only_time_zone_database}
iex> DateTime.now("not a real time zone name", FakeTimeZoneDatabase)
{:error, :time_zone_not_found}
@@ -553,18 +557,17 @@ defmodule DateTime do
"""
@spec to_naive(Calendar.datetime()) :: NaiveDateTime.t()
def to_naive(datetime) do
%{
calendar: calendar,
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
microsecond: microsecond
} = datetime
def to_naive(%{
calendar: calendar,
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
microsecond: microsecond,
time_zone: _
}) do
%NaiveDateTime{
year: year,
month: month,
@@ -593,8 +596,17 @@ defmodule DateTime do
"""
@spec to_date(Calendar.datetime()) :: Date.t()
def to_date(datetime) do
%{year: year, month: month, day: day, calendar: calendar} = datetime
def to_date(%{
year: year,
month: month,
day: day,
calendar: calendar,
hour: _,
minute: _,
second: _,
microsecond: _,
time_zone: _
}) do
%Date{year: year, month: month, day: day, calendar: calendar}
end
@@ -614,10 +626,17 @@ defmodule DateTime do
"""
@spec to_time(Calendar.datetime()) :: Time.t()
def to_time(datetime) do
%{hour: hour, minute: minute, second: second, microsecond: microsecond, calendar: calendar} =
datetime
def to_time(%{
year: _,
month: _,
day: _,
calendar: calendar,
hour: hour,
minute: minute,
second: second,
microsecond: microsecond,
time_zone: _
}) do
%Time{
hour: hour,
minute: minute,
@@ -730,23 +749,23 @@ defmodule DateTime do
iex> {:ok, datetime, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z")
iex> datetime
#DateTime<2015-01-23 23:50:07Z>
~U[2015-01-23 23:50:07Z]
iex> {:ok, datetime, 9000} = DateTime.from_iso8601("2015-01-23T23:50:07.123+02:30")
iex> datetime
#DateTime<2015-01-23 21:20:07.123Z>
~U[2015-01-23 21:20:07.123Z]
iex> {:ok, datetime, 9000} = DateTime.from_iso8601("2015-01-23T23:50:07,123+02:30")
iex> datetime
#DateTime<2015-01-23 21:20:07.123Z>
~U[2015-01-23 21:20:07.123Z]
iex> {:ok, datetime, 0} = DateTime.from_iso8601("-2015-01-23T23:50:07Z")
iex> datetime
#DateTime<-2015-01-23 23:50:07Z>
~U[-2015-01-23 23:50:07Z]
iex> {:ok, datetime, 9000} = DateTime.from_iso8601("-2015-01-23T23:50:07,123+02:30")
iex> datetime
#DateTime<-2015-01-23 21:20:07.123Z>
~U[-2015-01-23 21:20:07.123Z]
iex> DateTime.from_iso8601("2015-01-23P23:50:07")
{:error, :invalid_format}
@@ -1019,9 +1038,8 @@ defmodule DateTime do
iex> dt |> DateTime.add(3600, :second, FakeTimeZoneDatabase)
#DateTime<2018-11-15 11:00:00+01:00 CET Europe/Copenhagen>
iex> dt = DateTime.from_naive!(~N[2018-11-15 10:00:00], "Etc/UTC")
iex> dt |> DateTime.add(3600, :second)
#DateTime<2018-11-15 11:00:00Z>
iex> DateTime.add(~U[2018-11-15 10:00:00Z], 3600, :second)
~U[2018-11-15 11:00:00Z]
When adding 3 seconds just before "spring forward" we go from 1:59:59 to 3:00:02
@@ -1301,7 +1319,7 @@ defmodule DateTime do
std_offset: std_offset
} = datetime
"#DateTime<" <>
formatted =
Calendar.ISO.datetime_to_string(
year,
month,
@@ -1314,7 +1332,15 @@ defmodule DateTime do
zone_abbr,
utc_offset,
std_offset
) <> ">"
)
case datetime do
%{utc_offset: 0, std_offset: 0, time_zone: "Etc/UTC"} ->
"~U[" <> formatted <> "]"
_ ->
"#DateTime<" <> formatted <> ">"
end
end
def inspect(datetime, opts) do
+94 -45
View File
@@ -213,7 +213,7 @@ defmodule Calendar.ISO do
end
def date_to_iso_days(year, month, day) when year in -9999..9999 do
true = day <= days_in_month(year, month)
ensure_day_in_month!(year, month, day)
days_in_previous_years(year) + days_before_month(month) + leap_day_offset(year, month) + day -
1
@@ -260,6 +260,7 @@ defmodule Calendar.ISO do
31
"""
@doc since: "1.4.0"
@spec days_in_month(year, month) :: 28..31
@impl true
def days_in_month(year, month)
@@ -304,6 +305,7 @@ defmodule Calendar.ISO do
true
"""
@doc since: "1.3.0"
@spec leap_year?(year) :: boolean()
@impl true
def leap_year?(year) when is_integer(year) do
@@ -335,6 +337,7 @@ defmodule Calendar.ISO do
4
"""
@doc since: "1.4.0"
@spec day_of_week(year, month, day) :: 1..7
@impl true
def day_of_week(year, month, day)
@@ -366,7 +369,7 @@ defmodule Calendar.ISO do
@impl true
def day_of_year(year, month, day)
when is_integer(year) and is_integer(month) and is_integer(day) do
true = day <= days_in_month(year, month)
ensure_day_in_month!(year, month, day)
days_before_month(month) + leap_day_offset(year, month) + day
end
@@ -461,6 +464,10 @@ defmodule Calendar.ISO do
@doc """
Converts the given time into a string.
By default, returns times formatted in the "extended" format,
for human readability. It also supports the "basic" format
by passing the `:basic` option.
## Examples
iex> Calendar.ISO.time_to_string(2, 2, 2, {2, 6})
@@ -470,23 +477,29 @@ defmodule Calendar.ISO do
iex> Calendar.ISO.time_to_string(2, 2, 2, {2, 0})
"02:02:02"
iex> Calendar.ISO.time_to_string(2, 2, 2, {2, 6}, :basic)
"020202.000002"
iex> Calendar.ISO.time_to_string(2, 2, 2, {2, 6}, :extended)
"02:02:02.000002"
"""
@impl true
@doc since: "1.5.0"
@spec time_to_string(
Calendar.hour(),
Calendar.minute(),
Calendar.second(),
Calendar.microsecond()
Calendar.microsecond(),
:basic | :extended
) :: String.t()
@impl true
def time_to_string(hour, minute, second, microsecond) do
time_to_string(hour, minute, second, microsecond, :extended)
end
def time_to_string(hour, minute, second, microsecond, format \\ :extended)
def time_to_string(hour, minute, second, {_, 0}, format) do
def time_to_string(hour, minute, second, {_, 0}, format) when format in [:basic, :extended] do
time_to_string_format(hour, minute, second, format)
end
def time_to_string(hour, minute, second, {microsecond, precision}, format) do
def time_to_string(hour, minute, second, {microsecond, precision}, format)
when format in [:basic, :extended] do
time_to_string_format(hour, minute, second, format) <>
"." <> (microsecond |> zero_pad(6) |> binary_part(0, precision))
end
@@ -502,6 +515,10 @@ defmodule Calendar.ISO do
@doc """
Converts the given date into a string.
By default, returns dates formatted in the "extended" format,
for human readability. It also supports the "basic" format
by passing the `:basic` option.
## Examples
iex> Calendar.ISO.date_to_string(2015, 2, 28)
@@ -511,24 +528,32 @@ defmodule Calendar.ISO do
iex> Calendar.ISO.date_to_string(-99, 1, 31)
"-0099-01-31"
"""
@spec date_to_string(year, month, day) :: String.t()
@impl true
def date_to_string(year, month, day) do
date_to_string(year, month, day, :extended)
end
iex> Calendar.ISO.date_to_string(2015, 2, 28, :basic)
"20150228"
iex> Calendar.ISO.date_to_string(-99, 1, 31, :basic)
"-00990131"
defp date_to_string(year, month, day, :extended) do
"""
@doc since: "1.4.0"
@spec date_to_string(year, month, day, :basic | :extended) :: String.t()
@impl true
def date_to_string(year, month, day, format \\ :extended)
def date_to_string(year, month, day, :extended) do
zero_pad(year, 4) <> "-" <> zero_pad(month, 2) <> "-" <> zero_pad(day, 2)
end
defp date_to_string(year, month, day, :basic) do
def date_to_string(year, month, day, :basic) do
zero_pad(year, 4) <> zero_pad(month, 2) <> zero_pad(day, 2)
end
@doc """
Converts the datetime (without time zone) into a string.
By default, returns datetimes formatted in the "extended" format,
for human readability. It also supports the "basic" format
by passing the `:basic` option.
## Examples
iex> Calendar.ISO.naive_datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 6})
@@ -536,7 +561,11 @@ defmodule Calendar.ISO do
iex> Calendar.ISO.naive_datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5})
"2017-08-01 01:02:03.00000"
iex> Calendar.ISO.naive_datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 6}, :basic)
"20150228 010203.000004"
"""
@doc since: "1.4.0"
@impl true
@spec naive_datetime_to_string(
year,
@@ -545,15 +574,31 @@ defmodule Calendar.ISO do
Calendar.hour(),
Calendar.minute(),
Calendar.second(),
Calendar.microsecond()
Calendar.microsecond(),
:basic | :extended
) :: String.t()
def naive_datetime_to_string(year, month, day, hour, minute, second, microsecond) do
date_to_string(year, month, day) <> " " <> time_to_string(hour, minute, second, microsecond)
def naive_datetime_to_string(
year,
month,
day,
hour,
minute,
second,
microsecond,
format \\ :extended
)
when format in [:basic, :extended] do
date_to_string(year, month, day, format) <>
" " <> time_to_string(hour, minute, second, microsecond, format)
end
@doc """
Converts the datetime (with time zone) into a string.
By default, returns datetimes formatted in the "extended" format,
for human readability. It also supports the "basic" format
by passing the `:basic` option.
## Examples
iex> time_zone = "Europe/Berlin"
@@ -568,7 +613,12 @@ defmodule Calendar.ISO do
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, time_zone, "PDT", -28800, 3600)
"2015-02-28 01:02:03.00000-07:00 PDT America/Los_Angeles"
iex> time_zone = "Europe/Berlin"
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, time_zone, "CET", 3600, 0, :basic)
"20170801 010203.00000+0100 CET Europe/Berlin"
"""
@doc since: "1.4.0"
@impl true
@spec datetime_to_string(
year,
@@ -581,7 +631,8 @@ defmodule Calendar.ISO do
Calendar.time_zone(),
Calendar.zone_abbr(),
Calendar.utc_offset(),
Calendar.std_offset()
Calendar.std_offset(),
:basic | :extended
) :: String.t()
def datetime_to_string(
year,
@@ -594,12 +645,14 @@ defmodule Calendar.ISO do
time_zone,
zone_abbr,
utc_offset,
std_offset
) do
date_to_string(year, month, day) <>
std_offset,
format \\ :extended
)
when format in [:basic, :extended] do
date_to_string(year, month, day, format) <>
" " <>
time_to_string(hour, minute, second, microsecond) <>
offset_to_string(utc_offset, std_offset, time_zone) <>
time_to_string(hour, minute, second, microsecond, format) <>
offset_to_string(utc_offset, std_offset, time_zone, format) <>
zone_to_string(utc_offset, std_offset, zone_abbr, time_zone)
end
@@ -662,7 +715,6 @@ defmodule Calendar.ISO do
{0, 1}
end
defp offset_to_string(utc, std, zone, format \\ :extended)
defp offset_to_string(0, 0, "Etc/UTC", _format), do: "Z"
defp offset_to_string(utc, std, _zone, format) do
@@ -714,24 +766,15 @@ defmodule Calendar.ISO do
end
defp precision_for_unit(unit) do
subsecond = div(System.convert_time_unit(1, :second, unit), 10)
precision_for_unit(subsecond, 0)
end
defp precision_for_unit(0, precision), do: precision
defp precision_for_unit(_, 6), do: 6
defp precision_for_unit(number, precision),
do: precision_for_unit(div(number, 10), precision + 1)
@doc false
def date_to_iso8601(year, month, day, format \\ :extended) do
date_to_string(year, month, day, format)
end
@doc false
def time_to_iso8601(hour, minute, second, microsecond, format \\ :extended) do
time_to_string(hour, minute, second, microsecond, format)
case System.convert_time_unit(1, :second, unit) do
1 -> 0
10 -> 1
100 -> 2
1_000 -> 3
10_000 -> 4
100_000 -> 5
_ -> 6
end
end
@doc false
@@ -993,4 +1036,10 @@ defmodule Calendar.ISO do
{hour, minute, second}
end
defp ensure_day_in_month!(year, month, day) do
if day < 1 or day > days_in_month(year, month) do
raise ArgumentError, "invalid date: #{date_to_string(year, month, day)}"
end
end
end
+1 -23
View File
@@ -258,7 +258,7 @@ defmodule NaiveDateTime do
iex> NaiveDateTime.add(~N[0000-01-01 00:00:00], 63_579_428_950)
~N[2014-10-02 00:29:10]
Passing a `Datetime` automatically converts it to `NaiveDateTime`,
Passing a `DateTime` automatically converts it to `NaiveDateTime`,
discarding the time zone information:
iex> dt = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "CET",
@@ -392,10 +392,6 @@ defmodule NaiveDateTime do
"""
@spec to_date(Calendar.naive_datetime()) :: Date.t()
def to_date(%NaiveDateTime{year: year, month: month, day: day, calendar: calendar}) do
%Date{year: year, month: month, day: day, calendar: calendar}
end
def to_date(%{
year: year,
month: month,
@@ -422,24 +418,6 @@ defmodule NaiveDateTime do
"""
@spec to_time(Calendar.naive_datetime()) :: Time.t()
def to_time(%NaiveDateTime{} = naive_datetime) do
%{
hour: hour,
minute: minute,
second: second,
microsecond: microsecond,
calendar: calendar
} = naive_datetime
%Time{
hour: hour,
minute: minute,
second: second,
microsecond: microsecond,
calendar: calendar
}
end
def to_time(%{
year: _,
month: _,
+1 -1
View File
@@ -301,7 +301,7 @@ defmodule Time do
microsecond: microsecond
} = time
Calendar.ISO.time_to_iso8601(hour, minute, second, microsecond, format)
Calendar.ISO.time_to_string(hour, minute, second, microsecond, format)
end
def to_iso8601(%{calendar: _} = time, format) when format in [:extended, :basic] do
@@ -26,7 +26,7 @@ defmodule Calendar.TimeZoneDatabase do
A beginning is inclusive. An ending is exclusive. Eg. if a period is from
2015-03-29 01:00:00 and until 2015-10-25 01:00:00, the period includes and
begins from the begining of 2015-03-29 01:00:00 and lasts until just before
begins from the beginning of 2015-03-29 01:00:00 and lasts until just before
2015-10-25 01:00:00.
A beginning or end for certain periods are infinite. For instance the latest
+82 -36
View File
@@ -30,6 +30,14 @@ defmodule Code do
the result of evaluating the file rather than the modules it defines.
"""
@available_compiler_options [
:docs,
:debug_info,
:ignore_module_conflict,
:relative_paths,
:warnings_as_errors
]
@doc """
Lists all required files.
@@ -46,7 +54,7 @@ defmodule Code do
:elixir_code_server.call(:required)
end
# TODO: Deprecate me on 1.9
# TODO: Deprecate on v1.9
@doc false
def loaded_files do
required_files()
@@ -78,7 +86,7 @@ defmodule Code do
:elixir_code_server.cast({:unrequire_files, files})
end
# TODO: Deprecate me on 1.9
# TODO: Deprecate on v1.9
@doc false
def unload_files(files) do
unrequire_files(files)
@@ -264,6 +272,13 @@ defmodule Code do
expects a valid `Version` which is usually the minimum Elixir
version supported by the project.
* `:force_do_end_blocks` (since v1.9.0) - when `true`, converts all
inline usages of `do: ...`, `else: ...` and friends into `do/end`
blocks. Defaults to `false`. Notice this option is convergent:
once you set it to `true`, all keywords will be converted. If you
set it to `false` later on, `do/end` blocks won't be converted
back to keywords.
## Design principles
The formatter was designed under three principles.
@@ -409,8 +424,8 @@ defmodule Code do
broken into multiple lines if they are followed by a newline in the
opening bracket and preceded by a new line in the closing bracket
* Pipeline operators, like `|>` and others with the same precedence,
will span multiple lines if they spanned multiple lines in the input
* Newlines before certain operators (such as the pipeline operators)
and before other operators (such as comparison operators)
The behaviours above are not guaranteed. We may remove or add new
rules in the future. The goal of documenting them is to provide better
@@ -650,6 +665,12 @@ defmodule Code do
when non-existing atoms are found by the tokenizer.
Defaults to `false`.
* `:static_atom_encoder` - The static atom encoder function, see
"The `:static_atom_encoder` function" section below. This option
overrides the `:existing_atoms_only` behaviour for static atoms
but `:existing_atoms_only` is still used for dynamic atoms, such
as atoms with interpolations.
* `:warn_on_unnecessary_quotes` - when `false`, does not warn
when atoms, keywords or calls have unnecessary quotes on
them. Defaults to `true`.
@@ -659,6 +680,37 @@ defmodule Code do
The opposite of converting a string to its quoted form is
`Macro.to_string/2`, which converts a quoted form to a string/binary
representation.
## The `:static_atom_encoder` function
When `static_atom_encoder: &my_encoder/2` is passed as an argument,
`my_encoder/2` is called every time the tokenizer needs to create a
"static" atom. Static atoms are atoms in the AST that function as
aliases, remote calls, local calls, variable names, regular atoms
and keyword lists.
The encoder function will receive the atom name (as a binary) and a
keyword list with the current file, line and column. It must return
`{:ok, token :: term} | {:error, reason :: binary}`.
The encoder function is supposed to create an atom from the given
string. It is required to return either `{:ok, term}`, where term is
an atom. It is possible to return something else than an atom,
however, in that case the AST is no longer "valid" in that it cannot
be used to compile or evaluate Elixir code. A use case for this is
if you want to use the Elixir parser in a user-facing situation, but
you don't want to exhaust the atom table.
The atom encoder is not called for *all* atoms that are present in
the AST. It won't be invoked for the following atoms:
* operators (`:+`, `:-`, and so on)
* syntax keywords (`fn`, `do`, `else`, and so on)
* atoms containing interpolation (`:"\#{1 + 1} is two"`), as these
atoms are constructed at runtime.
"""
@spec string_to_quoted(List.Chars.t(), keyword) ::
{:ok, Macro.t()} | {:error, {line :: pos_integer, term, term}}
@@ -707,12 +759,12 @@ defmodule Code do
eval_string(File.read!(file), [], file: file, line: 1)
end
# TODO: Deprecate me on 1.9
# TODO: Deprecate on v1.9
@doc false
def load_file(file, relative_to \\ nil) when is_binary(file) do
file = find_file(file, relative_to)
:elixir_code_server.call({:acquire, file})
loaded = :elixir_compiler.file(file)
loaded = :elixir_compiler.file(file, fn _, _ -> :ok end)
:elixir_code_server.cast({:required, file})
loaded
end
@@ -753,18 +805,12 @@ defmodule Code do
def require_file(file, relative_to \\ nil) when is_binary(file) do
file = find_file(file, relative_to)
# TODO: Simply block until :required or :proceed once load_file is removed in 2.0
case :elixir_code_server.call({:acquire, file}) do
:required ->
nil
{:queued, ref} ->
receive do
{:elixir_code_server, ^ref, :required} -> nil
end
:proceed ->
loaded = :elixir_compiler.file(file)
loaded = :elixir_compiler.file(file, fn _, _ -> :ok end)
:elixir_code_server.cast({:required, file})
loaded
end
@@ -778,8 +824,7 @@ defmodule Code do
## Examples
Code.compiler_options()
#=> %{debug_info: true, docs: true,
#=> warnings_as_errors: false, ignore_module_conflict: false}
#=> %{debug_info: true, docs: true, ...}
"""
@spec compiler_options() :: %{optional(atom) => boolean}
@@ -794,13 +839,13 @@ defmodule Code do
## Examples
iex> Code.available_compiler_options()
[:docs, :debug_info, :ignore_module_conflict, :relative_paths, :warnings_as_errors]
Code.available_compiler_options()
#=> [:docs, :debug_info, ...]
"""
@spec available_compiler_options() :: [atom]
def available_compiler_options do
[:docs, :debug_info, :ignore_module_conflict, :relative_paths, :warnings_as_errors]
@available_compiler_options
end
@doc """
@@ -859,19 +904,14 @@ defmodule Code do
"""
@spec compiler_options(Enumerable.t()) :: %{optional(atom) => boolean}
def compiler_options(opts) do
available = available_compiler_options()
Enum.each(opts, fn {key, value} ->
cond do
key not in available ->
raise "unknown compiler option: #{inspect(key)}"
not is_boolean(value) ->
Enum.each(opts, fn
{key, value} when key in @available_compiler_options ->
if not is_boolean(value) do
raise "compiler option #{inspect(key)} should be a boolean, got: #{inspect(value)}"
end
true ->
:ok
end
{key, _} ->
raise "unknown compiler option: #{inspect(key)}"
end)
:elixir_config.update(:compiler_options, &Enum.into(opts, &1))
@@ -893,7 +933,7 @@ defmodule Code do
"""
@spec compile_string(List.Chars.t(), binary) :: [{module, binary}]
def compile_string(string, file \\ "nofile") when is_binary(file) do
:elixir_compiler.string(to_charlist(string), file)
:elixir_compiler.string(to_charlist(string), file, fn _, _ -> :ok end)
end
@doc """
@@ -906,7 +946,7 @@ defmodule Code do
"""
@spec compile_quoted(Macro.t(), binary) :: [{module, binary}]
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
:elixir_compiler.quoted(quoted, file)
:elixir_compiler.quoted(quoted, file, fn _, _ -> :ok end)
end
@doc """
@@ -923,9 +963,10 @@ defmodule Code do
For compiling many files concurrently, see `Kernel.ParallelCompiler.compile/2`.
"""
@doc since: "1.7.0"
@spec compile_file(binary, nil | binary) :: [{module, binary}]
def compile_file(file, relative_to \\ nil) when is_binary(file) do
:elixir_compiler.file(find_file(file, relative_to))
:elixir_compiler.file(find_file(file, relative_to), fn _, _ -> :ok end)
end
@doc """
@@ -1014,6 +1055,9 @@ defmodule Code do
If it succeeds in loading the module, it returns `{:module, module}`.
If not, returns `{:error, reason}` with the error reason.
If the module being checked is currently in a compiler deadlock,
this functions returns `{:error, :nofile}`.
Check `ensure_loaded/1` for more information on module loading
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
"""
@@ -1022,9 +1066,11 @@ defmodule Code do
def ensure_compiled(module) when is_atom(module) do
case :code.ensure_loaded(module) do
{:error, :nofile} = error ->
if is_pid(:erlang.get(:elixir_compiler_pid)) and
Kernel.ErrorHandler.ensure_compiled(module, :module) do
{:module, module}
if is_pid(:erlang.get(:elixir_compiler_pid)) do
case Kernel.ErrorHandler.ensure_compiled(module, :module, :soft) do
:found -> {:module, module}
:not_found -> error
end
else
error
end
@@ -1077,7 +1123,7 @@ defmodule Code do
| {:error, :module_not_found | :chunk_not_found | {:invalid_chunk, binary}}
when annotation: :erl_anno.anno(),
beam_language: :elixir | :erlang | :lfe | :alpaca | atom(),
doc_content: %{binary => binary} | :none | :hidden,
doc_content: %{required(binary) => binary} | :none | :hidden,
doc_element:
{{kind :: atom, function_name :: atom, arity}, annotation, signature, doc_content,
metadata},
+43 -45
View File
@@ -28,7 +28,7 @@ defmodule Code.Formatter do
@required_parens_logical_binary_operands [:||, :|||, :or, :&&, :&&&, :and]
# Operators with next break fits. = and :: do not consider new lines though
@next_break_fits_operators [:<-, :==, :!=, :=~, :===, :!==, :<, :>, :<=, :>=, :=, :::]
@next_break_fits_operators [:<-, :==, :!=, :=~, :===, :!==, :<, :>, :<=, :>=, :=, :"::"]
# Operators that always require parens on operands when they are the parent
@required_parens_on_binary_operands [
@@ -49,7 +49,7 @@ defmodule Code.Formatter do
:<>
]
locals_without_parens = [
@locals_without_parens [
# Special forms
alias: 1,
alias: 2,
@@ -77,6 +77,7 @@ defmodule Code.Formatter do
defmacro: 2,
defmacrop: 1,
defmacrop: 2,
defmodule: 2,
defdelegate: 2,
defexception: 1,
defoverridable: 1,
@@ -98,7 +99,6 @@ defmodule Code.Formatter do
defrecordp: 3,
# Testing
all: :*,
assert: 1,
assert: 2,
assert_in_delta: 3,
@@ -110,12 +110,8 @@ defmodule Code.Formatter do
assert_receive: 3,
assert_received: 1,
assert_received: 2,
check: 1,
check: 2,
doctest: 1,
doctest: 2,
property: 1,
property: 2,
refute: 1,
refute: 2,
refute_in_delta: 3,
@@ -138,7 +134,7 @@ defmodule Code.Formatter do
import_config: 1
]
@locals_without_parens MapSet.new(locals_without_parens)
@do_end_keywords [:rescue, :catch, :else, :after]
@doc """
Checks if two strings are equivalent.
@@ -242,6 +238,8 @@ defmodule Code.Formatter do
end
defp state(comments, opts) do
force_do_end_blocks = Keyword.get(opts, :force_do_end_blocks, false)
rename_deprecated_at =
if version = opts[:rename_deprecated_at] do
case Version.parse(version) do
@@ -255,13 +253,10 @@ defmodule Code.Formatter do
end
locals_without_parens =
opts
|> Keyword.get(:locals_without_parens, [])
|> MapSet.new()
|> MapSet.union(@locals_without_parens)
|> MapSet.to_list()
Keyword.get(opts, :locals_without_parens, []) ++ @locals_without_parens
%{
force_do_end_blocks: force_do_end_blocks,
locals_without_parens: locals_without_parens,
operand_nesting: 2,
rename_deprecated_at: rename_deprecated_at,
@@ -796,7 +791,7 @@ defmodule Code.Formatter do
end
# TODO: We can remove this workaround once we remove
# ?rearrange_uop from the parser in Elixir v2.0.
# ?rearrange_uop from the parser on v2.0.
# (! left) in right
# (not left) in right
defp binary_operand_to_algebra(
@@ -1058,7 +1053,7 @@ defmodule Code.Formatter do
end
# We can only rename functions in the same module because
# introducing a new module may wrong due to aliases.
# introducing a new module may be wrong due to aliases.
defp deprecated(Enum, :partition, 2), do: {"split_with", "~> 1.4"}
defp deprecated(Code, :unload_files, 2), do: {"unrequire_files", "~> 1.7"}
defp deprecated(Code, :loaded_files, 2), do: {"required_files", "~> 1.7"}
@@ -1114,7 +1109,7 @@ defmodule Code.Formatter do
defp call_args_to_algebra(args, meta, context, parens, list_to_keyword?, state) do
{rest, last} = split_last(args)
if blocks = do_end_blocks(last) do
if blocks = do_end_blocks(last, state) do
{call_doc, state} =
if rest == [] do
{" do", state}
@@ -1161,7 +1156,7 @@ defmodule Code.Formatter do
{left_doc, _join, state} =
args_to_algebra_with_comments(
left,
Keyword.delete(meta, :end_line),
Keyword.delete(meta, :closing),
skip_parens?,
:force_comma,
join,
@@ -1268,16 +1263,19 @@ defmodule Code.Formatter do
not Enum.any?(args, &match?({:<-, _, [_, _]}, &1))
end
defp do_end_blocks([{{:__block__, meta, [:do]}, _} | _] = blocks) do
if meta[:format] == :block do
defp do_end_blocks([{{:__block__, meta, [:do]}, _} | rest] = blocks, state) do
if meta[:format] == :block or can_force_do_end_blocks?(rest, state) do
blocks
|> Enum.map(fn {{:__block__, meta, [key]}, value} -> {key, line(meta), value} end)
|> do_end_blocks_with_range(end_line(meta))
end
end
defp do_end_blocks(_) do
nil
defp do_end_blocks(_, _), do: nil
defp can_force_do_end_blocks?(rest, state) do
state.force_do_end_blocks and
Enum.all?(rest, fn {{:__block__, _, [key]}, _} -> key in @do_end_keywords end)
end
defp do_end_blocks_with_range([{key1, line1, value1}, {_, line2, _} = h | t], end_line) do
@@ -1316,7 +1314,7 @@ defmodule Code.Formatter do
defp interpolated?(entries) do
Enum.all?(entries, fn
{:::, _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
{:"::", _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
entry when is_binary(entry) -> true
_ -> false
end)
@@ -1354,7 +1352,7 @@ defmodule Code.Formatter do
end
defp interpolation_to_algebra([entry | entries], escape, state, acc, last) do
{:::, _, [{{:., _, [Kernel, :to_string]}, meta, [quoted]}, {:binary, _, _}]} = entry
{:"::", _, [{{:., _, [Kernel, :to_string]}, meta, [quoted]}, {:binary, _, _}]} = entry
{doc, state} = block_to_algebra(quoted, line(meta), end_line(meta), state)
doc = surround("\#{", doc, "}")
interpolation_to_algebra(entries, escape, state, concat(acc, doc), last)
@@ -1367,26 +1365,26 @@ defmodule Code.Formatter do
## Sigils
defp maybe_sigil_to_algebra(fun, meta, args, state) do
case {Atom.to_string(fun), args} do
{<<"sigil_", name>>, [{:<<>>, _, entries}, modifiers]} ->
opening_terminator = Keyword.fetch!(meta, :terminator)
doc = <<?~, name, opening_terminator::binary>>
with <<"sigil_", name>> <- Atom.to_string(fun),
[{:<<>>, _, entries}, modifiers] when is_list(modifiers) <- args,
opening_terminator when not is_nil(opening_terminator) <- Keyword.get(meta, :terminator) do
doc = <<?~, name, opening_terminator::binary>>
if opening_terminator in [@double_heredoc, @single_heredoc] do
closing_terminator = concat(opening_terminator, List.to_string(modifiers))
if opening_terminator in [@double_heredoc, @single_heredoc] do
closing_terminator = concat(opening_terminator, List.to_string(modifiers))
{doc, state} =
entries
|> prepend_heredoc_line()
|> interpolation_to_algebra(:heredoc, state, doc, closing_terminator)
{force_unfit(doc), state}
else
escape = closing_sigil_terminator(opening_terminator)
closing_terminator = concat(escape, List.to_string(modifiers))
interpolation_to_algebra(entries, escape, state, doc, closing_terminator)
end
{doc, state} =
entries
|> prepend_heredoc_line()
|> interpolation_to_algebra(:heredoc, state, doc, closing_terminator)
{force_unfit(doc), state}
else
escape = closing_sigil_terminator(opening_terminator)
closing_terminator = concat(escape, List.to_string(modifiers))
interpolation_to_algebra(entries, escape, state, doc, closing_terminator)
end
else
_ ->
:error
end
@@ -1423,7 +1421,7 @@ defmodule Code.Formatter do
{bitstring_wrap_parens(doc, i, last), state}
end
defp bitstring_segment_to_algebra({{:::, _, [segment, spec]}, i}, state, last) do
defp bitstring_segment_to_algebra({{:"::", _, [segment, spec]}, i}, state, last) do
{doc, state} = quoted_to_algebra(segment, :parens_arg, state)
{spec, state} = bitstring_spec_to_algebra(spec, state)
@@ -1849,7 +1847,7 @@ defmodule Code.Formatter do
end
defp add_max_line_to_last_clause([{op, meta, args}], max_line) do
[{op, [end_line: max_line] ++ meta, args}]
[{op, [closing: [line: max_line]] ++ meta, args}]
end
defp add_max_line_to_last_clause([clause | clauses], max_line) do
@@ -2021,7 +2019,7 @@ defmodule Code.Formatter do
end
# TODO: We can remove this workaround once we remove
# ?rearrange_uop from the parser in Elixir v2.0.
# ?rearrange_uop from the parser on v2.0.
defp wrap_in_parens_if_operator(doc, {:__block__, _, [expr]}) do
wrap_in_parens_if_operator(doc, expr)
end
@@ -2237,11 +2235,11 @@ defmodule Code.Formatter do
end
defp line(meta) do
Keyword.get(meta, :line, @max_line)
meta[:line] || @max_line
end
defp end_line(meta) do
Keyword.get(meta, :end_line, @min_line)
meta[:closing][:line] || @min_line
end
## Algebra helpers
+11 -4
View File
@@ -34,7 +34,7 @@ defmodule Code.Identifier do
cond do
op in [:<-, :\\] -> {:left, 40}
op in [:when] -> {:right, 50}
op in [:::] -> {:right, 60}
op in [:"::"] -> {:right, 60}
op in [:|] -> {:right, 70}
op in [:=] -> {:right, 100}
op in [:||, :|||, :or] -> {:left, 130}
@@ -60,9 +60,13 @@ defmodule Code.Identifier do
* `:callable_local` - an atom that can be used as a local call;
this category includes identifiers like `:foo`
* `:callable_operators` - all callable operators, such as `:<>`. Note
* `:callable_operator` - all callable operators, such as `:<>`. Note
operators such as `:..` are not callable because of ambiguity
* `:not_atomable` - callable operators that must be wrapped in quotes when
defined as an atom. For example, `::` must be written as `:"::"` to avoid
the ambiguity between the atom and the keyword identifier
* `:not_callable` - an atom that cannot be used as a function call after the
`.` operator (for example, `:<<>>` is not callable because `Foo.<<>>` is a
syntax error); this category includes atoms like `:Foo`, since they are
@@ -80,6 +84,9 @@ defmodule Code.Identifier do
atom in [:%, :%{}, :{}, :<<>>, :..., :.., :., :->] ->
:not_callable
atom in [:"::"] ->
:not_atomable
unary_op(atom) != :error or binary_op(atom) != :error ->
:callable_operator
@@ -143,7 +150,7 @@ defmodule Code.Identifier do
type when type in [:callable_local, :callable_operator, :not_callable] ->
":" <> binary
:other ->
_ ->
{escaped, _} = escape(binary, ?")
IO.iodata_to_binary([?:, ?", escaped, ?"])
end
@@ -172,7 +179,7 @@ defmodule Code.Identifier do
binary = Atom.to_string(atom)
case classify(atom) do
type when type in [:callable_local, :callable_operator] ->
type when type in [:callable_local, :callable_operator, :not_atomable] ->
binary
type ->
+5 -5
View File
@@ -18,7 +18,7 @@ defmodule Code.Typespec do
uniq: true,
do: {var, {:var, meta, nil}}
spec = {:::, meta, [body, typespec_to_quoted(result)]}
spec = {:"::", meta, [body, typespec_to_quoted(result)]}
if vars == [] do
spec
@@ -28,7 +28,7 @@ defmodule Code.Typespec do
end
def spec_to_quoted(name, {:type, line, :fun, []}) when is_atom(name) do
{:::, [line: line], [{name, [line: line], []}, quote(do: term)]}
{:"::", [line: line], [{name, [line: line], []}, quote(do: term)]}
end
def spec_to_quoted(name, {:type, line, :bounded_fun, [type, constrs]}) when is_atom(name) do
@@ -52,7 +52,7 @@ defmodule Code.Typespec do
args = for arg <- args, do: typespec_to_quoted(arg)
when_args = [
{:::, meta, [{name, [line: line], args}, typespec_to_quoted(result)]},
{:"::", meta, [{name, [line: line], args}, typespec_to_quoted(result)]},
guards ++ vars
]
@@ -329,7 +329,7 @@ defmodule Code.Typespec do
end
defp typespec_to_quoted({:var, line, var}) do
{erl_to_ex_var(var), line, nil}
{erl_to_ex_var(var), [line: line], nil}
end
defp typespec_to_quoted({:op, line, op, arg}) do
@@ -341,7 +341,7 @@ defmodule Code.Typespec do
end
defp typespec_to_quoted({:ann_type, line, [var, type]}) do
{:::, [line: line], [typespec_to_quoted(var), typespec_to_quoted(type)]}
{:"::", [line: line], [typespec_to_quoted(var), typespec_to_quoted(type)]}
end
defp typespec_to_quoted(
+3 -3
View File
@@ -21,9 +21,9 @@ defprotocol Collectable do
shape where just the range limits are stored.
The `Collectable` module was designed to fill the gap left by the
`Enumerable` protocol. `into/1` can be seen as the opposite of
`Enumerable.reduce/3`. If `Enumerable` is about taking values out,
`Collectable.into/1` is about collecting those values into a structure.
`Enumerable` protocol. `Collectable.into/1` can be seen as the opposite of
`Enumerable.reduce/3`. If the functions in `Enumerable` are about taking values out,
then `Collectable.into/1` is about collecting those values into a structure.
## Examples
+266
View File
@@ -0,0 +1,266 @@
defmodule Config do
@moduledoc ~S"""
A simple keyword-based configuration API.
## Example
This module is most commonly used to define application configuration,
typically in `config/config.exs`:
import Config
config :some_app,
key1: "value1",
key2: "value2"
import_config "#{Mix.env()}.exs"
`import Config` will import the functions `config/2`, `config/3`
and `import_config/1` to help you manage your configuration.
`config/2` and `config/3` are used to define key-value configuration
for a given application. Once Mix starts, it will automatically
evaluate the configuration file and persist the configuration above
into `:some_app`'s application environment, which can be accessed in
as follows:
"value1" = Application.fetch_env!(:some_app, :key1)
Finally, the line `import_config "#{Mix.env()}.exs"` will import other
config files, based on the current Mix environment, such as
`config/dev.exs` and `config/test.exs`.
`Config` also provides a low-level API for evaluating and reading
configuration, under the `Config.Reader` module.
**Important:** if you are writing a library to be used by other developers,
it is generally recommended to avoid the application environment, as the
application environment is effectively a global storage. For more information,
read our [library guidelines](library-guidelines.html).
## Migrating from `use Mix.Config`
The `Config` module in Elixir was introduced in v1.9 as a replacement to
`Mix.Config`, which was specific to Mix and has been deprecated.
You can leverage `Config` instead of `Mix.Config` in two steps. The first
step is to replace `use Mix.Config` at the top of your config files by
`import Config`.
The second is to make sure your `import_config/1` calls do not have a
wildcard character. If so, you need to perform the wildcard lookup
manually. For example, if you did:
import_config "../apps/*/config/config.exs"
It has to be replaced by:
for config <- "apps/*/config/config.exs" |> Path.expand() |> Path.wildcard() do
import_config config
end
## config/releases.exs
If you are using releases, see `mix release`, there another configuration
file called `config/releases.exs`. While `config/config.exs` and friends
mentioned in the previous section are executed whenever you run a Mix
command, including when you assemble a release, `config/releases.exs` is
execute every time your production system boots. Since Mix is not available
in a production system, `config/releases.exs` must not use any of the
functions from Mix.
"""
@config_key {__MODULE__, :config}
@files_key {__MODULE__, :files}
defp get_config!() do
Process.get(@config_key) || raise_improper_use!()
end
defp put_config(value) do
Process.put(@config_key, value)
end
defp delete_config() do
Process.delete(@config_key)
end
defp get_files!() do
Process.get(@files_key) || raise_improper_use!()
end
defp put_files(value) do
Process.put(@files_key, value)
end
defp delete_files() do
Process.delete(@files_key)
end
defp raise_improper_use!() do
raise "could not set configuration via Config. " <>
"This usually means you are trying to execute a configuration file " <>
"directly, instead of reading it with Config.Reader"
end
@doc """
Configures the given `root_key`.
Keyword lists are always deep-merged.
## Examples
The given `opts` are merged into the existing configuration
for the given `root_key`. Conflicting keys are overridden by the
ones specified in `opts`. For example, the application
configuration below
config :logger,
level: :warn,
backends: [:console]
config :logger,
level: :info,
truncate: 1024
will have a final configuration for `:logger` of:
[level: :info, backends: [:console], truncate: 1024]
"""
@doc since: "1.9.0"
def config(root_key, opts) when is_atom(root_key) and is_list(opts) do
unless Keyword.keyword?(opts) do
raise ArgumentError, "config/2 expected a keyword list, got: #{inspect(opts)}"
end
get_config!()
|> __merge__([{root_key, opts}])
|> put_config()
end
@doc """
Configures the given `key` for the given `root_key`.
Keyword lists are always deep merged.
## Examples
The given `opts` are merged into the existing values for `key`
in the given `root_key`. Conflicting keys are overridden by the
ones specified in `opts`. For example, the application
configuration below
config :ecto, Repo,
log_level: :warn,
adapter: Ecto.Adapters.Postgres
config :ecto, Repo,
log_level: :info,
pool_size: 10
will have a final value of the configuration for the `Repo`
key in the `:ecto` application of:
[log_level: :info, pool_size: 10, adapter: Ecto.Adapters.Postgres]
"""
@doc since: "1.9.0"
def config(root_key, key, opts) when is_atom(root_key) and is_atom(key) do
get_config!()
|> __merge__([{root_key, [{key, opts}]}])
|> put_config()
end
@doc ~S"""
Imports configuration from the given file.
In case the file doesn't exist, an error is raised.
If file is a relative, it will be expanded relatively to the
directory the current configuration file is in.
## Examples
This is often used to emulate configuration across environments:
import_config "#{Mix.env()}.exs"
"""
@doc since: "1.9.0"
defmacro import_config(file) do
quote do
Config.__import__!(Path.expand(unquote(file), __DIR__))
:ok
end
end
@doc false
@spec __import__!(Path.t()) :: keyword()
def __import__!(file) when is_binary(file) do
current_files = get_files!()
if file in current_files do
raise ArgumentError,
"attempting to load configuration #{Path.relative_to_cwd(file)} recursively"
end
put_files([file | current_files])
Code.eval_file(file)
end
@doc false
@spec __eval__!(Path.t(), [Path.t()]) :: {keyword, [Path.t()]}
def __eval__!(file, imported_paths \\ []) when is_binary(file) and is_list(imported_paths) do
previous_config = put_config([])
previous_files = put_files(imported_paths)
try do
{eval_config, _} = __import__!(Path.expand(file))
case get_config!() do
[] when is_list(eval_config) ->
{validate!(eval_config, file), get_files!()}
pdict_config ->
{pdict_config, get_files!()}
end
after
if previous_config, do: put_config(previous_config), else: delete_config()
if previous_files, do: put_files(previous_files), else: delete_files()
end
end
@doc false
def __merge__(config1, config2) when is_list(config1) and is_list(config2) do
Keyword.merge(config1, config2, fn _, app1, app2 ->
Keyword.merge(app1, app2, &deep_merge/3)
end)
end
defp deep_merge(_key, value1, value2) do
if Keyword.keyword?(value1) and Keyword.keyword?(value2) do
Keyword.merge(value1, value2, &deep_merge/3)
else
value2
end
end
defp validate!(config, file) do
Enum.all?(config, fn
{app, value} when is_atom(app) ->
if Keyword.keyword?(value) do
true
else
raise ArgumentError,
"expected config for app #{inspect(app)} in #{Path.relative_to_cwd(file)} " <>
"to return keyword list, got: #{inspect(value)}"
end
_ ->
false
end)
config
end
end
+249
View File
@@ -0,0 +1,249 @@
defmodule Config.Provider do
@moduledoc """
Specifies a provider API that loads configuration during boot.
Config providers are typically used during releases to load
external configuration while the system boots. This is done
by starting the VM with the minimum amount of applications
running, then invoking all of the providers, and then
restarting the system. This requires a mutable configuration
file on disk, as the results of the providers are written to
the file system. For more information on runtime configuration,
see `mix release`.
## Sample config provider
For example, imagine you need to load some configuration from
a JSON file and load that into the system. Said configuration
provider would look like:
defmodule JSONConfigProvider do
@behaviour Config.Provider
# Let's pass the path to the JSON file as config
def init(path) when is_binary(path), do: path
def load(config, path) do
# We need to start any app we may depend on.
{:ok, _} = Application.ensure_all_started(:jason)
json = path |> File.read!() |> Jason.decode!()
Config.Reader.merge(
config,
my_app: [
some_value: json["my_app_some_value"],
another_value: json["my_app_another_value"],
]
)
end
end
Then when specifying your release, you can specify the provider:
config_providers: [{JSONConfigProvider, "/etc/config.json"}]
Now once the system boots, it will invoke the provider early in
the boot process, save the merged configuration to the disk, and
reboot the system with the new values in place.
"""
@type config :: keyword
@type state :: term
@typedoc """
A path pointing to a configuration file.
Since configuration files are often accessed on target machines,
it can be expressed either as:
* a binary representing an absolute path
* a tuple {:system, system_var, path} where the config is the
concatenation of the `system_var` with the given `path`
"""
@type config_path :: {:system, binary(), binary()} | binary()
@doc """
Invoked when initializing a config provider.
A config provider is typically initialized on the machine
where the system is assembled and not on the target machine.
The `c:init/1` callback is useful to verify the arguments
given to the provider and prepare the state that will be
given to `c:load/2`.
Furthermore, because the state returned by `c:init/1` can
be written to text-based config files, it should be
restricted only to simple data types, such as integers,
strings, atoms, tuples, maps, and lists. Entries such as
PIDs, references, and functions cannot be serialized.
"""
@callback init(term) :: state
@doc """
Loads configuration (typically during system boot).
It receives the current `config` and the `state` returned by
`c:init/1`. Then you typically read the extra configuration
from an external source and merge it into the received `config`.
Merging should be done with `Config.Reader.merge/2`, as it
performs deep merge. It should return the updated config.
Note that `c:load/2` is typically invoked very early in the
boot process, therefore if you need to use an application
in the provider, it is your responsibility to start it.
"""
@callback load(config, state) :: config
@doc false
defstruct [:providers, :config_path, extra_config: [], prune_after_boot: false]
@doc """
Validates a `t:config_path/0`.
"""
@doc since: "1.9.0"
@spec validate_config_path!(config_path) :: :ok
def validate_config_path!({:system, name, path})
when is_binary(name) and is_binary(path),
do: :ok
def validate_config_path!(path) do
if is_binary(path) and Path.type(path) != :relative do
:ok
else
raise ArgumentError, """
expected configuration path to be:
* a binary representing an absolute path
* a tuple {:system, system_var, path} where the config is the \
concatenation of the `system_var` with the given `path`
Got: #{inspect(path)}
"""
end
end
@doc """
Resolves a `t:config_path/0` to an actual path.
"""
@doc since: "1.9.0"
@spec resolve_config_path!(config_path) :: binary
def resolve_config_path!(path) when is_binary(path), do: path
def resolve_config_path!({:system, name, path}), do: System.fetch_env!(name) <> path
@doc false
def init(providers, config_path, opts \\ []) when is_list(providers) and is_list(opts) do
validate_config_path!(config_path)
providers = for {provider, init} <- providers, do: {provider, provider.init(init)}
struct!(%Config.Provider{config_path: config_path, providers: providers}, opts)
end
@doc false
def boot(app, key, restart_fun \\ &System.restart/0) do
# The app with the config provider settings may not
# have been loaded at this point, so make sure we load
# its environment before querying it.
_ = :application.load(app)
# The config provider typically runs very early in the
# release process, so we need to make sure Elixir is started
# before we go around running Elixir code.
{:ok, _} = :application.ensure_all_started(:elixir)
case :application.get_env(app, key) do
{:ok, %Config.Provider{} = provider} ->
path = resolve_config_path!(provider.config_path)
validate_no_cyclic_boot!(path)
read_config!(path)
|> Config.__merge__([{app, [{key, booted_key(provider, path)}]} | provider.extra_config])
|> run_providers(provider)
|> write_config!(path)
restart_fun.()
{:ok, {:booted, path}} ->
File.rm(path)
:booted
{:ok, :booted} ->
:booted
_ ->
:skip
end
end
defp booted_key(%{prune_after_boot: true}, path), do: {:booted, path}
defp booted_key(%{prune_after_boot: false}, _path), do: :booted
defp validate_no_cyclic_boot!(path) do
if System.get_env("ELIXIR_CONFIG_PROVIDER_BOOTED") do
bad_path_abort("Got infinite loop when running Config.Provider", path)
else
System.put_env("ELIXIR_CONFIG_PROVIDER_BOOTED", "1")
end
end
defp read_config!(path) do
case :file.consult(path) do
{:ok, [inner]} ->
inner
{:error, reason} ->
bad_path_abort(
"Could not read runtime configuration due to reason: #{inspect(reason)}",
path
)
end
end
defp run_providers(config, %{providers: providers}) do
Enum.reduce(providers, config, fn {provider, state}, acc ->
try do
provider.load(acc, state)
catch
kind, error ->
IO.puts(:stderr, "ERROR! Config provider #{inspect(provider)} failed with:")
IO.puts(:stderr, Exception.format(kind, error, __STACKTRACE__))
:erlang.raise(kind, error, __STACKTRACE__)
else
term when is_list(term) ->
term
term ->
abort("Expected provider #{inspect(provider)} to return a list, got: #{inspect(term)}")
end
end)
end
defp write_config!(config, path) do
contents = :io_lib.format("%% coding: utf-8~n~p.~n", [config])
case File.write(path, contents) do
:ok ->
:ok
{:error, reason} ->
bad_path_abort(
"Could not write runtime configuration due to reason: #{inspect(reason)}",
path
)
end
end
defp bad_path_abort(msg, path) do
abort(
msg <>
". Please make sure #{inspect(path)} is writable and accessible " <>
"or choose a different path"
)
end
defp abort(msg) do
IO.puts(:stderr, "ERROR! " <> msg)
raise(msg)
end
end
+87
View File
@@ -0,0 +1,87 @@
defmodule Config.Reader do
@moduledoc """
API for reading config files defined with `Config`.
## As a provider
`Config.Reader` can also be used as a `Config.Provider`.
When used as a provider, it expects a single argument:
which the configuration path (as outlined in
`t:Config.Provider.config_path/0`) for the configuration
to be read and loaded during the system boot.
"""
@behaviour Config.Provider
@impl true
def init(path) do
Config.Provider.validate_config_path!(path)
path
end
@impl true
def load(config, path) do
merge(config, path |> Config.Provider.resolve_config_path!() |> read!())
end
@doc """
Reads the configuration file.
The same as `read_imports!/2` but only returns the configuration
in the given file, without returning the imported paths.
It exists for convenience purposes. For example, you could
invoke it inside your `mix.exs` to read some external data
you decided to move to a configuration file:
releases: Config.Reader.read!("rel/releases.exs")
"""
@doc since: "1.9.0"
@spec read!(Path.t(), [Path.t()]) :: keyword
def read!(file, imported_paths \\ [])
when is_binary(file) and is_list(imported_paths) do
Config.__eval__!(file, imported_paths) |> elem(0)
end
@doc """
Reads the given configuration file alongside its imports.
It accepts a list of `imported_paths` that should raise if attempted
to be imported again (to avoid recursive imports).
It returns a tuple with the configuration and the imported paths.
"""
@doc since: "1.9.0"
@spec read_imports!(Path.t(), [Path.t()]) :: {keyword, [Path.t()]}
def read_imports!(file, imported_paths \\ [])
when is_binary(file) and is_list(imported_paths) do
Config.__eval__!(file, imported_paths)
end
@doc """
Merges two configurations.
The configurations are merged together with the values in
the second one having higher preference than the first in
case of conflicts. In case both values are set to keyword
lists, it deep merges them.
## Examples
iex> Config.Reader.merge([app: [k: :v1]], [app: [k: :v2]])
[app: [k: :v2]]
iex> Config.Reader.merge([app: [k: [v1: 1, v2: 2]]], [app: [k: [v2: :a, v3: :b]]])
[app: [k: [v1: 1, v2: :a, v3: :b]]]
iex> Config.Reader.merge([app1: []], [app2: []])
[app1: [], app2: []]
"""
@doc since: "1.9.0"
@spec merge(keyword, keyword) :: keyword
def merge(config1, config2) when is_list(config1) and is_list(config2) do
Config.__merge__(config1, config2)
end
end
-2
View File
@@ -18,8 +18,6 @@ defmodule Dict do
message =
"Use the Map module for working with maps or the Keyword module for working with keyword lists"
# TODO: Remove by 2.0
@deprecated message
defmacro __using__(_) do
# Use this import to guarantee proper code expansion
+19 -19
View File
@@ -47,19 +47,19 @@ defmodule DynamicSupervisor do
# Automatically defines child_spec/1
use DynamicSupervisor
def start_link(arg) do
DynamicSupervisor.start_link(__MODULE__, arg, name: __MODULE__)
def start_link(init_arg) do
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_arg) do
def init(_init_arg) do
DynamicSupervisor.init(strategy: :one_for_one)
end
end
See the `Supervisor` docs for a discussion of when you may want to use
module-based supervisors. The `@doc` annotation immediately preceding
`use DymamicSupervisor` will be attached to the generated `child_spec/1`
module-based supervisors. A `@doc` annotation immediately preceding
`use DynamicSupervisor` will be attached to the generated `child_spec/1`
function.
## Name registration
@@ -78,20 +78,20 @@ defmodule DynamicSupervisor do
defmodule MySupervisor do
use Supervisor
def start_link(arg) do
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
def start_link(init_arg) do
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
def start_child(foo, bar, baz) do
# This will start child by calling MyWorker.start_link(initial_arg, foo, bar, baz)
# This will start child by calling MyWorker.start_link(init_arg, foo, bar, baz)
Supervisor.start_child(__MODULE__, [foo, bar, baz])
end
@impl true
def init(initial_arg) do
def init(init_arg) do
children = [
# Or the deprecated: worker(MyWorker, [initial_arg])
%{id: MyWorker, start: {MyWorker, :start_link, [initial_arg]}}
# Or the deprecated: worker(MyWorker, [init_arg])
%{id: MyWorker, start: {MyWorker, :start_link, [init_arg]}}
]
Supervisor.init(children, strategy: :simple_one_for_one)
@@ -103,8 +103,8 @@ defmodule DynamicSupervisor do
defmodule MySupervisor do
use DynamicSupervisor
def start_link(arg) do
DynamicSupervisor.start_link(__MODULE__, arg, name: __MODULE__)
def start_link(init_arg) do
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
def start_child(foo, bar, baz) do
@@ -115,10 +115,10 @@ defmodule DynamicSupervisor do
end
@impl true
def init(initial_arg) do
def init(init_arg) do
DynamicSupervisor.init(
strategy: :one_for_one,
extra_arguments: [initial_arg]
extra_arguments: [init_arg]
)
end
end
@@ -302,8 +302,8 @@ defmodule DynamicSupervisor do
If the child process start function returns an error tuple or an erroneous
value, or if it fails, the child specification is discarded and this function
returns `{:error, error}` where `error` is a term containing information about
the error and child specification.
returns `{:error, error}` where `error` is the error or erroneous value
returned from child process start function, or failure reason if it fails.
If the supervisor already has N children in a way that N exceeds the amount
of `:max_children` set on the supervisor initialization (see `init/1`), then
@@ -405,7 +405,7 @@ defmodule DynamicSupervisor do
* `id` - it is always `:undefined` for dynamic supervisors
* `child` - the pid of the corresponding child process or the
* `child` - the PID of the corresponding child process or the
atom `:restarting` if the process is about to be restarted
* `type` - `:worker` or `:supervisor` as defined in the child
@@ -487,7 +487,7 @@ defmodule DynamicSupervisor do
* `:strategy` - the restart strategy option. The only supported
value is `:one_for_one` which means that no other child is
terminate if a child process terminates. You can learn more
terminated if a child process terminates. You can learn more
about strategies in the `Supervisor` module docs.
* `:max_restarts` - the maximum number of restarts allowed in
+106 -98
View File
@@ -121,7 +121,7 @@ defprotocol Enumerable do
Most of the operations in `Enum` are implemented in terms of reduce.
This function should apply the given `t:reducer/0` function to each
item in the `enumerable` and proceed as expected by the returned
element in the `enumerable` and proceed as expected by the returned
accumulator.
See the documentation of the types `t:result/0` and `t:acc/0` for
@@ -223,11 +223,11 @@ defmodule Enum do
and the data type returned by `File.stream!/3` which allows a file to be
traversed as if it was an enumerable.
The functions in this module work in linear time. This means that,
the larger the enumerable, the longer it will take to perform the desired
operation. This is expected on operations such as `Enum.map/2`. After all,
if we want to traverse every element on a list, the longer the list, the
more elements we need to traverse, and the longer it will take.
The functions in this module work in linear time. This means that, the
time it takes to perform an operation grows at the same rate as the length
of the enumerable. This is expected on operations such as `Enum.map/2`.
After all, if we want to traverse every element on a list, the longer the
list, the more elements we need to traverse, and the longer it will take.
This linear behaviour should also be expected on operations like `count/1`,
`member?/2`, `at/2` and similar. While Elixir does allow data types to
@@ -276,10 +276,11 @@ defmodule Enum do
end
@doc """
Returns `true` if the given `fun` evaluates to a truthy value (neither `false` nor `nil`)
on all of the items in the `enumerable`.
Returns `true` if `fun.(element)` is truthy for all elements in `enumerable`.
It stops the iteration at the first invocation that returns either `false` or `nil`.
Iterates over the `enumerable` and invokes `fun` on each element. When an invocation
of `fun` returns a falsy value (`false` or `nil`) iteration stops immediately and
`false` is returned. In all other cases `true` is returned.
## Examples
@@ -289,8 +290,12 @@ defmodule Enum do
iex> Enum.all?([2, 3, 4], fn x -> rem(x, 2) == 0 end)
false
If no function is given, it defaults to checking if
all items in the `enumerable` are truthy values.
iex> Enum.all?([], fn x -> x > 0 end)
true
If no function is given, the truthiness of each element is checked during iteration.
When an element has a falsy value (`false` or `nil`) iteration stops immediately and
`false` is returned. In all other cases `true` is returned.
iex> Enum.all?([1, 2, 3])
true
@@ -298,6 +303,9 @@ defmodule Enum do
iex> Enum.all?([1, nil, 3])
false
iex> Enum.all?([])
true
"""
@spec all?(t, (element -> as_boolean(term))) :: boolean
@@ -315,9 +323,11 @@ defmodule Enum do
end
@doc """
Returns `true` if the given `fun` evaluates to true on any of the items in the `enumerable`.
Returns `true` if `fun.(element)` is truthy for at least one element in `enumerable`.
It stops the iteration at the first invocation that returns a truthy value (neither `false` nor `nil`).
Iterates over the `enumerable` and invokes `fun` on each element. When an invocation
of `fun` returns a truthy value (neither `false` nor `nil`) iteration stops
immediately and `true` is returned. In all other cases `false` is returned.
## Examples
@@ -327,8 +337,12 @@ defmodule Enum do
iex> Enum.any?([2, 3, 4], fn x -> rem(x, 2) == 1 end)
true
If no function is given, it defaults to checking if at least one item
in the `enumerable` is a truthy value.
iex> Enum.any?([], fn x -> x > 0 end)
false
If no function is given, the truthiness of each element is checked during iteration.
When an element has a truthy value (neither `false` nor `nil`) iteration stops
immediately and `true` is returned. In all other cases `false` is returned.
iex> Enum.any?([false, false, false])
false
@@ -336,6 +350,9 @@ defmodule Enum do
iex> Enum.any?([false, true, false])
true
iex> Enum.any?([])
false
"""
@spec any?(t, (element -> as_boolean(term))) :: boolean
@@ -358,7 +375,7 @@ defmodule Enum do
Returns `default` if `index` is out of bounds.
A negative `index` can be passed, which means the `enumerable` is
enumerated once and the `index` is counted from the end (e.g.
enumerated once and the `index` is counted from the end (for example,
`-1` finds the last element).
## Examples
@@ -384,19 +401,16 @@ defmodule Enum do
end
end
# TODO: Remove by 2.0
@doc false
@deprecated "Use Enum.chunk_every/2 instead"
def chunk(enumerable, count), do: chunk(enumerable, count, count, nil)
# TODO: Remove by 2.0
@doc false
@deprecated "Use Enum.chunk_every/3 instead"
def chunk(enum, n, step) do
chunk_every(enum, n, step, nil)
end
# TODO: Remove by 2.0
@doc false
@deprecated "Use Enum.chunk_every/4 instead"
def chunk(enumerable, count, step, leftover) do
@@ -411,7 +425,7 @@ defmodule Enum do
def chunk_every(enumerable, count), do: chunk_every(enumerable, count, count, [])
@doc """
Returns list of lists containing `count` items each, where
Returns list of lists containing `count` elements each, where
each new chunk starts `step` elements into the `enumerable`.
`step` is optional and, if not passed, defaults to `count`, i.e.
@@ -468,11 +482,11 @@ defmodule Enum do
## Examples
iex> chunk_fun = fn item, acc ->
...> if rem(item, 2) == 0 do
...> {:cont, Enum.reverse([item | acc]), []}
iex> chunk_fun = fn element, acc ->
...> if rem(element, 2) == 0 do
...> {:cont, Enum.reverse([element | acc]), []}
...> else
...> {:cont, [item | acc]}
...> {:cont, [element | acc]}
...> end
...> end
iex> after_fun = fn
@@ -593,7 +607,7 @@ defmodule Enum do
end
@doc """
Returns the count of items in the `enumerable` for which `fun` returns
Returns the count of elements in the `enumerable` for which `fun` returns
a truthy value.
## Examples
@@ -655,7 +669,7 @@ defmodule Enum do
end
@doc """
Drops the `amount` of items from the `enumerable`.
Drops the `amount` of elements from the `enumerable`.
If a negative `amount` is given, the `amount` of last values will be dropped.
The `enumerable` will be enumerated once to retrieve the proper index and
@@ -699,12 +713,12 @@ defmodule Enum do
end
@doc """
Returns a list of every `nth` item in the `enumerable` dropped,
Returns a list of every `nth` element in the `enumerable` dropped,
starting with the first element.
The first item is always dropped, unless `nth` is 0.
The first element is always dropped, unless `nth` is 0.
The second argument specifying every `nth` item must be a non-negative
The second argument specifying every `nth` element must be a non-negative
integer.
## Examples
@@ -732,7 +746,7 @@ defmodule Enum do
end
@doc """
Drops items at the beginning of the `enumerable` while `fun` returns a
Drops elements at the beginning of the `enumerable` while `fun` returns a
truthy value.
## Examples
@@ -752,7 +766,7 @@ defmodule Enum do
end
@doc """
Invokes the given `fun` for each item in the `enumerable`.
Invokes the given `fun` for each element in the `enumerable`.
Returns `:ok`.
@@ -799,7 +813,7 @@ defmodule Enum do
end
def empty?(enumerable) do
case backwards_compatible_slice(enumerable) do
case Enumerable.slice(enumerable) do
{:ok, value, _} ->
value == 0
@@ -816,7 +830,7 @@ defmodule Enum do
Returns `{:ok, element}` if found, otherwise `:error`.
A negative `index` can be passed, which means the `enumerable` is
enumerated once and the `index` is counted from the end (e.g.
enumerated once and the `index` is counted from the end (for example,
`-1` fetches the last element).
## Examples
@@ -908,10 +922,9 @@ defmodule Enum do
end
@doc false
# TODO: Remove on 2.0
@deprecated "Use Enum.filter/2 + Enum.map/2 or for comprehensions instead"
def filter_map(enumerable, filter, mapper) when is_list(enumerable) do
for item <- enumerable, filter.(item), do: mapper.(item)
for element <- enumerable, filter.(element), do: mapper.(element)
end
def filter_map(enumerable, filter, mapper) do
@@ -921,8 +934,8 @@ defmodule Enum do
end
@doc """
Returns the first item for which `fun` returns a truthy value.
If no such item is found, returns `default`.
Returns the first element for which `fun` returns a truthy value.
If no such element is found, returns `default`.
## Examples
@@ -1049,7 +1062,7 @@ defmodule Enum do
Maps and reduces an `enumerable`, flattening the given results (only one level deep).
It expects an accumulator and a function that receives each enumerable
item, and must return a tuple containing a new enumerable (often a list)
element, and must return a tuple containing a new enumerable (often a list)
with the new accumulator or a tuple with `:halt` as first element and
the accumulator as second.
@@ -1066,9 +1079,8 @@ defmodule Enum do
{[[1], [2], [3], [4], [5]], 15}
"""
@spec flat_map_reduce(t, acc, fun) :: {[any], any}
when fun: (element, acc -> {t, acc} | {:halt, acc}),
acc: any
@spec flat_map_reduce(t, acc, fun) :: {[any], acc}
when fun: (element, acc -> {t, acc} | {:halt, acc})
def flat_map_reduce(enumerable, acc, fun) do
{_, {list, acc}} =
Enumerable.reduce(enumerable, {:cont, {[], acc}}, fn entry, {list, acc} ->
@@ -1122,7 +1134,6 @@ defmodule Enum do
end)
end
# TODO: Remove on 2.0
def group_by(enumerable, dict, fun) do
IO.warn(
"Enum.group_by/3 with a map/dictionary as second element is deprecated. " <>
@@ -1140,8 +1151,6 @@ defmodule Enum do
@doc """
Intersperses `element` between each element of the enumeration.
Complexity: O(n).
## Examples
iex> Enum.intersperse([1, 2, 3], 0)
@@ -1276,7 +1285,7 @@ defmodule Enum do
If `joiner` is not passed at all, it defaults to the empty binary.
All items in the `enumerable` must be convertible to a binary,
All elements in the `enumerable` must be convertible to a binary,
otherwise an error is raised.
## Examples
@@ -1306,8 +1315,8 @@ defmodule Enum do
end
@doc """
Returns a list where each item is the result of invoking
`fun` on each corresponding item of `enumerable`.
Returns a list where each element is the result of invoking
`fun` on each corresponding element of `enumerable`.
For maps, the function expects a key-value tuple.
@@ -1333,11 +1342,11 @@ defmodule Enum do
@doc """
Returns a list of results of invoking `fun` on every `nth`
item of `enumerable`, starting with the first element.
element of `enumerable`, starting with the first element.
The first item is always passed to the given function, unless `nth` is `0`.
The first element is always passed to the given function, unless `nth` is `0`.
The second argument specifying every `nth` item must be a non-negative
The second argument specifying every `nth` element must be a non-negative
integer.
If `nth` is `0`, then `enumerable` is directly converted to a list,
@@ -1378,7 +1387,7 @@ defmodule Enum do
the same type as `joiner`.
If `joiner` is not passed at all, it defaults to an empty binary.
All items returned from invoking the `mapper` must be convertible to
All elements returned from invoking the `mapper` must be convertible to
a binary, otherwise an error is raised.
## Examples
@@ -1408,7 +1417,7 @@ defmodule Enum do
end
@doc """
Invokes the given function to each item in the `enumerable` to reduce
Invokes the given function to each element in the `enumerable` to reduce
it to a single element, while keeping an accumulator.
Returns a tuple where the first element is the mapped enumerable and
@@ -1426,7 +1435,7 @@ defmodule Enum do
{[2, 4, 6], 6}
"""
@spec map_reduce(t, any, (element, any -> {any, any})) :: {any, any}
@spec map_reduce(t, acc, (element, acc -> {element, acc})) :: {list, acc}
def map_reduce(enumerable, acc, fun) when is_list(enumerable) do
:lists.mapfoldl(fun, acc, enumerable)
end
@@ -1467,7 +1476,7 @@ defmodule Enum do
In the example above, `max/1` returned March 31st instead of April 1st
because the structural comparison compares the day before the year. This
can be addressed by using `max_by/1` and by relying on structures where
can be addressed by using `max_by/3` and by relying on structures where
the most significant digits come first. In this particular case, we can
use `Date.to_erl/1` to get a tuple representation with year, month and day
fields:
@@ -1589,7 +1598,7 @@ defmodule Enum do
In the example above, `min/1` returned April 1st instead of March 31st
because the structural comparison compares the day before the year. This
can be addressed by using `min_by/1` and by relying on structures where
can be addressed by using `min_by/3` and by relying on structures where
the most significant digits come first. In this particular case, we can
use `Date.to_erl/1` to get a tuple representation with year, month and day
fields:
@@ -1676,7 +1685,7 @@ defmodule Enum do
first_fun = &{&1, &1}
reduce_fun = fn entry, {min, max} ->
{Kernel.min(entry, min), Kernel.max(entry, max)}
{Kernel.min(min, entry), Kernel.max(max, entry)}
end
case reduce_by(enumerable, first_fun, reduce_fun) do
@@ -1747,8 +1756,8 @@ defmodule Enum do
`fun` returned a falsy value (`false` or `nil`).
The elements in both the returned lists are in the same relative order as they
were in the original enumerable (if such enumerable was ordered, e.g., a
list); see the examples below.
were in the original enumerable (if such enumerable was ordered, like a
list). See the examples below.
## Examples
@@ -1766,7 +1775,7 @@ defmodule Enum do
"""
@doc since: "1.4.0"
@spec split_with(t, (element -> any)) :: {list, list}
@spec split_with(t, (element -> as_boolean(term))) :: {list, list}
def split_with(enumerable, fun) do
{acc1, acc2} =
reduce(enumerable, {[], []}, fn entry, {acc1, acc2} ->
@@ -1781,7 +1790,6 @@ defmodule Enum do
end
@doc false
# TODO: Remove on 2.0
@deprecated "Use Enum.split_with/2 instead"
def partition(enumerable, fun) do
split_with(enumerable, fun)
@@ -1830,7 +1838,7 @@ defmodule Enum do
def random(enumerable) do
result =
case backwards_compatible_slice(enumerable) do
case Enumerable.slice(enumerable) do
{:ok, 0, _} ->
[]
@@ -1873,7 +1881,7 @@ defmodule Enum do
24
"""
@spec reduce(t, (element, any -> any)) :: any
@spec reduce(t, (element, acc -> acc)) :: acc
def reduce(enumerable, fun)
def reduce([h | t], fun) do
@@ -2025,8 +2033,8 @@ defmodule Enum do
def reverse([]), do: []
def reverse([_] = list), do: list
def reverse([item1, item2]), do: [item2, item1]
def reverse([item1, item2 | rest]), do: :lists.reverse(rest, [item2, item1])
def reverse([element1, element2]), do: [element2, element1]
def reverse([element1, element2 | rest]), do: :lists.reverse(rest, [element2, element1])
def reverse(enumerable), do: reduce(enumerable, [], &[&1 | &2])
@doc """
@@ -2150,7 +2158,7 @@ defmodule Enum do
until element `index_range.last` (inclusively).
Indexes are normalized, meaning that negative indexes will be counted
from the end (e.g. `-1` means the last element of the `enumerable`).
from the end (for example, `-1` means the last element of the `enumerable`).
If `index_range.last` is out of bounds, then it is assigned as the index
of the last element.
@@ -2206,8 +2214,12 @@ defmodule Enum do
with `amount` number of elements if available.
Given an `enumerable`, it drops elements right before element `start_index`,
then takes `amount` of elements, returning as many elements as possible if there are not enough
elements.
then takes `amount` of elements, returning as many elements as possible if
there are not enough elements.
A negative `start_index` can be passed, which means the `enumerable` is
enumerated once and the index is counted from the end (for example,
`-1` starts slicing from the last element).
It returns `[]` if `amount` is `0` or if `start_index` is out of bounds.
@@ -2223,7 +2235,11 @@ defmodule Enum do
iex> Enum.slice(1..10, 5, 0)
[]
# out of bound start index
# using a negative start index
iex> Enum.slice(1..10, -6, 3)
[5, 6, 7]
# out of bound start index (positive)
iex> Enum.slice(1..10, 10, 5)
[]
@@ -2450,12 +2466,17 @@ defmodule Enum do
end
@doc """
Takes the first `amount` items from the `enumerable`.
Takes an `amount` of elements from the beginning or the end of the `enumerable`.
If a negative `amount` is given, the `amount` of last values will be taken.
If a positive `amount` is given, it takes the `amount` elements from the
beginning of the `enumerable`.
If a negative `amount` is given, the `amount` of elements will be taken from the end.
The `enumerable` will be enumerated once to retrieve the proper index and
the remaining calculation is performed from the end.
If amount is `0`, it returns `[]`.
## Examples
iex> Enum.take([1, 2, 3], 2)
@@ -2500,12 +2521,12 @@ defmodule Enum do
end
@doc """
Returns a list of every `nth` item in the `enumerable`,
Returns a list of every `nth` element in the `enumerable`,
starting with the first element.
The first item is always included, unless `nth` is 0.
The first element is always included, unless `nth` is 0.
The second argument specifying every `nth` item must be a non-negative
The second argument specifying every `nth` element must be a non-negative
integer.
## Examples
@@ -2533,7 +2554,7 @@ defmodule Enum do
end
@doc """
Takes `count` random items from `enumerable`.
Takes `count` random elements from `enumerable`.
Notice this function will traverse the whole `enumerable` to
get the random sublist.
@@ -2606,7 +2627,7 @@ defmodule Enum do
end
@doc """
Takes the items from the beginning of the `enumerable` while `fun` returns
Takes the elements from the beginning of the `enumerable` while `fun` returns
a truthy value.
## Examples
@@ -2663,7 +2684,6 @@ defmodule Enum do
end
@doc false
# TODO: Remove on 2.0
@deprecated "Use Enum.uniq_by/2 instead"
def uniq(enumerable, fun) do
uniq_by(enumerable, fun)
@@ -2671,7 +2691,7 @@ defmodule Enum do
@doc """
Enumerates the `enumerable`, by removing the elements for which
function `fun` returned duplicate items.
function `fun` returned duplicate elements.
The function `fun` maps every element to a term. Two elements are
considered duplicates if the return value of `fun` is equal for
@@ -2703,7 +2723,7 @@ defmodule Enum do
Opposite of `zip/2`. Extracts two-element tuples from the
given `enumerable` and groups them together.
It takes an `enumerable` with items being two-element tuples and returns
It takes an `enumerable` with elements being two-element tuples and returns
a tuple with two lists, each of which is formed by the first and
second element of each tuple, respectively.
@@ -2793,9 +2813,7 @@ defmodule Enum do
"""
@doc since: "1.4.0"
@spec zip([t]) :: t
@spec zip(t) :: t
@spec zip(enumerables) :: [tuple()] when enumerables: [t()] | t()
def zip([]), do: []
def zip(enumerables) do
@@ -2813,7 +2831,7 @@ defmodule Enum do
defp entry_to_string(entry), do: String.Chars.to_string(entry)
defp aggregate([head | tail], fun, _empty) do
:lists.foldl(fun, head, tail)
aggregate_list(tail, head, fun)
end
defp aggregate([], _fun, empty) do
@@ -2830,7 +2848,7 @@ defmodule Enum do
enumerable
|> reduce(ref, fn
element, ^ref -> element
element, acc -> fun.(element, acc)
element, acc -> fun.(acc, element)
end)
|> case do
^ref -> empty.()
@@ -2838,6 +2856,9 @@ defmodule Enum do
end
end
defp aggregate_list([head | tail], acc, fun), do: aggregate_list(tail, fun.(acc, head), fun)
defp aggregate_list([], acc, _fun), do: acc
defp reduce_by([head | tail], first, fun) do
:lists.foldl(fun, first.(head), tail)
end
@@ -2865,19 +2886,6 @@ defmodule Enum do
lower_limit + :rand.uniform(upper_limit - lower_limit + 1) - 1
end
# TODO: Remove me on Elixir v1.9
defp backwards_compatible_slice(args) do
try do
Enumerable.slice(args)
catch
:error, :undef ->
case __STACKTRACE__ do
[{module, :slice, [^args], _} | _] -> {:error, module}
stack -> :erlang.raise(:error, :undef, stack)
end
end
end
## Implementations
## all?
@@ -3072,7 +3080,7 @@ defmodule Enum do
end
defp slice_any(enumerable, start, amount) do
case backwards_compatible_slice(enumerable) do
case Enumerable.slice(enumerable) do
{:ok, count, _} when start >= count ->
[]
@@ -3105,7 +3113,7 @@ defmodule Enum do
end
defp slice_count_and_fun(enumerable) do
case backwards_compatible_slice(enumerable) do
case Enumerable.slice(enumerable) do
{:ok, count, fun} when is_function(fun) ->
{count, fun}
+60 -30
View File
@@ -924,9 +924,7 @@ defmodule UndefinedFunctionError do
defp message(:"function not exported", module, function, arity) do
formatted_fun = Exception.format_mfa(module, function, arity)
fun_message = "function #{formatted_fun} is undefined or private"
behaviour_hint = behaviour_hint(module, function, arity)
{fun_message <> behaviour_hint, true}
{"function #{formatted_fun} is undefined or private", true}
end
defp message(reason, module, function, arity) do
@@ -934,28 +932,6 @@ defmodule UndefinedFunctionError do
{"function #{formatted_fun} is undefined (#{reason})", false}
end
defp behaviour_hint(module, function, arity) do
case behaviours_for(module) do
[] ->
""
behaviours ->
case Enum.find(behaviours, &expects_callback?(&1, function, arity)) do
nil -> ""
behaviour -> ", but the behaviour #{inspect(behaviour)} expects it to be present"
end
end
rescue
# In case the module was removed while we are computing this
UndefinedFunctionError ->
[]
end
defp expects_callback?(behaviour, function, arity) do
callbacks = behaviour.behaviour_info(:callbacks)
Enum.member?(callbacks, {function, arity})
end
@impl true
def blame(exception, stacktrace) do
%{reason: reason, module: module, function: function, arity: arity} = exception
@@ -970,7 +946,8 @@ defmodule UndefinedFunctionError do
end
defp hint(module, function, arity, true) do
hint_for_loaded_module(module, function, arity, nil)
behaviour_hint(module, function, arity) <>
hint_for_loaded_module(module, function, arity, nil)
end
defp hint(_module, _function, _arity, _loaded?) do
@@ -1021,12 +998,33 @@ defmodule UndefinedFunctionError do
[" * ", Code.Identifier.inspect_as_function(fun), ?/, Integer.to_string(arity), ?\n]
end
defp behaviour_hint(module, function, arity) do
case behaviours_for(module) do
[] ->
""
behaviours ->
case Enum.find(behaviours, &expects_callback?(&1, function, arity)) do
nil -> ""
behaviour -> ", but the behaviour #{inspect(behaviour)} expects it to be present"
end
end
rescue
# In case the module was removed while we are computing this
UndefinedFunctionError -> ""
end
defp behaviours_for(module) do
:attributes
|> module.module_info()
|> Keyword.get(:behaviour, [])
end
defp expects_callback?(behaviour, function, arity) do
callbacks = behaviour.behaviour_info(:callbacks)
Enum.member?(callbacks, {function, arity})
end
defp exports_for(module) do
if function_exported?(module, :__info__, 1) do
module.__info__(:macros) ++ module.__info__(:functions)
@@ -1150,10 +1148,23 @@ defmodule Protocol.UndefinedError do
@impl true
def message(%{protocol: protocol, value: value, description: description}) do
"protocol #{inspect(protocol)} not implemented for #{inspect(value)}" <>
maybe_description(description) <> maybe_available(protocol)
"protocol #{inspect(protocol)} not implemented for #{inspect(value)} of type " <>
value_type(value) <> maybe_description(description) <> maybe_available(protocol)
end
defp value_type(%{__struct__: struct}), do: "#{inspect(struct)} (a struct)"
defp value_type(value) when is_atom(value), do: "Atom"
defp value_type(value) when is_bitstring(value), do: "BitString"
defp value_type(value) when is_float(value), do: "Float"
defp value_type(value) when is_function(value), do: "Function"
defp value_type(value) when is_integer(value), do: "Integer"
defp value_type(value) when is_list(value), do: "List"
defp value_type(value) when is_map(value), do: "Map"
defp value_type(value) when is_pid(value), do: "PID"
defp value_type(value) when is_port(value), do: "Port"
defp value_type(value) when is_reference(value), do: "Reference"
defp value_type(value) when is_tuple(value), do: "Tuple"
defp maybe_description(""), do: ""
defp maybe_description(description), do: ", " <> description
@@ -1163,7 +1174,8 @@ defmodule Protocol.UndefinedError do
". There are no implementations for this protocol."
{:consolidated, types} ->
". This protocol is implemented for: #{Enum.map_join(types, ", ", &inspect/1)}"
". This protocol is implemented for the following type(s): " <>
Enum.map_join(types, ", ", &inspect/1)
:not_consolidated ->
""
@@ -1178,7 +1190,7 @@ defmodule KeyError do
def message(exception = %{message: nil}), do: message(exception.key, exception.term)
def message(%{message: message}), do: message
def message(key, term) do
defp message(key, term) do
message = "key #{inspect(key)} not found"
if term != nil do
@@ -1307,6 +1319,24 @@ defmodule File.CopyError do
end
end
defmodule File.RenameError do
defexception [:reason, :source, :destination, on: "", action: ""]
@impl true
def message(exception) do
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
location =
case exception.on() do
"" -> ""
on -> ". #{on}"
end
"could not #{exception.action} from #{inspect(exception.source)} to " <>
"#{inspect(exception.destination)}#{location}: #{formatted}"
end
end
defmodule File.LinkError do
defexception [:reason, :existing, :new, action: ""]
+69 -33
View File
@@ -30,7 +30,7 @@ defmodule File do
always treated as UTF-8. In particular, we expect that the
shell and the operating system are configured to use UTF-8
encoding. Binary filenames are considered raw and passed
to the OS as is.
to the operating system as is.
## API
@@ -373,6 +373,8 @@ defmodule File do
machine
* `:posix` - returns the time as integer seconds since epoch
Note: Since file times are stored in POSIX time format on most operating systems,
it is faster to retrieve file information with the `time: :posix` option.
"""
@spec stat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix}
def stat(path, opts \\ []) do
@@ -424,6 +426,8 @@ defmodule File do
* `:local` - returns a `{date, time}` tuple using the machine time
* `:posix` - returns the time as integer seconds since epoch
Note: Since file times are stored in POSIX time format on most operating systems,
it is faster to retrieve file information with the `time: :posix` option.
"""
@spec lstat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix}
def lstat(path, opts \\ []) do
@@ -532,6 +536,12 @@ defmodule File do
(as returned by `:erlang.universaltime()`) or an integer
representing the POSIX timestamp (as returned by `System.os_time(:second)`).
In Unix-like systems, changing the modification time may require
you to be either `root` or the owner of the file. Having write
access may not be enough. In those cases, touching the file the
first time (to create it) will succeed, but touching an existing
file with fail with `{:error, :eperm}`.
## Examples
File.touch("/tmp/a.txt", {{2018, 1, 30}, {13, 59, 59}})
@@ -712,8 +722,8 @@ defmodule File do
Returns `:ok` in case of success, `{:error, reason}` otherwise.
Note: The command `mv` in Unix systems behaves differently depending
if `source` is a file and the `destination` is an existing directory.
Note: The command `mv` in Unix systems behaves differently depending on
whether `source` is a file and the `destination` is an existing directory.
We have chosen to explicitly disallow this behaviour.
## Examples
@@ -731,30 +741,54 @@ defmodule File do
end
@doc """
Copies the contents in `source` to `destination` preserving its mode.
The same as `rename/2` but raises a `File.RenameError` exception if it fails.
Returns `:ok` otherwise.
"""
@doc since: "1.9.0"
@spec rename!(Path.t(), Path.t()) :: :ok
def rename!(source, destination) do
case rename(source, destination) do
:ok ->
:ok
{:error, reason} ->
raise File.RenameError,
reason: reason,
action: "rename",
source: IO.chardata_to_string(source),
destination: IO.chardata_to_string(destination)
end
end
@doc """
Copies the contents in `source_file` to `destination_file` preserving its modes.
`source_file` and `destination_file` must be a file or a symbolic link to one,
or in the case of destination, a path to a non-existent file. If either one of
them is a directory, `{:error, :eisdir}` will be returned.
If a file already exists in the destination, it invokes a
callback which should return `true` if the existing file
should be overwritten, `false` otherwise. The callback defaults to return `true`.
The function returns `:ok` in case of success, returns
`{:error, reason}` otherwise.
The function returns `:ok` in case of success. Otherwise, it returns
`{:error, reason}`.
If you want to copy contents from an IO device to another device
or do a straight copy from a source to a destination without
preserving modes, check `copy/3` instead.
Note: The command `cp` in Unix systems behaves differently depending
if `destination` is an existing directory or not. We have chosen to
explicitly disallow this behaviour. If destination is a directory, an
error will be returned.
Note: The command `cp` in Unix systems behaves differently depending on
whether the destination is an existing directory or not. We have chosen to
explicitly disallow copying to a destination which is a directory,
and an error will be returned if tried.
"""
@spec cp(Path.t(), Path.t(), (Path.t(), Path.t() -> boolean)) :: :ok | {:error, posix}
def cp(source, destination, callback \\ fn _, _ -> true end) do
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
def cp(source_file, destination_file, callback \\ fn _, _ -> true end) do
source_file = IO.chardata_to_string(source_file)
destination_file = IO.chardata_to_string(destination_file)
case do_cp_file(source, destination, callback, []) do
case do_cp_file(source_file, destination_file, callback, []) do
{:error, reason, _} -> {:error, reason}
_ -> :ok
end
@@ -771,8 +805,8 @@ defmodule File do
Returns `:ok` otherwise.
"""
@spec cp!(Path.t(), Path.t(), (Path.t(), Path.t() -> boolean)) :: :ok
def cp!(source, destination, callback \\ fn _, _ -> true end) do
case cp(source, destination, callback) do
def cp!(source_file, destination_file, callback \\ fn _, _ -> true end) do
case cp(source_file, destination_file, callback) do
:ok ->
:ok
@@ -780,26 +814,29 @@ defmodule File do
raise File.CopyError,
reason: reason,
action: "copy",
source: IO.chardata_to_string(source),
destination: IO.chardata_to_string(destination)
source: IO.chardata_to_string(source_file),
destination: IO.chardata_to_string(destination_file)
end
end
@doc ~S"""
Copies the contents in source to destination.
Copies the contents in `source` to `destination` recursively, maintaining the
source directory structure and modes.
If `source` is a file or a symbolic link to it, `destination` must be a path
to an existent file, a symbolic link to one, or a path to a non-existent file.
If `source` is a directory, or a symbolic link to it, then `destination` must
be an existent `directory` or a symbolic link to one, or a path to a non-existent directory.
If the source is a file, it copies `source` to
`destination`. If the source is a directory, it copies
the contents inside source into the destination.
`destination`. If the `source` is a directory, it copies
the contents inside source into the `destination` directory.
If a file already exists in the destination, it invokes `callback`.
`callback` must be a function that takes two arguments: `source` and `destination`.
The callback should return `true` if the existing file should be overwritten and `false` otherwise.
If a directory already exists in the destination
where a file is meant to be (or vice versa), this
function will fail.
This function may fail while copying files,
in such cases, it will leave the destination
directory in a dirty state, where file which have already been copied
@@ -809,9 +846,10 @@ defmodule File do
success, `files_and_directories` lists all files and directories copied in no
specific order. It returns `{:error, reason, file}` otherwise.
Note: The command `cp` in Unix systems behaves differently
depending if `destination` is an existing directory or not.
We have chosen to explicitly disallow this behaviour.
Note: The command `cp` in Unix systems behaves differently depending on
whether `destination` is an existing directory or not. We have chosen to
explicitly disallow this behaviour. If `source` is a `file` and `destination`
is a directory, `{:error, :eisdir}` will be returned.
## Examples
@@ -866,8 +904,6 @@ defmodule File do
end
end
# src may be a file or a directory, dest is definitely
# a directory. Returns nil unless an error is found.
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
case :elixir_utils.read_link_type(src) do
{:ok, :regular} ->
@@ -1655,7 +1691,7 @@ defmodule File do
end
@doc """
Changes the group given by the group id `gid`
Changes the group given by the group ID `gid`
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
"""
@@ -1683,7 +1719,7 @@ defmodule File do
end
@doc """
Changes the owner given by the user id `uid`
Changes the owner given by the user ID `uid`
for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure.
"""
@@ -1733,7 +1769,7 @@ defmodule File do
[read_ahead: @read_ahead_size] ++ normalize_modes(rest, binary?)
end
# TODO: Remove :char_list mode by 2.0
# TODO: Remove :char_list mode on v2.0
defp normalize_modes([mode | rest], _binary?) when mode in [:charlist, :char_list] do
if mode == :char_list do
IO.warn("the :char_list mode is deprecated, use :charlist")
+5 -3
View File
@@ -63,9 +63,9 @@ defmodule File.Stat do
size: non_neg_integer(),
type: :device | :directory | :regular | :other | :symlink,
access: :read | :write | :read_write | :none,
atime: :calendar.datetime(),
mtime: :calendar.datetime(),
ctime: :calendar.datetime(),
atime: :calendar.datetime() | integer(),
mtime: :calendar.datetime() | integer(),
ctime: :calendar.datetime() | integer(),
mode: non_neg_integer(),
links: non_neg_integer(),
major_device: non_neg_integer(),
@@ -78,6 +78,7 @@ defmodule File.Stat do
@doc """
Converts a `File.Stat` struct to a `:file_info` record.
"""
@spec to_record(t()) :: :file.file_info()
def to_record(%File.Stat{unquote_splicing(pairs)}) do
{:file_info, unquote_splicing(vals)}
end
@@ -85,6 +86,7 @@ defmodule File.Stat do
@doc """
Converts a `:file_info` record into a `File.Stat`.
"""
@spec from_record(:file.file_info()) :: t()
def from_record(file_info)
def from_record({:file_info, unquote_splicing(vals)}) do
+6 -11
View File
@@ -36,7 +36,7 @@ defmodule Float do
To learn more about floating-point arithmetic visit:
* [0.30000000000000004.com](http://0.30000000000000004.com/)
* [What Every Programmer Should Know About Floating-Point Arithmetic](http://floating-point-gui.de/)
* [What Every Programmer Should Know About Floating-Point Arithmetic](https://floating-point-gui.de/)
"""
@@ -268,16 +268,14 @@ defmodule Float do
raise ArgumentError, invalid_precision_message(precision)
end
defp round(0.0, _precision, _rounding), do: 0.0
defp round(float, precision, rounding) do
<<sign::1, exp::11, significant::52-bitstring>> = <<float::float>>
{num, count, _} = decompose(significant, 1)
count = count - exp + 1023
cond do
# There is no decimal precision on subnormal floats
count <= 0 or exp == 0 ->
float
# Precision beyond 15 digits
count >= 104 ->
case rounding do
@@ -307,7 +305,7 @@ defmodule Float do
num = rounding(rounding, sign, num, div)
# Convert back to float without loss
# http://www.exploringbinary.com/correct-decimal-to-floating-point-using-big-integers/
# https://www.exploringbinary.com/correct-decimal-to-floating-point-using-big-integers/
den = power_of_10(precision)
boundary = den <<< 52
@@ -444,11 +442,11 @@ defmodule Float do
{acc, last_count, last_power}
end
@compile {:inline, sign: 2, shift_left: 2}
defp sign(0, num), do: num
defp sign(1, num), do: -num
defp shift_left(num, 0), do: num
defp shift_left(num, times), do: shift_left(num <<< 1, times - 1)
defp shift_left(num, times), do: num <<< times
defp shift_right(num, 0), do: {num, 0}
defp shift_right(1, times), do: {1, times}
@@ -495,19 +493,16 @@ defmodule Float do
end
@doc false
# TODO: Remove by 2.0
@deprecated "Use Float.to_charlist/1 instead"
def to_char_list(float), do: Float.to_charlist(float)
@doc false
# TODO: Remove by 2.0
@deprecated "Use :erlang.float_to_list/2 instead"
def to_char_list(float, options) do
:erlang.float_to_list(float, expand_compact(options))
end
@doc false
# TODO: Remove by 2.0
@deprecated "Use :erlang.float_to_binary/2 instead"
def to_string(float, options) do
:erlang.float_to_binary(float, expand_compact(options))
+2 -13
View File
@@ -1,6 +1,4 @@
defmodule GenEvent do
# TODO: Remove by 2.0
# Functions from this module are deprecated in elixir_dispatch.
@moduledoc """
@@ -90,12 +88,10 @@ defmodule GenEvent do
@deprecated message
@doc false
defmacro __using__(_) do
%{file: file, line: line} = __CALLER__
deprecation_message =
"the GenEvent module is deprecated, see its documentation for alternatives"
:elixir_errors.warn(line, file, deprecation_message)
IO.warn(deprecation_message, Macro.Env.stacktrace(__CALLER__))
quote location: :keep do
@behaviour :gen_event
@@ -328,14 +324,7 @@ defmodule GenEvent do
def init_it(starter, parent, name, _mod, _args, options) do
Process.put(:"$initial_call", {__MODULE__, :init_it, 6})
debug =
if function_exported?(:gen, :debug_options, 2) do
:gen.debug_options(name, options)
else
:gen.debug_options(options)
end
debug = :gen.debug_options(name, options)
:proc_lib.init_ack(starter, {:ok, self()})
loop(parent, name(name), [], debug, false)
end
+77 -32
View File
@@ -16,7 +16,7 @@ defmodule GenServer do
Let's start with a code example and then explore the available callbacks.
Imagine we want a GenServer that works like a stack, allowing us to push
and pop items:
and pop elements:
defmodule Stack do
use GenServer
@@ -34,8 +34,8 @@ defmodule GenServer do
end
@impl true
def handle_cast({:push, item}, state) do
{:noreply, [item | state]}
def handle_cast({:push, element}, state) do
{:noreply, [element | state]}
end
end
@@ -54,14 +54,16 @@ defmodule GenServer do
We start our `Stack` by calling `start_link/2`, passing the module
with the server implementation and its initial argument (a list
representing the stack containing the item `:hello`). We can primarily
representing the stack containing the element `:hello`). We can primarily
interact with the server by sending two types of messages. **call**
messages expect a reply from the server (and are therefore synchronous)
while **cast** messages do not.
Every time you do a `GenServer.call/3`, the client will send a message
that must be handled by the `c:handle_call/3` callback in the GenServer.
A `cast/2` message must be handled by `c:handle_cast/2`.
A `cast/2` message must be handled by `c:handle_cast/2`. There are 7 possible
callbacks to be implemented when you use a `GenServer`. The only required
callback is `c:init/1`.
## Client / Server APIs
@@ -81,8 +83,8 @@ defmodule GenServer do
GenServer.start_link(__MODULE__, default)
end
def push(pid, item) do
GenServer.cast(pid, {:push, item})
def push(pid, element) do
GenServer.cast(pid, {:push, element})
end
def pop(pid) do
@@ -102,8 +104,8 @@ defmodule GenServer do
end
@impl true
def handle_cast({:push, item}, state) do
{:noreply, [item | state]}
def handle_cast({:push, element}, state) do
{:noreply, [element | state]}
end
end
@@ -111,14 +113,33 @@ defmodule GenServer do
the same module. If the server and/or client implementations are growing
complex, you may want to have them in different modules.
## use GenServer and callbacks
## How to supervise
There are 7 callbacks to be implemented when you use a `GenServer`.
The only required callback is `init/1`.
A `GenServer` is most commonly started under a supervision tree.
When we invoke `use GenServer`, it automatically defines a `child_spec/1`
function that allows us to start the `Stack` directly under a supervisor.
To start a default stack of `[:hello]` under a supervisor, one may do:
`use GenServer` also defines a `child_spec/1` function, allowing the
defined module to be put under a supervision tree. The generated
`child_spec/1` can be customized with the following options:
children = [
{Stack, [:hello]}
]
Supervisor.start_link(children, strategy: :one_for_all)
Note you can also start it simply as `Stack`, which is the same as
`{Stack, []}`:
children = [
Stack # The same as {Stack, []}
]
Supervisor.start_link(children, strategy: :one_for_all)
In both cases, `Stack.start_link/1` is always invoked.
`use GenServer` also accepts a list of options which configures the
child specification and therefore how it runs under a supervisor.
The generated `child_spec/1` can be customized with the following options:
* `:id` - the child specification identifier, defaults to the current module
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
@@ -213,7 +234,7 @@ defmodule GenServer do
@impl true
def handle_info(:work, state) do
# Do the desired work here
...
# ...
# Reschedule once more
schedule_work()
@@ -227,6 +248,22 @@ defmodule GenServer do
end
end
## Timeouts
The return value of `c:init/1` or any of the `handle_*` callbacks may include
a timeout value in milliseconds; if not, `:infinity` is assumed.
The timeout can be used to detect a lull in incoming messages.
If the process has no messages waiting when the timeout is set and the
number of given milliseconds pass without any message arriving,
then `handle_info/2` will be called with `:timeout` as the first argument.
The timeout is cleared if any message is waiting or arrives before the
given timeout.
Because a message may arrive before the timeout is set, even a timeout of `0`
milliseconds is not guaranteed to execute. To take another action immediately
and unconditionally, use a `:continue` instruction.
## When (not) to use a GenServer
So far, we have learned that a `GenServer` can be used as a supervised process
@@ -375,9 +412,9 @@ defmodule GenServer do
Returning `{:ok, state}` will cause `start_link/3` to return
`{:ok, pid}` and the process to enter its loop.
Returning `{:ok, state, timeout}` is similar to `{:ok, state}`
except `handle_info(:timeout, state)` will be called after `timeout`
milliseconds if no messages are received within the timeout.
Returning `{:ok, state, timeout}` is similar to `{:ok, state}`,
except that it also sets a timeout. See the "Timeouts" section
in the module documentation for more information.
Returning `{:ok, state, :hibernate}` is similar to `{:ok, state}`
except the process is hibernated before entering the loop. See
@@ -426,8 +463,8 @@ defmodule GenServer do
caller and continues the loop with new state `new_state`.
Returning `{:reply, reply, new_state, timeout}` is similar to
`{:reply, reply, new_state}` except `handle_info(:timeout, new_state)` will be
called after `timeout` milliseconds if no messages are received.
`{:reply, reply, new_state}` except that it also sets a timeout.
See the "Timeouts" section in the module documentation for more information.
Returning `{:reply, reply, new_state, :hibernate}` is similar to
`{:reply, reply, new_state}` except the process is hibernated and will
@@ -492,9 +529,9 @@ defmodule GenServer do
Returning `{:noreply, new_state}` continues the loop with new state `new_state`.
Returning `{:noreply, new_state, timeout}` is similar to
`{:noreply, new_state}` except `handle_info(:timeout, new_state)` will be
called after `timeout` milliseconds if no messages are received.
Returning `{:noreply, new_state, timeout}` is similar to `{:noreply, new_state}`
except that it also sets a timeout. See the "Timeouts" section in the module
documentation for more information.
Returning `{:noreply, new_state, :hibernate}` is similar to
`{:noreply, new_state}` except the process is hibernated before continuing the
@@ -569,9 +606,9 @@ defmodule GenServer do
* the `GenServer` traps exits (using `Process.flag/2`) *and* the parent
process sends an exit signal
If part of a supervision tree, a `GenServer`'s will receive an exit
If part of a supervision tree, a `GenServer` will receive an exit
signal when the tree is shutting down. The exit signal is based on
the shutdown strategy in the child's specification, where we this
the shutdown strategy in the child's specification, where this
value can be:
* `:brutal_kill`: the `GenServer` is killed and so `c:terminate/2` is not called.
@@ -630,8 +667,7 @@ defmodule GenServer do
@callback code_change(old_vsn, state :: term, extra :: term) ::
{:ok, new_state :: term}
| {:error, reason :: term}
| {:down, term}
when old_vsn: term
when old_vsn: term | {:down, term}
@doc """
Invoked in some cases to retrieve a formatted version of the `GenServer` status.
@@ -681,7 +717,12 @@ defmodule GenServer do
@typedoc "Debug options supported by the `start*` functions"
@type debug :: [:trace | :log | :statistics | {:log_to_file, Path.t()}]
@typedoc "The server reference"
@typedoc """
The server reference.
This is either a plain PID or a value representing a registered name.
See the "Name registration" section of this document for more information.
"""
@type server :: pid | name | {atom, node}
@typedoc """
@@ -716,7 +757,7 @@ defmodule GenServer do
defoverridable child_spec: 1
# TODO: Remove this on Elixir v2.0
# TODO: Remove this on v2.0
@before_compile GenServer
@doc false
@@ -798,7 +839,7 @@ defmodule GenServer do
the arguments given to GenServer.start_link/3 to the server state.
"""
:elixir_errors.warn(env.line, env.file, message)
IO.warn(message, Macro.Env.stacktrace(env))
quote do
@doc false
@@ -952,7 +993,8 @@ defmodule GenServer do
element.
"""
@spec call(server, term, timeout) :: term
def call(server, request, timeout \\ 5000) do
def call(server, request, timeout \\ 5000)
when (is_integer(timeout) and timeout >= 0) or timeout == :infinity do
case whereis(server) do
nil ->
exit({:noproc, {__MODULE__, :call, [server, request, timeout]}})
@@ -985,6 +1027,9 @@ defmodule GenServer do
not yet connected to the caller one, the semantics differ
depending on the used Erlang/OTP version.
`server` can be any of the values described in the "Name registration"
section of the documentation for this module.
Before Erlang/OTP 21, the call is going to block until a
connection happens. This was done to guarantee ordering.
Starting with Erlang/OTP 21, both Erlang and Elixir do
-2
View File
@@ -7,8 +7,6 @@ defmodule HashDict do
@moduledoc deprecated: "Use Map instead"
# TODO: Remove by 2.0
use Dict
@node_bitmap 0b111
+23 -7
View File
@@ -31,7 +31,7 @@ defprotocol Inspect do
end
end
The `concat/1` function comes from `Inspect.Algebra` and it
The [`concat/1`](`Inspect.Algebra.concat/1`) function comes from `Inspect.Algebra` and it
concatenates algebra documents together. In the example above,
it is concatenating the string `"MapSet<"` (all strings are
valid algebra documents that keep their formatting when pretty
@@ -77,6 +77,15 @@ defprotocol Inspect do
# Handle structs in Any
@fallback_to_any true
@doc """
Converts `term` into an algebra document.
This function shouldn't be invoked directly, unless when implementing
a custom `inspect_fun` to be given to `Inspect.Opts`. Everywhere else,
`Inspect.Algebra.to_doc/2` should be preferred as it handles structs
and exceptions.
"""
@spec inspect(t, Inspect.Opts.t()) :: Inspect.Algebra.t()
def inspect(term, opts)
end
@@ -160,7 +169,7 @@ defimpl Inspect, for: List do
color("[]", :list, opts)
end
# TODO: Remove :char_list and :as_char_lists handling in 2.0
# TODO: Remove :char_list and :as_char_lists handling on v2.0
def inspect(term, opts) do
%Inspect.Opts{
charlists: lists,
@@ -307,11 +316,20 @@ end
defimpl Inspect, for: Regex do
def inspect(regex, opts) do
{escaped, _} = Identifier.escape(regex.source, ?/, :infinity, &escape_map/1)
{escaped, _} =
regex.source
|> normalize(<<>>)
|> Identifier.escape(?/, :infinity, &escape_map/1)
source = IO.iodata_to_binary(['~r/', escaped, ?/, regex.opts])
color(source, :regex, opts)
end
defp normalize(<<?\\, ?\\, rest::binary>>, acc), do: normalize(rest, <<acc::binary, ?\\, ?\\>>)
defp normalize(<<?\\, ?/, rest::binary>>, acc), do: normalize(rest, <<acc::binary, ?/>>)
defp normalize(<<char, rest::binary>>, acc), do: normalize(rest, <<acc::binary, char>>)
defp normalize(<<>>, acc), do: acc
defp escape_map(?\a), do: '\\a'
defp escape_map(?\f), do: '\\f'
defp escape_map(?\n), do: '\\n'
@@ -415,8 +433,7 @@ defimpl Inspect, for: Any do
defimpl Inspect, for: unquote(module) do
def inspect(struct, opts) do
map = Map.take(struct, unquote(filtered_fields))
colorless_opts = %{opts | syntax_colors: []}
name = Inspect.Atom.inspect(unquote(module), colorless_opts)
name = Identifier.inspect_as_atom(unquote(module))
unquote(inspect_module).inspect(map, name, opts)
end
end
@@ -432,8 +449,7 @@ defimpl Inspect, for: Any do
dunder ->
if :maps.keys(dunder) == :maps.keys(struct) do
pruned = :maps.remove(:__exception__, :maps.remove(:__struct__, struct))
colorless_opts = %{opts | syntax_colors: []}
Inspect.Map.inspect(pruned, Inspect.Atom.inspect(module, colorless_opts), opts)
Inspect.Map.inspect(pruned, Identifier.inspect_as_atom(module), opts)
else
Inspect.Map.inspect(struct, opts)
end
+40 -25
View File
@@ -13,7 +13,8 @@ defmodule Inspect.Opts do
When `:as_binaries` all binaries will be printed in bit syntax.
When the default `:infer`, the binary will be printed as a string if it
is printable, otherwise in bit syntax.
is printable, otherwise in bit syntax. See `String.printable?/1` to learn
when a string is printable.
* `:charlists` - when `:as_charlists` all lists will be printed as charlists,
non-printable elements will be escaped.
@@ -21,16 +22,20 @@ defmodule Inspect.Opts do
When `:as_lists` all lists will be printed as lists.
When the default `:infer`, the list will be printed as a charlist if it
is printable, otherwise as list.
is printable, otherwise as list. See `List.ascii_printable?/1` to learn
when a charlist is printable.
* `:limit` - limits the number of items that are printed for tuples,
* `:limit` - limits the number of items that are inspected for tuples,
bitstrings, maps, lists and any other collection of items. It does not
apply to strings nor charlists and defaults to 50. If you don't want to limit
the number of items to a particular number, use `:infinity`.
apply to printable strings nor printable charlists and defaults to 50.
If you don't want to limit the number of items to a particular number,
use `:infinity`.
* `:printable_limit` - limits the number of bytes that are printed for strings
and charlists. Defaults to 4096. If you don't want to limit the number of items
to a particular number, use `:infinity`.
* `:printable_limit` - limits the number of characters that are inspected
on printable strings and printable charlists. You can use `String.printable?/1`
and `List.ascii_printable?/1` to check if a given string or charlist is
printable. Defaults to 4096. If you don't want to limit the number of
characters to a particular number, use `:infinity`.
* `:pretty` - if set to `true` enables pretty printing, defaults to `false`.
@@ -46,17 +51,24 @@ defmodule Inspect.Opts do
* `:safe` - when `false`, failures while inspecting structs will be raised
as errors instead of being wrapped in the `Inspect.Error` exception. This
is useful when debugging failures and crashes for custom inspect
implementations
implementations.
* `:syntax_colors` - when set to a keyword list of colors the output will
be colorized. The keys are types and the values are the colors to use for
* `:syntax_colors` - when set to a keyword list of colors the output is
colorized. The keys are types and the values are the colors to use for
each type (for example, `[number: :red, atom: :blue]`). Types can include
`:number`, `:atom`, `regex`, `:tuple`, `:map`, `:list`, and `:reset`.
Colors can be any `t:IO.ANSI.ansidata/0` as accepted by `IO.ANSI.format/1`.
* `:inspect_fun` (since v1.9.0) - a function to build algebra documents,
defaults to `Inspect.inspect/2`
* `:custom_options` (since v1.9.0) - a keyword list storing custom user-defined
options. Useful when implementing the `Inspect` protocol for nested structs
to pass the custom options through.
"""
# TODO: Remove :char_lists key by 2.0
# TODO: Remove :char_lists key on v2.0
defstruct structs: true,
binaries: :infer,
charlists: :infer,
@@ -67,11 +79,13 @@ defmodule Inspect.Opts do
base: :decimal,
pretty: false,
safe: true,
syntax_colors: []
syntax_colors: [],
inspect_fun: &Inspect.inspect/2,
custom_options: []
@type color_key :: atom
# TODO: Remove :char_lists key and :as_char_lists value by 2.0
# TODO: Remove :char_lists key and :as_char_lists value on v2.0
@type t :: %__MODULE__{
structs: boolean,
binaries: :infer | :as_binaries | :as_strings,
@@ -83,7 +97,9 @@ defmodule Inspect.Opts do
base: :decimal | :binary | :hex | :octal,
pretty: boolean,
safe: boolean,
syntax_colors: [{color_key, IO.ANSI.ansidata()}]
syntax_colors: [{color_key, IO.ANSI.ansidata()}],
inspect_fun: (any, t -> Inspect.Algebra.t()),
custom_options: keyword
}
end
@@ -257,10 +273,10 @@ defmodule Inspect.Algebra do
@spec to_doc(any, Inspect.Opts.t()) :: t
def to_doc(term, opts)
def to_doc(%_{} = struct, %Inspect.Opts{} = opts) do
def to_doc(%_{} = struct, %Inspect.Opts{inspect_fun: fun} = opts) do
if opts.structs do
try do
Inspect.inspect(struct, opts)
fun.(struct, opts)
rescue
caught_exception ->
# Because we try to raise a nice error message in case
@@ -299,8 +315,8 @@ defmodule Inspect.Algebra do
end
end
def to_doc(arg, %Inspect.Opts{} = opts) do
Inspect.inspect(arg, opts)
def to_doc(arg, %Inspect.Opts{inspect_fun: fun} = opts) do
fun.(arg, opts)
end
@doc ~S"""
@@ -410,14 +426,12 @@ defmodule Inspect.Algebra do
defp simple?(:doc_nil), do: true
defp simple?(other), do: is_binary(other)
# TODO: Remove on 2.0
@doc false
@deprecated "Use a combination of concat/2 and nest/2 instead"
def surround(left, doc, right) when is_doc(left) and is_doc(doc) and is_doc(right) do
concat(concat(left, nest(doc, 1)), right)
end
# TODO: Remove on 2.0
@doc false
@deprecated "Use Inspect.Algebra.container_doc/6 instead"
def surround_many(
@@ -686,7 +700,7 @@ defmodule Inspect.Algebra do
to the document fitting. On the other hand, they are more expensive
since each break needs to be re-evaluated.
This function is used by `container_doc/4` and friends to the
This function is used by `container_doc/6` and friends to the
maximum number of entries on the same line.
"""
@doc since: "1.6.0"
@@ -809,7 +823,7 @@ defmodule Inspect.Algebra do
@doc ~S"""
Inserts a mandatory linebreak between two documents.
See `line/1`.
See `line/0`.
## Examples
@@ -882,10 +896,11 @@ defmodule Inspect.Algebra do
# * flat_no_break - represents a document with breaks as flat not allowed to enter in break mode
# * break_no_flat - represents a document with breaks as breaks not allowed to enter in flat mode
#
@typep mode :: :flat | :flat_no_break | :break
@typep mode :: :flat | :flat_no_break | :break | :break_no_flat
@spec fits?(width :: integer(), column :: integer(), break? :: boolean(), entries) :: boolean()
when entries: [{integer(), mode(), t()}] | {:tail, boolean(), entries}
when entries:
maybe_improper_list({integer(), mode(), t()}, {:tail, boolean(), entries} | [])
# We need at least a break to consider the document does not fit since a
# large document without breaks has no option but fitting its current line.
+7 -4
View File
@@ -269,6 +269,9 @@ defmodule Integer do
defp count_digits_nosign(<<_::binary>>, _, count), do: count
# TODO: Remove Integer.to_string/1 once the minimum supported version is
# Erlang/OTP 22, since it is covered by the now BIF Integer.to_string/2.
# Please reapply commit 2622fd6b0aa419a983a899a1fbdb5deefba3d85d.
@doc """
Returns a binary which corresponds to the text representation
of `integer`.
@@ -320,6 +323,9 @@ defmodule Integer do
:erlang.integer_to_binary(integer, base)
end
# TODO: Remove Integer.to_charlist/1 once the minimum supported version is
# Erlang/OTP 22, since it is covered by the now BIF Integer.to_charlist/2.
# Please reapply commit 2622fd6b0aa419a983a899a1fbdb5deefba3d85d.
@doc """
Returns a charlist which corresponds to the text representation of the given `integer`.
@@ -399,8 +405,7 @@ defmodule Integer do
"""
@doc since: "1.5.0"
@spec gcd(0, 0) :: 0
@spec gcd(integer, integer) :: pos_integer
@spec gcd(integer, integer) :: non_neg_integer
def gcd(integer1, integer2) when is_integer(integer1) and is_integer(integer2) do
gcd_positive(abs(integer1), abs(integer2))
end
@@ -409,12 +414,10 @@ defmodule Integer do
defp gcd_positive(integer1, 0), do: integer1
defp gcd_positive(integer1, integer2), do: gcd_positive(integer2, rem(integer1, integer2))
# TODO: Remove by 2.0
@doc false
@deprecated "Use Integer.to_charlist/1 instead"
def to_char_list(integer), do: Integer.to_charlist(integer)
# TODO: Remove by 2.0
@doc false
@deprecated "Use Integer.to_charlist/2 instead"
def to_char_list(integer, base), do: Integer.to_charlist(integer, base)
+143 -42
View File
@@ -1,5 +1,5 @@
defmodule IO do
@moduledoc """
@moduledoc ~S"""
Functions handling input/output (IO).
Many functions in this module expect an IO device as an argument.
@@ -7,13 +7,10 @@ defmodule IO do
For convenience, Elixir provides `:stdio` and `:stderr` as
shortcuts to Erlang's `:standard_io` and `:standard_error`.
The majority of the functions expect chardata, i.e. strings or
lists of characters and strings. In case another type is given,
functions will convert to string via the `String.Chars` protocol
(as shown in typespecs).
The functions starting with `bin` expect iodata as an argument,
i.e. binaries or lists of bytes and binaries.
The majority of the functions expect chardata. In case another type is given,
functions will convert those types to string via the `String.Chars` protocol
(as shown in typespecs). For more information on chardata, see the
"IO data" section below.
## IO devices
@@ -32,17 +29,100 @@ defmodule IO do
was last accessed. The position of files can be changed using the
`:file.position/2` function.
## IO data
IO data is a data type that can be used as a more efficient alternative to binaries
in certain situations.
A term of type **IO data** is a binary or a list containing bytes (integers in `0..255`)
or nested IO data. The type is recursive. Let's see an example of one of
the possible IO data representing the binary `"hello"`:
[?h, "el", ["l", [?o]]]
The built-in `t:iodata/0` type is defined in terms of `t:iolist/0`. An IO list is
the same as IO data but it doesn't allow for a binary at the top level (but binaries
are still allowed in the list itself).
### Use cases for IO data
IO data exists because often you need to do many append operations
on smaller chunks of binaries in order to create a bigger binary. However, in
Erlang and Elixir concatenating binaries will copy the concatenated binaries
into a new binary.
def email(username, domain) do
username <> "@" <> domain
end
In this function, creating the email address will copy the `username` and `domain`
binaries. Now imagine you want to use the resulting email inside another binary:
def welcome_message(name, username, domain) do
"Welcome #{name}, your email is: #{email(username, domain)}"
end
IO.puts(welcome_message("Meg", "meg", "example.com"))
#=> "Welcome Meg, your email is: meg@example.com"
Every time you concatenate binaries or use interpolation (`#{}`) you are making
copies of those binaries. However, in many cases you don't need the complete
binary while you create it, but only at the end to print it out or send it
somewhere. In such cases, you can construct the binary by creating IO data:
def email(username, domain) do
[username, ?@, domain]
end
def welcome_message(name, username, domain) do
["Welcome ", name, ", your email is: ", email(username, domain)]
end
IO.puts(welcome_message("Meg", "meg", "example.com"))
#=> "Welcome Meg, your email is: meg@example.com"
Building IO data is cheaper than concatenating binaries. Concatenating multiple
pieces of IO data just means putting them together inside a list since IO data
can be arbitrarily nested, and that's a cheap and efficient operation. Most of
the IO-based APIs, such as `:gen_tcp`, `IO`, etc, receive IO data and write it
to the socket directly without converting it to binary.
One drawback of IO data is that you can't do things like pattern match on the
first part of a piece of IO data like you can with a binary, because you usually
don't know the shape of the IO data. In those cases, you may need to convert it
to a binary by calling `iodata_to_binary/1`, which is reasonably efficient
since it's implemented natively in C. Other functionality, like computing the
length of IO data, can be computed directly on the iodata by calling `iodata_length/1`.
### Chardata
Erlang and Elixir also have the idea of `t:chardata/0`. Chardata is very
similar to IO data: the only difference is that integers in IO data represent
bytes while integers in chardata represent Unicode codepoints. Bytes
(`t:byte/0`) are integers in the `0..255` range, while Unicode codepoints
(`t:char/0`) are integers in the range `0..0x10FFFF`. The `IO` module provides
the `chardata_to_string/1` function for chardata as the "counter-part" of the
`iodata_to_binary/1` function for IO data.
If you try to use `iodata_to_binary/1` on chardata, it will result in an
argument error. For example, let's try to put a codepoint that is not
representable with one byte, like `?π`, inside IO data:
iex> IO.iodata_to_binary(["The symbol for pi is: ", ?π])
** (ArgumentError) argument error
If we use chardata instead, it will work as expected:
iex> IO.chardata_to_string(["The symbol for pi is: ", ?π])
"The symbol for pi is: π"
"""
@type device :: atom | pid
@type nodata :: {:error, term} | :eof
@type chardata :: String.t() | maybe_improper_list(char | chardata, String.t() | [])
defmacrop is_iodata(data) do
quote do
is_list(unquote(data)) or is_binary(unquote(data))
end
end
defguardp is_iodata(data) when is_list(data) or is_binary(data)
@doc """
Reads from the IO `device`.
@@ -141,7 +221,7 @@ defmodule IO do
end
@doc """
Writes `item` to the given `device`.
Writes `chardata` to the given `device`.
By default, the `device` is the standard output.
@@ -155,23 +235,30 @@ defmodule IO do
"""
@spec write(device, chardata | String.Chars.t()) :: :ok
def write(device \\ :stdio, item) do
:io.put_chars(map_dev(device), to_chardata(item))
def write(device \\ :stdio, chardata) do
:io.put_chars(map_dev(device), to_chardata(chardata))
end
@doc """
Writes `item` as a binary to the given `device`.
No Unicode conversion happens.
The operation is Unicode unsafe.
Writes `iodata` to the given `device`.
Check `write/2` for more information.
This operation is meant to be used with "raw" devices
that are started without an encoding. The given `iodata`
is written as is to the device, without conversion. For
more information on IO data, see the "IO data" section in
the module documentation.
Note: do not use this function on IO devices in Unicode mode
as it will return the wrong result.
Use `write/2` for devices with encoding.
Important: do **not** use this function on IO devices in
Unicode mode as it will write the wrong data. In particular,
the standard IO device is set to Unicode by default, so writing
to stdio with this function will likely result in the wrong data
being sent down the wire.
"""
@spec binwrite(device, iodata) :: :ok | {:error, term}
def binwrite(device \\ :stdio, item) when is_iodata(item) do
:file.write(map_dev(device), item)
def binwrite(device \\ :stdio, iodata) when is_iodata(iodata) do
:file.write(map_dev(device), iodata)
end
@doc """
@@ -214,15 +301,22 @@ defmodule IO do
"""
@spec warn(chardata | String.Chars.t(), Exception.stacktrace()) :: :ok
def warn(message, []) do
:elixir_errors.bare_warn(nil, nil, [to_chardata(message), ?\n])
message = [to_chardata(message), ?\n]
:elixir_errors.io_warn(nil, nil, message, message)
end
def warn(message, [{_, _, _, opts} | _] = stacktrace) do
message = to_chardata(message)
formatted_trace = Enum.map_join(stacktrace, "\n ", &Exception.format_stacktrace_entry(&1))
message = [to_chardata(message), ?\n, " ", formatted_trace, ?\n]
line = opts[:line]
file = opts[:file]
:elixir_errors.bare_warn(line, file && List.to_string(file), message)
:elixir_errors.io_warn(
line,
file && List.to_string(file),
message,
[message, ?\n, " ", formatted_trace, ?\n]
)
end
@doc """
@@ -315,7 +409,7 @@ defmodule IO do
Gets a number of bytes from IO device `:stdio`.
If `:stdio` is a Unicode device, `count` implies
the number of Unicode codepoints to be retrieved.
the number of Unicode code points to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
See `IO.getn/3` for a description of return values.
@@ -337,7 +431,7 @@ defmodule IO do
Gets a number of bytes from the IO `device`.
If the IO `device` is a Unicode device, `count` implies
the number of Unicode codepoints to be retrieved.
the number of Unicode code points to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
It returns:
@@ -439,8 +533,10 @@ defmodule IO do
end
@doc """
Converts chardata (a list of integers representing codepoints,
lists and strings) into a string.
Converts chardata into a string.
For more information about chardata, see the ["Chardata"](#module-chardata)
section in the module documentation.
In case the conversion fails, it raises an `UnicodeConversionError`.
If a string is given, it returns the string itself.
@@ -467,14 +563,16 @@ defmodule IO do
end
@doc """
Converts iodata (a list of integers representing bytes, lists
and binaries) into a binary.
Converts IO data into a binary
The operation is Unicode unsafe.
Notice that this function treats lists of integers as raw bytes
and does not perform any kind of encoding conversion. If you want
to convert from a charlist to a string (UTF-8 encoded), please
use `chardata_to_string/1` instead.
Notice that this function treats integers in the given IO data as
raw bytes and does not perform any kind of encoding conversion.
If you want to convert from a charlist to a UTF-8-encoded string,
use `chardata_to_string/1` instead. For more information about
IO data and chardata, see the ["IO data"](#module-io-data) section in the
module documentation.
If this function receives a binary, the same binary is returned.
@@ -494,12 +592,15 @@ defmodule IO do
"""
@spec iodata_to_binary(iodata) :: binary
def iodata_to_binary(item) do
:erlang.iolist_to_binary(item)
def iodata_to_binary(iodata) do
:erlang.iolist_to_binary(iodata)
end
@doc """
Returns the size of an iodata.
Returns the size of an IO data.
For more information about IO data, see the ["IO data"](#module-io-data)
section in the module documentation.
Inlined by the compiler.
@@ -510,8 +611,8 @@ defmodule IO do
"""
@spec iodata_length(iodata) :: non_neg_integer
def iodata_length(item) do
:erlang.iolist_size(item)
def iodata_length(iodata) do
:erlang.iolist_size(iodata)
end
@doc false
+1 -1
View File
@@ -237,7 +237,7 @@ defmodule IO.ANSI do
The named sequences are represented by atoms.
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
emitting actual ANSI codes. When `false`, no ANSI codes will be emitted.
By default checks if ANSI is enabled using the `enabled?/0` function.
## Examples
+2 -6
View File
@@ -525,15 +525,11 @@ defmodule IO.ANSI.Docs do
defp escape_underlines_in_link(text) do
# Regular expression adapted from https://tools.ietf.org/html/rfc3986#appendix-B
~r{[a-z][a-z0-9\+\-\.]*://\S*}i
|> Regex.recompile!()
|> Regex.replace(text, &String.replace(&1, "_", "\\_"))
Regex.replace(~r{[a-z][a-z0-9\+\-\.]*://\S*}i, text, &String.replace(&1, "_", "\\_"))
end
defp remove_square_brackets_in_link(text) do
~r{\[(.*?)\]\((.*?)\)}
|> Regex.recompile!()
|> Regex.replace(text, "\\1 (\\2)")
Regex.replace(~r{\[([^\]]*?)\]\((.*?)\)}, text, "\\1 (\\2)")
end
# We have four entries: **, *, _ and `.
+270 -393
View File
File diff suppressed because it is too large Load Diff
+50 -22
View File
@@ -5,7 +5,7 @@ defmodule Kernel.CLI do
commands: [],
output: ".",
compile: [],
halt: true,
no_halt: false,
compiler_options: [],
errors: [],
pa: [],
@@ -21,6 +21,7 @@ defmodule Kernel.CLI do
{config, argv} = parse_argv(argv)
System.argv(argv)
System.no_halt(config.no_halt)
fun = fn _ ->
errors = process_commands(config)
@@ -31,7 +32,7 @@ defmodule Kernel.CLI do
end
end
run(fun, config.halt)
run(fun)
end
@doc """
@@ -42,10 +43,10 @@ defmodule Kernel.CLI do
This function is used by Elixir's CLI and also
by escripts generated by Elixir.
"""
def run(fun, halt \\ true) do
def run(fun) do
{ok_or_shutdown, status} = exec_fun(fun, {:ok, 0})
if ok_or_shutdown == :shutdown or halt do
if ok_or_shutdown == :shutdown or not System.no_halt() do
{_, status} = at_exit({ok_or_shutdown, status})
# Ensure Logger messages are flushed before halting
@@ -58,19 +59,25 @@ defmodule Kernel.CLI do
end
end
@doc false
@doc """
Parses the CLI arguments. Made public for testing.
"""
def parse_argv(argv) do
parse_argv(argv, @blank_config)
end
@doc false
@doc """
Process CLI commands. Made public for testing.
"""
def process_commands(config) do
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
errors = for {:error, msg} <- results, do: msg
Enum.reverse(config.errors, errors)
end
@doc false
@doc """
Shared helper for error formatting on CLI tools.
"""
def format_error(kind, reason, stacktrace) do
{blamed, stacktrace} = Exception.blame(kind, reason, stacktrace)
@@ -88,6 +95,15 @@ defmodule Kernel.CLI do
[iodata, ?\n, Exception.format_stacktrace(prune_stacktrace(stacktrace))]
end
@doc """
Function invoked across nodes for `--rpc-eval`.
"""
def rpc_eval(expr) do
wrapper(fn -> :elixir.eval(to_charlist(expr), [], []) end)
catch
kind, reason -> {kind, reason, __STACKTRACE__}
end
## Helpers
defp at_exit(res) do
@@ -225,13 +241,22 @@ defmodule Kernel.CLI do
end
defp parse_shared(["--no-halt" | t], config) do
parse_shared(t, %{config | halt: false})
parse_shared(t, %{config | no_halt: true})
end
defp parse_shared(["-e", h | t], config) do
parse_shared(t, %{config | commands: [{:eval, h} | config.commands]})
end
defp parse_shared(["--eval", h | t], config) do
parse_shared(t, %{config | commands: [{:eval, h} | config.commands]})
end
defp parse_shared(["--rpc-eval", node, h | t], config) do
node = append_hostname(node)
parse_shared(t, %{config | commands: [{:rpc_eval, node, h} | config.commands]})
end
defp parse_shared(["-r", h | t], config) do
parse_shared(t, %{config | commands: [{:require, h} | config.commands]})
end
@@ -240,23 +265,17 @@ defmodule Kernel.CLI do
parse_shared(t, %{config | commands: [{:parallel_require, h} | config.commands]})
end
@erl_arg_options ["--erl", "--sname", "--name", "--cookie"] ++
["--logger-otp-reports", "--logger-sasl-reports"]
@erl_boolean_options ["--detached", "--hidden", "--werl"]
defp parse_shared([erl, _ | t], config) when erl in @erl_arg_options do
parse_shared(t, config)
end
defp parse_shared([erl | t], config) when erl in @erl_boolean_options do
parse_shared(t, config)
end
defp parse_shared(list, config) do
{list, config}
end
defp append_hostname(node) do
case :string.find(node, "@") do
:nomatch -> node <> :string.find(Atom.to_string(node()), "@")
_ -> node
end
end
defp expand_code_path(path) do
path = Path.expand(path)
@@ -290,7 +309,7 @@ defmodule Kernel.CLI do
shared_option?(list, config, &parse_argv(&1, &2))
_ ->
if Keyword.has_key?(config.commands, :eval) do
if List.keymember?(config.commands, :eval, 0) do
{config, list}
else
{%{config | commands: [{:file, h} | config.commands]}, t}
@@ -395,6 +414,15 @@ defmodule Kernel.CLI do
wrapper(fn -> Code.eval_string(expr, []) end)
end
defp process_command({:rpc_eval, node, expr}, _config) when is_binary(expr) do
case :rpc.call(String.to_atom(node), __MODULE__, :rpc_eval, [expr]) do
:ok -> :ok
{:badrpc, {:EXIT, exit}} -> Process.exit(self(), exit)
{:badrpc, reason} -> {:error, "--rpc-eval : RPC failed with reason #{inspect(reason)}"}
{kind, error, stack} -> :erlang.raise(kind, error, stack)
end
end
defp process_command({:app, app}, _config) when is_binary(app) do
case Application.ensure_all_started(String.to_atom(app)) do
{:error, {app, reason}} ->
+9 -9
View File
@@ -5,13 +5,13 @@ defmodule Kernel.ErrorHandler do
@spec undefined_function(module, atom, list) :: term
def undefined_function(module, fun, args) do
ensure_loaded(module) or ensure_compiled(module, :module)
ensure_loaded(module) or ensure_compiled(module, :module, :raise)
:error_handler.undefined_function(module, fun, args)
end
@spec undefined_lambda(module, fun, list) :: term
def undefined_lambda(module, fun, args) do
ensure_loaded(module) or ensure_compiled(module, :module)
ensure_loaded(module) or ensure_compiled(module, :module, :raise)
:error_handler.undefined_lambda(module, fun, args)
end
@@ -23,21 +23,21 @@ defmodule Kernel.ErrorHandler do
end
end
@spec ensure_compiled(module, atom) :: boolean
@spec ensure_compiled(module, atom, atom) :: :found | :not_found | :deadlock
# Never wait on nil because it should never be defined.
def ensure_compiled(nil, _kind) do
false
def ensure_compiled(nil, _kind, _deadlock) do
:not_found
end
def ensure_compiled(module, kind) do
def ensure_compiled(module, kind, deadlock) do
parent = :erlang.get(:elixir_compiler_pid)
ref = :erlang.make_ref()
send(parent, {:waiting, kind, self(), ref, module, :elixir_module.compiler_modules()})
modules = :elixir_module.compiler_modules()
send(parent, {:waiting, kind, self(), ref, module, modules, deadlock})
:erlang.garbage_collect(self())
receive do
{^ref, :found} -> true
{^ref, :not_found} -> false
{^ref, value} -> value
end
end
end
+9 -16
View File
@@ -12,22 +12,15 @@ defmodule Kernel.LexicalTracker do
@doc """
Returns all remotes referenced in this lexical scope.
"""
def remote_references(arg) do
:gen_server.call(to_pid(arg), :remote_references, @timeout)
def remote_references(pid) do
:gen_server.call(pid, :remote_references, @timeout)
end
@doc """
Returns all remote dispatches in this lexical scope.
"""
def remote_dispatches(arg) do
:gen_server.call(to_pid(arg), :remote_dispatches, @timeout)
end
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
{set, _} = :elixir_module.data_tables(mod)
:ets.lookup_element(set, {:elixir, :lexical_tracker}, 2)
def remote_dispatches(pid) do
:gen_server.call(pid, :remote_dispatches, @timeout)
end
# Internal API
@@ -40,7 +33,7 @@ defmodule Kernel.LexicalTracker do
@doc false
def stop(pid) do
:gen_server.cast(pid, :stop)
:gen_server.call(pid, :stop)
end
@doc false
@@ -153,6 +146,10 @@ defmodule Kernel.LexicalTracker do
{:reply, :maps.get(key, cache), state}
end
def handle_call(:stop, _from, state) do
{:stop, :normal, :ok, state}
end
def handle_cast({:write_cache, key, value}, %{cache: cache} = state) do
{:noreply, %{state | cache: :maps.put(key, value, cache)}}
end
@@ -213,10 +210,6 @@ defmodule Kernel.LexicalTracker do
{:noreply, %{state | directives: add_directive(state.directives, module, line, warn, :alias)}}
end
def handle_cast(:stop, state) do
{:stop, :normal, state}
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
+176 -155
View File
@@ -22,6 +22,7 @@ defmodule Kernel.ParallelCompiler do
{:error_handler, error_handler} = :erlang.process_info(self(), :error_handler)
Task.async(fn ->
send(parent, {:async, self()})
:erlang.put(:elixir_compiler_pid, parent)
:erlang.put(:elixir_compiler_file, file)
dest != :undefined and :erlang.put(:elixir_compiler_dest, dest)
@@ -66,7 +67,7 @@ defmodule Kernel.ParallelCompiler do
* `:long_compilation_threshold` - the timeout (in seconds) after the
`:each_long_compilation` callback is invoked; defaults to `15`
* `:dest` - the destination directory for the BEAM files. When using `files/2`,
* `:dest` - the destination directory for the BEAM files. When using `compile/2`,
this information is only used to properly annotate the BEAM files before
they are loaded into memory. If you want a file to actually be written to
`dest`, use `compile_to_path/3` instead.
@@ -107,7 +108,6 @@ defmodule Kernel.ParallelCompiler do
spawn_workers(files, :require, options)
end
# TODO: Remove on 2.0
@doc false
@deprecated "Use Kernel.ParallelCompiler.compile/2 instead"
def files(files, options \\ []) when is_list(options) do
@@ -117,7 +117,6 @@ defmodule Kernel.ParallelCompiler do
end
end
# TODO: Remove on 2.0
@doc false
@deprecated "Use Kernel.ParallelCompiler.compile_to_path/2 instead"
def files_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do
@@ -134,10 +133,10 @@ defmodule Kernel.ParallelCompiler do
schedulers = max(:erlang.system_info(:schedulers_online), 2)
result =
spawn_workers(files, [], [], [], [], %{
spawn_workers(files, 0, [], [], %{}, [], %{
dest: Keyword.get(options, :dest),
each_cycle: Keyword.get(options, :each_cycle, fn -> [] end),
each_file: Keyword.get(options, :each_file, fn _file -> :ok end),
each_file: Keyword.get(options, :each_file, fn _, _ -> :ok end) |> each_file(),
each_long_compilation: Keyword.get(options, :each_long_compilation, fn _file -> :ok end),
each_module: Keyword.get(options, :each_module, fn _file, _module, _binary -> :ok end),
output: output,
@@ -163,165 +162,189 @@ defmodule Kernel.ParallelCompiler do
end
end
defp each_file(fun) when is_function(fun, 1), do: fn file, _ -> fun.(file) end
defp each_file(fun) when is_function(fun, 2), do: fun
defp each_file(file, lexical, parent) do
ref = Process.monitor(parent)
send(parent, {:file_ok, self(), ref, file, lexical})
receive do
^ref -> :ok
{:DOWN, ^ref, _, _, _} -> :ok
end
end
# We already have n=schedulers currently running, don't spawn new ones
defp spawn_workers(files, waiting, queued, result, warnings, %{schedulers: schedulers} = state)
when length(queued) - length(waiting) >= schedulers do
wait_for_messages(files, waiting, queued, result, warnings, state)
defp spawn_workers(
queue,
spawned,
waiting,
files,
result,
warnings,
%{schedulers: schedulers} = state
)
when spawned - length(waiting) >= schedulers do
wait_for_messages(queue, spawned, waiting, files, result, warnings, state)
end
# Release waiting processes
defp spawn_workers([{ref, found} | t], waiting, queued, result, warnings, state) do
defp spawn_workers([{ref, found} | t], spawned, waiting, files, result, warnings, state) do
waiting =
case List.keytake(waiting, ref, 2) do
{{_kind, pid, ^ref, _on, _defining}, waiting} ->
{{_kind, pid, ^ref, _on, _defining, _deadlock}, waiting} ->
send(pid, {ref, found})
waiting
nil ->
# In case the waiting process died (for example, it was an async process),
# it will no longer be on the list. So we need to take it into account here.
waiting
end
spawn_workers(t, waiting, queued, result, warnings, state)
spawn_workers(t, spawned, waiting, files, result, warnings, state)
end
defp spawn_workers([file | files], waiting, queued, result, warnings, state) do
defp spawn_workers([file | queue], spawned, waiting, files, result, warnings, state) do
%{output: output, long_compilation_threshold: threshold, dest: dest} = state
parent = self()
file = Path.expand(file)
{pid, ref} =
:erlang.spawn_monitor(fn ->
:erlang.put(:elixir_compiler_pid, parent)
:erlang.put(:elixir_compiler_file, file)
result =
try do
_ =
case output do
{:compile, path} ->
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
:erlang.put(:elixir_compiler_dest, path)
:elixir_compiler.file_to_path(Path.expand(file), path)
try do
case output do
{:compile, path} ->
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
:erlang.put(:elixir_compiler_dest, path)
:elixir_compiler.file_to_path(file, path, &each_file(&1, &2, parent))
:compile ->
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
:erlang.put(:elixir_compiler_dest, dest)
Code.compile_file(file)
:compile ->
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
:erlang.put(:elixir_compiler_dest, dest)
:elixir_compiler.file(file, &each_file(&1, &2, parent))
:require ->
Code.require_file(file)
:require ->
case :elixir_code_server.call({:acquire, file}) do
:required ->
send(parent, {:file_cancel, self()})
:proceed ->
:elixir_compiler.file(file, &each_file(&1, &2, parent))
:elixir_code_server.cast({:required, file})
end
:ok
catch
kind, reason ->
{kind, reason, __STACKTRACE__}
end
catch
kind, reason ->
send(parent, {:file_error, self(), file, {kind, reason, __STACKTRACE__}})
end
send(parent, {:file_done, self(), file, result})
exit(:shutdown)
end)
timer_ref = Process.send_after(self(), {:timed_out, pid}, threshold * 1000)
queued = [{pid, ref, file, timer_ref} | queued]
spawn_workers(files, waiting, queued, result, warnings, state)
files = [{pid, ref, file, timer_ref} | files]
spawn_workers(queue, spawned + 1, waiting, files, result, warnings, state)
end
# No more files, nothing waiting, queue is empty, this cycle is done
defp spawn_workers([], [], [], result, warnings, state) do
# No more queue, nothing waiting, this cycle is done
defp spawn_workers([], 0, [], [], result, warnings, state) do
case state.each_cycle.() do
[] ->
modules = for {:module, mod} <- result, do: mod
modules = for {{:module, mod}, _} <- result, do: mod
warnings = Enum.reverse(warnings)
{:ok, modules, warnings}
more ->
spawn_workers(more, [], [], result, warnings, state)
spawn_workers(more, 0, [], [], result, warnings, state)
end
end
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
# files x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
# Single entry, just release it.
defp spawn_workers([], [_] = waiting, [_] = queued, result, warnings, state) do
[{_, _, ref, _, _}] = waiting
spawn_workers([{ref, :not_found}], waiting, queued, result, warnings, state)
defp spawn_workers(
[],
1,
[{_, pid, ref, _, _, _}] = waiting,
[{pid, _, _, _}] = files,
result,
warnings,
state
) do
spawn_workers([{ref, :not_found}], 1, waiting, files, result, warnings, state)
end
# Multiple entries, try to release modules.
defp spawn_workers([], waiting, queued, result, warnings, state)
when length(waiting) == length(queued) do
# The goal of this function is to find leaves in the dependency graph,
# i.e. to find code that depends on code that we know is not being defined.
without_definition =
for {pid, _, _, _} <- queued,
entry = waiting_on_without_definition(waiting, pid),
do: entry
defp spawn_workers([], spawned, waiting, files, result, warnings, state)
when length(waiting) == spawned do
# There is potentially a deadlock. We will release modules with
# the following order:
#
# 1. Code.ensure_compiled?/1 checks (deadlock = soft)
# 2. Struct checks (deadlock = hard)
# 3. Modules without a known definition
# 4. Code invocation (deadlock = raise)
#
# In theory there is no difference between hard and raise, the
# difference is where the raise is happening, inside the compiler
# or in the caller.
cond do
deadlocked = deadlocked(waiting, :soft) || deadlocked(waiting, :hard) ->
spawn_workers(deadlocked, spawned, waiting, files, result, warnings, state)
# Note we only release modules because those can be rescued. A missing
# struct is a guaranteed compile error, so we never release it and treat
# it exclusively a missing entry/deadlock.
pending =
for {:module, _, ref, on, _} <- without_definition,
do: {on, {ref, :not_found}}
without_definition = without_definition(waiting, files) ->
spawn_workers(without_definition, spawned, waiting, files, result, warnings, state)
# Instead of releasing all files at once, we release them in groups
# based on the module they are waiting on. We pick the module being
# depended on with less edges, as it is the mostly likely source of
# error (for example, someone made a typo). This may not always be
# true though. For example, if there is a macro injecting code into
# multiple modules and such code becomes faulty, now multiple modules
# are waiting on the same module required by the faulty code. However,
# since we need to pick something to be first, the one with fewer edges
# sounds like a sane choice.
pending
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
|> Enum.sort_by(&length(elem(&1, 1)))
|> case do
[{_on, refs} | _] ->
spawn_workers(refs, waiting, queued, result, warnings, state)
[] ->
# There is a deadlock. Instead of printing a deadlock, let's release
# structs, as a missing struct error is clearer than a deadlock one.
structs = for {:struct, _, ref, _, _} <- without_definition, do: {ref, :not_found}
if structs != [] do
spawn_workers(structs, waiting, queued, result, warnings, state)
else
errors = handle_deadlock(waiting, queued)
{:error, errors, warnings}
end
true ->
errors = handle_deadlock(waiting, files)
{:error, errors, warnings}
end
end
# No more files, but queue and waiting are not full or do not match
defp spawn_workers([], waiting, queued, result, warnings, state) do
wait_for_messages([], waiting, queued, result, warnings, state)
# No more queue, but spawned and length(waiting) do not match
defp spawn_workers([], spawned, waiting, files, result, warnings, state) do
wait_for_messages([], spawned, waiting, files, result, warnings, state)
end
defp waiting_on_without_definition(waiting, pid) do
{_, ^pid, _, on, _} = entry = List.keyfind(waiting, pid, 1)
if Enum.any?(waiting, fn {_, _, _, _, defining} -> on in defining end) do
nil
else
entry
end
# The goal of this function is to find leaves in the dependency graph,
# i.e. to find code that depends on code that we know is not being defined.
defp without_definition(waiting, files) do
nillify_empty(
for {pid, _, _, _} <- files,
{_, ^pid, ref, on, _, _} = List.keyfind(waiting, pid, 1),
not Enum.any?(waiting, fn {_, _, _, _, defining, _} -> on in defining end),
do: {ref, :not_found}
)
end
defp deadlocked(waiting, type) do
nillify_empty(for {_, _, ref, _, _, ^type} <- waiting, do: {ref, :not_found})
end
defp nillify_empty([]), do: nil
defp nillify_empty([_ | _] = list), do: list
# Wait for messages from child processes
defp wait_for_messages(files, waiting, queued, result, warnings, state) do
defp wait_for_messages(queue, spawned, waiting, files, result, warnings, state) do
%{output: output} = state
receive do
{:struct_available, module} ->
{:async, process} ->
Process.monitor(process)
wait_for_messages(queue, spawned + 1, waiting, files, result, warnings, state)
{:available, kind, module} ->
available =
for {:struct, _, ref, waiting_module, _defining} <- waiting,
module == waiting_module,
for {^kind, _, ref, ^module, _defining, _deadlock} <- waiting,
do: {ref, :found}
result = [{:struct, module} | result]
spawn_workers(available ++ files, waiting, queued, result, warnings, state)
result = Map.put(result, {kind, module}, true)
spawn_workers(available ++ queue, spawned, waiting, files, result, warnings, state)
{:module_available, child, ref, file, module, binary} ->
state.each_module.(file, module, binary)
@@ -330,77 +353,77 @@ defmodule Kernel.ParallelCompiler do
send(child, {ref, :ack})
available =
for {:module, _, ref, waiting_module, _defining} <- waiting,
module == waiting_module,
for {:module, _, ref, ^module, _defining, _deadlock} <- waiting,
do: {ref, :found}
cancel_waiting_timer(queued, child)
result = [{:module, module} | result]
spawn_workers(available ++ files, waiting, queued, result, warnings, state)
cancel_waiting_timer(files, child)
result = Map.put(result, {:module, module}, true)
spawn_workers(available ++ queue, spawned, waiting, files, result, warnings, state)
# If we are simply requiring files, we do not add to waiting.
{:waiting, _kind, child, ref, _on, _defining} when output == :require ->
{:waiting, _kind, child, ref, _on, _defining, _deadlock} when output == :require ->
send(child, {ref, :not_found})
spawn_workers(files, waiting, queued, result, warnings, state)
spawn_workers(queue, spawned, waiting, files, result, warnings, state)
{:waiting, kind, child, ref, on, defining} ->
# Oops, we already got it, do not put it on waiting.
{:waiting, kind, child, ref, on, defining, deadlock?} ->
# If we already got what we were waiting for, do not put it on waiting.
# Alternatively, we're waiting on ourselves,
# send :found so that we can crash with a better error.
waiting =
if :lists.any(&match?({^kind, ^on}, &1), result) or on in defining do
if Map.has_key?(result, {kind, on}) or on in defining do
send(child, {ref, :found})
waiting
else
[{kind, child, ref, on, defining} | waiting]
[{kind, child, ref, on, defining, deadlock?} | waiting]
end
spawn_workers(files, waiting, queued, result, warnings, state)
spawn_workers(queue, spawned, waiting, files, result, warnings, state)
{:timed_out, child} ->
case List.keyfind(queued, child, 0) do
{^child, _, file, _} ->
state.each_long_compilation.(file)
_ ->
:ok
case List.keyfind(files, child, 0) do
{^child, _, file, _} -> state.each_long_compilation.(file)
_ -> :ok
end
spawn_workers(files, waiting, queued, result, warnings, state)
spawn_workers(queue, spawned, waiting, files, result, warnings, state)
{:warning, file, line, message} ->
file = file && Path.absname(file)
message = :unicode.characters_to_binary(message)
warning = {file, line, message}
wait_for_messages(files, waiting, queued, result, [warning | warnings], state)
wait_for_messages(queue, spawned, waiting, files, result, [warning | warnings], state)
{:file_ok, child_pid, ref, file, lexical} ->
state.each_file.(file, lexical)
send(child_pid, ref)
cancel_waiting_timer(files, child_pid)
{:file_done, child_pid, file, :ok} ->
discard_down(child_pid)
state.each_file.(file)
cancel_waiting_timer(queued, child_pid)
new_files = List.keydelete(files, child_pid, 0)
# Sometimes we may have spurious entries in the waiting
# list because someone invoked try/rescue UndefinedFunctionError
new_files = List.delete(files, child_pid)
new_queued = List.keydelete(queued, child_pid, 0)
# Sometimes we may have spurious entries in the waiting list
# because someone invoked try/rescue UndefinedFunctionError
new_waiting = List.keydelete(waiting, child_pid, 1)
spawn_workers(new_files, new_waiting, new_queued, result, warnings, state)
spawn_workers(queue, spawned - 1, new_waiting, new_files, result, warnings, state)
{:file_done, child_pid, file, {kind, reason, stack}} ->
{:file_cancel, child_pid} ->
cancel_waiting_timer(files, child_pid)
discard_down(child_pid)
new_files = List.keydelete(files, child_pid, 0)
spawn_workers(queue, spawned - 1, waiting, new_files, result, warnings, state)
{:file_error, child_pid, file, {kind, reason, stack}} ->
print_error(file, kind, reason, stack)
cancel_waiting_timer(queued, child_pid)
queued
|> List.keydelete(child_pid, 0)
|> terminate()
cancel_waiting_timer(files, child_pid)
discard_down(child_pid)
files |> List.keydelete(child_pid, 0) |> terminate()
{:error, [to_error(file, kind, reason, stack)], warnings}
{:DOWN, ref, :process, _pid, reason} ->
case handle_down(queued, ref, reason) do
:ok -> wait_for_messages(files, waiting, queued, result, warnings, state)
{:DOWN, ref, :process, pid, reason} ->
waiting = List.keydelete(waiting, pid, 1)
case handle_down(files, ref, reason) do
:ok -> wait_for_messages(queue, spawned - 1, waiting, files, result, warnings, state)
{:error, errors} -> {:error, errors, warnings}
end
end
@@ -412,16 +435,16 @@ defmodule Kernel.ParallelCompiler do
end
end
defp handle_down(_queued, _ref, :normal) do
defp handle_down(_files, _ref, :normal) do
:ok
end
defp handle_down(queued, ref, reason) do
case List.keyfind(queued, ref, 1) do
defp handle_down(files, ref, reason) do
case List.keyfind(files, ref, 1) do
{child_pid, ^ref, file, _timer_ref} ->
print_error(file, :exit, reason, [])
queued
files
|> List.keydelete(child_pid, 0)
|> terminate()
@@ -432,18 +455,17 @@ defmodule Kernel.ParallelCompiler do
end
end
defp handle_deadlock(waiting, queued) do
defp handle_deadlock(waiting, files) do
deadlock =
for {pid, _, file, _} <- queued do
for {pid, _, file, _} <- files do
{:current_stacktrace, stacktrace} = Process.info(pid, :current_stacktrace)
Process.exit(pid, :kill)
{kind, ^pid, _, on, _} = List.keyfind(waiting, pid, 1)
{kind, ^pid, _, on, _, _} = List.keyfind(waiting, pid, 1)
description = "deadlocked waiting on #{kind} #{inspect(on)}"
error = CompileError.exception(description: description, file: nil, line: nil)
print_error(file, :error, error, stacktrace)
{file, on, description}
{Path.relative_to_cwd(file), on, description}
end
IO.puts("""
@@ -469,9 +491,9 @@ defmodule Kernel.ParallelCompiler do
for {file, _, description} <- deadlock, do: {Path.absname(file), nil, description}
end
defp terminate(queued) do
for {pid, _, _, _} <- queued, do: Process.exit(pid, :kill)
for {pid, _, _, _} <- queued, do: discard_down(pid)
defp terminate(files) do
for {pid, _, _, _} <- files, do: Process.exit(pid, :kill)
for {pid, _, _, _} <- files, do: discard_down(pid)
:ok
end
@@ -482,12 +504,11 @@ defmodule Kernel.ParallelCompiler do
])
end
defp cancel_waiting_timer(queued, child_pid) do
case List.keyfind(queued, child_pid, 0) do
defp cancel_waiting_timer(files, child_pid) do
case List.keyfind(files, child_pid, 0) do
{^child_pid, _ref, _file, timer_ref} ->
Process.cancel_timer(timer_ref)
# Let's flush the message in case it arrived before we canceled the
# timeout.
# Let's flush the message in case it arrived before we canceled the timeout.
receive do
{:timed_out, ^child_pid} -> :ok
after
@@ -1,5 +1,4 @@
defmodule Kernel.ParallelRequire do
# TODO: Remove on 2.0
@moduledoc false
@deprecated "Use Kernel.ParallelCompiler.require/2 instead"
+81 -63
View File
@@ -37,7 +37,7 @@ defmodule Kernel.SpecialForms do
## AST representation
Only two-item tuples are considered literals in Elixir and return themselves
Only two-element tuples are considered literals in Elixir and return themselves
when quoted. Therefore, all other tuples are represented in the AST as calls to
the `:{}` special form.
@@ -192,7 +192,7 @@ defmodule Kernel.SpecialForms do
iex> <<102, rest::binary>>
"foo"
The `utf8`, `utf16`, and `utf32` types are for Unicode codepoints. They
The `utf8`, `utf16`, and `utf32` types are for Unicode code points. They
can also be applied to literal strings and charlists:
iex> <<"foo"::utf16>>
@@ -246,12 +246,17 @@ defmodule Kernel.SpecialForms do
iex> {name, species}
{"Frank", "Walrus"}
And the variable can be defined in the match itself:
And the variable can be defined in the match itself (prior to its use):
iex> <<name_size::size(8), name::binary-size(name_size), " the ", species::binary>> = <<5, "Frank the Walrus">>
iex> {name, species}
{"Frank", "Walrus"}
However, the size cannot be defined in the match outside the binary/bitstring match:
{name_size, <<name::binary-size(name_size), _rest::binary>>} = {5, <<"Frank the Walrus">>}
** (CompileError): undefined variable "name_size" in bitstring segment
Failing to specify the size for the non-last causes compilation to fail:
<<name::binary, " the ", species::binary>> = <<"Frank the Walrus">>
@@ -341,7 +346,7 @@ defmodule Kernel.SpecialForms do
def type(<<@png_signature, rest::binary>>), do: :png
def type(<<@jpg_signature, rest::binary>>), do: :jpg
def type(_), do :unknown
def type(_), do: :unknown
end
### Performance & Optimizations
@@ -481,11 +486,11 @@ defmodule Kernel.SpecialForms do
defmacro unquote(:.)(left, right), do: error!([left, right])
@doc """
`alias/2` is used to setup aliases, often useful with modules names.
`alias/2` is used to set up aliases, often useful with modules' names.
## Examples
`alias/2` can be used to setup an alias for any module:
`alias/2` can be used to set up an alias for any module:
defmodule Math do
alias MyKeyword, as: Keyword
@@ -498,7 +503,7 @@ defmodule Kernel.SpecialForms do
In case one wants to access the original `Keyword`, it can be done
by accessing `Elixir`:
Keyword.values #=> uses MyKeyword.values
Keyword.values #=> uses MyKeyword.values
Elixir.Keyword.values #=> uses Keyword.values
Notice that calling `alias` without the `:as` option automatically
@@ -537,6 +542,7 @@ defmodule Kernel.SpecialForms do
Both warning behaviours could be changed by explicitly
setting the `:warn` option to `true` or `false`.
"""
defmacro alias(module, opts), do: error!([module, opts])
@@ -571,7 +577,7 @@ defmodule Kernel.SpecialForms do
Imports functions and macros from other modules.
`import/2` allows one to easily access functions or macros from
others modules without using the qualified name.
other modules without using the qualified name.
## Examples
@@ -847,7 +853,7 @@ defmodule Kernel.SpecialForms do
`quote/2` is commonly used with macros for code generation. As an exercise,
let's define a macro that multiplies a number by itself (squared). In practice,
there is no reason to define such as a macro (and it would actually be
there is no reason to define such a macro (and it would actually be
seen as a bad practice), but it is simple enough that it allows us to focus
on the important aspects of quotes and macros:
@@ -862,7 +868,7 @@ defmodule Kernel.SpecialForms do
We can invoke it as:
import Math
IO.puts "Got #{squared(5)}"
IO.puts("Got #{squared(5)}")
At first, there is nothing in this example that actually reveals it is a
macro. But what is happening is that, at compilation time, `squared(5)`
@@ -872,10 +878,10 @@ defmodule Kernel.SpecialForms do
import Math
my_number = fn ->
IO.puts "Returning 5"
IO.puts("Returning 5")
5
end
IO.puts "Got #{squared(my_number.())}"
IO.puts("Got #{squared(my_number.())}")
The example above will print:
@@ -941,7 +947,8 @@ defmodule Kernel.SpecialForms do
import Math
squared(5)
x #=> ** (CompileError) undefined variable x or undefined function x/0
x
#=> ** (CompileError) undefined variable x or undefined function x/0
We can see that `x` did not leak to the user context. This happens
because Elixir macros are hygienic, a topic we will discuss at length
@@ -962,8 +969,9 @@ defmodule Kernel.SpecialForms do
require Hygiene
a = 10
Hygiene.no_interference
a #=> 10
Hygiene.no_interference()
a
#=> 10
In the example above, `a` returns 10 even if the macro
is apparently setting it to 1 because variables defined
@@ -982,8 +990,9 @@ defmodule Kernel.SpecialForms do
require NoHygiene
a = 10
NoHygiene.interference
a #=> 1
NoHygiene.interference()
a
#=> 1
You cannot even access variables defined in the same module unless
you explicitly give it a context:
@@ -1002,8 +1011,8 @@ defmodule Kernel.SpecialForms do
end
end
Hygiene.write
Hygiene.read
Hygiene.write()
Hygiene.read()
#=> ** (RuntimeError) undefined variable a or undefined function a/0
For such, you can explicitly pass the current module scope as
@@ -1023,8 +1032,8 @@ defmodule Kernel.SpecialForms do
end
end
ContextHygiene.write
ContextHygiene.read
ContextHygiene.write()
ContextHygiene.read()
#=> 1
## Hygiene in aliases
@@ -1037,13 +1046,14 @@ defmodule Kernel.SpecialForms do
defmacro no_interference do
quote do
M.new
M.new()
end
end
end
require Hygiene
Hygiene.no_interference #=> %{}
Hygiene.no_interference()
#=> %{}
Notice that, even though the alias `M` is not available
in the context the macro is expanded, the code above works
@@ -1057,31 +1067,32 @@ defmodule Kernel.SpecialForms do
defmacro no_interference do
quote do
M.new
M.new()
end
end
end
require Hygiene
alias SomethingElse, as: M
Hygiene.no_interference #=> %{}
Hygiene.no_interference()
#=> %{}
In some cases, you want to access an alias or a module defined
in the caller. For such, you can use the `alias!` macro:
defmodule Hygiene do
# This will expand to Elixir.Nested.hello
# This will expand to Elixir.Nested.hello()
defmacro no_interference do
quote do
Nested.hello
Nested.hello()
end
end
# This will expand to Nested.hello for
# This will expand to Nested.hello() for
# whatever is Nested in the caller
defmacro interference do
quote do
alias!(Nested).hello
alias!(Nested).hello()
end
end
end
@@ -1092,10 +1103,10 @@ defmodule Kernel.SpecialForms do
end
require Hygiene
Hygiene.no_interference
Hygiene.no_interference()
#=> ** (UndefinedFunctionError) ...
Hygiene.interference
Hygiene.interference()
#=> "world"
end
@@ -1117,7 +1128,8 @@ defmodule Kernel.SpecialForms do
end
end
Hygiene.return_length #=> 3
Hygiene.return_length()
#=> 3
Notice how `Hygiene.return_length/0` returns `3` even though the `Kernel.length/1`
function is not imported. In fact, even if `return_length/0`
@@ -1152,7 +1164,8 @@ defmodule Kernel.SpecialForms do
end
end
Lazy.return_length #=> 5
Lazy.return_length()
#=> 5
## Stacktrace information
@@ -1189,25 +1202,25 @@ defmodule Kernel.SpecialForms do
## Binding and unquote fragments
Elixir quote/unquote mechanisms provides a functionality called
Elixir quote/unquote mechanisms provide a functionality called
unquote fragments. Unquote fragments provide an easy way to generate
functions on the fly. Consider this example:
kv = [foo: 1, bar: 2]
Enum.each kv, fn {k, v} ->
Enum.each(kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end)
In the example above, we have generated the functions `foo/0` and
`bar/0` dynamically. Now, imagine that, we want to convert this
`bar/0` dynamically. Now, imagine that we want to convert this
functionality into a macro:
defmacro defkv(kv) do
Enum.map kv, fn {k, v} ->
Enum.map(kv, fn {k, v} ->
quote do
def unquote(k)(), do: unquote(v)
end
end
end)
end
We can invoke this macro as:
@@ -1230,9 +1243,9 @@ defmodule Kernel.SpecialForms do
defmacro defkv(kv) do
quote do
Enum.each unquote(kv), fn {k, v} ->
Enum.each(unquote(kv), fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end)
end
end
@@ -1251,9 +1264,9 @@ defmodule Kernel.SpecialForms do
defmacro defkv(kv) do
quote bind_quoted: [kv: kv] do
Enum.each kv, fn {k, v} ->
Enum.each(kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end)
end
end
@@ -1596,6 +1609,8 @@ defmodule Kernel.SpecialForms do
&local_function/1
See also `Function.capture/3`.
## Anonymous functions
The capture operator can also be used to partially apply
@@ -1671,7 +1686,7 @@ defmodule Kernel.SpecialForms do
it can be sure that it represents a call and the second argument
in the list is an atom.
On the other hand, aliases holds some properties:
On the other hand, aliases hold some properties:
1. The head element of aliases can be any term that must expand to
an atom at compilation time.
@@ -1720,7 +1735,7 @@ defmodule Kernel.SpecialForms do
end
#=> "This clause would match any value (x = 10)"
## Variables handling
## Variable handling
Notice that variables bound in a clause "head" do not leak to the
outer context:
@@ -1730,7 +1745,8 @@ defmodule Kernel.SpecialForms do
:error -> nil
end
value #=> unbound variable value
value
#=> unbound variable value
However, variables explicitly bound in the clause "body" are
accessible from the outer context:
@@ -1739,12 +1755,13 @@ defmodule Kernel.SpecialForms do
case lucky? do
false -> value = 13
true -> true
true -> true
end
value #=> 7 or 13
value
#=> 7 or 13
In the example above, value is going to be `7` or `13` depending on
In the example above, `value` is going to be `7` or `13` depending on
the value of `lucky?`. In case `value` has no previous value before
case, clauses that do not explicitly bind a value have the variable
bound to `nil`.
@@ -1756,7 +1773,7 @@ defmodule Kernel.SpecialForms do
case 10 do
^x -> "Won't match"
_ -> "Will match"
_ -> "Will match"
end
#=> "Will match"
@@ -1802,15 +1819,15 @@ defmodule Kernel.SpecialForms do
do_something_that_may_fail(some_arg)
rescue
ArgumentError ->
IO.puts "Invalid argument given"
IO.puts("Invalid argument given")
catch
value ->
IO.puts "Caught #{inspect(value)}"
IO.puts("Caught #{inspect(value)}")
else
value ->
IO.puts "Success! The result was #{inspect(value)}"
IO.puts("Success! The result was #{inspect(value)}")
after
IO.puts "This is printed regardless if it failed or succeed"
IO.puts("This is printed regardless if it failed or succeeded")
end
The `rescue` clause is used to handle exceptions while the `catch`
@@ -1905,7 +1922,7 @@ defmodule Kernel.SpecialForms do
throw(:some_value)
catch
thrown_value ->
IO.puts "A value was thrown: #{inspect(thrown_value)}"
IO.puts("A value was thrown: #{inspect(thrown_value)}")
end
### Catching values of any kind
@@ -1917,15 +1934,15 @@ defmodule Kernel.SpecialForms do
try do
exit(:shutdown)
catch
:exit, value
IO.puts "Exited with value #{inspect(value)}"
:exit, value ->
IO.puts("Exited with value #{inspect(value)}")
end
try do
exit(:shutdown)
catch
kind, value when kind in [:exit, :throw] ->
IO.puts "Caught exit or throw with value #{inspect(value)}"
IO.puts("Caught exit or throw with value #{inspect(value)}")
end
The `catch` clause also supports `:error` alongside `:exit` and `:throw` as
@@ -2063,7 +2080,8 @@ defmodule Kernel.SpecialForms do
_, _ -> :failed
end
x #=> unbound variable "x"
x
#=> unbound variable "x"
In the example above, `x` cannot be accessed since it was defined
inside the `try` clause. A common practice to address this issue
@@ -2096,7 +2114,7 @@ defmodule Kernel.SpecialForms do
name when is_atom(name) ->
name
_ ->
IO.puts :stderr, "Unexpected message received"
IO.puts(:stderr, "Unexpected message received")
end
An optional `after` clause can be given in case the message was not
@@ -2108,10 +2126,10 @@ defmodule Kernel.SpecialForms do
name when is_atom(name) ->
name
_ ->
IO.puts :stderr, "Unexpected message received"
IO.puts(:stderr, "Unexpected message received")
after
5000 ->
IO.puts :stderr, "No message in 5 seconds"
IO.puts(:stderr, "No message in 5 seconds")
end
The `after` clause can be specified even if there are no match clauses.
@@ -2128,7 +2146,7 @@ defmodule Kernel.SpecialForms do
in hexadecimal notation) - it should be possible to represent the timeout
value as an unsigned 32-bit integer.
## Variables handling
## Variable handling
The `receive/1` special form handles variables exactly as the `case/2`
special macro. For more information, check the docs for `case/2`.
+187 -142
View File
@@ -1,5 +1,4 @@
defmodule Kernel.Typespec do
# TODO: Remove deprecated code on 2.0 and move this module to Module.Typespec.
@moduledoc false
## Deprecated API moved to Code.Typespec
@@ -76,12 +75,21 @@ defmodule Kernel.Typespec do
def spec_to_callback(module, {name, arity} = signature)
when is_atom(module) and is_atom(name) and arity in 0..255 do
{_set, bag} = :elixir_module.data_tables(module)
{set, bag} = :elixir_module.data_tables(module)
filter = fn {:spec, expr, pos} ->
if spec_to_signature(expr) == signature do
delete_typespec(bag, :spec, expr, pos)
store_typespec(bag, :callback, expr, pos)
kind = :callback
store_typespec(bag, kind, expr, pos)
case :ets.lookup(set, {:function, name, arity}) do
[{{:function, ^name, ^arity}, line, _, doc, doc_meta}] ->
store_doc(set, kind, name, arity, line, :doc, doc, doc_meta)
_ ->
nil
end
true
else
false
@@ -109,8 +117,11 @@ defmodule Kernel.Typespec do
case spec_to_signature(expr) do
{name, arity} ->
{line, doc} = get_doc_info(set, :doc, line)
store_doc(set, kind, name, arity, line, :doc, doc, %{})
# store doc only once in case callback has multiple clauses
unless :ets.member(set, {kind, name, arity}) do
{line, doc} = get_doc_info(set, :doc, line)
store_doc(set, kind, name, arity, line, :doc, doc, %{})
end
:error ->
:error
@@ -131,7 +142,7 @@ defmodule Kernel.Typespec do
warning =
"type #{name}/#{arity} is private, @typedoc's are always discarded for private types"
:elixir_errors.warn(line, file, warning)
:elixir_errors.erl_warn(line, file, warning)
end
{name, arity} ->
@@ -169,11 +180,6 @@ defmodule Kernel.Typespec do
:ok
end
defp delete_typespec(bag, key, expr, pos) do
:ets.delete_object(bag, {{:accumulate, key}, {key, expr, pos}})
:ok
end
defp store_doc(set, kind, name, arity, line, doc_kind, doc, spec_meta) do
doc_meta = get_doc_meta(spec_meta, doc_kind, set)
:ets.insert(set, {{kind, name, arity}, line, doc, doc_meta})
@@ -196,11 +202,11 @@ defmodule Kernel.Typespec do
defp spec_to_signature({:when, _, [spec, _]}), do: type_to_signature(spec)
defp spec_to_signature(other), do: type_to_signature(other)
defp type_to_signature({:::, _, [{name, _, context}, _]})
when is_atom(name) and name != ::: and is_atom(context),
defp type_to_signature({:"::", _, [{name, _, context}, _]})
when is_atom(name) and name != :"::" and is_atom(context),
do: {name, 0}
defp type_to_signature({:::, _, [{name, _, args}, _]}) when is_atom(name) and name != :::,
defp type_to_signature({:"::", _, [{name, _, args}, _]}) when is_atom(name) and name != :"::",
do: {name, length(args)}
defp type_to_signature(_), do: :error
@@ -219,21 +225,21 @@ defmodule Kernel.Typespec do
undefined_type_error_enabled?: true
}
{types, state} = Enum.map_reduce(type_typespecs, state, &translate_type/2)
{specs, state} = Enum.map_reduce(take_typespecs(bag, :spec), state, &translate_spec/2)
{callbacks, state} = Enum.map_reduce(take_typespecs(bag, :callback), state, &translate_spec/2)
{types, state} = :lists.mapfoldl(&translate_type/2, state, type_typespecs)
{specs, state} = :lists.mapfoldl(&translate_spec/2, state, take_typespecs(bag, :spec))
{callbacks, state} = :lists.mapfoldl(&translate_spec/2, state, take_typespecs(bag, :callback))
{macrocallbacks, state} =
Enum.map_reduce(take_typespecs(bag, :macrocallback), state, &translate_spec/2)
:lists.mapfoldl(&translate_spec/2, state, take_typespecs(bag, :macrocallback))
optional_callbacks = List.flatten(get_typespecs(bag, :optional_callbacks))
optional_callbacks = :lists.flatten(get_typespecs(bag, :optional_callbacks))
used_types = filter_used_types(types, state)
{used_types, specs, callbacks, macrocallbacks, optional_callbacks}
end
defp collect_defined_type_pairs(type_typespecs) do
Enum.reduce(type_typespecs, %{}, fn {_kind, expr, pos}, type_pairs ->
fun = fn {_kind, expr, pos}, type_pairs ->
%{file: file, line: line} = env = :elixir_locals.get_cached_env(pos)
case type_to_signature(expr) do
@@ -252,22 +258,26 @@ defmodule Kernel.Typespec do
:error ->
compile_error(env, "invalid type specification: #{Macro.to_string(expr)}")
end
end)
end
:lists.foldl(fun, %{}, type_typespecs)
end
defp filter_used_types(types, state) do
Enum.filter(types, fn {_kind, {name, arity} = type_pair, _line, _type, export} ->
if type_pair not in state.used_type_pairs and not export do
fun = fn {_kind, {name, arity} = type_pair, _line, _type, export} ->
if not export and not :lists.member(type_pair, state.used_type_pairs) do
%{^type_pair => {file, line}} = state.defined_type_pairs
:elixir_errors.warn(line, file, "type #{name}/#{arity} is unused")
:elixir_errors.erl_warn(line, file, "type #{name}/#{arity} is unused")
false
else
true
end
end)
end
:lists.filter(fun, types)
end
defp translate_type({kind, {:::, _, [{name, _, args}, definition]}, pos}, state) do
defp translate_type({kind, {:"::", _, [{name, _, args}, definition]}, pos}, state) do
caller = :elixir_locals.get_cached_env(pos)
state = clean_local_state(state)
@@ -278,13 +288,14 @@ defmodule Kernel.Typespec do
for(arg <- args, do: variable(arg))
end
vars = for {:var, _, var} <- args, do: var
state = Enum.reduce(vars, state, &update_local_vars(&2, &1))
{spec, state} = typespec(definition, vars, caller, state)
vars = for {:var, _, _} = var <- args, do: var
vars = :lists.filter(&match?({:var, _, _}, &1), args)
var_names = :lists.map(&elem(&1, 2), vars)
state = :lists.foldl(&update_local_vars(&2, &1), state, var_names)
{spec, state} = typespec(definition, var_names, caller, state)
type = {name, spec, vars}
arity = length(args)
ensure_no_underscore_local_vars!(caller, var_names)
ensure_no_unused_local_vars!(caller, state.local_vars)
{kind, export} =
@@ -294,10 +305,10 @@ defmodule Kernel.Typespec do
:opaque -> {:opaque, true}
end
invalid_args = Enum.reject(args, &valid_variable_ast?/1)
invalid_args = :lists.filter(&(not valid_variable_ast?(&1)), args)
unless invalid_args == [] do
invalid_args = invalid_args |> Enum.map(&Macro.to_string/1) |> Enum.join(", ")
invalid_args = :lists.join(", ", :lists.map(&Macro.to_string/1, invalid_args))
message =
"@type definitions expect all arguments to be variables. The type " <>
@@ -308,7 +319,7 @@ defmodule Kernel.Typespec do
if underspecified?(kind, arity, spec) do
message = "@#{kind} type #{name}/#{arity} is underspecified and therefore meaningless"
:elixir_errors.warn(caller.line, caller.file, message)
:elixir_errors.erl_warn(caller.line, caller.file, message)
end
{{kind, {name, arity}, caller.line, type, export}, state}
@@ -333,13 +344,13 @@ defmodule Kernel.Typespec do
translate_spec(kind, spec, [], caller, state)
end
defp translate_spec(kind, {:::, meta, [{name, _, args}, return]}, guard, caller, state)
when is_atom(name) and name != ::: do
defp translate_spec(kind, {:"::", meta, [{name, _, args}, return]}, guard, caller, state)
when is_atom(name) and name != :"::" do
translate_spec(kind, meta, name, args, return, guard, caller, state)
end
defp translate_spec(_kind, {name, _meta, _args} = spec, _guard, caller, _state)
when is_atom(name) and name != ::: do
when is_atom(name) and name != :"::" do
spec = Macro.to_string(spec)
compile_error(caller, "type specification missing return type: #{spec}")
end
@@ -378,7 +389,7 @@ defmodule Kernel.Typespec do
{{kind, {name, arity}, caller.line, spec}, state}
end
# TODO: Remove char_list type by 2.0
# TODO: Remove char_list type by v2.0
defp built_in_type?(:char_list, 0), do: true
defp built_in_type?(:charlist, 0), do: true
defp built_in_type?(:as_boolean, 1), do: true
@@ -390,8 +401,8 @@ defmodule Kernel.Typespec do
defp built_in_type?(name, arity), do: :erl_internal.is_type(name, arity)
defp ensure_no_defaults!(args) do
Enum.each(args, fn
{:::, _, [left, right]} ->
fun = fn
{:"::", _, [left, right]} ->
ensure_not_default(left)
ensure_not_default(right)
left
@@ -399,7 +410,9 @@ defmodule Kernel.Typespec do
other ->
ensure_not_default(other)
other
end)
end
:lists.foreach(fun, args)
end
defp ensure_not_default({:\\, _, [_, _]}) do
@@ -411,17 +424,19 @@ defmodule Kernel.Typespec do
defp guard_to_constraints(guard, vars, meta, caller, state) do
line = line(meta)
Enum.flat_map_reduce(guard, state, fn
{_name, {:var, _, context}}, state when is_atom(context) ->
{[], state}
fun = fn
{_name, {:var, _, context}}, {constraints, state} when is_atom(context) ->
{constraints, state}
{name, type}, state ->
{name, type}, {constraints, state} ->
{spec, state} = typespec(type, vars, caller, state)
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, spec]]
state = update_local_vars(state, name)
{[{:type, line, :constraint, constraint} | constraints], state}
end
{[{:type, line, :constraint, constraint}], state}
end)
{constraints, state} = :lists.foldl(fun, {[], state}, guard)
{:lists.reverse(constraints), state}
end
## To typespec conversion
@@ -433,7 +448,7 @@ defmodule Kernel.Typespec do
# Handle unions
defp typespec({:|, meta, [_, _]} = exprs, vars, caller, state) do
exprs = collect_union(exprs)
{union, state} = Enum.map_reduce(exprs, state, &typespec(&1, vars, caller, &2))
{union, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, exprs)
{{:type, line(meta), :union, union}, state}
end
@@ -444,7 +459,7 @@ defmodule Kernel.Typespec do
end
defp typespec(
{:<<>>, meta, [{:::, unit_meta, [{:_, _, ctx1}, {:*, _, [{:_, _, ctx2}, unit]}]}]},
{:<<>>, meta, [{:"::", unit_meta, [{:_, _, ctx1}, {:*, _, [{:_, _, ctx2}, unit]}]}]},
_,
_,
state
@@ -454,7 +469,7 @@ defmodule Kernel.Typespec do
{{:type, line, :binary, [{:integer, line, 0}, {:integer, line(unit_meta), unit}]}, state}
end
defp typespec({:<<>>, meta, [{:::, size_meta, [{:_, _, ctx}, size]}]}, _, _, state)
defp typespec({:<<>>, meta, [{:"::", size_meta, [{:_, _, ctx}, size]}]}, _, _, state)
when is_atom(ctx) and is_integer(size) and size >= 0 do
line = line(meta)
{{:type, line, :binary, [{:integer, line(size_meta), size}, {:integer, line, 0}]}, state}
@@ -465,8 +480,8 @@ defmodule Kernel.Typespec do
:<<>>,
meta,
[
{:::, size_meta, [{:_, _, ctx1}, size]},
{:::, unit_meta, [{:_, _, ctx2}, {:*, _, [{:_, _, ctx3}, unit]}]}
{:"::", size_meta, [{:_, _, ctx1}, size]},
{:"::", unit_meta, [{:_, _, ctx2}, {:*, _, [{:_, _, ctx3}, unit]}]}
]
},
_,
@@ -493,45 +508,34 @@ defmodule Kernel.Typespec do
end
defp typespec({:%{}, meta, fields} = map, vars, caller, state) do
{fields, state} =
Enum.map_reduce(fields, state, fn
{k, v}, state when is_atom(k) ->
{arg1, state} = typespec(k, vars, caller, state)
{arg2, state} = typespec(v, vars, caller, state)
{{:type, line(meta), :map_field_exact, [arg1, arg2]}, state}
fun = fn
{{:required, meta2, [k]}, v}, state ->
{arg1, state} = typespec(k, vars, caller, state)
{arg2, state} = typespec(v, vars, caller, state)
{{:type, line(meta2), :map_field_exact, [arg1, arg2]}, state}
{{:required, meta2, [k]}, v}, state ->
{arg1, state} = typespec(k, vars, caller, state)
{arg2, state} = typespec(v, vars, caller, state)
{{:type, line(meta2), :map_field_exact, [arg1, arg2]}, state}
{{:optional, meta2, [k]}, v}, state ->
{arg1, state} = typespec(k, vars, caller, state)
{arg2, state} = typespec(v, vars, caller, state)
{{:type, line(meta2), :map_field_assoc, [arg1, arg2]}, state}
{{:optional, meta2, [k]}, v}, state ->
{arg1, state} = typespec(k, vars, caller, state)
{arg2, state} = typespec(v, vars, caller, state)
{{:type, line(meta2), :map_field_assoc, [arg1, arg2]}, state}
{k, v}, state ->
{arg1, state} = typespec(k, vars, caller, state)
{arg2, state} = typespec(v, vars, caller, state)
{{:type, line(meta), :map_field_exact, [arg1, arg2]}, state}
{k, v}, state ->
# TODO: Warn on Elixir v1.8 (since v1.6 is the first version to drop support for 18 and
# older)
# warning =
# "invalid map specification. %{foo => bar} is deprecated in favor of " <>
# "%{required(foo) => bar} and %{optional(foo) => bar}."
# :elixir_errors.warn(caller.line, caller.file, warning)
{arg1, state} = typespec(k, vars, caller, state)
{arg2, state} = typespec(v, vars, caller, state)
{{:type, line(meta), :map_field_assoc, [arg1, arg2]}, state}
{:|, _, [_, _]}, _state ->
error =
"invalid map specification. When using the | operator in the map key, " <>
"make sure to wrap the key type in parentheses: #{Macro.to_string(map)}"
{:|, _, [_, _]}, _state ->
error =
"invalid map specification. When using the | operator in the map key, " <>
"make sure to wrap the key type in parentheses: #{Macro.to_string(map)}"
compile_error(caller, error)
compile_error(caller, error)
_, _state ->
compile_error(caller, "invalid map specification: #{Macro.to_string(map)}")
end)
_, _state ->
compile_error(caller, "invalid map specification: #{Macro.to_string(map)}")
end
{fields, state} = :lists.mapfoldl(fun, state, fields)
{{:type, line(meta), :map, fields}, state}
end
@@ -543,7 +547,7 @@ defmodule Kernel.Typespec do
struct =
module
|> Macro.struct!(caller)
|> Map.from_struct()
|> Map.delete(:__struct__)
|> Map.to_list()
unless Keyword.keyword?(fields) do
@@ -551,43 +555,54 @@ defmodule Kernel.Typespec do
end
types =
Enum.map(struct, fn {field, _} ->
{field, Keyword.get(fields, field, quote(do: term()))}
end)
:lists.map(
fn {field, _} -> {field, Keyword.get(fields, field, quote(do: term()))} end,
:lists.sort(struct)
)
Enum.each(fields, fn {field, _} ->
fun = fn {field, _} ->
unless Keyword.has_key?(struct, field) do
compile_error(
caller,
"undefined field #{inspect(field)} on struct #{Macro.to_string(name)}"
)
end
end)
end
:lists.foreach(fun, fields)
typespec({:%{}, meta, [__struct__: module] ++ types}, vars, caller, state)
end
# Handle records
defp typespec({:record, meta, [atom]}, vars, caller, state) when is_atom(atom) do
defp typespec({:record, meta, [atom]}, vars, caller, state) do
typespec({:record, meta, [atom, []]}, vars, caller, state)
end
defp typespec({:record, meta, [tag, field_specs]}, vars, caller, state) when is_atom(tag) do
defp typespec({:record, meta, [tag, field_specs]}, vars, caller, state)
when is_atom(tag) and is_list(field_specs) do
# We cannot set a function name to avoid tracking
# as a compile time dependency because for records it actually is one.
case Macro.expand({tag, [], [{:{}, [], []}]}, caller) do
{_, _, [name, fields | _]} when is_list(fields) ->
types =
Enum.map(fields, fn {field, _} ->
Keyword.get(field_specs, field, quote(do: term()))
end)
:lists.map(
fn {field, _} ->
{:"::", [],
[
{field, [], nil},
Keyword.get(field_specs, field, quote(do: term()))
]}
end,
fields
)
Enum.each(field_specs, fn {field, _} ->
fun = fn {field, _} ->
unless Keyword.has_key?(fields, field) do
compile_error(caller, "undefined field #{field} on record #{inspect(tag)}")
end
end)
end
:lists.foreach(fun, field_specs)
typespec({:{}, meta, [name | types]}, vars, caller, state)
_ ->
@@ -595,8 +610,9 @@ defmodule Kernel.Typespec do
end
end
defp typespec({:record, _meta, _args}, _vars, caller, _state) do
compile_error(caller, "invalid record specification, expected the record name to be an atom")
defp typespec({:record, _meta, [_tag, _field_specs]}, _vars, caller, _state) do
message = "invalid record specification, expected the record name to be an atom literal"
compile_error(caller, message)
end
# Handle ranges
@@ -621,15 +637,15 @@ defmodule Kernel.Typespec do
end
# Handle funs
defp typespec([{:->, meta, [arguments, return]}], vars, caller, state)
when is_list(arguments) do
{args, state} = fn_args(meta, arguments, return, vars, caller, state)
defp typespec([{:->, meta, [args, return]}], vars, caller, state)
when is_list(args) do
{args, state} = fn_args(meta, args, return, vars, caller, state)
{{:type, line(meta), :fun, args}, state}
end
# Handle type operator
defp typespec(
{:::, meta, [{var_name, var_meta, context}, expr]} = ann_type,
{:"::", meta, [{var_name, var_meta, context}, expr]} = ann_type,
vars,
caller,
state
@@ -641,8 +657,8 @@ defmodule Kernel.Typespec do
"invalid type annotation. Type annotations cannot be nested: " <>
"#{Macro.to_string(ann_type)}"
# TODO: make this an error in elixir 2.0 and remove the code below
:elixir_errors.warn(caller.line, caller.file, message)
# TODO: Make this an error on v2.0 and remove the code below
:elixir_errors.erl_warn(caller.line, caller.file, message)
# This may be generating an invalid typespec but we need to generate it
# to avoid breaking existing code that was valid but only broke dialyzer
@@ -654,14 +670,14 @@ defmodule Kernel.Typespec do
end
end
defp typespec({:::, meta, [left, right]} = expr, vars, caller, state) do
defp typespec({:"::", meta, [left, right]} = expr, vars, caller, state) do
message =
"invalid type annotation. When using the | operator to represent the union of types, " <>
"make sure to wrap type annotations in parentheses: #{Macro.to_string(expr)}"
# TODO: make this an error in Elixir 2.0, and remove the code below and the
# :undefined_type_error_enabled? key from the state
:elixir_errors.warn(caller.line, caller.file, message)
# TODO: Make this an error on v2.0, and remove the code below and
# the :undefined_type_error_enabled? key from the state
:elixir_errors.erl_warn(caller.line, caller.file, message)
# This may be generating an invalid typespec but we need to generate it
# to avoid breaking existing code that was valid but only broke dialyzer
@@ -725,7 +741,7 @@ defmodule Kernel.Typespec do
end
defp typespec({:{}, meta, t}, vars, caller, state) when is_list(t) do
{args, state} = Enum.map_reduce(t, state, &typespec(&1, vars, caller, &2))
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, t)
{{:type, line(meta), :tuple, args}, state}
end
@@ -740,7 +756,7 @@ defmodule Kernel.Typespec do
# Handle variables or local calls
defp typespec({name, meta, atom}, vars, caller, state) when is_atom(atom) do
if name in vars do
if :lists.member(name, vars) do
state = update_local_vars(state, name)
{{:var, line(meta), name}, state}
else
@@ -749,35 +765,32 @@ defmodule Kernel.Typespec do
end
# Handle local calls
defp typespec({:string, meta, arguments}, vars, caller, state) do
defp typespec({:string, meta, args}, vars, caller, state) do
warning =
"string() type use is discouraged. " <>
"For character lists, use charlist() type, for strings, String.t()\n" <>
Exception.format_stacktrace(Macro.Env.stacktrace(caller))
:elixir_errors.warn(caller.line, caller.file, warning)
{arguments, state} = Enum.map_reduce(arguments, state, &typespec(&1, vars, caller, &2))
{{:type, line(meta), :string, arguments}, state}
:elixir_errors.erl_warn(caller.line, caller.file, warning)
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
{{:type, line(meta), :string, args}, state}
end
defp typespec({:nonempty_string, meta, arguments}, vars, caller, state) do
defp typespec({:nonempty_string, meta, args}, vars, caller, state) do
warning =
"nonempty_string() type use is discouraged. " <>
"For non-empty character lists, use nonempty_charlist() type, for strings, String.t()\n" <>
Exception.format_stacktrace(Macro.Env.stacktrace(caller))
:elixir_errors.warn(caller.line, caller.file, warning)
{arguments, state} = Enum.map_reduce(arguments, state, &typespec(&1, vars, caller, &2))
{{:type, line(meta), :nonempty_string, arguments}, state}
:elixir_errors.erl_warn(caller.line, caller.file, warning)
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
{{:type, line(meta), :nonempty_string, args}, state}
end
# TODO: Remove char_list type by 2.0
defp typespec({type, _meta, []}, vars, caller, state) when type in [:charlist, :char_list] do
if type == :char_list do
warning = "the char_list() type is deprecated, use charlist()"
:elixir_errors.warn(caller.line, caller.file, warning)
:elixir_errors.erl_warn(caller.line, caller.file, warning)
end
typespec(quote(do: :elixir.charlist()), vars, caller, state)
@@ -800,17 +813,17 @@ defmodule Kernel.Typespec do
end
defp typespec({:fun, meta, args}, vars, caller, state) do
{args, state} = Enum.map_reduce(args, state, &typespec(&1, vars, caller, &2))
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
{{:type, line(meta), :fun, args}, state}
end
defp typespec({name, meta, arguments}, vars, caller, state) do
{arguments, state} = Enum.map_reduce(arguments, state, &typespec(&1, vars, caller, &2))
arity = length(arguments)
defp typespec({name, meta, args}, vars, caller, state) do
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
arity = length(args)
case :erl_internal.is_type(name, arity) do
true ->
{{:type, line(meta), name, arguments}, state}
{{:type, line(meta), name, args}, state}
false ->
if state.undefined_type_error_enabled? and
@@ -819,13 +832,13 @@ defmodule Kernel.Typespec do
end
state =
if {name, arity} in state.used_type_pairs do
if :lists.member({name, arity}, state.used_type_pairs) do
state
else
%{state | used_type_pairs: [{name, arity} | state.used_type_pairs]}
end
{{:user_type, line(meta), name, arguments}, state}
{{:user_type, line(meta), name, args}, state}
end
end
@@ -855,12 +868,14 @@ defmodule Kernel.Typespec do
end
defp typespec(list, vars, caller, state) when is_list(list) do
[head | tail] = Enum.reverse(list)
[head | tail] = :lists.reverse(list)
union =
Enum.reduce(tail, validate_kw(head, list, caller), fn elem, acc ->
{:|, [], [validate_kw(elem, list, caller), acc]}
end)
:lists.foldl(
fn elem, acc -> {:|, [], [validate_kw(elem, list, caller), acc]} end,
validate_kw(head, list, caller),
tail
)
typespec({:list, [], [union]}, vars, caller, state)
end
@@ -875,9 +890,9 @@ defmodule Kernel.Typespec do
raise CompileError, file: caller.file, line: caller.line, description: desc
end
defp remote_type({remote, meta, name, arguments}, vars, caller, state) do
{arguments, state} = Enum.map_reduce(arguments, state, &typespec(&1, vars, caller, &2))
{{:remote_type, line(meta), [remote, name, arguments]}, state}
defp remote_type({remote, meta, name, args}, vars, caller, state) do
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
{{:remote_type, line(meta), [remote, name, args]}, state}
end
defp collect_union({:|, _, [a, b]}), do: [a | collect_union(b)]
@@ -924,7 +939,7 @@ defmodule Kernel.Typespec do
end
defp fn_args(meta, args, vars, caller, state) do
{args, state} = Enum.map_reduce(args, state, &typespec(&1, vars, caller, &2))
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
{{:type, line(meta), :product, args}, state}
end
@@ -946,9 +961,39 @@ defmodule Kernel.Typespec do
end
end
defp ensure_no_unused_local_vars!(caller, local_vars) do
for {name, :used_once} <- local_vars do
compile_error(caller, "type variable #{name} is unused")
defp ensure_no_underscore_local_vars!(caller, var_names) do
case :lists.member(:_, var_names) do
true ->
compile_error(caller, "type variable '_' is invalid")
false ->
:ok
end
end
defp ensure_no_unused_local_vars!(caller, local_vars) do
fun = fn {name, used_times} ->
case {:erlang.atom_to_list(name), used_times} do
{[?_ | _], :used_once} ->
:ok
{[?_ | _], :used_multiple} ->
warning =
"the underscored type variable \"#{name}\" is used more than once in the " <>
"type specification. A leading underscore indicates that the value of the " <>
"variable should be ignored. If this is intended please rename the variable to " <>
"remove the underscore"
:elixir_errors.erl_warn(caller.line, caller.file, warning)
{_, :used_once} ->
compile_error(caller, "type variable #{name} is unused")
_ ->
:ok
end
end
:lists.foreach(fun, :maps.to_list(local_vars))
end
end
+8 -6
View File
@@ -25,7 +25,7 @@ defmodule Kernel.Utils do
Callback for defdelegate.
"""
def defdelegate(fun, opts) when is_list(opts) do
# TODO: Remove by 2.0
# TODO: Remove on v2.0
append_first? = Keyword.get(opts, :append_first, false)
{name, args} =
@@ -114,7 +114,7 @@ defmodule Kernel.Utils do
def announce_struct(module) do
case :erlang.get(:elixir_compiler_pid) do
:undefined -> :ok
pid -> send(pid, {:struct_available, module})
pid -> send(pid, {:available, :struct, module})
end
end
@@ -164,11 +164,13 @@ defmodule Kernel.Utils do
that checks for its presence in a guard, then unquotes the variable references as
appropriate.
The resulting transformation looks something like this:
The following code
> expression = quote do: is_integer(value) and rem(value, 2) == 0
> variable_references = [value: Elixir]
> Kernel.Utils.defguard(expression, variable_references) |> Macro.to_string |> IO.puts
expression = quote do: is_integer(value) and rem(value, 2) == 0
variable_references = [value: Elixir]
Kernel.Utils.defguard(expression, variable_references) |> Macro.to_string() |> IO.puts()
would print a code similar to:
case Macro.Env.in_guard?(__CALLER__) do
true ->
+26 -17
View File
@@ -2,7 +2,7 @@ defmodule Keyword do
@moduledoc """
A set of functions for working with keywords.
A keyword is a list of two-element tuples where the first
A keyword list is a list of two-element tuples where the first
element of the tuple is an atom and the second element
can be any value.
@@ -67,6 +67,10 @@ defmodule Keyword do
it comes to ordering. However, since a keyword list is simply a
list, all the operations defined in `Enum` and `List` can be
applied too, especially when ordering is required.
Most of the functions in this module work in linear time. This means
that, the time it takes to perform an operation grows at the same
rate as the length of the list.
"""
@compile :inline_list_funcs
@@ -116,7 +120,7 @@ defmodule Keyword do
def new, do: []
@doc """
Creates a keyword from an enumerable.
Creates a keyword list from an enumerable.
Duplicated entries are removed, the latest one prevails.
Unlike `Enum.into(enumerable, [])`, `Keyword.new(enumerable)`
@@ -137,7 +141,7 @@ defmodule Keyword do
end
@doc """
Creates a keyword from an enumerable via the transformation function.
Creates a keyword list from an enumerable via the transformation function.
Duplicated entries are removed, the latest one prevails.
Unlike `Enum.into(enumerable, [], fun)`,
@@ -150,7 +154,7 @@ defmodule Keyword do
"""
@spec new(Enum.t(), (term -> {key, value})) :: t
def new(pairs, transform) do
def new(pairs, transform) when is_function(transform, 1) do
fun = fn el, acc ->
{k, v} = transform.(el)
put_new(acc, k, v)
@@ -704,8 +708,8 @@ defmodule Keyword do
@spec merge(t, t) :: t
def merge(keywords1, keywords2)
def merge(keywords1, []), do: keywords1
def merge([], keywords2), do: keywords2
def merge(keywords1, []) when is_list(keywords1), do: keywords1
def merge([], keywords2) when is_list(keywords2), do: keywords2
def merge(keywords1, keywords2) when is_list(keywords1) and is_list(keywords2) do
if keyword?(keywords2) do
@@ -827,7 +831,8 @@ defmodule Keyword do
"""
@spec update!(t, key, (value -> value)) :: t
def update!(keywords, key, fun) do
def update!(keywords, key, fun)
when is_list(keywords) and is_atom(key) and is_function(fun, 1) do
update!(keywords, key, fun, keywords)
end
@@ -895,7 +900,7 @@ defmodule Keyword do
"""
@spec split(t, [key]) :: {t, t}
def split(keywords, keys) when is_list(keywords) do
def split(keywords, keys) when is_list(keywords) and is_list(keys) do
fun = fn {k, v}, {take, drop} ->
case k in keys do
true -> {[{k, v} | take], drop}
@@ -923,7 +928,7 @@ defmodule Keyword do
"""
@spec take(t, [key]) :: t
def take(keywords, keys) when is_list(keywords) do
def take(keywords, keys) when is_list(keywords) and is_list(keys) do
:lists.filter(fn {k, _} -> k in keys end, keywords)
end
@@ -941,15 +946,20 @@ defmodule Keyword do
"""
@spec drop(t, [key]) :: t
def drop(keywords, keys) when is_list(keywords) do
def drop(keywords, keys) when is_list(keywords) and is_list(keys) do
:lists.filter(fn {key, _} -> key not in keys end, keywords)
end
@doc """
Returns and removes all values associated with `key` in the keyword list.
Returns the first value for `key` and removes all associated entries in the keyword list.
All duplicated keys are removed. See `pop_first/3` for
removing only the first entry.
It returns a tuple where the first element is the first value for `key` and the
second element is a keyword list with all entries associated with `key` removed.
If the `key` is not present in the keyword list, `{default, keyword_list}` is
returned.
If you don't want to remove all the entries associated with `key` use `pop_first/3`
instead, that function will remove only the first entry.
## Examples
@@ -964,7 +974,7 @@ defmodule Keyword do
"""
@spec pop(t, key, value) :: {value, t}
def pop(keywords, key, default \\ nil) when is_list(keywords) do
def pop(keywords, key, default \\ nil) when is_list(keywords) and is_atom(key) do
case fetch(keywords, key) do
{:ok, value} ->
{value, delete(keywords, key)}
@@ -998,7 +1008,7 @@ defmodule Keyword do
"""
@spec pop_lazy(t, key, (() -> value)) :: {value, t}
def pop_lazy(keywords, key, fun)
when is_list(keywords) and is_function(fun, 0) do
when is_list(keywords) and is_atom(key) and is_function(fun, 0) do
case fetch(keywords, key) do
{:ok, value} ->
{value, delete(keywords, key)}
@@ -1026,7 +1036,7 @@ defmodule Keyword do
"""
@spec pop_first(t, key, value) :: {value, t}
def pop_first(keywords, key, default \\ nil) when is_list(keywords) do
def pop_first(keywords, key, default \\ nil) when is_list(keywords) and is_atom(key) do
case :lists.keytake(key, 1, keywords) do
{:value, {^key, value}, rest} -> {value, rest}
false -> {default, keywords}
@@ -1048,7 +1058,6 @@ defmodule Keyword do
end
@doc false
# TODO: Remove on 2.0
@deprecated "Use Kernel.length/1 instead"
def size(keyword) do
length(keyword)
+122 -90
View File
@@ -63,21 +63,29 @@ defmodule List do
iex> list ++ [4] # slow
[1, 2, 3, 4]
Additionally, getting a list's length and accessing it by index are
linear time operations. Negative indexes are also supported but
they imply the list will be iterated twice, once to calculate the
proper index and another time to perform the operation.
Most of the functions in this module work in linear time. This means that,
that the time it takes to perform an operation grows at the same rate as the
length of the list. For example `length/1` and `last/1` will run in linear
time because they need to iterate through every element of the list, but
`first/1` will run in constant time because it only needs the first element.
## Charlists
If a list is made of non-negative integers, it can also be called
a charlist. Elixir uses single quotes to define charlists:
If a list is made of non-negative integers, where each integer represents a
Unicode code point, the list can also be called a charlist. These integers
must:
* be within the range `0..0x10FFFF` (`0..1_114_111`);
* and be out of the range `0xD800..0xDFFF` (`55_296..57_343`), which is
reserved in Unicode for UTF-16 surrogate pairs.
Elixir uses single quotes to define charlists:
iex> 'héllo'
[104, 233, 108, 108, 111]
In particular, charlists may be printed back in single
quotes if they contain only ASCII-printable codepoints:
In particular, charlists will be printed back by default in single
quotes if they contain only printable ASCII characters:
iex> 'abc'
'abc'
@@ -96,17 +104,19 @@ defmodule List do
#=> {:logger, 'logger', '1.0.0'}
#=> ]
A list can be checked if it is made of printable ASCII
codepoints with `ascii_printable?/2`.
A list can be checked if it is made of only printable ASCII
characters with `ascii_printable?/2`.
Improper lists are never deemed as charlists.
"""
@compile :inline_list_funcs
@doc """
Deletes the given `item` from the `list`. Returns a new list without
the item.
Deletes the given `element` from the `list`. Returns a new list without
the element.
If the `item` occurs more than once in the `list`, just
If the `element` occurs more than once in the `list`, just
the first occurrence is removed.
## Examples
@@ -114,29 +124,47 @@ defmodule List do
iex> List.delete([:a, :b, :c], :a)
[:b, :c]
iex> List.delete([:a, :b, :c], :d)
[:a, :b, :c]
iex> List.delete([:a, :b, :b, :c], :b)
[:a, :b, :c]
iex> List.delete([], :b)
[]
"""
@spec delete(list, any) :: list
def delete(list, item)
def delete([item | list], item), do: list
def delete([other | list], item), do: [other | delete(list, item)]
def delete([], _item), do: []
@spec delete([], any) :: []
@spec delete([...], any) :: list
def delete(list, element)
def delete([element | list], element), do: list
def delete([other | list], element), do: [other | delete(list, element)]
def delete([], _element), do: []
@doc """
Duplicates the given element `n` times in a list.
`n` is an integer greater than or equal to `0`.
If `n` is `0`, an empty list is returned.
## Examples
iex> List.duplicate("hello", 3)
["hello", "hello", "hello"]
iex> List.duplicate("hello", 0)
[]
iex> List.duplicate([1, 2], 2)
[[1, 2], [1, 2]]
iex> List.duplicate("hi", 1)
["hi"]
iex> List.duplicate("bye", 2)
["bye", "bye"]
iex> List.duplicate([1, 2], 3)
[[1, 2], [1, 2], [1, 2]]
"""
@spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
@spec duplicate(any, 0) :: []
@spec duplicate(elem, pos_integer) :: [elem, ...] when elem: var
def duplicate(elem, n) do
:lists.duplicate(n, elem)
end
@@ -144,11 +172,16 @@ defmodule List do
@doc """
Flattens the given `list` of nested lists.
Empty list elements are discarded.
## Examples
iex> List.flatten([1, [[2], 3]])
[1, 2, 3]
iex> List.flatten([[], [[], []]])
[]
"""
@spec flatten(deep_list) :: list when deep_list: [any | deep_list]
def flatten(list) do
@@ -160,11 +193,17 @@ defmodule List do
The list `tail` will be added at the end of
the flattened list.
Empty list elements from `list` are discarded,
but not the ones from `tail`.
## Examples
iex> List.flatten([1, [[2], 3]], [4, 5])
[1, 2, 3, 4, 5]
iex> List.flatten([1, [], 2], [3, [], 4])
[1, 2, 3, [], 4]
"""
@spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
def flatten(list, tail) do
@@ -219,7 +258,8 @@ defmodule List do
1
"""
@spec first([elem]) :: nil | elem when elem: var
@spec first([]) :: nil
@spec first([elem, ...]) :: elem when elem: var
def first([]), do: nil
def first([head | _]), do: head
@@ -238,16 +278,19 @@ defmodule List do
3
"""
@spec last([elem]) :: nil | elem when elem: var
@spec last([]) :: nil
@spec last([elem, ...]) :: elem when elem: var
def last([]), do: nil
def last([head]), do: head
def last([_ | tail]), do: last(tail)
@doc """
Receives a list of tuples and returns the first tuple
where the item at `position` in the tuple matches the
where the element at `position` in the tuple matches the
given `key`.
If no matching tuple is found, `default` is returned.
## Examples
iex> List.keyfind([a: 1, b: 2], :a, 0)
@@ -267,7 +310,7 @@ defmodule List do
@doc """
Receives a list of tuples and returns `true` if there is
a tuple where the item at `position` in the tuple matches
a tuple where the element at `position` in the tuple matches
the given `key`.
## Examples
@@ -288,7 +331,7 @@ defmodule List do
end
@doc """
Receives a list of tuples and if the identified item by `key` at `position`
Receives a list of tuples and if the identified element by `key` at `position`
exists, it is replaced with `new_tuple`.
## Examples
@@ -306,7 +349,7 @@ defmodule List do
end
@doc """
Receives a list of tuples and sorts the items
Receives a list of tuples and sorts the elements
at `position` of the tuples. The sort is stable.
## Examples
@@ -324,10 +367,10 @@ defmodule List do
end
@doc """
Receives a `list` of tuples and replaces the item
Receives a `list` of tuples and replaces the element
identified by `key` at `position` with `new_tuple`.
If the item does not exist, it is added to the end of the `list`.
If the element does not exist, it is added to the end of the `list`.
## Examples
@@ -345,7 +388,7 @@ defmodule List do
@doc """
Receives a `list` of tuples and deletes the first tuple
where the item at `position` matches the
where the element at `position` matches the
given `key`. Returns the new list.
## Examples
@@ -387,7 +430,7 @@ defmodule List do
@spec keytake([tuple], any, non_neg_integer) :: {tuple, [tuple]} | nil
def keytake(list, key, position) do
case :lists.keytake(key, position + 1, list) do
{:value, item, list} -> {item, list}
{:value, element, list} -> {element, list}
false -> nil
end
end
@@ -410,9 +453,7 @@ defmodule List do
[]
"""
@spec wrap(nil) :: []
@spec wrap(list) :: list when list: maybe_improper_list()
@spec wrap(term) :: nonempty_list(term) when term: any()
@spec wrap(term) :: maybe_improper_list()
def wrap(term)
def wrap(list) when is_list(list) do
@@ -488,8 +529,11 @@ defmodule List do
"""
@doc since: "1.6.0"
@spec ascii_printable?(list, limit) :: boolean
when limit: :infinity | non_neg_integer
@spec ascii_printable?(list, 0) :: true
@spec ascii_printable?([], limit) :: true
when limit: :infinity | pos_integer
@spec ascii_printable?([...], limit) :: boolean
when limit: :infinity | pos_integer
def ascii_printable?(list, limit \\ :infinity)
when is_list(list) and (limit == :infinity or (is_integer(limit) and limit >= 0)) do
ascii_printable_guarded?(list, limit)
@@ -500,39 +544,9 @@ defmodule List do
end
defp ascii_printable_guarded?([char | rest], counter)
when is_integer(char) and char >= 32 and char <= 126 do
ascii_printable_guarded?(rest, decrement(counter))
end
defp ascii_printable_guarded?([?\n | rest], counter) do
ascii_printable_guarded?(rest, decrement(counter))
end
defp ascii_printable_guarded?([?\r | rest], counter) do
ascii_printable_guarded?(rest, decrement(counter))
end
defp ascii_printable_guarded?([?\t | rest], counter) do
ascii_printable_guarded?(rest, decrement(counter))
end
defp ascii_printable_guarded?([?\v | rest], counter) do
ascii_printable_guarded?(rest, decrement(counter))
end
defp ascii_printable_guarded?([?\b | rest], counter) do
ascii_printable_guarded?(rest, decrement(counter))
end
defp ascii_printable_guarded?([?\f | rest], counter) do
ascii_printable_guarded?(rest, decrement(counter))
end
defp ascii_printable_guarded?([?\e | rest], counter) do
ascii_printable_guarded?(rest, decrement(counter))
end
defp ascii_printable_guarded?([?\a | rest], counter) do
# 7..13 is the range '\a\b\t\n\v\f\r'. 32..126 are ASCII printables.
when is_integer(char) and
((char >= 7 and char <= 13) or char == ?\e or (char >= 32 and char <= 126)) do
ascii_printable_guarded?(rest, decrement(counter))
end
@@ -548,11 +562,11 @@ defmodule List do
## Examples
iex> List.improper?([1, 2 | 3])
true
iex> List.improper?([1, 2 | 3])
true
iex> List.improper?([1, 2, 3])
false
iex> List.improper?([1, 2, 3])
false
"""
@doc since: "1.8.0"
@@ -736,7 +750,7 @@ defmodule List do
"""
@doc since: "1.5.0"
@spec starts_with?(list, list) :: boolean
@spec starts_with?(nonempty_list, nonempty_list) :: boolean
@spec starts_with?(list, []) :: true
@spec starts_with?([], nonempty_list) :: false
def starts_with?(list, prefix)
@@ -748,15 +762,18 @@ defmodule List do
@doc """
Converts a charlist to an atom.
Currently Elixir does not support conversions from charlists
which contains Unicode codepoints greater than 0xFF.
Elixir supports conversions from charlists which contains any Unicode
code point.
Inlined by the compiler.
## Examples
iex> List.to_atom('elixir')
:elixir
iex> List.to_atom('Elixir')
:Elixir
iex> List.to_atom('🌢 Elixir')
:"🌢 Elixir"
"""
@spec to_atom(charlist) :: atom
@@ -768,8 +785,8 @@ defmodule List do
Converts a charlist to an existing atom. Raises an `ArgumentError`
if the atom does not exist.
Currently Elixir does not support conversions from charlists
which contains Unicode codepoints greater than 0xFF.
Elixir supports conversions from charlists which contains any Unicode
code point.
Inlined by the compiler.
@@ -779,6 +796,10 @@ defmodule List do
iex> List.to_existing_atom('my_atom')
:my_atom
iex> _ = :"🌢 Elixir"
iex> List.to_existing_atom('🌢 Elixir')
:"🌢 Elixir"
iex> List.to_existing_atom('this_atom_will_never_exist')
** (ArgumentError) argument error
@@ -853,11 +874,18 @@ defmodule List do
end
@doc """
Converts a list of integers representing codepoints, lists or
Converts a list of integers representing code points, lists or
strings into a string.
To be converted to a string, a list must either be empty or only
contain the following elements:
* strings
* integers representing Unicode code points
* a list containing one of these three elements
Notice that this function expects a list of integers representing
UTF-8 codepoints. If you have a list of bytes, you must instead use
UTF-8 code points. If you have a list of bytes, you must instead use
the [`:binary` module](http://www.erlang.org/doc/man/binary.html).
## Examples
@@ -871,6 +899,9 @@ defmodule List do
iex> List.to_string([0x0064, "ee", ['p']])
"deep"
iex> List.to_string([])
""
"""
@spec to_string(:unicode.charlist()) :: String.t()
def to_string(list) when is_list(list) do
@@ -881,11 +912,12 @@ defmodule List do
raise ArgumentError, """
cannot convert the given list to a string.
To be converted to a string, a list must contain only:
To be converted to a string, a list must either be empty or only
contain the following elements:
* strings
* integers representing Unicode codepoints
* or a list containing one of these three elements
* integers representing Unicode code points
* a list containing one of these three elements
Please check the given list or call inspect/1 to get the list representation, got:
@@ -904,11 +936,11 @@ defmodule List do
end
@doc """
Converts a list of integers representing codepoints, lists or
Converts a list of integers representing code points, lists or
strings into a charlist.
Notice that this function expects a list of integers representing
UTF-8 codepoints. If you have a list of bytes, you must instead use
UTF-8 code points. If you have a list of bytes, you must instead use
the [`:binary` module](http://www.erlang.org/doc/man/binary.html).
## Examples
@@ -936,7 +968,7 @@ defmodule List do
To be converted to a charlist, a list must contain only:
* strings
* integers representing Unicode codepoints
* integers representing Unicode code points
* or a list containing one of these three elements
Please check the given list or call inspect/1 to get the list representation, got:
-1
View File
@@ -17,7 +17,6 @@ defprotocol List.Chars do
def to_charlist(term)
@doc false
# TODO: Remove by 2.0
@deprecated "Use List.Chars.to_charlist/1 instead"
Kernel.def to_char_list(term) do
__MODULE__.to_charlist(term)
+29 -19
View File
@@ -206,7 +206,11 @@ defmodule Macro do
"""
@doc since: "1.5.0"
def generate_arguments(0, _), do: []
@spec generate_arguments(0, context :: atom) :: []
@spec generate_arguments(pos_integer, context) :: [{atom, [], context}, ...] when context: atom
def generate_arguments(amount, context)
def generate_arguments(0, context) when is_atom(context), do: []
def generate_arguments(amount, context)
when is_integer(amount) and amount > 0 and is_atom(context) do
@@ -502,7 +506,7 @@ defmodule Macro do
In this setup, Elixir will escape the following: `\0`, `\a`, `\b`,
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Bytes can be
given as hexadecimals via `\xNN` and Unicode Codepoints as
given as hexadecimals via `\xNN` and Unicode code points as
`\uNNNN` escapes.
This function is commonly used on sigil implementations
@@ -531,7 +535,7 @@ defmodule Macro do
## Map
The map must be a function. The function receives an integer
representing the codepoint of the character it wants to unescape.
representing the code point of the character it wants to unescape.
Here is the default mapping function implemented by Elixir:
def unescape_map(unicode), do: true
@@ -552,8 +556,8 @@ defmodule Macro do
If the `unescape_map/1` function returns `false`, the char is
not escaped and the backslash is kept in the string.
Hexadecimals and Unicode codepoints will be escaped if the map
function returns `true` for `?x`. Unicode codepoints if the map
Hexadecimals and Unicode code points will be escaped if the map
function returns `true` for `?x`. Unicode code points if the map
function returns `true` for `?u`.
## Examples
@@ -613,7 +617,7 @@ defmodule Macro do
def to_string(tree, fun \\ fn _ast, string -> string end)
# Variables
def to_string({var, _, atom} = ast, fun) when is_atom(atom) do
def to_string({var, _, context} = ast, fun) when is_atom(var) and is_atom(context) do
fun.(ast, Atom.to_string(var))
end
@@ -808,9 +812,10 @@ defmodule Macro do
Kernel.inspect(value, limit: :infinity, printable_limit: :infinity)
end
defp bitpart_to_string({:::, _, [left, right]} = ast, fun) do
defp bitpart_to_string({:"::", _, [left, right]} = ast, fun) do
result =
op_to_string(left, fun, :::, :left) <> "::" <> bitmods_to_string(right, fun, :::, :right)
op_to_string(left, fun, :"::", :left) <>
"::" <> bitmods_to_string(right, fun, :"::", :right)
fun.(ast, result)
end
@@ -843,7 +848,7 @@ defmodule Macro do
# Check if we have an interpolated string.
defp interpolated?({:<<>>, _, [_ | _] = parts}) do
Enum.all?(parts, fn
{:::, _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
{:"::", _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
binary when is_binary(binary) -> true
_ -> false
end)
@@ -856,7 +861,7 @@ defmodule Macro do
defp interpolate({:<<>>, _, parts}, fun) do
parts =
Enum.map_join(parts, "", fn
{:::, _, [{{:., _, [Kernel, :to_string]}, _, [arg]}, {:binary, _, _}]} ->
{:"::", _, [{{:., _, [Kernel, :to_string]}, _, [arg]}, {:binary, _, _}]} ->
"\#{" <> to_string(arg, fun) <> "}"
binary when is_binary(binary) ->
@@ -1137,9 +1142,11 @@ defmodule Macro do
* Module attributes reader (`@foo`)
If the expression cannot be expanded, it returns the expression
itself. Notice that `expand_once/2` performs the expansion just
once and it is not recursive. Check `expand/2` for expansion
until the node can no longer be expanded.
itself. This function does not traverse the AST, only the root
node is expanded.
`expand_once/2` performs the expansion just once. Check `expand/2`
to perform expansion until the node can no longer be expanded.
## Examples
@@ -1358,19 +1365,22 @@ defmodule Macro do
Receives an AST node and expands it until it can no longer
be expanded.
Note this function does not traverse the AST, only the root
node is expanded.
This function uses `expand_once/2` under the hood. Check
it out for more information and examples.
"""
def expand(tree, env) do
expand_until({tree, true}, env)
def expand(ast, env) do
expand_until({ast, true}, env)
end
defp expand_until({tree, true}, env) do
expand_until(do_expand_once(tree, env), env)
defp expand_until({ast, true}, env) do
expand_until(do_expand_once(ast, env), env)
end
defp expand_until({tree, false}, _env) do
tree
defp expand_until({ast, false}, _env) do
ast
end
@doc """
+3 -3
View File
@@ -69,8 +69,8 @@ defmodule Macro.Env do
@typep vars :: [variable]
@typep var_type :: :term
@typep var_version :: non_neg_integer
@typep unused_vars :: %{{variable, var_version} => non_neg_integer | false}
@typep current_vars :: %{variable => {var_version, var_type}}
@typep unused_vars :: %{optional({variable, var_version}) => non_neg_integer | false}
@typep current_vars :: %{optional(variable) => {var_version, var_type}}
@typep prematch_vars :: current_vars | :warn | :raise | :pin | :apply
@typep contextual_vars :: [atom]
@@ -85,7 +85,7 @@ defmodule Macro.Env do
aliases: aliases,
functions: functions,
macros: macros,
macro_aliases: aliases,
macro_aliases: macro_aliases,
context_modules: context_modules,
vars: vars,
unused_vars: unused_vars,
+47 -20
View File
@@ -91,6 +91,12 @@ defmodule Map do
iex> %{map | three: 3}
** (KeyError) key :three not found
The functions in this module that need to find a specific key work in logarithmic time.
This means that the time it takes to find keys grows as the map grows, but it's not
directly proportional to the map size. In comparison to finding an element in a list,
it performs better because lists have a linear time complexity. Some functions,
such as `keys/1` and `values/1`, run in linear time because they need to get to every
element in the map.
"""
@type key :: any
@@ -206,8 +212,8 @@ defmodule Map do
|> :maps.from_list()
end
defp new_transform([item | rest], fun, acc) do
new_transform(rest, fun, [fun.(item) | acc])
defp new_transform([element | rest], fun, acc) do
new_transform(rest, fun, [fun.(element) | acc])
end
@doc """
@@ -378,13 +384,20 @@ defmodule Map do
%{a: 1, c: 3}
"""
@spec take(map, Enumerable.t()) :: map
@spec take(map, [key]) :: map
def take(map, keys)
def take(map, keys) when is_map(map) and is_list(keys) do
take(keys, map, _acc = [])
end
def take(map, keys) when is_map(map) do
keys
|> Enum.to_list()
|> take(map, [])
IO.warn(
"Map.take/2 with an Enumerable of keys that is not a list is deprecated. " <>
" Use a list of keys instead."
)
take(map, Enum.to_list(keys))
end
def take(non_map, _keys) do
@@ -409,8 +422,9 @@ defmodule Map do
Gets the value for a specific `key` in `map`.
If `key` is present in `map` with value `value`, then `value` is
returned. Otherwise, `default` is returned (which is `nil` unless
specified otherwise).
returned. Otherwise, `default` is returned.
If `default` is not provided, `nil` is used.
## Examples
@@ -670,23 +684,30 @@ defmodule Map do
%{a: 1, c: 3}
"""
@spec drop(map, Enumerable.t()) :: map
@spec drop(map, [key]) :: map
def drop(map, keys)
def drop(map, keys) when is_map(map) and is_list(keys) do
drop_keys(keys, map)
end
def drop(map, keys) when is_map(map) do
keys
|> Enum.to_list()
|> drop_list(map)
IO.warn(
"Map.drop/2 with an Enumerable of keys that is not a list is deprecated. " <>
" Use a list of keys instead."
)
drop(map, Enum.to_list(keys))
end
def drop(non_map, keys) do
:erlang.error({:badmap, non_map}, [non_map, keys])
end
defp drop_list([], acc), do: acc
defp drop_keys([], acc), do: acc
defp drop_list([key | rest], acc) do
drop_list(rest, delete(acc, key))
defp drop_keys([key | rest], acc) do
drop_keys(rest, delete(acc, key))
end
@doc """
@@ -703,13 +724,20 @@ defmodule Map do
{%{a: 1, c: 3}, %{b: 2}}
"""
@spec split(map, Enumerable.t()) :: {map, map}
@spec split(map, [key]) :: {map, map}
def split(map, keys)
def split(map, keys) when is_map(map) and is_list(keys) do
split(keys, [], map)
end
def split(map, keys) when is_map(map) do
keys
|> Enum.to_list()
|> split([], map)
IO.warn(
"Map.split/2 with an Enumerable of keys that is not a list is deprecated. " <>
" Use a list of keys instead."
)
split(map, Enum.to_list(keys))
end
def split(non_map, keys) do
@@ -892,7 +920,6 @@ defmodule Map do
def equal?(term, other), do: :erlang.error({:badmap, term}, [term, other])
@doc false
# TODO: Remove on 2.0
@deprecated "Use Kernel.map_size/1 instead"
def size(map) do
map_size(map)
+11 -7
View File
@@ -30,6 +30,10 @@ defmodule MapSet do
`MapSet`s can also be constructed starting from other collection-type data
structures: for example, see `MapSet.new/1` or `Enum.into/2`.
`MapSet` is built on top of `Map`, this means that they share many properties,
including logarithmic time complexity. See the documentation for `Map` for more
information on its execution time complexity.
"""
# MapSets have an underlying Map. MapSet elements are keys of said map,
@@ -41,7 +45,7 @@ defmodule MapSet do
@opaque t(value) :: %__MODULE__{map: %{optional(value) => []}}
@type t :: t(term)
# TODO: Remove version key on Elixir 2.0
# TODO: Remove version key on v2.0
defstruct map: %{}, version: 2
@doc """
@@ -104,16 +108,16 @@ defmodule MapSet do
:maps.from_list(acc)
end
defp new_from_list([item | rest], acc) do
new_from_list(rest, [{item, @dummy_value} | acc])
defp new_from_list([element | rest], acc) do
new_from_list(rest, [{element, @dummy_value} | acc])
end
defp new_from_list_transform([], _fun, acc) do
:maps.from_list(acc)
end
defp new_from_list_transform([item | rest], fun, acc) do
new_from_list_transform(rest, fun, [{fun.(item), @dummy_value} | acc])
defp new_from_list_transform([element | rest], fun, acc) do
new_from_list_transform(rest, fun, [{fun.(element), @dummy_value} | acc])
end
@doc """
@@ -148,7 +152,7 @@ defmodule MapSet do
def difference(map_set1, map_set2)
# If the first set is less than twice the size of the second map,
# it is fastest to re-accumulate items in the first set that are not
# it is fastest to re-accumulate elements in the first set that are not
# present in the second set.
def difference(%MapSet{map: map1}, %MapSet{map: map2})
when map_size(map1) < map_size(map2) * 2 do
@@ -161,7 +165,7 @@ defmodule MapSet do
end
# If the second set is less than half the size of the first set, it's fastest
# to simply iterate through each item in the second set, deleting them from
# to simply iterate through each element in the second set, deleting them from
# the first set.
def difference(%MapSet{map: map1} = map_set, %MapSet{map: map2}) do
%{map_set | map: Map.drop(map1, Map.keys(map2))}
+142 -92
View File
@@ -8,7 +8,8 @@ defmodule Module do
After a module is compiled, using many of the functions in
this module will raise errors, since it is out of their scope
to inspect runtime data. Most of the runtime data can be inspected
via the `__info__/1` function attached to each compiled module.
via the [`__info__/1`](`c:Module.__info__/1`) function attached to
each compiled module.
## Module attributes
@@ -174,7 +175,7 @@ defmodule Module do
Accepts a string (often a heredoc) or `false` where `@doc false` will
make the entity invisible to documentation extraction tools like
ExDoc. For example:
[`ExDoc`](https://hexdocs.pm/ex_doc/). For example:
defmodule MyModule do
@typedoc "This type"
@@ -197,9 +198,10 @@ defmodule Module do
As can be seen in the example above, `@doc` and `@typedoc` also accept
a keyword list that serves as a way to provide arbitrary metadata
about the entity. Tools like ExDoc and IEx may use this information to
display annotations. A common use case is `since` that may be used
to annotate in which version the function was introduced.
about the entity. Tools like [`ExDoc`](https://hexdocs.pm/ex_doc/) and
`IEx` may use this information to display annotations. A common use
case is `since` that may be used to annotate in which version the
function was introduced.
As illustrated in the example, it is possible to use these attributes
more than once before an entity. However, the compiler will warn if
@@ -211,6 +213,9 @@ defmodule Module do
there are a few reserved keys that will be ignored and warned if used.
Currently these are: `:opaque` and `:defaults`.
Once this module is compiled, this information becomes available via
the `Code.fetch_docs/1` function.
### `@dialyzer`
Defines warnings to request or suppress when using a version of
@@ -269,12 +274,15 @@ defmodule Module do
Accepts a string (often a heredoc) or `false` where `@moduledoc false`
will make the module invisible to documentation extraction tools like
ExDoc.
[`ExDoc`](https://hexdocs.pm/ex_doc/).
Similarly to `@doc` also accepts a keyword list to provide metadata
about the module. For more details, see the documentation of `@doc`
above.
Once this module is compiled, this information becomes available via
the `Code.fetch_docs/1` function.
### `@on_definition`
A hook that will be invoked when each function or macro in the current
@@ -337,8 +345,9 @@ defmodule Module do
### Custom attributes
In addition to the built-in attributes outlined above, custom attributes may
also be added. A custom attribute is any valid identifier prefixed with an
`@` and followed by a valid Elixir value:
also be added. Custom attributes are expressed using the `@/1` operator followed
by a valid variable name. The value given to the custom attribute must be a valid
Elixir value:
defmodule MyModule do
@custom_attr [some: "stuff"]
@@ -493,27 +502,37 @@ defmodule Module do
@typep definition :: {atom, arity}
@typep def_kind :: :def | :defp | :defmacro | :defmacrop
@extra_error_msg_defines? "Use Kernel.function_exported?/3 and Kernel.macro_exported?/3 " <>
"to check for public functions and macros instead"
@extra_error_msg_definitions_in "Use the Module.__info__/1 callback to get public functions and macros instead"
@doc """
Provides runtime information about functions and macros defined by the
module, etc.
Provides runtime information about functions, macros, and other information
defined by the module.
Each module gets an `__info__/1` function when it's compiled. The function
takes one of the following atoms:
takes one of the following items:
* `:functions` - keyword list of public functions along with their arities
* `:macros` - keyword list of public macros along with their arities
* `:module` - the module atom name
* `:md5` - the MD5 of the module
* `:attributes` - a keyword list with all persisted attributes
* `:compile` - a list with compiler metadata
* `:attributes` - a list with all persisted attributes
* `:functions` - a keyword list of public functions and their arities
* `:macros` - a keyword list of public macros and their arities
* `:md5` - the MD5 of the module
* `:module` - the module atom name
"""
@callback __info__(:functions | :macros | :module | :md5 | :compile | :attributes) :: term()
@callback __info__(:attributes) :: keyword()
@callback __info__(:compile) :: [term()]
@callback __info__(:functions) :: keyword()
@callback __info__(:macros) :: keyword()
@callback __info__(:md5) :: binary()
@callback __info__(:module) :: module()
@doc """
Checks if a module is open.
@@ -584,7 +603,7 @@ defmodule Module do
def eval_quoted(module, quoted, binding, opts)
when is_atom(module) and is_list(binding) and is_list(opts) do
assert_not_compiled!(:eval_quoted, module)
assert_not_compiled!(__ENV__.function, module)
:elixir_def.reset_last(module)
{value, binding, _env, _scope} =
@@ -900,7 +919,13 @@ defmodule Module do
Use `defines?/3` to assert for a specific type.
This function can only be used on modules that have not yet been compiled.
Use `Kernel.function_exported?/3` to check compiled modules.
Use `Kernel.function_exported?/3` and `Kernel.macro_exported?/3` to check for
public functions and macros respectively in compiled modules.
Note that `defines?` returns false for functions and macros that have
been defined but then marked as overridable and no other implementation
has been provided. You can check the overridable status by calling
`overridable?/2`.
## Examples
@@ -914,7 +939,7 @@ defmodule Module do
@spec defines?(module, definition) :: boolean
def defines?(module, {name, arity} = tuple)
when is_atom(module) and is_atom(name) and is_integer(arity) and arity >= 0 and arity <= 255 do
assert_not_compiled!(:defines?, module)
assert_not_compiled!(__ENV__.function, module, @extra_error_msg_defines?)
{set, _bag} = data_tables_for(module)
:ets.member(set, {:def, tuple})
end
@@ -926,7 +951,8 @@ defmodule Module do
`kind` can be any of `:def`, `:defp`, `:defmacro`, or `:defmacrop`.
This function can only be used on modules that have not yet been compiled.
Use `Kernel.function_exported?/3` to check compiled modules.
Use `Kernel.function_exported?/3` and `Kernel.macro_exported?/3` to check for
public functions and macros respectively in compiled modules.
## Examples
@@ -941,7 +967,8 @@ defmodule Module do
def defines?(module, {name, arity} = tuple, def_kind)
when is_atom(module) and is_atom(name) and is_integer(arity) and arity >= 0 and arity <= 255 and
def_kind in [:def, :defp, :defmacro, :defmacrop] do
assert_not_compiled!(:defines?, module)
assert_not_compiled!(__ENV__.function, module, @extra_error_msg_defines?)
{set, _bag} = data_tables_for(module)
case :ets.lookup(set, {:def, tuple}) do
@@ -962,9 +989,11 @@ defmodule Module do
end
@doc """
Converts the given spec to a callback.
Copies the given spec as a callback.
Returns `true` if there is such a spec and it was converted to a callback.
Returns `true` if there is such a spec and it was copied as a callback.
If the function associated to the spec has documentation defined prior to
invoking this function, the docs are copied too.
"""
@doc since: "1.7.0"
@spec spec_to_callback(module, definition) :: boolean
@@ -973,19 +1002,27 @@ defmodule Module do
end
@doc """
Returns all functions defined in `module`.
Returns all functions and macros defined in `module`.
It returns a list with all defined functions and macros, public and private,
in the shape of `[{name, arity}, ...]`.
This function can only be used on modules that have not yet been compiled.
Use the `c:Module.__info__/1` callback to get the public functions and macros in
compiled modules.
## Examples
defmodule Example do
def version, do: 1
Module.definitions_in(__MODULE__) #=> [{:version, 0}]
defmacrop test(arg), do: arg
Module.definitions_in(__MODULE__) #=> [{:version, 0}, {:test, 1}]
end
"""
@spec definitions_in(module) :: [definition]
def definitions_in(module) when is_atom(module) do
assert_not_compiled!(:definitions_in, module)
assert_not_compiled!(__ENV__.function, module, @extra_error_msg_definitions_in)
{_, bag} = data_tables_for(module)
bag_lookup_element(bag, :defs, 2)
end
@@ -994,6 +1031,10 @@ defmodule Module do
Returns all functions defined in `module`, according
to its kind.
This function can only be used on modules that have not yet been compiled.
Use the `c:Module.__info__/1` callback to get the public functions and macros in
compiled modules.
## Examples
defmodule Example do
@@ -1006,7 +1047,7 @@ defmodule Module do
@spec definitions_in(module, def_kind) :: [definition]
def definitions_in(module, def_kind)
when is_atom(module) and def_kind in [:def, :defp, :defmacro, :defmacrop] do
assert_not_compiled!(:definitions_in, module)
assert_not_compiled!(__ENV__.function, module, @extra_error_msg_definitions_in)
{set, _} = data_tables_for(module)
:lists.concat(:ets.match(set, {{:def, :"$1"}, def_kind, :_, :_, :_, :_}))
end
@@ -1017,10 +1058,15 @@ defmodule Module do
An overridable function is lazily defined, allowing a
developer to customize it. See `Kernel.defoverridable/1` for
more information and documentation.
Once a function or a macro is marked as overridable, it will
no longer be listed under `definitions_in/1` or return true
when given to `defines?/2` until another implementation is
given.
"""
@spec make_overridable(module, [definition]) :: :ok
def make_overridable(module, tuples) when is_atom(module) and is_list(tuples) do
assert_not_compiled!(:make_overridable, module)
assert_not_compiled!(__ENV__.function, module)
func = fn
{function_name, arity} = tuple
@@ -1033,18 +1079,7 @@ defmodule Module do
clause ->
neighbours = :elixir_locals.yank(tuple, module)
overridable_definitions = :elixir_overridable.overridable(module)
count =
case :maps.find(tuple, overridable_definitions) do
{:ok, {count, _, _, _}} -> count + 1
:error -> 1
end
overridable_definitions =
:maps.put(tuple, {count, clause, neighbours, false}, overridable_definitions)
:elixir_overridable.overridable(module, overridable_definitions)
:elixir_overridable.record_overridable(module, tuple, clause, neighbours)
end
other ->
@@ -1129,7 +1164,7 @@ defmodule Module do
@spec overridable?(module, definition) :: boolean
def overridable?(module, {function_name, arity} = tuple)
when is_atom(function_name) and is_integer(arity) and arity >= 0 and arity <= 255 do
:maps.is_key(tuple, :elixir_overridable.overridable(module))
:elixir_overridable.overridable_for(module, tuple) != :not_overridable
end
@doc """
@@ -1144,7 +1179,7 @@ defmodule Module do
"""
@spec put_attribute(module, atom, term) :: :ok
def put_attribute(module, key, value) when is_atom(module) and is_atom(key) do
put_attribute(module, key, value, nil)
__put_attribute__(module, key, value, nil)
end
@doc """
@@ -1164,21 +1199,32 @@ defmodule Module do
Module.get_attribute(__MODULE__, :foo)
This function can only be used on modules that have not yet been compiled.
Use the `c:Module.__info__/1` callback to get all persisted attributes, or
`Code.fetch_docs/1` to retrieve all documentation related attributes in
compiled modules.
## Examples
defmodule Foo do
Module.put_attribute(__MODULE__, :value, 1)
Module.get_attribute(__MODULE__, :value) #=> 1
Module.get_attribute(__MODULE__, :value, :default) #=> 1
Module.get_attribute(__MODULE__, :not_found, :default) #=> :default
Module.register_attribute(__MODULE__, :value, accumulate: true)
Module.put_attribute(__MODULE__, :value, 1)
Module.get_attribute(__MODULE__, :value) #=> [1]
end
"""
@spec get_attribute(module, atom) :: term
def get_attribute(module, key) when is_atom(module) and is_atom(key) do
get_attribute(module, key, nil)
@spec get_attribute(module, atom, term) :: term
def get_attribute(module, key, default \\ nil) when is_atom(module) and is_atom(key) do
case __get_attribute__(module, key, nil) do
nil -> default
value -> value
end
end
@doc """
@@ -1196,7 +1242,7 @@ defmodule Module do
"""
@spec delete_attribute(module, atom) :: term
def delete_attribute(module, key) when is_atom(module) and is_atom(key) do
assert_not_compiled!(:delete_attribute, module)
assert_not_compiled!(__ENV__.function, module)
{set, bag} = data_tables_for(module)
case :ets.lookup(set, key) do
@@ -1248,7 +1294,7 @@ defmodule Module do
@spec register_attribute(module, atom, [{:accumulate, boolean}, {:persist, boolean}]) :: :ok
def register_attribute(module, attribute, options)
when is_atom(module) and is_atom(attribute) and is_list(options) do
assert_not_compiled!(:register_attribute, module)
assert_not_compiled!(__ENV__.function, module)
{set, bag} = data_tables_for(module)
if Keyword.get(options, :persist) do
@@ -1300,10 +1346,9 @@ defmodule Module do
end
@doc false
# TODO: Remove by 2.0
@deprecated "Use @doc instead"
def add_doc(module, line, kind, {name, arity}, signature \\ [], doc) do
assert_not_compiled!(:add_doc, module)
assert_not_compiled!(__ENV__.function, module)
if kind in [:defp, :defmacrop, :typep] do
if doc, do: {:error, :private_doc}, else: :ok
@@ -1338,7 +1383,7 @@ defmodule Module do
"#{kind} #{name}/#{arity} is private, " <>
"@doc attribute is always discarded for private functions/macros/types"
:elixir_errors.warn(line, env.file, message)
IO.warn(message, Macro.Env.stacktrace(%{env | line: line}))
end
end
@@ -1438,7 +1483,7 @@ defmodule Module do
pending_callbacks =
if impls != [] do
{non_implemented_callbacks, contexts} = check_impls(behaviours, callbacks, impls)
{non_implemented_callbacks, contexts} = check_impls(env, behaviours, callbacks, impls)
warn_missing_impls(env, non_implemented_callbacks, contexts, all_definitions)
non_implemented_callbacks
else
@@ -1456,27 +1501,21 @@ defmodule Module do
message =
"@behaviour #{inspect(behaviour)} must be an atom (in module #{inspect(env.module)})"
:elixir_errors.warn(env.line, env.file, message)
IO.warn(message, Macro.Env.stacktrace(env))
acc
not Code.ensure_compiled?(behaviour) ->
message =
"@behaviour #{inspect(behaviour)} does not exist (in module #{inspect(env.module)})"
unless standard_behaviour?(behaviour) do
:elixir_errors.warn(env.line, env.file, message)
end
IO.warn(message, Macro.Env.stacktrace(env))
acc
not function_exported?(behaviour, :behaviour_info, 1) ->
message =
"module #{inspect(behaviour)} is not a behaviour (in module #{inspect(env.module)})"
unless standard_behaviour?(behaviour) do
:elixir_errors.warn(env.line, env.file, message)
end
IO.warn(message, Macro.Env.stacktrace(env))
acc
true ->
@@ -1494,10 +1533,15 @@ defmodule Module do
case acc do
%{^callback => {_kind, conflict, _optional?}} ->
message =
"conflicting behaviours found. #{format_definition(kind, callback)} is required by " <>
"#{inspect(conflict)} and #{inspect(behaviour)} (in module #{inspect(env.module)})"
if conflict == behaviour do
"the behavior #{inspect(conflict)} has been declared twice " <>
"(conflict in #{format_definition(kind, callback)} in module #{inspect(env.module)})"
else
"conflicting behaviours found. #{format_definition(kind, callback)} is required by " <>
"#{inspect(conflict)} and #{inspect(behaviour)} (in module #{inspect(env.module)})"
end
:elixir_errors.warn(env.line, env.file, message)
IO.warn(message, Macro.Env.stacktrace(env))
%{} ->
:ok
@@ -1506,16 +1550,6 @@ defmodule Module do
Map.put(acc, callback, {kind, behaviour, original in optional_callbacks})
end
defp standard_behaviour?(behaviour) do
behaviour in [
Collectable,
Enumerable,
Inspect,
List.Chars,
String.Chars
]
end
defp check_callbacks(env, callbacks, all_definitions) do
for {callback, {kind, behaviour, optional?}} <- callbacks do
case :lists.keyfind(callback, 1, all_definitions) do
@@ -1524,7 +1558,7 @@ defmodule Module do
format_callback(callback, kind, behaviour) <>
" is not implemented (in module #{inspect(env.module)})"
:elixir_errors.warn(env.line, env.file, message)
IO.warn(message, Macro.Env.stacktrace(env))
{_, wrong_kind, _, _} when kind != wrong_kind ->
message =
@@ -1532,7 +1566,7 @@ defmodule Module do
" was implemented as \"#{wrong_kind}\" but should have been \"#{kind}\" " <>
"(in module #{inspect(env.module)})"
:elixir_errors.warn(env.line, env.file, message)
IO.warn(message, Macro.Env.stacktrace(env))
_ ->
:ok
@@ -1554,7 +1588,7 @@ defmodule Module do
module.__protocol__(:module) == module
end
defp check_impls(behaviours, callbacks, impls) do
defp check_impls(env, behaviours, callbacks, impls) do
acc = {callbacks, %{}}
Enum.reduce(impls, acc, fn {fa, context, defaults, kind, line, file, value}, acc ->
@@ -1567,7 +1601,8 @@ defmodule Module do
end)
{:error, message} ->
:elixir_errors.warn(line, file, format_impl_warning(fa, kind, message))
formatted = format_impl_warning(fa, kind, message)
IO.warn(formatted, Macro.Env.stacktrace(%{env | line: line, file: file}))
acc
end
end)
@@ -1683,7 +1718,7 @@ defmodule Module do
"This either means you forgot to add the \"@impl true\" annotation before the " <>
"definition or that you are accidentally overriding this callback"
:elixir_errors.warn(:elixir_utils.get_line(meta), env.file, message)
IO.warn(message, Macro.Env.stacktrace(%{env | line: :elixir_utils.get_line(meta)}))
end
end
@@ -1723,8 +1758,13 @@ defmodule Module do
@doc false
# Used internally by Kernel's @.
# This function is private and must be used only internally.
def get_attribute(module, key, line) when is_atom(key) do
assert_not_compiled!(:get_attribute, module)
def __get_attribute__(module, key, line) when is_atom(key) do
assert_not_compiled!(
{:get_attribute, 2},
module,
"Use the Module.__info__/1 callback or Code.fetch_docs/1 instead"
)
{set, bag} = data_tables_for(module)
case :ets.lookup(set, key) do
@@ -1755,8 +1795,8 @@ defmodule Module do
@doc false
# Used internally by Kernel's @.
# This function is private and must be used only internally.
def put_attribute(module, key, value, line) when is_atom(key) do
assert_not_compiled!(:put_attribute, module)
def __put_attribute__(module, key, value, line) when is_atom(key) do
assert_not_compiled!(__ENV__.function, module)
{set, bag} = data_tables_for(module)
value = preprocess_attribute(key, value)
put_attribute(module, key, value, line, set, bag)
@@ -1834,7 +1874,7 @@ defmodule Module do
defp preprocess_attribute(key, value) when key in [:moduledoc, :typedoc, :doc] do
case value do
{line, doc} when is_integer(line) and (is_binary(doc) or is_boolean(doc) or is_nil(doc)) ->
{line, doc} when is_integer(line) and (is_binary(doc) or doc == false or is_nil(doc)) ->
value
{line, [{key, _} | _]} when is_integer(line) and is_atom(key) ->
@@ -1842,13 +1882,13 @@ defmodule Module do
{line, doc} when is_integer(line) ->
raise ArgumentError,
"@#{key} is a built-in module attribute for documentation. It should be " <>
"a string, boolean, keyword list, or nil, got: #{inspect(doc)}"
"@#{key} is a built-in module attribute for documentation. It should be either " <>
"false, nil, a string, or a keyword list, got: #{inspect(doc)}"
_other ->
raise ArgumentError,
"@#{key} is a built-in module attribute for documentation. When set dynamically, " <>
"it should be {line, doc} (where \"doc\" is a string, boolean, keyword list, or nil), " <>
"it should be {line, doc} (where \"doc\" is either false, nil, a string, or a keyword list), " <>
"got: #{inspect(value)}"
end
end
@@ -1995,9 +2035,19 @@ defmodule Module do
:error, :badarg -> []
end
defp assert_not_compiled!(fun, module) do
defp assert_not_compiled!(function_name_arity, module, extra_msg \\ "") do
open?(module) ||
raise ArgumentError,
"could not call #{fun} with argument #{inspect(module)} because the module is already compiled"
assert_not_compiled_message(function_name_arity, module, extra_msg)
end
defp assert_not_compiled_message({function_name, arity}, module, extra_msg) do
mfa = "Module.#{function_name}/#{arity}"
"could not call #{mfa} because the module #{inspect(module)} is already compiled" <>
case extra_msg do
"" -> ""
_ -> ". " <> extra_msg
end
end
end
+46 -18
View File
@@ -8,19 +8,21 @@
# resembling a graph. The keys and what they point to are:
#
# * `:reattach` points to `{name, arity}`
# * `{:local, {name, arity}}` points to `{name, arity}`
# * `{:local, {name, arity}}` points to `{{name, arity}, line, macro_dispatch?}`
# * `{:import, {name, arity}}` points to `Module`
#
# This is built on top of the internal module tables.
defmodule Module.LocalsTracker do
@moduledoc false
@defmacros [:defmacro, :defmacrop]
@doc """
Adds and tracks defaults for a definition into the tracker.
"""
def add_defaults({_set, bag}, _kind, {name, arity} = pair, defaults, meta) do
def add_defaults({_set, bag}, kind, {name, arity} = pair, defaults, meta) do
for i <- :lists.seq(arity - defaults, arity - 1) do
put_edge(bag, {:local, {name, i}}, {pair, get_line(meta)})
put_edge(bag, {:local, {name, i}}, {pair, get_line(meta), kind in @defmacros})
end
:ok
@@ -29,11 +31,9 @@ defmodule Module.LocalsTracker do
@doc """
Adds a local dispatch from-to the given target.
"""
def add_local({_set, bag}, from, to, meta) when is_tuple(from) and is_tuple(to) do
if from != to do
put_edge(bag, {:local, from}, {to, get_line(meta)})
end
def add_local({_set, bag}, from, to, meta, macro_dispatch?)
when is_tuple(from) and is_tuple(to) and is_boolean(macro_dispatch?) do
put_edge(bag, {:local, from}, {to, get_line(meta), macro_dispatch?})
:ok
end
@@ -56,14 +56,14 @@ defmodule Module.LocalsTracker do
@doc """
Reattach a previously yanked node.
"""
def reattach({_set, bag}, tuple, _kind, function, out_neighbours, meta) do
def reattach({_set, bag}, tuple, kind, function, out_neighbours, meta) do
for out_neighbour <- out_neighbours do
put_edge(bag, {:local, function}, out_neighbour)
end
# Make a call from the old function to the new one
if function != tuple do
put_edge(bag, {:local, function}, {tuple, get_line(meta)})
put_edge(bag, {:local, function}, {tuple, get_line(meta), kind in @defmacros})
end
# Finally marked the new one as reattached
@@ -99,18 +99,37 @@ defmodule Module.LocalsTracker do
end
@doc """
Collect undefined functions based on local calls and existing definitions
Collect undefined functions based on local calls and existing definitions.
"""
def collect_undefined_locals({set, bag}, all_defined) do
undefined =
for {pair, _, _, _} <- all_defined,
{local, line} <- out_neighbours(bag, {:local, pair}),
not :ets.member(set, {:def, local}),
do: {build_meta(line), local}
for {pair, _, meta, _} <- all_defined,
{local, line, macro_dispatch?} <- out_neighbours(bag, {:local, pair}),
error = undefined_local_error(set, local, macro_dispatch?),
do: {build_meta(line, meta), local, error}
:lists.usort(undefined)
end
defp undefined_local_error(set, local, true) do
case :ets.member(set, {:def, local}) do
true -> false
false -> :undefined_function
end
end
defp undefined_local_error(set, local, false) do
try do
if :ets.lookup_element(set, {:def, local}, 2) in @defmacros do
:incorrect_dispatch
else
false
end
catch
_, _ -> :undefined_function
end
end
defp unreachable(reachable, reattached, private) do
for {tuple, kind, _, _} <- private,
not reachable?(tuple, kind, reachable, reattached),
@@ -183,7 +202,7 @@ defmodule Module.LocalsTracker do
defp reachable_from(bag, local, vertices) do
vertices = Map.put(vertices, local, true)
Enum.reduce(out_neighbours(bag, {:local, local}), vertices, fn {local, _line}, acc ->
Enum.reduce(out_neighbours(bag, {:local, local}), vertices, fn {local, _line, _}, acc ->
case acc do
%{^local => true} -> acc
_ -> reachable_from(bag, local, acc)
@@ -193,8 +212,17 @@ defmodule Module.LocalsTracker do
defp get_line(meta), do: Keyword.get(meta, :line)
defp build_meta(nil), do: []
defp build_meta(line), do: [line: line]
defp build_meta(nil, _meta), do: []
# We need to transform any file annotation in the function
# definition into a keep annotation that is used by the
# error handling system in order to respect line/file.
defp build_meta(line, meta) do
case Keyword.get(meta, :file) do
{file, _} -> [keep: {file, line}]
_ -> [line: line]
end
end
## Lightweight digraph implementation
+2
View File
@@ -250,6 +250,7 @@ defmodule Node do
This function will raise `FunctionClauseError` if the given `node` is not alive.
"""
@spec set_cookie(t, atom) :: true
def set_cookie(node \\ Node.self(), cookie) when is_atom(cookie) do
:erlang.set_cookie(node, cookie)
end
@@ -259,6 +260,7 @@ defmodule Node do
Returns the cookie if the node is alive, otherwise `:nocookie`.
"""
@spec get_cookie() :: atom
def get_cookie() do
:erlang.get_cookie()
end
+40 -17
View File
@@ -1,6 +1,29 @@
defmodule OptionParser do
@moduledoc """
This module contains functions to parse command line options.
Functions for parsing command line arguments.
When calling a command, it's possible to pass command line options
to modify what the command does. In this documentation, those are
called "switches", in other situations they may be called "flags"
or simply "options". A switch can be given a value, also called an
"argument".
The main function in this module is `parse/2`, which parses a list
of command line options and arguments into a keyword list:
iex> OptionParser.parse(["--debug"], strict: [debug: :boolean])
{[debug: true], [], []}
`OptionParser` provides some conveniences out of the box,
such as aliases and automatic handling of negation switches.
The `parse_head/2` function is an alternative to `parse/2`
which stops parsing as soon as it finds a value that is not
a switch nor a value for a previous switch.
This module also provides low-level functions, such as `next/2`,
for parsing switches manually, as well as `split/1` and `to_argv/1`
for parsing from and converting switches to strings.
"""
@type argv :: [String.t()]
@@ -60,15 +83,16 @@ defmodule OptionParser do
Switches can be specified via one of two options:
* `:strict` - defines strict switches. Any switch in `argv` that is not
specified in the list is returned in the invalid options list.
* `:switches` - defines some switches and their types. This function
* `:strict` - defines strict switches and their types. Any switch
in `argv` that is not specified in the list is returned in the
invalid options list. This is the preferred way to parse options.
* `:switches` - defines switches and their types. This function
still attempts to parse switches that are not in this list.
Both these options accept a keyword list of `{name, type}` tuples where `name`
is an atom defining the name of the switch and `type` is an atom that
specifies the type for the value of this switch (see the "Types" section below
for the possible types and more information about type casting).
Both these options accept a keyword list where the key is an atom
defining the name of the switch and value is the `type` of the
switch (see the "Types" section below for more information).
Note that you should only supply the `:switches` or the `:strict` option.
If you supply both, an `ArgumentError` exception will be raised.
@@ -97,7 +121,7 @@ defmodule OptionParser do
Switches can be specified with modifiers, which change how
they behave. The following modifiers are supported:
* `:keep` - keeps duplicated items instead of overriding them;
* `:keep` - keeps duplicated elements instead of overriding them;
works with all types except `:count`. Specifying `switch_name: :keep`
assumes the type of `:switch_name` will be `:string`.
@@ -141,16 +165,17 @@ defmodule OptionParser do
# The :option_parser_example atom is not used anywhere below
However, the code below would work as long as `:option_parser_example` atom is
used at some point later (or earlier) **in the same module**:
used at some point later (or earlier) **in the same module**. For example:
{opts, _, _} = OptionParser.parse(["--option-parser-example"], switches: [debug: :boolean])
# ... then somewhere in the same module you access it ...
opts[:option_parser_example]
In other words, Elixir will do the correct thing and only parse options that are
used by the runtime, ignoring all others. If you would like to parse all switches,
regardless if they exist or not, you can force creation of atoms by passing
`allow_nonexistent_atoms: true` as option. Use this option with care. It is only
useful when you are building command-line applications that receive
In other words, Elixir will only parse options that are used by the runtime,
ignoring all others. If you would like to parse all switches, regardless if
they exist or not, you can force creation of atoms by passing
`allow_nonexistent_atoms: true` as option. Use this option with care. It is
only useful when you are building command-line applications that receive
dynamically-named arguments and must be avoided in long-running systems.
## Aliases
@@ -427,7 +452,6 @@ defmodule OptionParser do
option_key = config.aliases[key]
if key && option_key do
# TODO: Remove this in Elixir v2.0
IO.warn("multi-letter aliases are deprecated, got: #{inspect(key)}")
next_tagged({:default, option_key}, value, original, rest, config)
else
@@ -580,7 +604,6 @@ defmodule OptionParser do
{strict, true}
true ->
# TODO: Remove this in Elixir v2.0
IO.warn("not passing the :switches or :strict option to OptionParser is deprecated")
{[], false}
end
+6 -8
View File
@@ -401,6 +401,9 @@ defmodule Path do
iex> Path.dirname("/foo/bar/")
"/foo/bar"
iex> Path.dirname("bar.ex")
"."
"""
@spec dirname(t) :: binary
def dirname(path) do
@@ -684,14 +687,9 @@ defmodule Path do
defp resolve_home(rest) do
case {rest, major_os_type()} do
{"\\" <> _, :win32} ->
System.user_home!() <> rest
{"/" <> _, _} ->
System.user_home!() <> rest
_ ->
rest
{"\\" <> _, :win32} -> System.user_home!() <> rest
{"/" <> _, _} -> System.user_home!() <> rest
_ -> "~" <> rest
end
end
+4 -3
View File
@@ -89,7 +89,7 @@ defmodule Port do
{#Port<0.1444>, {:data, "hello\n"}}
`:spawn` will retrieve the program name from the argument and traverse your
OS `$PATH` environment variable looking for a matching program.
operating system `$PATH` environment variable looking for a matching program.
Although the above is handy, it means it is impossible to invoke an executable
that has whitespaces on its name or in any of its arguments. For those reasons,
@@ -117,7 +117,7 @@ defmodule Port do
reimplementing core part of the Runtime System, such as the `:user` and
`:shell` processes.
## Zombie OS processes
## Zombie operating system processes
A port can be closed via the `close/1` function or by sending a `{pid, :close}`
message. However, if the VM crashes, a long-running program started by the port
@@ -226,6 +226,7 @@ defmodule Port do
For more information, see `:erlang.port_info/1`.
"""
@spec info(port) :: keyword | nil
def info(port) do
nillify(:erlang.port_info(port))
end
@@ -261,7 +262,7 @@ defmodule Port do
where:
* `ref` is a monitor reference returned by this function;
* `object` is either the `port` being monitored (when monitoring by port id)
* `object` is either the `port` being monitored (when monitoring by port ID)
or `{name, node}` (when monitoring by a port name);
* `reason` is the exit reason.
+251 -25
View File
@@ -1,15 +1,241 @@
defmodule Protocol do
@moduledoc """
Functions for working with protocols.
@moduledoc ~S"""
Reference and functions for working with protocols.
A protocol specifies an API that should be defined by its
implementations. A protocol is defined with `Kernel.defprotocol/2`
and its implementations with `Kernel.defimpl/2`.
## Examples
In Elixir, we have two verbs for checking how many items there
are in a data structure: `length` and `size`. `length` means the
information must be computed. For example, `length(list)` needs to
traverse the whole list to calculate its length. On the other hand,
`tuple_size(tuple)` and `byte_size(binary)` do not depend on the
tuple and binary size as the size information is precomputed in
the data structure.
Although Elixir includes specific functions such as `tuple_size`,
`binary_size` and `map_size`, sometimes we want to be able to
retrieve the size of a data structure regardless of its type.
In Elixir we can write polymorphic code, i.e. code that works
with different shapes/types, by using protocols. A size protocol
could be implemented as follows:
defprotocol Size do
@doc "Calculates the size (and not the length!) of a data structure"
def size(data)
end
Now that the protocol can be implemented for every data structure
the protocol may have a compliant implementation for:
defimpl Size, for: BitString do
def size(binary), do: byte_size(binary)
end
defimpl Size, for: Map do
def size(map), do: map_size(map)
end
defimpl Size, for: Tuple do
def size(tuple), do: tuple_size(tuple)
end
Notice we didn't implement it for lists as we don't have the
`size` information on lists, rather its value needs to be
computed with `length`.
It is possible to implement protocols for all Elixir types:
* Structs (see below)
* `Tuple`
* `Atom`
* `List`
* `BitString`
* `Integer`
* `Float`
* `Function`
* `PID`
* `Map`
* `Port`
* `Reference`
* `Any` (see below)
## Protocols and Structs
The real benefit of protocols comes when mixed with structs.
For instance, Elixir ships with many data types implemented as
structs, like `MapSet`. We can implement the `Size` protocol
for those types as well:
defimpl Size, for: MapSet do
def size(map_set), do: MapSet.size(map_set)
end
When implementing a protocol for a struct, the `:for` option can
be omitted if the `defimpl` call is inside the module that defines
the struct:
defmodule User do
defstruct [:email, :name]
defimpl Size do
# two fields
def size(%User{}), do: 2
end
end
If a protocol implementation is not found for a given type,
invoking the protocol will raise unless it is configured to
fall back to `Any`. Conveniences for building implementations
on top of existing ones are also available, look at `defstruct/1`
for more information about deriving
protocols.
## Fallback to `Any`
In some cases, it may be convenient to provide a default
implementation for all types. This can be achieved by setting
the `@fallback_to_any` attribute to `true` in the protocol
definition:
defprotocol Size do
@fallback_to_any true
def size(data)
end
The `Size` protocol can now be implemented for `Any`:
defimpl Size, for: Any do
def size(_), do: 0
end
Although the implementation above is arguably not a reasonable
one. For example, it makes no sense to say a PID or an integer
have a size of `0`. That's one of the reasons why `@fallback_to_any`
is an opt-in behaviour. For the majority of protocols, raising
an error when a protocol is not implemented is the proper behaviour.
## Multiple implementations
Protocols can also be implemented for multiple types at once:
defprotocol Reversible do
def reverse(term)
end
defimpl Reversible, for: [Map, List] do
def reverse(term), do: Enum.reverse(term)
end
Inside `defimpl/2`, you can use `@protocol` to access the protocol
being implemented and `@for` to access the module it is being
defined for.
## Types
Defining a protocol automatically defines a type named `t`, which
can be used as follows:
@spec print_size(Size.t()) :: :ok
def print_size(data) do
result =
case Size.size(data) do
0 -> "data has no items"
1 -> "data has one item"
n -> "data has #{n} items"
end
IO.puts(result)
end
The `@spec` above expresses that all types allowed to implement the
given protocol are valid argument types for the given function.
## Reflection
Any protocol module contains three extra functions:
* `__protocol__/1` - returns the protocol information. The function takes
one of the following atoms:
* `:consolidated?` - returns whether the protocol is consolidated
* `:functions` - returns keyword list of protocol functions and their arities
* `:impls` - if consolidated, returns `{:consolidated, modules}` with the list of modules
implementing the protocol, otherwise `:not_consolidated`
* `:module` - the protocol module atom name
* `impl_for/1` - receives a structure and returns the module that
implements the protocol for the structure, `nil` otherwise
* `impl_for!/1` - same as above but raises an error if an implementation is
not found
For example, for the `Enumerable` protocol we have:
iex> Enumerable.__protocol__(:functions)
[count: 1, member?: 2, reduce: 3, slice: 1]
iex> Enumerable.impl_for([])
Enumerable.List
iex> Enumerable.impl_for(42)
nil
## Consolidation
In order to cope with code loading in development, protocols in
Elixir provide a slow implementation of protocol dispatching specific
to development.
In order to speed up dispatching in production environments, where
all implementations are known up-front, Elixir provides a feature
called protocol consolidation. Consolidation directly links protocols
to their implementations in a way that invoking a function from a
consolidated protocol is equivalent to invoking two remote functions.
Protocol consolidation is applied by default to all Mix projects during
compilation. This may be an issue during test. For instance, if you want
to implement a protocol during test, the implementation will have no
effect, as the protocol has already been consolidated. One possible
solution is to include compilation directories that are specific to your
test environment in your mix.exs:
def project do
...
elixirc_paths: elixirc_paths(Mix.env())
...
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
And then you can define the implementations specific to the test environment
inside `test/support/some_file.ex`.
Another approach is to disable protocol consolidation during tests in your
mix.exs:
def project do
...
consolidate_protocols: Mix.env() != :test
...
end
Although doing so is not recommended as it may affect your test suite
performance.
Finally note all protocols are compiled with `debug_info` set to `true`,
regardless of the option set by `elixirc` compiler. The debug info is
used for consolidation and it may be removed after consolidation.
"""
@doc """
Defines a new protocol function.
Protocols do not allow functions to be defined directly, instead, the
regular `Kernel.def/*` macros are replaced by this macro which
defines the protocol functions with the appropriate callbacks.
"""
@doc false
defmacro def(signature)
defmacro def({_, _, args}) when args == [] or is_atom(args) do
@@ -42,7 +268,7 @@ defmodule Protocol do
impl_for!(term).unquote(name)(unquote_splicing(call_args))
end
# Convert the spec to callback if possible,
# Copy spec as callback if possible,
# otherwise generate a dummy callback
Module.spec_to_callback(__MODULE__, {name, arity}) ||
@callback unquote(name)(unquote_splicing(type_args)) :: term
@@ -125,7 +351,7 @@ defmodule Protocol do
## Examples
defprotocol Derivable do
def ok(a)
def ok(arg)
end
defimpl Derivable, for: Any do
@@ -147,14 +373,11 @@ defmodule Protocol do
defmodule ImplStruct do
@derive [Derivable]
defstruct a: 0, b: 0
defimpl Sample do
def ok(struct) do
Unknown.undefined(struct)
end
end
end
Derivable.ok(%ImplStruct{})
{:ok, %ImplStruct{a: 0, b: 0}, %ImplStruct{a: 0, b: 0}, []}
Explicit derivations can now be called via `__deriving__`:
# Explicitly derived via `__deriving__`
@@ -497,10 +720,11 @@ defmodule Protocol do
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
case Code.ensure_compiled?(unquote(target)) and
function_exported?(unquote(target), :__impl__, 1) do
true -> unquote(target).__impl__(:target)
false -> unquote(any_impl_for)
try do
unquote(target).__impl__(:target)
rescue
UndefinedFunctionError ->
unquote(any_impl_for)
end
end
end,
@@ -533,9 +757,11 @@ defmodule Protocol do
Kernel.defp struct_impl_for(struct) do
target = Module.concat(__MODULE__, struct)
case Code.ensure_compiled?(target) and function_exported?(target, :__impl__, 1) do
true -> target.__impl__(:target)
false -> unquote(any_impl_for)
try do
target.__impl__(:target)
rescue
UndefinedFunctionError ->
unquote(any_impl_for)
end
end
@@ -676,7 +902,7 @@ defmodule Protocol do
"implement protocols after compilation or during tests, check the " <>
"\"Consolidation\" section in the documentation for Kernel.defprotocol/2"
:elixir_errors.warn(env.line, env.file, message)
IO.warn(message, Macro.Env.stacktrace(env))
end
:ok
+1 -2
View File
@@ -78,7 +78,7 @@ defmodule Range do
"""
@doc since: "1.8.0"
@spec disjoint?(t, t) :: boolean
def disjoint?(first1..last1, first2..last2) do
def disjoint?(first1..last1 = _range1, first2..last2 = _range2) do
{first1, last1} = normalize(first1, last1)
{first2, last2} = normalize(first2, last2)
last2 < first1 or last1 < first2
@@ -88,7 +88,6 @@ defmodule Range do
defp normalize(first, last) when first > last, do: {last, first}
defp normalize(first, last), do: {first, last}
# TODO: Remove by 2.0
@doc false
@deprecated "Pattern match on first..last instead"
def range?(term)
+82 -25
View File
@@ -33,19 +33,6 @@ defmodule Regex do
~r/(?<foo>.)(?<bar>.)/.source == ~r/(?<foo>.)(?<bar>.)/.source
## Precompilation
Regular expressions built with sigil are precompiled and stored in `.beam`
files. This may be a problem if you are precompiling Elixir to run in
different OTP releases, as OTP releases may update the underlying regular
expression engine at any time.
For such reasons, we always recommend precompiling Elixir projects using
the Erlang/OTP version meant to run in production. In case cross-compilation is
really necessary, you can manually invoke `Regex.recompile/1` or
`Regex.recompile!/1` to perform a runtime version check and recompile the
regex if necessary.
## Modifiers
The modifiers available when creating a Regex are:
@@ -103,6 +90,56 @@ defmodule Regex do
* `list(binary)` - a list of named captures to capture
## Character classes
Regex supports several built in named character classes. These are used by
enclosing the class name in `[: :]` inside a group. For example:
iex> String.match?("123", ~r/^[[:alnum:]]+$/)
true
iex> String.match?("123 456", ~r/^[[:alnum:][:blank:]]+$/)
true
The supported class names are:
* alnum - Letters and digits
* alpha - Letters
* ascii - Character codes 0-127
* blank - Space or tab only
* cntrl - Control characters
* digit - Decimal digits (same as \\d)
* graph - Printing characters, excluding space
* lower - Lowercase letters
* print - Printing characters, including space
* punct - Printing characters, excluding letters, digits, and space
* space - Whitespace (the same as \s from PCRE 8.34)
* upper - Uppercase letters
* word - "Word" characters (same as \w)
* xdigit - Hexadecimal digits
Note the behaviour of those classes may change according to the Unicode
and other modifiers:
iex> String.match?("josé", ~r/^[[:lower:]]+$/)
false
iex> String.match?("josé", ~r/^[[:lower:]]+$/u)
true
## Precompilation
Regular expressions built with sigil are precompiled and stored in `.beam`
files. Precompiled regexes will be checked in runtime and may work slower
between operating systems and OTP releases. This is rarely a problem, as most Elixir code
shared during development is compiled on the target (such as dependencies,
archives, and escripts) and, when running in production, the code must either
be compiled on the target (via `mix compile` or similar) or released on the
host (via `mix releases` or similar) with a matching OTP, OS and architecture
as as the target.
If you know you are running on a different system that the current one and
you are doing multiple matches with the regex, you can manually invoke
`Regex.recompile/1` or `Regex.recompile!/1` to perform a runtime version
check and recompile the regex if necessary.
"""
defstruct re_pattern: nil, source: "", opts: "", re_version: ""
@@ -228,8 +265,8 @@ defmodule Regex do
"""
@spec match?(t, String.t()) :: boolean
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
:re.run(string, compiled, [{:capture, :none}]) == :match
def match?(%Regex{} = regex, string) when is_binary(string) do
safe_run(regex, string, [{:capture, :none}]) == :match
end
@doc """
@@ -276,11 +313,11 @@ defmodule Regex do
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
def run(regex, string, options \\ [])
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
def run(%Regex{} = regex, string, options) when is_binary(string) do
return = Keyword.get(options, :return, :binary)
captures = Keyword.get(options, :capture, :all)
case :re.run(string, compiled, [{:capture, captures, return}]) do
case safe_run(regex, string, [{:capture, captures, return}]) do
:nomatch -> nil
:match -> []
{:match, results} -> results
@@ -361,7 +398,17 @@ defmodule Regex do
"""
@spec names(t) :: [String.t()]
def names(%Regex{re_pattern: re_pattern}) do
def names(%Regex{re_pattern: compiled, re_version: version, source: source}) do
re_pattern =
case version() do
^version ->
compiled
_ ->
{:ok, recompiled} = :re.compile(source)
recompiled
end
{:namelist, names} = :re.inspect(re_pattern, :namelist)
names
end
@@ -401,18 +448,29 @@ defmodule Regex do
@spec scan(t, String.t(), [term]) :: [[String.t()]]
def scan(regex, string, options \\ [])
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
def scan(%Regex{} = regex, string, options) when is_binary(string) do
return = Keyword.get(options, :return, :binary)
captures = Keyword.get(options, :capture, :all)
options = [{:capture, captures, return}, :global]
case :re.run(string, compiled, options) do
case safe_run(regex, string, options) do
:match -> []
:nomatch -> []
{:match, results} -> results
end
end
defp safe_run(
%Regex{re_pattern: compiled, source: source, re_version: version},
string,
options
) do
case version() do
^version -> :re.run(string, compiled, options)
_ -> :re.run(string, source, options)
end
end
@doc """
Splits the given target based on the given pattern and in the given number of
parts.
@@ -472,11 +530,11 @@ defmodule Regex do
end
end
def split(%Regex{re_pattern: compiled}, string, opts)
def split(%Regex{} = regex, string, opts)
when is_binary(string) and is_list(opts) do
on = Keyword.get(opts, :on, :first)
case :re.run(string, compiled, [:global, capture: on]) do
case safe_run(regex, string, [:global, capture: on]) do
{:match, matches} ->
index = parts_to_index(Keyword.get(opts, :parts, :infinity))
trim = Keyword.get(opts, :trim, false)
@@ -598,11 +656,11 @@ defmodule Regex do
do_replace(regex, string, {replacement, arity}, options)
end
defp do_replace(%Regex{re_pattern: compiled}, string, replacement, options) do
defp do_replace(%Regex{} = regex, string, replacement, options) do
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
opts = [{:capture, :all, :index} | opts]
case :re.run(string, compiled, opts) do
case safe_run(regex, string, opts) do
:nomatch ->
string
@@ -786,7 +844,6 @@ defmodule Regex do
defp translate_options(<<?m, t::binary>>, acc), do: translate_options(t, [:multiline | acc])
# TODO: Remove on 2.0
defp translate_options(<<?r, t::binary>>, acc) do
IO.warn("the /r modifier in regular expressions is deprecated, please use /U instead")
translate_options(t, [:ungreedy | acc])
+136 -29
View File
@@ -78,14 +78,14 @@ defmodule Registry do
Now, an entity interested in dispatching events for a given key may call
`dispatch/3` passing in the key and a callback. This callback will be invoked
with a list of all the values registered under the requested key, alongside
the pid of the process that registered each value, in the form of `{pid,
the PID of the process that registered each value, in the form of `{pid,
value}` tuples. In our example, `value` will be the `{module, function}` tuple
in the code above:
Registry.dispatch(Registry.DispatcherTest, "hello", fn entries ->
for {pid, {module, function}} <- entries, do: apply(module, function, [pid])
end)
# Prints #PID<...> where the pid is for the process that called register/3 above
# Prints #PID<...> where the PID is for the process that called register/3 above
#=> :ok
Dispatching happens in the process that calls `dispatch/3` either serially or
@@ -161,7 +161,7 @@ defmodule Registry do
in the function documentation.
However, keep in mind those cases are typically not an issue. After all, a
process referenced by a pid may crash at any time, including between getting
process referenced by a PID may crash at any time, including between getting
the value from the registry and sending it a message. Many parts of the standard
library are designed to cope with that, such as `Process.monitor/1` which will
deliver the `:DOWN` message immediately if the monitored process is already dead
@@ -203,6 +203,12 @@ defmodule Registry do
@typedoc "A list of guards to be evaluated when matching on objects in a registry"
@type guards :: [guard] | []
@typedoc "A pattern used to representing the output format part of a match spec"
@type body :: [atom | tuple]
@typedoc "A full match spec used when selecting objects in the registry"
@type spec :: [{match_pattern, guards, body}]
## Via callbacks
@doc false
@@ -316,11 +322,17 @@ defmodule Registry do
"expected :keys to be given and be one of :unique or :duplicate, got: #{inspect(keys)}"
end
name = Keyword.get(options, :name)
name =
case Keyword.fetch(options, :name) do
{:ok, name} when is_atom(name) ->
name
unless is_atom(name) do
raise ArgumentError, "expected :name to be given and to be an atom, got: #{inspect(name)}"
end
{:ok, other} ->
raise ArgumentError, "expected :name to be an atom, got: #{inspect(other)}"
:error ->
raise ArgumentError, "expected :name option to be present"
end
meta = Keyword.get(options, :meta, [])
@@ -423,8 +435,8 @@ defmodule Registry do
for the given `registry`.
The list of `entries` is a non-empty list of two-element tuples where
the first element is the pid and the second element is the value
associated to the pid. If there are no entries for the given key,
the first element is the PID and the second element is the value
associated to the PID. If there are no entries for the given key,
the callback is never invoked.
If the registry is partitioned, the callback is invoked multiple times
@@ -679,7 +691,7 @@ defmodule Registry do
keys =
try do
spec = [{{pid, :"$1", :"$2"}, [], [{{:"$1", :"$2"}}]}]
spec = [{{pid, :"$1", :"$2", :_}, [], [{{:"$1", :"$2"}}]}]
:ets.select(pid_ets, spec)
catch
:error, :badarg -> []
@@ -756,8 +768,8 @@ defmodule Registry do
# Remove first from the key_ets because in case of crashes
# the pid_ets will still be able to clean up. The last step is
# to clean if we have no more entries.
true = :ets.match_delete(key_ets, {key, {self, :_}})
true = :ets.delete_object(pid_ets, {self, key, key_ets})
true = __unregister__(key_ets, {key, {self, :_}}, 1)
true = __unregister__(pid_ets, {self, key, key_ets, :_}, 2)
unlink_if_unregistered(pid_server, pid_ets, self)
@@ -806,6 +818,7 @@ defmodule Registry do
"""
@doc since: "1.5.0"
@spec unregister_match(registry, key, match_pattern, guards) :: :ok
def unregister_match(registry, key, pattern, guards \\ []) when is_list(guards) do
self = self()
@@ -818,8 +831,7 @@ defmodule Registry do
# the pid_ets will still be able to clean up. The last step is
# to clean if we have no more entries.
# Here we want to count all entries for this pid under this key, regardless
# of pattern.
# Here we want to count all entries for this pid under this key, regardless of pattern.
underscore_guard = {:"=:=", {:element, 1, :"$_"}, {:const, key}}
total_spec = [{{:_, {self, :_}}, [underscore_guard], [true]}]
total = :ets.select_count(key_ets, total_spec)
@@ -830,8 +842,7 @@ defmodule Registry do
case :ets.select_delete(key_ets, delete_spec) do
# We deleted everything, we can just delete the object
^total ->
true = :ets.delete_object(pid_ets, {self, key, key_ets})
true = __unregister__(pid_ets, {self, key, key_ets, :_}, 2)
unlink_if_unregistered(pid_server, pid_ets, self)
for listener <- listeners do
@@ -846,11 +857,12 @@ defmodule Registry do
# duplicate_bag tables will remove every entry, but we only want to
# remove those we have deleted. The solution is to introduce a temp_entry
# that indicates how many keys WILL be remaining after the delete operation.
counter = System.unique_integer()
remaining = total - deleted
temp_entry = {self, key, {key_ets, remaining}}
temp_entry = {self, key, {key_ets, remaining}, counter}
true = :ets.insert(pid_ets, temp_entry)
true = :ets.delete_object(pid_ets, {self, key, key_ets})
real_keys = List.duplicate({self, key, key_ets}, remaining)
true = __unregister__(pid_ets, {self, key, key_ets, :_}, 2)
real_keys = List.duplicate({self, key, key_ets, counter}, remaining)
true = :ets.insert(pid_ets, real_keys)
# We've recreated the real remaining key entries, so we can now delete
# our temporary entry.
@@ -868,11 +880,11 @@ defmodule Registry do
lookup.
This function returns `{:ok, owner}` or `{:error, reason}`.
The `owner` is the pid in the registry partition responsible for
the pid. The owner is automatically linked to the caller.
The `owner` is the PID in the registry partition responsible for
the PID. The owner is automatically linked to the caller.
If the registry has unique keys, it will return `{:ok, owner}` unless
the key is already associated to a pid, in which case it returns
the key is already associated to a PID, in which case it returns
`{:error, {:already_registered, pid}}`.
If the registry has duplicate keys, multiple registrations from the
@@ -911,7 +923,9 @@ defmodule Registry do
# always be able to do the cleanup. If we register first to the
# key one and the process crashes, the key will stay there forever.
Process.link(pid_server)
true = :ets.insert(pid_ets, {self, key, key_ets})
counter = System.unique_integer()
true = :ets.insert(pid_ets, {self, key, key_ets, counter})
case register_key(kind, pid_server, key_ets, key, {key, {self, value}}) do
{:ok, _} = ok ->
@@ -922,10 +936,11 @@ defmodule Registry do
ok
{:error, {:already_registered, ^self}} = error ->
true = :ets.delete_object(pid_ets, {self, key, key_ets, counter})
error
{:error, _} = error ->
true = :ets.delete_object(pid_ets, {self, key, key_ets})
true = :ets.delete_object(pid_ets, {self, key, key_ets, counter})
unlink_if_unregistered(pid_server, pid_ets, self)
error
end
@@ -958,7 +973,7 @@ defmodule Registry do
end
@doc """
Reads registry metadata given on `start_link/3`.
Reads registry metadata given on `start_link/1`.
Atoms and tuples are allowed as keys.
@@ -1129,6 +1144,80 @@ defmodule Registry do
end
end
@doc """
Select key, pid, and values registered using full match specs.
The `spec` consists of a list of three part tuples, in the shape of `[{match_pattern, guards, body}]`.
The first part, the match pattern, must be a tuple that will match the structure of the
the data stored in the registry, which is `{key, pid, value}`. The atom `:_` can be used to
ignore a given value or tuple element, while the atom `:"$1"` can be used to temporarily
assign part of pattern to a variable for a subsequent comparison. This can be combined
like `{:"$1", :_, :_}`.
The second part, the guards, is a list of conditions that allow filtering the results.
Each guard is a tuple, which describes checks that should be passed by assigned part of pattern.
For example the `$1 > 1` guard condition would be expressed as the `{:>, :"$1", 1}` tuple.
Please note that guard conditions will work only for assigned variables like `:"$1"`, `:"$2"`, etc.
The third part, the body, is a list of shapes of the returned entries. Like guards, you have access to
assigned variables like `:"$1"`, which you can combine with hardcoded values to freely shape entries
Note that tuples have to be wrapped in an additional tuple. To get a result format like
`%{key: key, pid: pid, value: value}`, assuming you bound those variables in order in the match part,
you would provide a body like `[%{key: :"$1", pid: :"$2", value: :"$3"}]`. Like guards, you can use
some operations like `:element` to modify the output format.
Do not use special match variables `:"$_"` and `:"$$"`, because they might not work as expected.
Note that for large registries with many partitions this will be costly as it builds the result by
concatenating all the partitions.
## Examples
This example shows how to get everything from the registry.
iex> Registry.start_link(keys: :unique, name: Registry.SelectAllTest)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "hello", :value)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "world", :value)
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :"$2", :"$3"}, [], [{{:"$1", :"$2", :"$3"}}]}])
[{"world", self(), :value}, {"hello", self(), :value}]
Get all keys in the registry.
iex> Registry.start_link(keys: :unique, name: Registry.SelectAllTest)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "hello", :value)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "world", :value)
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :_, :_}, [], [:"$1"]}])
["world", "hello"]
"""
@doc since: "1.9.0"
@spec select(registry, spec) :: [term]
def select(registry, spec)
when is_atom(registry) and is_list(spec) do
spec =
for part <- spec do
case part do
{{key, pid, value}, guards, select} ->
{{key, {pid, value}}, guards, select}
_ ->
raise ArgumentError,
"invalid match specification in Registry.select/2: #{inspect(spec)}"
end
end
case key_info!(registry) do
{_kind, partitions, nil} ->
Enum.flat_map(0..(partitions - 1), fn partition_index ->
:ets.select(key_ets!(registry, partition_index), spec)
end)
{_kind, 1, key_ets} ->
:ets.select(key_ets, spec)
end
end
## Helpers
@compile {:inline, hash: 2}
@@ -1193,6 +1282,24 @@ defmodule Registry do
Process.unlink(pid_server)
end
end
@doc false
def __unregister__(table, match, pos) do
key = :erlang.element(pos, match)
# We need to perform an element comparison if we have an special atom key.
if is_atom(key) and reserved_atom?(Atom.to_string(key)) do
match = :erlang.setelement(pos, match, :_)
guard = {:"=:=", {:element, pos, :"$_"}, {:const, key}}
:ets.select_delete(table, [{match, [guard], [true]}]) >= 0
else
:ets.match_delete(table, match)
end
end
defp reserved_atom?("_"), do: true
defp reserved_atom?("$" <> _), do: true
defp reserved_atom?(_), do: false
end
defmodule Registry.Supervisor do
@@ -1220,12 +1327,12 @@ defmodule Registry.Supervisor do
end
# Unique registries have their key partition hashed by key.
# This means that, if a pid partition crashes, it may have
# This means that, if a PID partition crashes, it may have
# entries from all key partitions, so we need to crash all.
defp strategy_for_kind(:unique), do: :one_for_all
# Duplicate registries have both key and pid partitions hashed
# by pid. This means that, if a pid partition crashes, all of
# by pid. This means that, if a PID partition crashes, all of
# its associated entries are in its sibling table, so we crash one.
defp strategy_for_kind(:duplicate), do: :one_for_one
end
@@ -1322,7 +1429,7 @@ defmodule Registry.Partition do
def handle_info({:EXIT, pid, _reason}, ets) do
entries = :ets.take(ets, pid)
for {_pid, key, key_ets} <- entries do
for {_pid, key, key_ets, _counter} <- entries do
key_ets =
case key_ets do
# In case the fake key_ets is being used. See unregister_match/2.
@@ -1334,7 +1441,7 @@ defmodule Registry.Partition do
end
try do
:ets.match_delete(key_ets, {key, {pid, :_}})
Registry.__unregister__(key_ets, {key, {pid, :_}}, 1)
catch
:error, :badarg -> :badarg
end
-1
View File
@@ -11,7 +11,6 @@ defmodule Set do
@type values :: [value]
@type t :: map
# TODO: Remove by 2.0
message = "Use the MapSet module for working with sets"
defmacrop target(set) do
+91 -76
View File
@@ -4,7 +4,7 @@ defmodule Stream do
Streams are composable, lazy enumerables (for an introduction on
enumerables, see the `Enum` module). Any enumerable that generates
items one by one during enumeration is called a stream. For example,
elements one by one during enumeration is called a stream. For example,
Elixir's `Range` is a stream:
iex> range = 1..5
@@ -22,9 +22,9 @@ defmodule Stream do
[3, 5, 7]
Notice we started with a range and then we created a stream that is
meant to multiply each item in the range by 2. At this point, no
meant to multiply each element in the range by 2. At this point, no
computation was done. Only when `Enum.map/2` is called we actually
enumerate over each item in the range, multiplying it by 2 and adding 1.
enumerate over each element in the range, multiplying it by 2 and adding 1.
We say the functions in `Stream` are *lazy* and the functions in `Enum`
are *eager*.
@@ -46,7 +46,7 @@ defmodule Stream do
6
#=> [2, 4, 6]
Notice that we first printed each item in the list, then multiplied each
Notice that we first printed each element in the list, then multiplied each
element by 2 and finally printed each new value. In this example, the list
was enumerated three times. Let's see an example with streams:
@@ -63,8 +63,8 @@ defmodule Stream do
6
#=> [2, 4, 6]
Although the end result is the same, the order in which the items were
printed changed! With streams, we print the first item and then print
Although the end result is the same, the order in which the elements were
printed changed! With streams, we print the first element and then print
its double. In this example, the list was enumerated just once!
That's what we meant when we said earlier that streams are composable,
@@ -72,6 +72,13 @@ defmodule Stream do
effectively composing the streams and keeping them lazy. The computations
are only performed when you call a function from the `Enum` module.
Like with `Enum`, the functions in this module work in linear time. This
means that, the time it takes to perform an operation grows at the same
rate as the length of the list. This is expected on operations such as
`Stream.map/2`. After all, if we want to traverse every element on a
stream, the longer the stream, the more elements we need to traverse,
and the longer it will take.
## Creating Streams
There are many functions in Elixir's standard library that return
@@ -125,19 +132,16 @@ defmodule Stream do
## Transformers
# TODO: Remove by 2.0
@doc false
@deprecated "Use Stream.chunk_every/2 instead"
def chunk(enum, n), do: chunk(enum, n, n, nil)
# TODO: Remove by 2.0
@doc false
@deprecated "Use Stream.chunk_every/3 instead"
def chunk(enum, n, step) do
chunk_every(enum, n, step, nil)
end
# TODO: Remove by 2.0
@doc false
@deprecated "Use Stream.chunk_every/4 instead"
def chunk(enum, n, step, leftover)
@@ -153,7 +157,7 @@ defmodule Stream do
def chunk_every(enum, count), do: chunk_every(enum, count, count, [])
@doc """
Streams the enumerable in chunks, containing `count` items each,
Streams the enumerable in chunks, containing `count` elements each,
where each new chunk starts `step` elements into the enumerable.
`step` is optional and, if not passed, defaults to `count`, i.e.
@@ -203,7 +207,7 @@ defmodule Stream do
"""
@spec chunk_by(Enumerable.t(), (element -> any)) :: Enumerable.t()
def chunk_by(enum, fun) do
def chunk_by(enum, fun) when is_function(fun, 1) do
R.chunk_by(&chunk_while/4, enum, fun)
end
@@ -220,11 +224,11 @@ defmodule Stream do
## Examples
iex> chunk_fun = fn item, acc ->
...> if rem(item, 2) == 0 do
...> {:cont, Enum.reverse([item | acc]), []}
iex> chunk_fun = fn element, acc ->
...> if rem(element, 2) == 0 do
...> {:cont, Enum.reverse([element | acc]), []}
...> else
...> {:cont, [item | acc]}
...> {:cont, [element | acc]}
...> end
...> end
iex> after_fun = fn
@@ -244,7 +248,8 @@ defmodule Stream do
(acc -> {:cont, chunk, acc} | {:cont, acc})
) :: Enumerable.t()
when chunk: any
def chunk_while(enum, acc, chunk_fun, after_fun) do
def chunk_while(enum, acc, chunk_fun, after_fun)
when is_function(chunk_fun, 2) and is_function(after_fun, 1) do
lazy(
enum,
[acc | after_fun],
@@ -257,9 +262,9 @@ defmodule Stream do
fn entry, acc(head, [acc | after_fun], tail) ->
case callback.(entry, acc) do
{:cont, emit, acc} ->
# If we emit an item and then we have to halt,
# If we emit an element and then we have to halt,
# we need to disable the after_fun callback to
# avoid emitting even more items.
# avoid emitting even more elements.
case next(fun, emit, [head | tail]) do
{:halt, [head | tail]} -> {:halt, acc(head, [acc | &{:cont, &1}], tail)}
{command, [head | tail]} -> {command, acc(head, [acc | after_fun], tail)}
@@ -310,16 +315,16 @@ defmodule Stream do
"""
@spec dedup_by(Enumerable.t(), (element -> term)) :: Enumerable.t()
def dedup_by(enum, fun) do
def dedup_by(enum, fun) when is_function(fun, 1) do
lazy(enum, nil, fn f1 -> R.dedup(fun, f1) end)
end
@doc """
Lazily drops the next `n` items from the enumerable.
Lazily drops the next `n` elements from the enumerable.
If a negative `n` is given, it will drop the last `n` items from
If a negative `n` is given, it will drop the last `n` elements from
the collection. Note that the mechanism by which this is implemented
will delay the emission of any item until `n` additional items have
will delay the emission of any element until `n` additional elements have
been emitted by the enum.
## Examples
@@ -333,7 +338,7 @@ defmodule Stream do
[1, 2, 3, 4, 5]
"""
@spec drop(Enumerable.t(), non_neg_integer) :: Enumerable.t()
@spec drop(Enumerable.t(), integer) :: Enumerable.t()
def drop(enum, n) when is_integer(n) and n >= 0 do
lazy(enum, n, fn f1 -> R.drop(f1) end)
end
@@ -365,9 +370,9 @@ defmodule Stream do
end
@doc """
Creates a stream that drops every `nth` item from the enumerable.
Creates a stream that drops every `nth` element from the enumerable.
The first item is always dropped, unless `nth` is 0.
The first element is always dropped, unless `nth` is 0.
`nth` must be a non-negative integer.
@@ -407,12 +412,12 @@ defmodule Stream do
"""
@spec drop_while(Enumerable.t(), (element -> as_boolean(term))) :: Enumerable.t()
def drop_while(enum, fun) do
def drop_while(enum, fun) when is_function(fun, 1) do
lazy(enum, true, fn f1 -> R.drop_while(fun, f1) end)
end
@doc """
Executes the given function for each item.
Executes the given function for each element.
Useful for adding side effects (like printing) to a stream.
@@ -429,7 +434,7 @@ defmodule Stream do
"""
@spec each(Enumerable.t(), (element -> term)) :: Enumerable.t()
def each(enum, fun) do
def each(enum, fun) when is_function(fun, 1) do
lazy(enum, fn f1 ->
fn x, acc ->
fun.(x)
@@ -456,7 +461,7 @@ defmodule Stream do
"""
@spec flat_map(Enumerable.t(), (element -> Enumerable.t())) :: Enumerable.t()
def flat_map(enum, mapper) do
def flat_map(enum, mapper) when is_function(mapper, 1) do
transform(enum, nil, fn val, nil -> {mapper.(val), nil} end)
end
@@ -472,12 +477,11 @@ defmodule Stream do
"""
@spec filter(Enumerable.t(), (element -> as_boolean(term))) :: Enumerable.t()
def filter(enum, fun) do
def filter(enum, fun) when is_function(fun, 1) do
lazy(enum, fn f1 -> R.filter(fun, f1) end)
end
@doc false
# TODO: Remove on 2.0
@deprecated "Use Stream.filter/2 + Stream.map/2 instead"
def filter_map(enum, filter, mapper) do
lazy(enum, fn f1 -> R.filter_map(filter, mapper, f1) end)
@@ -489,7 +493,7 @@ defmodule Stream do
The values emitted are an increasing counter starting at `0`.
This operation will block the caller by the given interval
every time a new item is streamed.
every time a new element is streamed.
Do not use this function to generate a sequence of numbers.
If blocking the caller process is not necessary, use
@@ -502,7 +506,7 @@ defmodule Stream do
"""
@spec interval(non_neg_integer) :: Enumerable.t()
def interval(n) do
def interval(n) when is_integer(n) and n >= 0 do
unfold(0, fn count ->
Process.sleep(n)
{count, count + 1}
@@ -516,7 +520,7 @@ defmodule Stream do
is delayed until the stream is executed. See `run/1` for an example.
"""
@spec into(Enumerable.t(), Collectable.t(), (term -> term)) :: Enumerable.t()
def into(enum, collectable, transform \\ fn x -> x end) do
def into(enum, collectable, transform \\ fn x -> x end) when is_function(transform, 1) do
&do_into(enum, collectable, transform, &1, &2)
end
@@ -561,15 +565,15 @@ defmodule Stream do
"""
@spec map(Enumerable.t(), (element -> any)) :: Enumerable.t()
def map(enum, fun) do
def map(enum, fun) when is_function(fun, 1) do
lazy(enum, fn f1 -> R.map(fun, f1) end)
end
@doc """
Creates a stream that will apply the given function on
every `nth` item from the enumerable.
every `nth` element from the enumerable.
The first item is always passed to the given function.
The first element is always passed to the given function.
`nth` must be a non-negative integer.
@@ -590,13 +594,15 @@ defmodule Stream do
"""
@doc since: "1.4.0"
@spec map_every(Enumerable.t(), non_neg_integer, (element -> any)) :: Enumerable.t()
def map_every(enum, nth, fun)
def map_every(enum, nth, fun) when is_integer(nth) and nth >= 0 and is_function(fun, 1) do
map_every_after_guards(enum, nth, fun)
end
def map_every(enum, 1, fun), do: map(enum, fun)
def map_every(enum, 0, _fun), do: %Stream{enum: enum}
def map_every([], _nth, _fun), do: %Stream{enum: []}
defp map_every_after_guards(enum, 1, fun), do: map(enum, fun)
defp map_every_after_guards(enum, 0, _fun), do: %Stream{enum: enum}
defp map_every_after_guards([], _nth, _fun), do: %Stream{enum: []}
def map_every(enum, nth, fun) when is_integer(nth) and nth > 0 do
defp map_every_after_guards(enum, nth, fun) do
lazy(enum, nth, fn f1 -> R.map_every(nth, fun, f1) end)
end
@@ -612,7 +618,7 @@ defmodule Stream do
"""
@spec reject(Enumerable.t(), (element -> as_boolean(term))) :: Enumerable.t()
def reject(enum, fun) do
def reject(enum, fun) when is_function(fun, 1) do
lazy(enum, fn f1 -> R.reject(fun, f1) end)
end
@@ -655,7 +661,7 @@ defmodule Stream do
"""
@spec scan(Enumerable.t(), (element, acc -> any)) :: Enumerable.t()
def scan(enum, fun) do
def scan(enum, fun) when is_function(fun, 2) do
lazy(enum, :first, fn f1 -> R.scan2(fun, f1) end)
end
@@ -672,12 +678,12 @@ defmodule Stream do
"""
@spec scan(Enumerable.t(), acc, (element, acc -> any)) :: Enumerable.t()
def scan(enum, acc, fun) do
def scan(enum, acc, fun) when is_function(fun, 2) do
lazy(enum, acc, fn f1 -> R.scan3(fun, f1) end)
end
@doc """
Lazily takes the next `count` items from the enumerable and stops
Lazily takes the next `count` elements from the enumerable and stops
enumeration.
If a negative `count` is given, the last `count` values will be taken.
@@ -702,21 +708,26 @@ defmodule Stream do
"""
@spec take(Enumerable.t(), integer) :: Enumerable.t()
def take(_enum, 0), do: %Stream{enum: []}
def take([], _count), do: %Stream{enum: []}
def take(enum, count) when is_integer(count) do
take_after_guards(enum, count)
end
def take(enum, count) when is_integer(count) and count > 0 do
defp take_after_guards(_enum, 0), do: %Stream{enum: []}
defp take_after_guards([], _count), do: %Stream{enum: []}
defp take_after_guards(enum, count) when count > 0 do
lazy(enum, count, fn f1 -> R.take(f1) end)
end
def take(enum, count) when is_integer(count) and count < 0 do
defp take_after_guards(enum, count) when count < 0 do
&Enumerable.reduce(Enum.take(enum, count), &1, &2)
end
@doc """
Creates a stream that takes every `nth` item from the enumerable.
Creates a stream that takes every `nth` element from the enumerable.
The first item is always included, unless `nth` is 0.
The first element is always included, unless `nth` is 0.
`nth` must be a non-negative integer.
@@ -736,11 +747,15 @@ defmodule Stream do
"""
@spec take_every(Enumerable.t(), non_neg_integer) :: Enumerable.t()
def take_every(enum, nth)
def take_every(_enum, 0), do: %Stream{enum: []}
def take_every([], _nth), do: %Stream{enum: []}
def take_every(enum, nth) when is_integer(nth) and nth >= 0 do
take_every_after_guards(enum, nth)
end
def take_every(enum, nth) when is_integer(nth) and nth > 0 do
defp take_every_after_guards(_enum, 0), do: %Stream{enum: []}
defp take_every_after_guards([], _nth), do: %Stream{enum: []}
defp take_every_after_guards(enum, nth) do
lazy(enum, nth, fn f1 -> R.take_every(nth, f1) end)
end
@@ -756,7 +771,7 @@ defmodule Stream do
"""
@spec take_while(Enumerable.t(), (element -> as_boolean(term))) :: Enumerable.t()
def take_while(enum, fun) do
def take_while(enum, fun) when is_function(fun, 1) do
lazy(enum, fn f1 -> R.take_while(fun, f1) end)
end
@@ -764,7 +779,7 @@ defmodule Stream do
Creates a stream that emits a single value after `n` milliseconds.
The value emitted is `0`. This operation will block the caller by
the given time until the item is streamed.
the given time until the element is streamed.
## Examples
@@ -773,14 +788,14 @@ defmodule Stream do
"""
@spec timer(non_neg_integer) :: Enumerable.t()
def timer(n) do
def timer(n) when is_integer(n) and n >= 0 do
take(interval(n), 1)
end
@doc """
Transforms an existing stream.
It expects an accumulator and a function that receives each stream item
It expects an accumulator and a function that receives each stream element
and an accumulator, and must return a tuple containing a new stream
(often a list) with the new accumulator or a tuple with `:halt` as first
element and the accumulator as second.
@@ -807,7 +822,7 @@ defmodule Stream do
@spec transform(Enumerable.t(), acc, fun) :: Enumerable.t()
when fun: (element, acc -> {Enumerable.t(), acc} | {:halt, acc}),
acc: any
def transform(enum, acc, reducer) do
def transform(enum, acc, reducer) when is_function(reducer, 2) do
&do_transform(enum, fn -> acc end, reducer, &1, &2, nil)
end
@@ -824,7 +839,8 @@ defmodule Stream do
@spec transform(Enumerable.t(), (() -> acc), fun, (acc -> term)) :: Enumerable.t()
when fun: (element, acc -> {Enumerable.t(), acc} | {:halt, acc}),
acc: any
def transform(enum, start_fun, reducer, after_fun) do
def transform(enum, start_fun, reducer, after_fun)
when is_function(start_fun, 0) and is_function(reducer, 2) and is_function(after_fun, 1) do
&do_transform(enum, start_fun, reducer, &1, &2, after_fun)
end
@@ -979,7 +995,7 @@ defmodule Stream do
Keep in mind that, in order to know if an element is unique
or not, this function needs to store all unique values emitted
by the stream. Therefore, if the stream is infinite, the number
of items stored will grow infinitely, never being garbage-collected.
of elements stored will grow infinitely, never being garbage-collected.
## Examples
@@ -993,7 +1009,6 @@ defmodule Stream do
end
@doc false
# TODO: Remove on 2.0
@deprecated "Use Stream.uniq_by/2 instead"
def uniq(enum, fun) do
uniq_by(enum, fun)
@@ -1001,7 +1016,7 @@ defmodule Stream do
@doc """
Creates a stream that only emits elements if they are unique, by removing the
elements for which function `fun` returned duplicate items.
elements for which function `fun` returned duplicate elements.
The function `fun` maps every element to a term which is used to
determine if two elements are duplicates.
@@ -1009,7 +1024,7 @@ defmodule Stream do
Keep in mind that, in order to know if an element is unique
or not, this function needs to store all unique values emitted
by the stream. Therefore, if the stream is infinite, the number
of items stored will grow infinitely, never being garbage-collected.
of elements stored will grow infinitely, never being garbage-collected.
## Example
@@ -1021,12 +1036,12 @@ defmodule Stream do
"""
@spec uniq_by(Enumerable.t(), (element -> term)) :: Enumerable.t()
def uniq_by(enum, fun) do
def uniq_by(enum, fun) when is_function(fun, 1) do
lazy(enum, %{}, fn f1 -> R.uniq_by(fun, f1) end)
end
@doc """
Creates a stream where each item in the enumerable will
Creates a stream where each element in the enumerable will
be wrapped in a tuple alongside its index.
If an `offset` is given, we will index from the given offset instead of from zero.
@@ -1043,7 +1058,7 @@ defmodule Stream do
"""
@spec with_index(Enumerable.t(), integer) :: Enumerable.t()
def with_index(enum, offset \\ 0) do
def with_index(enum, offset \\ 0) when is_integer(offset) do
lazy(enum, offset, fn f1 -> R.with_index(f1) end)
end
@@ -1116,8 +1131,7 @@ defmodule Stream do
"""
@doc since: "1.4.0"
@spec zip([Enumerable.t()]) :: Enumerable.t()
@spec zip(Enumerable.t()) :: Enumerable.t()
@spec zip(enumerables) :: Enumerable.t() when enumerables: [Enumerable.t()] | Enumerable.t()
def zip(enumerables) do
&prepare_zip(enumerables, &1, &2)
end
@@ -1302,7 +1316,7 @@ defmodule Stream do
"""
@spec iterate(element, (element -> element)) :: Enumerable.t()
def iterate(start_value, next_fun) do
def iterate(start_value, next_fun) when is_function(next_fun, 1) do
unfold({:ok, start_value}, fn
{:ok, value} ->
{value, {:next, value}}
@@ -1325,7 +1339,7 @@ defmodule Stream do
"""
@spec repeatedly((() -> element)) :: Enumerable.t()
def repeatedly(generator_fun) do
def repeatedly(generator_fun) when is_function(generator_fun, 0) do
&do_repeatedly(generator_fun, &1, &2)
end
@@ -1351,7 +1365,7 @@ defmodule Stream do
Successive values are generated by calling `next_fun` with the
previous accumulator (the initial value being the result returned
by `start_fun`) and it must return a tuple containing a list
of items to be emitted and the next accumulator. The enumeration
of elements to be emitted and the next accumulator. The enumeration
finishes if it returns `{:halt, acc}`.
As the name says, this function is useful to stream values from
@@ -1373,7 +1387,8 @@ defmodule Stream do
"""
@spec resource((() -> acc), (acc -> {[element], acc} | {:halt, acc}), (acc -> term)) ::
Enumerable.t()
def resource(start_fun, next_fun, after_fun) do
def resource(start_fun, next_fun, after_fun)
when is_function(start_fun, 0) and is_function(next_fun, 1) and is_function(after_fun, 1) do
&do_resource(start_fun.(), next_fun, &1, &2, after_fun)
end
@@ -1481,7 +1496,7 @@ defmodule Stream do
"""
@spec unfold(acc, (acc -> {element, acc} | nil)) :: Enumerable.t()
def unfold(next_acc, next_fun) do
def unfold(next_acc, next_fun) when is_function(next_fun, 1) do
&do_unfold(next_acc, next_fun, &1, &2)
end
+109 -84
View File
@@ -4,15 +4,15 @@ defmodule String do
@moduledoc ~S"""
A String in Elixir is a UTF-8 encoded binary.
## Codepoints and grapheme cluster
## Code points and grapheme cluster
The functions in this module act according to the Unicode
Standard, version 11.0.0.
As per the standard, a codepoint is a single Unicode Character,
As per the standard, a code point is a single Unicode Character,
which may be represented by one or more bytes.
For example, the codepoint "é" is two bytes:
For example, the code point "é" is two bytes:
iex> byte_size("é")
2
@@ -24,9 +24,9 @@ defmodule String do
Furthermore, this module also presents the concept of grapheme cluster
(from now on referenced as graphemes). Graphemes can consist of multiple
codepoints that may be perceived as a single character by readers. For
example, "é" can be represented either as a single "e with acute" codepoint
or as the letter "e" followed by a "combining acute accent" (two codepoints):
code points that may be perceived as a single character by readers. For
example, "é" can be represented either as a single "e with acute" code point
or as the letter "e" followed by a "combining acute accent" (two code points):
iex> string = "\u0065\u0301"
iex> byte_size(string)
@@ -51,7 +51,7 @@ defmodule String do
Standard, but do not contain any of the locale specific behaviour.
More information about graphemes can be found in the [Unicode
Standard Annex #29](http://www.unicode.org/reports/tr29/).
Standard Annex #29](https://www.unicode.org/reports/tr29/).
The current Elixir version implements Extended Grapheme Cluster
algorithm.
@@ -62,7 +62,7 @@ defmodule String do
To act according to the Unicode Standard, many functions
in this module run in linear time, as they need to traverse
the whole string considering the proper Unicode codepoints.
the whole string considering the proper Unicode code points.
For example, `String.length/1` will take longer as
the input grows. On the other hand, `Kernel.byte_size/1` always runs
@@ -110,7 +110,7 @@ defmodule String do
it could still be improved. In this case, since we want to
extract a substring from a string, we can use `Kernel.byte_size/1`
and `Kernel.binary_part/3` as there is no chance we will slice in
the middle of a codepoint made of more than one byte:
the middle of a code point made of more than one byte:
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
@@ -132,18 +132,18 @@ defmodule String do
On the other hand, if you want to dynamically slice a string
based on an integer value, then using `String.slice/3` is the
best option as it guarantees we won't incorrectly split a valid
codepoint into multiple bytes.
code point into multiple bytes.
## Integer codepoints
## Integer code points
Although codepoints could be represented as integers, this
module represents all codepoints as strings. For example:
Although code points could be represented as integers, this
module represents all code points as strings. For example:
iex> String.codepoints("olá")
["o", "l", "á"]
There are a couple of ways to retrieve a character integer
codepoint. One may use the `?` construct:
code point. One may use the `?` construct:
iex> ?o
111
@@ -157,7 +157,7 @@ defmodule String do
iex> aacute
225
As we have seen above, codepoints can be inserted into
As we have seen above, code points can be inserted into
a string by their hexadecimal code:
"ol\u0061\u0301" #=>
@@ -168,11 +168,11 @@ defmodule String do
The UTF-8 encoding is self-synchronizing. This means that
if malformed data (i.e., data that is not possible according
to the definition of the encoding) is encountered, only one
codepoint needs to be rejected.
code point needs to be rejected.
This module relies on this behaviour to ignore such invalid
characters. For example, `length/1` will return
a correct result even if an invalid codepoint is fed into it.
a correct result even if an invalid code point is fed into it.
In other words, this module expects invalid data to be detected
elsewhere, usually when retrieving data from the external source.
@@ -212,10 +212,10 @@ defmodule String do
"""
@type t :: binary
@typedoc "A UTF-8 codepoint. It may be one or more bytes."
@typedoc "A UTF-8 code point. It may be one or more bytes."
@type codepoint :: t
@typedoc "Multiple codepoints that may be perceived as a single character by readers"
@typedoc "Multiple code points that may be perceived as a single character by readers"
@type grapheme :: t
@typedoc "Pattern used in functions like `replace/3` and `split/2`"
@@ -798,12 +798,10 @@ defmodule String do
end
@doc false
# TODO: Remove by 2.0
@deprecated "Use String.trim_trailing/1 instead"
defdelegate rstrip(binary), to: String.Break, as: :trim_trailing
@doc false
# TODO: Remove by 2.0
@deprecated "Use String.trim_trailing/2 with a binary as second argument instead"
def rstrip(string, char) when is_integer(char) do
replace_trailing(string, <<char::utf8>>, "")
@@ -1013,26 +1011,22 @@ defmodule String do
defp append_unless_empty(prefix, suffix), do: prefix <> suffix
@doc false
# TODO: Remove by 2.0
@deprecated "Use String.trim_leading/1 instead"
defdelegate lstrip(binary), to: String.Break, as: :trim_leading
@doc false
# TODO: Remove by 2.0
@deprecated "Use String.trim_leading/2 with a binary as second argument instead"
def lstrip(string, char) when is_integer(char) do
replace_leading(string, <<char::utf8>>, "")
end
@doc false
# TODO: Remove by 2.0
@deprecated "Use String.trim/1 instead"
def strip(string) do
trim(string)
end
@doc false
# TODO: Remove by 2.0
@deprecated "Use String.trim/2 with a binary second argument instead"
def strip(string, char) do
trim(string, <<char::utf8>>)
@@ -1052,7 +1046,7 @@ defmodule String do
defdelegate trim_leading(string), to: String.Break
@doc """
Returns a string where all leading `to_trim`s have been removed.
Returns a string where all leading `to_trim` characters have been removed.
## Examples
@@ -1082,7 +1076,7 @@ defmodule String do
defdelegate trim_trailing(string), to: String.Break
@doc """
Returns a string where all trailing `to_trim`s have been removed.
Returns a string where all trailing `to_trim` characters have been removed.
## Examples
@@ -1116,7 +1110,7 @@ defmodule String do
end
@doc """
Returns a string where all leading and trailing `to_trim`s have been
Returns a string where all leading and trailing `to_trim` characters have been
removed.
## Examples
@@ -1257,28 +1251,24 @@ defmodule String do
end
@doc false
# TODO: Remove by 2.0
@deprecated "Use String.pad_leading/2 instead"
def rjust(subject, len) do
rjust(subject, len, ?\s)
end
@doc false
# TODO: Remove by 2.0
@deprecated "Use String.pad_leading/3 with a binary padding instead"
def rjust(subject, len, pad) when is_integer(pad) and is_integer(len) and len >= 0 do
pad(:leading, subject, len, [<<pad::utf8>>])
end
@doc false
# TODO: Remove by 2.0
@deprecated "Use String.pad_trailing/2 instead"
def ljust(subject, len) do
ljust(subject, len, ?\s)
end
@doc false
# TODO: Remove by 2.0
@deprecated "Use String.pad_trailing/3 with a binary padding instead"
def ljust(subject, len, pad) when is_integer(pad) and is_integer(len) and len >= 0 do
pad(:trailing, subject, len, [<<pad::utf8>>])
@@ -1288,8 +1278,13 @@ defmodule String do
Returns a new string created by replacing occurrences of `pattern` in
`subject` with `replacement`.
The `subject` is always a string.
The `pattern` may be a string, a regular expression, or a compiled pattern.
The `replacement` may be a string or a function that receives the matched
pattern and must return the replacement as a string or iodata.
By default it replaces all occurrences but this behaviour can be controlled
through the `:global` option; see the "Options" section below.
@@ -1299,12 +1294,6 @@ defmodule String do
with `replacement`, otherwise only the first occurrence is
replaced. Defaults to `true`
* `:insert_replaced` - (integer or list of integers) specifies the position
where to insert the replaced part inside the `replacement`. If any
position given in the `:insert_replaced` option is larger than the
replacement string, or is negative, an `ArgumentError` is raised. See the
examples below
## Examples
iex> String.replace("a,b,c", ",", "-")
@@ -1313,6 +1302,12 @@ defmodule String do
iex> String.replace("a,b,c", ",", "-", global: false)
"a-b,c"
The pattern may also be a list of strings and the replacement may also
be a function that receives the matched patterns:
iex> String.replace("a,b,c", ["a", "c"], fn <<char>> -> <<char + 1>> end)
"b,b,d"
When the pattern is a regular expression, one can give `\N` or
`\g{N}` in the `replacement` string to access a specific capture in the
regular expression:
@@ -1325,25 +1320,11 @@ defmodule String do
giving `\0`, one can inject the whole matched pattern in the replacement
string.
When the pattern is a string, a developer can use the replaced part inside
the `replacement` by using the `:insert_replaced` option and specifying the
position(s) inside the `replacement` where the string pattern will be
inserted:
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
"a,[b],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
"a[],b[],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
"a[,,]b[,,]c"
A compiled pattern can also be given:
iex> pattern = :binary.compile_pattern(",")
iex> String.replace("a,b,c", pattern, "[]", insert_replaced: 2)
"a[],b[],c"
iex> String.replace("a,b,c", pattern, "[]")
"a[]b[]c"
When an empty string is provided as a `pattern`, the function will treat it as
an implicit empty string between each grapheme and the string will be
@@ -1357,43 +1338,89 @@ defmodule String do
"ELIXIR"
"""
@spec replace(t, pattern | Regex.t(), t, keyword) :: t
@spec replace(t, pattern | Regex.t(), t | (t -> t | iodata), keyword) :: t
def replace(subject, pattern, replacement, options \\ [])
def replace(subject, "", "", _), do: subject
def replace(subject, "", replacement, options) do
def replace(subject, %{__struct__: Regex} = regex, replacement, options)
when is_binary(replacement) or is_function(replacement, 1) do
Regex.replace(regex, subject, replacement, options)
end
def replace(subject, "", "", _) when is_binary(subject) do
subject
end
def replace(subject, "", replacement, options)
when is_binary(subject) and is_binary(replacement) do
if Keyword.get(options, :global, true) do
IO.iodata_to_binary([replacement | intersperse(subject, replacement)])
IO.iodata_to_binary([replacement | intersperse_bin(subject, replacement)])
else
replacement <> subject
end
end
def replace(subject, pattern, replacement, options) when is_binary(replacement) do
if Regex.regex?(pattern) do
Regex.replace(pattern, subject, replacement, global: options[:global])
def replace(subject, "", replacement, options)
when is_binary(subject) and is_function(replacement, 1) do
if Keyword.get(options, :global, true) do
IO.iodata_to_binary([replacement.("") | intersperse_fun(subject, replacement)])
else
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
IO.iodata_to_binary([replacement.("") | subject])
end
end
defp intersperse(subject, replacement) do
def replace(subject, pattern, replacement, options) when is_binary(subject) do
if insert = Keyword.get(options, :insert_replaced) do
IO.warn(
"String.replace/4 with :insert_replaced option is deprecated. " <>
"Please use :binary.replace/4 instead or pass an anonymous function as replacement"
)
binary_options = if Keyword.get(options, :global) != false, do: [:global], else: []
:binary.replace(subject, pattern, replacement, [insert_replaced: insert] ++ binary_options)
else
matches =
if Keyword.get(options, :global, true) do
:binary.matches(subject, pattern)
else
case :binary.match(subject, pattern) do
:nomatch -> []
match -> [match]
end
end
IO.iodata_to_binary(do_replace(subject, matches, replacement, 0))
end
end
defp intersperse_bin(subject, replacement) do
case next_grapheme(subject) do
{current, rest} -> [current, replacement | intersperse(rest, replacement)]
{current, rest} -> [current, replacement | intersperse_bin(rest, replacement)]
nil -> []
end
end
defp translate_replace_options(options) do
global = if Keyword.get(options, :global) != false, do: [:global], else: []
defp intersperse_fun(subject, replacement) do
case next_grapheme(subject) do
{current, rest} -> [current, replacement.("") | intersperse_fun(rest, replacement)]
nil -> []
end
end
insert =
if insert = Keyword.get(options, :insert_replaced),
do: [{:insert_replaced, insert}],
else: []
defp do_replace(subject, [], _, n) do
[binary_part(subject, n, byte_size(subject) - n)]
end
global ++ insert
defp do_replace(subject, [{start, length} | matches], replacement, n) do
prefix = binary_part(subject, n, start - n)
middle =
if is_binary(replacement) do
replacement
else
replacement.(binary_part(subject, start, length))
end
[prefix, middle | do_replace(subject, matches, replacement, start + length)]
end
@doc ~S"""
@@ -1462,9 +1489,9 @@ defmodule String do
end
@doc """
Returns all codepoints in the string.
Returns all code points in the string.
For details about codepoints and graphemes, see the `String` module documentation.
For details about code points and graphemes, see the `String` module documentation.
## Examples
@@ -1488,9 +1515,9 @@ defmodule String do
defdelegate codepoints(string), to: String.Unicode
@doc ~S"""
Returns the next codepoint in a string.
Returns the next code point in a string.
The result is a tuple with the codepoint and the
The result is a tuple with the code point and the
remainder of the string or `nil` in case
the string reached its end.
@@ -1561,7 +1588,6 @@ defmodule String do
def valid?(_), do: false
@doc false
# TODO: Remove on 2.0
@deprecated "Use String.valid?/1 instead"
def valid_character?(string) do
case string do
@@ -1629,14 +1655,14 @@ defmodule String do
defp make_chunk_pred(:valid), do: &valid?/1
defp make_chunk_pred(:printable), do: &printable?/1
@doc """
@doc ~S"""
Returns Unicode graphemes in the string as per Extended Grapheme
Cluster algorithm.
The algorithm is outlined in the [Unicode Standard Annex #29,
Unicode Text Segmentation](http://www.unicode.org/reports/tr29/).
Unicode Text Segmentation](https://www.unicode.org/reports/tr29/).
For details about codepoints and graphemes, see the `String` module documentation.
For details about code points and graphemes, see the `String` module documentation.
## Examples
@@ -2132,7 +2158,7 @@ defmodule String do
Converts a string into a charlist.
Specifically, this function takes a UTF-8 encoded binary and returns a list of its integer
codepoints. It is similar to `codepoints/1` except that the latter returns a list of codepoints as
code points. It is similar to `codepoints/1` except that the latter returns a list of code points as
strings.
In case you need to work with bytes, take a look at the
@@ -2169,7 +2195,7 @@ defmodule String do
By default, the maximum number of atoms is `1_048_576`. This limit
can be raised or lowered using the VM option `+t`.
The maximum atom size is of 255 Unicode codepoints.
The maximum atom size is of 255 Unicode code points.
Inlined by the compiler.
@@ -2187,7 +2213,7 @@ defmodule String do
@doc """
Converts a string to an existing atom.
The maximum atom size is of 255 Unicode codepoints.
The maximum atom size is of 255 Unicode code points.
Inlined by the compiler.
@@ -2466,7 +2492,6 @@ defmodule String do
end
@doc false
# TODO: Remove by 2.0
@deprecated "Use String.to_charlist/1 instead"
@spec to_char_list(t) :: charlist
def to_char_list(string), do: String.to_charlist(string)
+2
View File
@@ -279,6 +279,8 @@ defmodule StringIO do
{_, _, _} ->
{{:error, req}, state}
end
rescue
ArgumentError -> {{:error, req}, state}
end
## get_chars
+97 -118
View File
@@ -99,48 +99,9 @@ defmodule Supervisor do
workers and/or supervisors as children, with each one having its own
configuration (as outlined in the "Child specification" section).
The rest of this document will cover how child processes are started,
how they can be specified, different supervision strategies and more.
## Start and shutdown
When the supervisor starts, it traverses all child specifications and
then starts each child in the order they are defined. This is done by
calling the function defined under the `:start` key in the child
specification and typically defaults to `start_link/1`.
The `start_link/1` (or a custom) is then called for each child process.
The `start_link/1` function must return `{:ok, pid}` where `pid` is the
process identifier of a new process that is linked to the supervisor.
The child process usually starts its work by executing the `init/1`
callback. Generally speaking, the `init` callback is where we initialize
and configure the child process.
The shutdown process happens in reverse order.
When a supervisor shuts down, it terminates all children in the opposite
order they are listed. The termination happens by sending a shutdown exit
signal, via `Process.exit(child_pid, :shutdown)`, to the child process and
then awaiting for a time interval for the child process to terminate. This
interval defaults to 5000 milliseconds. If the child process does not
terminate in this interval, the supervisor abruptly terminates the child
with reason `:kill`. The shutdown time can be configured in the child
specification which is fully detailed in the next section.
If the child process is not trapping exits, it will shutdown immediately
when it receives the first exit signal. If the child process is trapping
exits, then the `terminate` callback is invoked, and the child process
must terminate in a reasonable time interval before being abruptly
terminated by the supervisor.
In other words, if it is important that a process cleans after itself
when your application or the supervision tree is shutting down, then
this process must trap exits and its child specification should specify
the proper `:shutdown` value, ensuring it terminates within a reasonable
interval.
Now that we understand the start and shutdown process, let's take a
complete look at all of the options provided in the child specification.
The rest of this document will cover how child processes are specified,
how they can be started and stopped, different supervision strategies
and more.
## Child specification
@@ -247,30 +208,31 @@ defmodule Supervisor do
The supervisor will then invoke `Stack.child_spec([:hello])` to retrieve a
child specification. Now the `Stack` module is responsible for building its
own specification. By default, `use GenServer` defines a `Stack.child_spec/1`
function which returns the same child specification we had before:
own specification, for example, we could write:
%{
id: Stack,
start: {Stack, :start_link, [[:hello]]}
}
def child_spec(arg) do
%{
id: Stack,
start: {Stack, :start_link, [arg]}
}
end
It is also possible to simply pass the `Stack` module as a child:
Luckily for us, `use GenServer` already defines a `Stack.child_spec/1`
exactly like above. If you need to customize the `GenServer`, you can
pass the options directly to `use GenServer`:
use GenServer, restart: :transient
Finally, note it is also possible to simply pass the `Stack` module as
a child:
children = [
Stack
]
When only the module name is given, it is equivalent to `{Stack, []}`. In this
case, we will end-up with a child specification that looks like this:
%{
id: Stack,
start: {Stack, :start_link, [[]]}
}
When only the module name is given, it is equivalent to `{Stack, []}`.
By replacing the map specification by `{Stack, [:hello]}` or `Stack`, we keep
the child specification encapsulated in the Stack module, using the default
the child specification encapsulated in the `Stack` module, using the default
implementation defined by `use GenServer`. We can now share our `Stack` worker
with other developers and they can add it directly to their supervision tree
without worrying about the low-level details of the worker.
@@ -294,55 +256,6 @@ defmodule Supervisor do
Supervisor.child_spec({Stack, [:hello]}, id: MyStack, shutdown: 10_000)
]
The call to `Supervisor.child_spec/2` above will return the following specification:
%{
id: MyStack,
start: {Stack, :start_link, [[:hello]]},
shutdown: 10_000
}
You may also configure the child specification in the Stack module itself to
use a different `:id` or `:shutdown` value by passing options to `use GenServer`:
defmodule Stack do
use GenServer, id: MyStack, shutdown: 10_000
The options above will customize the `Stack.child_spec/1` function defined
by `use GenServer`. It accepts the same options as the `Supervisor.child_spec/2`
function.
You may also completely override the `child_spec/1` function in the Stack module
and return your own child specification. Note there is no guarantee the `child_spec/1`
function will be called by the Supervisor process, as other processes may invoke
it to retrieve the child specification before reaching the supervisor.
## Exit reasons and restarts
A supervisor restarts a child process depending on its `:restart`
configuration. For example, when `:restart` is set to `:transient`, the
supervisor does not restart the child in case it exits with reason `:normal`,
`:shutdown` or `{:shutdown, term}`.
So one may ask: which exit reason should I choose when exiting? There are
three options:
* `:normal` - in such cases, the exit won't be logged, there is no restart
in transient mode, and linked processes do not exit
* `:shutdown` or `{:shutdown, term}` - in such cases, the exit won't be
logged, there is no restart in transient mode, and linked processes exit
with the same reason unless they're trapping exits
* any other term - in such cases, the exit will be logged, there are
restarts in transient mode, and linked processes exit with the same
reason unless they're trapping exits
Notice that the supervisor that reaches maximum restart intensity will exit with
`:shutdown` reason. In this case the supervisor will only be restarted if its
child specification was defined with the `:restart` option set to `:permanent`
(the default).
## Module-based supervisors
In the example above, a supervisor was started by passing the supervision
@@ -375,7 +288,8 @@ defmodule Supervisor do
`c:init/1` callback.
`use Supervisor` also defines a `child_spec/1` function which allows
us to run `MyApp.Supervisor` as a child of another supervisor:
us to run `MyApp.Supervisor` as a child of another supervisor or
at the top of your supervision tree as:
children = [
MyApp.Supervisor
@@ -387,8 +301,9 @@ defmodule Supervisor do
module only at the top of your supervision tree, generally in the
`c:Application.start/2` callback. We recommend using module-based
supervisors for any other supervisor in your application, so they
can run as a child of another supervision in the tree. The generated
`child_spec/1` can be customized with the following options:
can run as a child of another supervisor in the tree. The `child_spec/1`
generated automatically by `Supervisor` can be customized with the
following options:
* `:id` - the child specification identifier, defaults to the current module
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
@@ -461,10 +376,73 @@ defmodule Supervisor do
differently when this strategy was used. See the `DynamicSupervisor` module
for more information and migration strategies.
## Name registration
### Name registration
A supervisor is bound to the same name registration rules as a `GenServer`.
Read more about these rules in the documentation for `GenServer`.
## Start and shutdown
When the supervisor starts, it traverses all child specifications and
then starts each child in the order they are defined. This is done by
calling the function defined under the `:start` key in the child
specification and typically defaults to `start_link/1`.
The `start_link/1` (or a custom) is then called for each child process.
The `start_link/1` function must return `{:ok, pid}` where `pid` is the
process identifier of a new process that is linked to the supervisor.
The child process usually starts its work by executing the `c:init/1`
callback. Generally speaking, the `init` callback is where we initialize
and configure the child process.
The shutdown process happens in reverse order.
When a supervisor shuts down, it terminates all children in the opposite
order they are listed. The termination happens by sending a shutdown exit
signal, via `Process.exit(child_pid, :shutdown)`, to the child process and
then awaiting for a time interval for the child process to terminate. This
interval defaults to 5000 milliseconds. If the child process does not
terminate in this interval, the supervisor abruptly terminates the child
with reason `:kill`. The shutdown time can be configured in the child
specification which is fully detailed in the next section.
If the child process is not trapping exits, it will shutdown immediately
when it receives the first exit signal. If the child process is trapping
exits, then the `terminate` callback is invoked, and the child process
must terminate in a reasonable time interval before being abruptly
terminated by the supervisor.
In other words, if it is important that a process cleans after itself
when your application or the supervision tree is shutting down, then
this process must trap exits and its child specification should specify
the proper `:shutdown` value, ensuring it terminates within a reasonable
interval.
## Exit reasons and restarts
A supervisor restarts a child process depending on its `:restart` configuration.
For example, when `:restart` is set to `:transient`, the supervisor does not
restart the child in case it exits with reason `:normal`, `:shutdown` or
`{:shutdown, term}`.
So one may ask: which exit reason should I choose when exiting? There are
three options:
* `:normal` - in such cases, the exit won't be logged, there is no restart
in transient mode, and linked processes do not exit
* `:shutdown` or `{:shutdown, term}` - in such cases, the exit won't be
logged, there is no restart in transient mode, and linked processes exit
with the same reason unless they're trapping exits
* any other term - in such cases, the exit will be logged, there are
restarts in transient mode, and linked processes exit with the same
reason unless they're trapping exits
Notice that the supervisor that reaches maximum restart intensity will exit with
`:shutdown` reason. In this case the supervisor will only be restarted if its
child specification was defined with the `:restart` option set to `:permanent`
(the default).
"""
@doc false
@@ -580,7 +558,8 @@ defmodule Supervisor do
process and exits not only on crashes but also if the parent process exits
with `:normal` reason.
"""
@spec start_link([:supervisor.child_spec() | {module, term} | module], options) :: on_start
@spec start_link([:supervisor.child_spec() | {module, term} | module], options) ::
{:ok, pid} | {:error, {:already_started, pid} | {:shutdown, term} | term}
def start_link(children, options) when is_list(children) do
{sup_opts, start_opts} = Keyword.split(options, [:strategy, :max_seconds, :max_restarts])
start_link(Supervisor.Default, init(children, sup_opts), start_opts)
@@ -624,7 +603,7 @@ defmodule Supervisor do
description of the available strategies.
"""
@doc since: "1.5.0"
# TODO: Warn if simple_one_for_one strategy is used on Elixir v1.9
# TODO: Warn if simple_one_for_one strategy is used on Elixir v1.10
@spec init([:supervisor.child_spec() | {module, term} | module], [init_option]) :: {:ok, tuple}
def init(children, options) when is_list(children) and is_list(options) do
unless strategy = options[:strategy] do
@@ -818,7 +797,7 @@ defmodule Supervisor do
`child_spec` should be a valid child specification. The child process will
be started as defined in the child specification.
If a child specification with the specified id already exists, `child_spec` is
If a child specification with the specified ID already exists, `child_spec` is
discarded and this function returns an error with `:already_started` or
`:already_present` if the corresponding child process is running or not,
respectively.
@@ -842,7 +821,7 @@ defmodule Supervisor do
call(supervisor, {:start_child, child_spec})
end
# TODO: Deprecate this on Elixir v1.9. Remove and update typespec on v2.0.
# TODO: Deprecate this clause on Elixir v1.10
def start_child(supervisor, args) when is_list(args) do
call(supervisor, {:start_child, args})
end
@@ -852,7 +831,7 @@ defmodule Supervisor do
end
@doc """
Terminates the given child identified by child id.
Terminates the given child identified by `child_id`.
The process is terminated, if there's one. The child specification is
kept unless the child is temporary.
@@ -862,14 +841,14 @@ defmodule Supervisor do
Use `delete_child/2` to remove the child specification.
If successful, this function returns `:ok`. If there is no child
specification for the given child id, this function returns
specification for the given child ID, this function returns
`{:error, :not_found}`.
"""
@spec terminate_child(supervisor, term()) :: :ok | {:error, error}
when error: :not_found | :simple_one_for_one
def terminate_child(supervisor, child_id)
# TODO: Deprecate this clause on Elixir v1.9
# TODO: Deprecate this clause on Elixir v1.10
def terminate_child(supervisor, pid) when is_pid(pid) do
call(supervisor, {:terminate_child, pid})
end
+2 -2
View File
@@ -127,7 +127,7 @@ defmodule Supervisor.Spec do
@typedoc "Supported module values"
@type modules :: :dynamic | [module]
@typedoc "Supported id values"
@typedoc "Supported ID values"
@type child_id :: term
@typedoc "The supervisor specification"
@@ -196,7 +196,7 @@ defmodule Supervisor.Spec do
defp assert_unique_ids([id | rest]) do
if id in rest do
raise ArgumentError,
"duplicated id #{inspect(id)} found in the supervisor specification, " <>
"duplicated ID #{inspect(id)} found in the supervisor specification, " <>
"please explicitly pass the :id option when defining this worker/supervisor"
else
assert_unique_ids(rest)
+179 -22
View File
@@ -36,11 +36,11 @@ defmodule System do
Generally speaking, the VM provides three time measurements:
* `os_time/0` - the time reported by the OS. This time may be
* `os_time/0` - the time reported by the operating system (OS). This time may be
adjusted forwards or backwards in time with no limitation;
* `system_time/0` - the VM view of the `os_time/0`. The system time and OS
time may not match in case of time warps although the VM works towards
* `system_time/0` - the VM view of the `os_time/0`. The system time and operating
system time may not match in case of time warps although the VM works towards
aligning them. This time is not monotonic (i.e., it may decrease)
as its behaviour is configured [by the VM time warp
mode](http://www.erlang.org/doc/apps/erts/time_correction.html#Time_Warp_Modes);
@@ -49,7 +49,7 @@ defmodule System do
by the Erlang VM.
The time functions in this module work in the `:native` unit
(unless specified otherwise), which is OS dependent. Most of
(unless specified otherwise), which is operating system dependent. Most of
the time, all calculations are done in the `:native` unit, to
avoid loss of precision, with `convert_time_unit/3` being
invoked at the end to convert to a specific time unit like
@@ -111,7 +111,7 @@ defmodule System do
:erlang.list_to_binary(:erlang.system_info(:otp_release))
end
# Tries to run "git rev-parse --short HEAD". In the case of success returns
# Tries to run "git rev-parse --short=7 HEAD". In the case of success returns
# the short revision hash. If that fails, returns an empty string.
defmacrop get_revision do
null =
@@ -120,7 +120,7 @@ defmodule System do
_ -> '/dev/null'
end
'git rev-parse --short HEAD 2> '
'git rev-parse --short=7 HEAD 2> '
|> Kernel.++(null)
|> :os.cmd()
|> strip
@@ -129,8 +129,21 @@ defmodule System do
defp revision, do: get_revision()
# Get the date at compilation time.
# Follows https://reproducible-builds.org/specs/source-date-epoch/
defmacrop get_date do
{{year, month, day}, {hour, minute, second}} = :calendar.universal_time()
unix_epoch =
if source_date_epoch = :os.getenv('SOURCE_DATE_EPOCH') do
try do
List.to_integer(source_date_epoch)
rescue
_ -> nil
end
end
unix_epoch = unix_epoch || :os.system_time(:second)
{{year, month, day}, {hour, minute, second}} =
:calendar.gregorian_seconds_to_datetime(unix_epoch + 62_167_219_200)
"~4..0b-~2..0b-~2..0bT~2..0b:~2..0b:~2..0bZ"
|> :io_lib.format([year, month, day, hour, minute, second])
@@ -165,9 +178,42 @@ defmodule System do
@doc """
Elixir build information.
Returns a keyword list with Elixir version, Git short revision hash and compilation date.
Returns a map with the Elixir version, the Erlang/OTP release it was compiled
with, a short Git revision hash and the date and time it was built.
Every value in the map is a string, and these are:
* `:build` - the Elixir version, short Git revision hash and
Erlang/OTP release it was compiled with
* `:date` - a string representation of the ISO8601 date and time it was built
* `:opt_release` - OTP release it was compiled with
* `:revision` - short Git revision hash. If Git was not available at building
time, it is set to `""`
* `:version` - the Elixir version
One should not rely on the specific formats returned by each of those fields.
Instead one should use specialized functions, such as `version/0` to retrieve
the Elixir version and `otp_release/0` to retrieve the Erlang/OTP release.
## Examples
iex> System.build_info()
%{
build: "1.9.0-dev (772a00a0c) (compiled with Erlang/OTP 21)",
date: "2018-12-24T01:09:21Z",
otp_release: "21",
revision: "772a00a0c",
version: "1.9.0-dev"
}
"""
@spec build_info() :: map
@spec build_info() :: %{
build: String.t(),
date: String.t(),
revision: String.t(),
version: String.t(),
otp_release: String.t()
}
def build_info do
%{
build: build(),
@@ -209,13 +255,30 @@ defmodule System do
:elixir_config.put(:argv, args)
end
@doc """
Marks if the system should halt or not at the end of ARGV processing.
"""
@doc since: "1.9.0"
@spec no_halt(boolean) :: :ok
def no_halt(boolean) when is_boolean(boolean) do
:elixir_config.put(:no_halt, boolean)
end
@doc """
Checks if the system will halt or not at the end of ARGV processing.
"""
@doc since: "1.9.0"
@spec no_halt() :: boolean
def no_halt() do
:elixir_config.get(:no_halt)
end
@doc """
Current working directory.
Returns the current working directory or `nil` if one
is not available.
"""
# TODO: Remove by 2.0
@deprecated "Use File.cwd/0 instead"
@spec cwd() :: String.t() | nil
def cwd do
@@ -230,7 +293,6 @@ defmodule System do
Returns the current working directory or raises `RuntimeError`.
"""
# TODO: Remove by 2.0
@deprecated "Use File.cwd!/0 instead"
@spec cwd!() :: String.t()
def cwd! do
@@ -351,7 +413,7 @@ defmodule System do
This function looks up an executable program given
its name using the environment variable PATH on Unix
and Windows. It also considers the proper executable
extension for each OS, so for Windows it will try to
extension for each operating system, so for Windows it will try to
lookup files with `.com`, `.cmd` or similar extensions.
"""
@spec find_executable(binary) :: binary | nil
@@ -383,17 +445,80 @@ defmodule System do
Returns the value of the given environment variable.
The returned value of the environment variable
`varname` is a string, or `nil` if the environment
variable is undefined.
`varname` is a string. If the environment variable
is not set, returns the string specified in `default` or
`nil` if none is specified.
## Examples
iex> System.get_env("PORT")
"4000"
iex> System.get_env("NOT_SET")
nil
iex> System.get_env("NOT_SET", "4001")
"4001"
"""
@spec get_env(String.t()) :: String.t() | nil
def get_env(varname) when is_binary(varname) do
@doc since: "1.9.0"
@spec get_env(String.t(), String.t() | nil) :: String.t() | nil
def get_env(varname, default \\ nil)
when is_binary(varname) and
(is_binary(default) or is_nil(default)) do
case :os.getenv(String.to_charlist(varname)) do
false -> nil
false -> default
other -> List.to_string(other)
end
end
@doc """
Returns the value of the given environment variable or `:error` if not found.
If the environment variable `varname` is set, then `{:ok, value}` is returned
where `value` is a string. If `varname` is not set, `:error` is returned.
## Examples
iex> System.fetch_env("PORT")
{:ok, "4000"}
iex> System.fetch_env("NOT_SET")
:error
"""
@doc since: "1.9.0"
@spec fetch_env(String.t()) :: {:ok, String.t()} | :error
def fetch_env(varname) when is_binary(varname) do
case :os.getenv(String.to_charlist(varname)) do
false -> :error
other -> {:ok, List.to_string(other)}
end
end
@doc """
Returns the value of the given environment variable or raises if not found.
Same as `get_env/1` but raises instead of returning `nil` when the variable is
not set.
## Examples
iex> System.fetch_env!("PORT")
"4000"
iex> System.fetch_env!("NOT_SET")
** (ArgumentError) could not fetch environment variable "NOT_SET" because it is not set
"""
@doc since: "1.9.0"
@spec fetch_env!(String.t()) :: String.t()
def fetch_env!(varname) when is_binary(varname) do
get_env(varname) ||
raise ArgumentError,
"could not fetch environment variable #{inspect(varname)} because it is not set"
end
@doc """
Erlang VM process identifier.
@@ -458,7 +583,7 @@ defmodule System do
latest exception. To retrieve the stacktrace of the current process,
use `Process.info(self(), :current_stacktrace)` instead.
"""
# TODO: Fully deprecate it on Elixir v1.9.
# TODO: Fully deprecate it on Elixir v1.11 via @deprecated
# It is currently partially deprecated in elixir_dispatch.erl
def stacktrace do
apply(:erlang, :get_stacktrace, [])
@@ -505,6 +630,40 @@ defmodule System do
:erlang.halt(String.to_charlist(status))
end
@doc """
Returns the operating system PID for the current Erlang runtime system instance.
Returns a string containing the (usually) numerical identifier for a process.
On UNIX, this is typically the return value of the `getpid()` system call.
On Windows, the process ID as returned by the `GetCurrentProcessId()` system
call is used.
## Examples
System.pid()
"""
@doc since: "1.9.0"
@spec pid :: String.t()
def pid do
List.to_string(:os.getpid())
end
@doc """
Restarts all applications in the Erlang runtime system.
All applications are taken down smoothly, all code is unloaded, and all ports
are closed before the system starts all applications once again.
## Examples
System.restart()
"""
@doc since: "1.9.0"
@spec restart :: :ok
defdelegate restart(), to: :init
@doc """
Carefully stops the Erlang runtime system.
@@ -517,8 +676,6 @@ defmodule System do
Note that on many platforms, only the status codes 0-255 are supported
by the operating system.
For more information, see `:init.stop/1`.
## Examples
System.stop(0)
@@ -793,7 +950,7 @@ defmodule System do
end
@doc """
Returns the current OS time.
Returns the current operating system (OS) time.
The result is returned in the `:native` time unit.
@@ -808,7 +965,7 @@ defmodule System do
end
@doc """
Returns the current OS time in the given time `unit`.
Returns the current operating system (OS) time in the given time `unit`.
This time may be adjusted forwards or backwards in time
with no limitation and is not monotonic.
+41 -11
View File
@@ -88,11 +88,11 @@ defmodule Task do
], strategy: :one_for_one)
Since these tasks are supervised and not directly linked to
the caller, they cannot be awaited on. Note that `start_link/1`,
unlike `async/1`, returns `{:ok, pid}` (which is the result
expected by supervisors).
the caller, they cannot be awaited on. `start_link/1`, unlike
`async/1`, returns `{:ok, pid}` (which is the result expected
by supervisors).
Note that `use Task` defines a `child_spec/1` function, allowing the
`use Task` defines a `child_spec/1` function, allowing the
defined module to be put under a supervision tree. The generated
`child_spec/1` can be customized with the following options:
@@ -158,7 +158,7 @@ defmodule Task do
## Distributed tasks
Since Elixir provides a `Task.Supervisor`, it is easy to use one
to dynamically spawn tasks across nodes:
to dynamically start tasks across nodes:
# On the remote node
Task.Supervisor.start_link(name: MyApp.DistSupervisor)
@@ -173,6 +173,37 @@ defmodule Task do
the same module version to exist on all involved nodes. Check the `Agent` module
documentation for more information on distributed processes as the limitations
described there apply to the whole ecosystem.
## Ancestor and Caller Tracking
Whenever you start a new process, Elixir annotates the parent of that process
through the `$ancestors` key in the process dictionary. This is often used to
track the hierarchy inside a supervision tree.
For example, we recommend developers to always start tasks under a supervisor.
This provides more visibility and allows you to control how those tasks are
terminated when a node shuts down. That might look something like
`Task.Supervisor.start_child(MySupervisor, task_specification)`. This means
that, although your code is the one who invokes the task, the actual ancestor of
the task is the supervisor, as the supervisor is the one effectively starting it.
To track the relationship between your code and the task, we use the `$callers`
key in the process dictionary. Therefore, assuming the `Task.Supervisor` call
above, we have:
[your code] -- calls --> [supervisor] ---- spawns --> [task]
Which means we store the following relationships:
[your code] [supervisor] <-- ancestor -- [task]
^ |
|--------------------- caller ---------------------|
The list of callers of the current process can be retrieved from the Process
dictionary with `Process.get(:"$callers")`. This will return either `nil` or
a list `[pid_n, ..., pid2, pid1]` with at least one entry Where `pid_n` is
the PID that called the current process, `pid2` called `pid_n`, and `pid2` was
called by `pid1`.
"""
@doc """
@@ -397,9 +428,9 @@ defmodule Task do
@doc """
Returns a stream where the given function (`module` and `function_name`)
is mapped concurrently on each item in `enumerable`.
is mapped concurrently on each element in `enumerable`.
Each item of `enumerable` will be prepended to the given `args` and
Each element of `enumerable` will be prepended to the given `args` and
processed by its own task. The tasks will be linked to an intermediate
process that is then linked to the current process. This means a failure
in a task terminates the current process and a failure in the current process
@@ -468,18 +499,18 @@ defmodule Task do
@doc """
Returns a stream that runs the given function `fun` concurrently
on each item in `enumerable`.
on each element in `enumerable`.
Works the same as `async_stream/5` but with an anonymous function instead of a
module-function-arguments tuple. `fun` must be a one-arity anonymous function.
Each `enumerable` item is passed as argument to the given function `fun` and
Each `enumerable` element is passed as argument to the given function `fun` and
processed by its own task. The tasks will be linked to the current process,
similarly to `async/1`.
## Example
Count the codepoints in each string asynchronously, then add the counts together using reduce.
Count the code points in each string asynchronously, then add the counts together using reduce.
iex> strings = ["long string", "longer string", "there are many of these"]
iex> stream = Task.async_stream(strings, fn text -> text |> String.codepoints() |> Enum.count() end)
@@ -579,7 +610,6 @@ defmodule Task do
end
@doc false
# TODO: Remove on 2.0
@deprecated "Pattern match directly on the message instead"
def find(tasks, {ref, reply}) when is_reference(ref) do
Enum.find_value(tasks, fn
+1 -1
View File
@@ -34,7 +34,7 @@ defmodule Task.Supervised do
_ = if mref, do: Process.demonitor(mref, [:flush])
send(owner_pid, {ref, invoke_mfa(owner, mfa)})
{:DOWN, ^mref, _, _, reason} when is_reference(mref) ->
{:DOWN, ^mref, _, _, reason} ->
exit({:shutdown, reason})
after
# There is a race condition on this operation when working across
+11 -11
View File
@@ -78,7 +78,7 @@ defmodule Task.Supervisor do
give them directly to `start_child` and `async`.
"""
@spec start_link([option]) :: Supervisor.on_start()
# TODO: Deprecate passing restart and shutdown here on Elixir v1.9.
# TODO: Deprecate passing restart and shutdown here on Elixir v1.10.
def start_link(options \\ []) do
{restart, options} = Keyword.pop(options, :restart, :temporary)
{shutdown, options} = Keyword.pop(options, :shutdown, 5000)
@@ -182,12 +182,12 @@ defmodule Task.Supervisor do
end
# In this case the task is already running, so we just return :ok.
def handle_call(:start_task, %{ref: ref} = state) when is_reference(ref) do
def handle_call(:start_task, _from, %{ref: ref} = state) when is_reference(ref) do
{:reply, :ok, state}
end
# The task is not running yet, so let's start it.
def handle_cast(:start_task, %{ref: nil} = state) do
def handle_call(:start_task, _from, %{ref: nil} = state) do
task =
Task.Supervisor.async_nolink(MyApp.TaskSupervisor, fn ->
...
@@ -239,9 +239,9 @@ defmodule Task.Supervisor do
@doc """
Returns a stream where the given function (`module` and `function`)
is mapped concurrently on each item in `enumerable`.
is mapped concurrently on each element in `enumerable`.
Each item will be prepended to the given `args` and processed by its
Each element will be prepended to the given `args` and processed by its
own task. The tasks will be spawned under the given `supervisor` and
linked to the current process, similarly to `async/4`.
@@ -298,9 +298,9 @@ defmodule Task.Supervisor do
@doc """
Returns a stream that runs the given function `fun` concurrently
on each item in `enumerable`.
on each element in `enumerable`.
Each item in `enumerable` is passed as argument to the given function `fun`
Each element in `enumerable` is passed as argument to the given function `fun`
and processed by its own task. The tasks will be spawned under the given
`supervisor` and linked to the current process, similarly to `async/2`.
@@ -315,9 +315,9 @@ defmodule Task.Supervisor do
@doc """
Returns a stream where the given function (`module` and `function`)
is mapped concurrently on each item in `enumerable`.
is mapped concurrently on each element in `enumerable`.
Each item in `enumerable` will be prepended to the given `args` and processed
Each element in `enumerable` will be prepended to the given `args` and processed
by its own task. The tasks will be spawned under the given `supervisor` and
will not be linked to the current process, similarly to `async_nolink/4`.
@@ -339,9 +339,9 @@ defmodule Task.Supervisor do
@doc """
Returns a stream that runs the given `function` concurrently on each
item in `enumerable`.
element in `enumerable`.
Each item in `enumerable` is passed as argument to the given function `fun`
Each element in `enumerable` is passed as argument to the given function `fun`
and processed by its own task. The tasks will be spawned under the given
`supervisor` and will not be linked to the current process, similarly to `async_nolink/2`.
+3 -3
View File
@@ -4,9 +4,9 @@ defmodule Tuple do
Please note the following functions for tuples are found in `Kernel`:
* `elem/2` - access a tuple by index
* `put_elem/3` - insert a value into a tuple by index
* `tuple_size/1` - get the number of elements in a tuple
* `elem/2` - accesses a tuple by index
* `put_elem/3` - inserts a value into a tuple by index
* `tuple_size/1` - gets the number of elements in a tuple
Tuples are intended as fixed-size containers for multiple elements.
To manipulate a collection of elements, use a list instead. `Enum`
+49 -44
View File
@@ -29,8 +29,11 @@ defmodule URI do
import Bitwise
@reserved_characters ':/?#[]@!$&\'()*+,;='
@formatted_reserved_characters Enum.map_join(@reserved_characters, ", ", &<<?`, &1, ?`>>)
@doc """
Returns the default port for a given scheme.
Returns the default port for a given `scheme`.
If the scheme is unknown to the `URI` module, this function returns
`nil`. The default port for any scheme can be configured globally
@@ -76,7 +79,7 @@ defmodule URI do
values are URL encoded as per `encode_www_form/1`.
Keys and values can be any term that implements the `String.Chars`
protocol, except lists which are explicitly forbidden.
protocol with the exception of lists, which are explicitly forbidden.
## Examples
@@ -92,7 +95,7 @@ defmodule URI do
** (ArgumentError) encode_query/1 values cannot be lists, got: [:a, :list]
"""
@spec encode_query(term) :: binary
@spec encode_query(Enum.t()) :: binary
def encode_query(enumerable) do
Enum.map_join(enumerable, "&", &encode_kv_pair/1)
end
@@ -112,7 +115,7 @@ defmodule URI do
@doc """
Decodes a query string into a map.
Given a query string of the form of `key1=value1&key2=value2...`, this
Given a query string in the form of `key1=value1&key2=value2...`, this
function inserts each key-value pair in the query string as one entry in the
given `map`. Keys and values in the resulting map will be binaries. Keys and
values will be percent-unescaped.
@@ -128,10 +131,9 @@ defmodule URI do
%{"percent" => "oh yes!", "starting" => "map"}
"""
@spec decode_query(binary, %{binary => binary}) :: %{binary => binary}
@spec decode_query(binary, %{optional(binary) => binary}) :: %{optional(binary) => binary}
def decode_query(query, map \\ %{})
# TODO: Remove on 2.0
def decode_query(query, %_{} = dict) when is_binary(query) do
IO.warn("URI.decode_query/2 is deprecated, please use URI.decode_query/1")
decode_query_into_dict(query, dict)
@@ -141,7 +143,6 @@ defmodule URI do
decode_query_into_map(query, map)
end
# TODO: Remove on 2.0
def decode_query(query, dict) when is_binary(query) do
IO.warn("URI.decode_query/2 is deprecated, please use URI.decode_query/1")
decode_query_into_dict(query, dict)
@@ -180,6 +181,9 @@ defmodule URI do
iex> URI.query_decoder("foo=1&bar=2") |> Enum.to_list()
[{"foo", "1"}, {"bar", "2"}]
iex> URI.query_decoder("food=bread%26butter&drinks=tap%20water") |> Enum.to_list()
[{"food", "bread&butter"}, {"drinks", "tap water"}]
"""
@spec query_decoder(binary) :: Enumerable.t()
def query_decoder(query) when is_binary(query) do
@@ -206,11 +210,11 @@ defmodule URI do
{next_pair, rest}
end
@doc """
Checks if the character is a "reserved" character in a URI.
@doc ~s"""
Checks if `character` is a reserved one in a URI.
Reserved characters are specified in
[RFC 3986, section 2.2](https://tools.ietf.org/html/rfc3986#section-2.2).
As specified in [RFC 3986, section 2.2](https://tools.ietf.org/html/rfc3986#section-2.2),
the following characters are reserved: #{@formatted_reserved_characters}
## Examples
@@ -218,16 +222,19 @@ defmodule URI do
true
"""
@spec char_reserved?(char) :: boolean
def char_reserved?(char) when char in 0..0x10FFFF do
char in ':/?#[]@!$&\'()*+,;='
@spec char_reserved?(byte) :: boolean
def char_reserved?(character) do
character in @reserved_characters
end
@doc """
Checks if the character is a "unreserved" character in a URI.
Checks if `character` is an unreserved one in a URI.
Unreserved characters are specified in
[RFC 3986, section 2.3](https://tools.ietf.org/html/rfc3986#section-2.3).
As specified in [RFC 3986, section 2.3](https://tools.ietf.org/html/rfc3986#section-2.3),
the following characters are unreserved:
* Alphanumeric characters: `A-Z`, `a-z`, `0-9`
* `~`, `_`, `-`
## Examples
@@ -235,13 +242,13 @@ defmodule URI do
true
"""
@spec char_unreserved?(char) :: boolean
def char_unreserved?(char) when char in 0..0x10FFFF do
char in ?0..?9 or char in ?a..?z or char in ?A..?Z or char in '~_-.'
@spec char_unreserved?(byte) :: boolean
def char_unreserved?(character) do
character in ?0..?9 or character in ?a..?z or character in ?A..?Z or character in '~_-.'
end
@doc """
Checks if the character is allowed unescaped in a URI.
Checks if `character` is allowed unescaped in a URI.
This is the default used by `URI.encode/2` where both
reserved and unreserved characters are kept unescaped.
@@ -252,16 +259,16 @@ defmodule URI do
false
"""
@spec char_unescaped?(char) :: boolean
def char_unescaped?(char) when char in 0..0x10FFFF do
char_reserved?(char) or char_unreserved?(char)
@spec char_unescaped?(byte) :: boolean
def char_unescaped?(character) do
char_reserved?(character) or char_unreserved?(character)
end
@doc """
Percent-escapes all characters that require escaping in a string.
Percent-escapes all characters that require escaping in `string`.
This means reserved characters, such as `:` and `/`, and the so-
called unreserved characters, which have the same meaning both
This means reserved characters, such as `:` and `/`, and the
so-called unreserved characters, which have the same meaning both
escaped and unescaped, won't be escaped by default.
See `encode_www_form` if you are interested in escaping reserved
@@ -269,8 +276,9 @@ defmodule URI do
This function also accepts a `predicate` function as an optional
argument. If passed, this function will be called with each byte
in `string` as its argument and should return `true` if the given
byte should be left as is.
in `string` as its argument and should return a truthy value (anything other
than `false` or `nil`) if the given byte should be left as is, or return a
falsy value (`false` or `nil`) if the character should be escaped.
## Examples
@@ -281,14 +289,14 @@ defmodule URI do
"a str%69ng"
"""
@spec encode(binary, (byte -> boolean)) :: binary
@spec encode(binary, (byte -> as_boolean(term))) :: binary
def encode(string, predicate \\ &char_unescaped?/1)
when is_binary(string) and is_function(predicate, 1) do
for <<char <- string>>, into: "", do: percent(char, predicate)
for <<byte <- string>>, into: "", do: percent(byte, predicate)
end
@doc """
Encodes a string as "x-www-form-urlencoded".
Encodes `string` as "x-www-form-urlencoded".
## Example
@@ -298,8 +306,8 @@ defmodule URI do
"""
@spec encode_www_form(binary) :: binary
def encode_www_form(string) when is_binary(string) do
for <<char <- string>>, into: "" do
case percent(char, &char_unreserved?/1) do
for <<byte <- string>>, into: "" do
case percent(byte, &char_unreserved?/1) do
"%20" -> "+"
percent -> percent
end
@@ -335,7 +343,7 @@ defmodule URI do
end
@doc """
Decodes a string as "x-www-form-urlencoded".
Decodes `string` as "x-www-form-urlencoded".
## Examples
@@ -344,7 +352,7 @@ defmodule URI do
"""
@spec decode_www_form(binary) :: binary
def decode_www_form(string) do
def decode_www_form(string) when is_binary(string) do
unpercent(string, "", true)
catch
:malformed_uri ->
@@ -440,14 +448,11 @@ defmodule URI do
"""
@spec parse(t | binary) :: t
def parse(uri)
def parse(%URI{} = uri), do: uri
def parse(string) when is_binary(string) do
# From https://tools.ietf.org/html/rfc3986#appendix-B
regex =
Regex.recompile!(~r{^(([a-z][a-z0-9\+\-\.]*):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?}i)
regex = ~r{^(([a-z][a-z0-9\+\-\.]*):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?}i
parts = Regex.run(regex, string)
@@ -480,7 +485,7 @@ defmodule URI do
# Split an authority into its userinfo, host and port parts.
defp split_authority(string) do
regex = Regex.recompile!(~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/)
regex = ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/
components = Regex.run(regex, string || "")
destructure [_, _, userinfo, host, _, port], components
@@ -497,7 +502,7 @@ defmodule URI do
defp nillify(other), do: other
@doc """
Returns the string representation of the given `URI` struct.
Returns the string representation of the given [URI struct](`t:t/0`).
## Examples
@@ -507,8 +512,8 @@ defmodule URI do
iex> URI.to_string(%URI{scheme: "foo", host: "bar.baz"})
"foo://bar.baz"
Note that when creating this string representation, the `authority` will be
used if the host is `nil`. Otherwise, the `userinfo`, `host`, and `port` will
Note that when creating this string representation, the `:authority` value will be
used if the `:host` is `nil`. Otherwise, the `:userinfo`, `:host`, and `:port` will
be used.
iex> URI.to_string(%URI{authority: "foo@example.com:80"})
+48 -14
View File
@@ -6,7 +6,7 @@ defmodule Version do
generated after parsing via `Version.parse/1`.
`Version` parsing and requirements follow
[SemVer 2.0 schema](http://semver.org/).
[SemVer 2.0 schema](https://semver.org/).
## Versions
@@ -107,9 +107,46 @@ defmodule Version do
@type t :: %__MODULE__{major: major, minor: minor, patch: patch, pre: pre, build: build}
defmodule Requirement do
@moduledoc false
@moduledoc """
A struct that holds version requirement information.
The struct fields are private and should not be accessed.
See the "Requirements" section in the `Version` module
for more information.
"""
defstruct [:source, :matchspec, :compiled]
@type t :: %__MODULE__{source: String.t(), matchspec: :ets.match_spec(), compiled: boolean}
@opaque t :: %__MODULE__{
source: String.t(),
matchspec: :ets.match_spec() | :ets.comp_match_spec(),
compiled: boolean
}
@doc false
@spec new(String.t(), :ets.match_spec()) :: t
def new(source, spec) do
%__MODULE__{source: source, matchspec: spec, compiled: false}
end
@doc false
@spec compile(t) :: t
def compile(%__MODULE__{matchspec: spec} = requirement) do
%{requirement | matchspec: :ets.match_spec_compile(spec), compiled: true}
end
@doc false
@spec match?(t, tuple) :: boolean
def match?(%__MODULE__{matchspec: spec, compiled: true}, matchable_pattern) do
matches = :ets.match_spec_run([matchable_pattern], spec)
matches != []
end
def match?(%__MODULE__{matchspec: spec, compiled: false}, matchable_pattern) do
{:ok, result} = :ets.test_ms(matchable_pattern, spec)
result != false
end
end
defmodule InvalidRequirementError do
@@ -183,15 +220,11 @@ defmodule Version do
match?(version, parse_requirement!(requirement), opts)
end
def match?(version, %Requirement{matchspec: spec, compiled: false}, opts) do
def match?(version, requirement, opts) do
allow_pre = Keyword.get(opts, :allow_pre, true)
{:ok, result} = :ets.test_ms(to_matchable(version, allow_pre), spec)
result != false
end
matchable_pattern = to_matchable(version, allow_pre)
def match?(version, %Requirement{matchspec: spec, compiled: true}, opts) do
allow_pre = Keyword.get(opts, :allow_pre, true)
:ets.match_spec_run([to_matchable(version, allow_pre)], spec) != []
Requirement.match?(requirement, matchable_pattern)
end
@doc """
@@ -312,7 +345,8 @@ defmodule Version do
def parse_requirement(string) when is_binary(string) do
case Version.Parser.parse_requirement(string) do
{:ok, spec} ->
{:ok, %Requirement{source: string, matchspec: spec, compiled: false}}
requirement = Requirement.new(string, spec)
{:ok, requirement}
:error ->
:error
@@ -338,7 +372,7 @@ defmodule Version do
def parse_requirement!(string) when is_binary(string) do
case Version.Parser.parse_requirement(string) do
{:ok, spec} ->
%Requirement{source: string, matchspec: spec, compiled: false}
Requirement.new(string, spec)
:error ->
raise InvalidRequirementError, string
@@ -355,8 +389,8 @@ defmodule Version do
compiled match_spec, nor can it be stored on disk).
"""
@spec compile_requirement(Requirement.t()) :: Requirement.t()
def compile_requirement(%Requirement{matchspec: spec} = req) do
%{req | matchspec: :ets.match_spec_compile(spec), compiled: true}
def compile_requirement(requirement) do
Requirement.compile(requirement)
end
defp to_matchable(%Version{major: major, minor: minor, patch: patch, pre: pre}, allow_pre?) do
@@ -4,15 +4,15 @@ Elixir is versioned according to a vMAJOR.MINOR.PATCH schema.
Elixir is currently at major version v1. A new backwards compatible minor release happens every 6 months. Patch releases are not scheduled and are made whenever there are bug fixes or security patches.
Elixir applies bug fixes only to the latest minor branch. Security patches are available for the last 5 minor branch:
Elixir applies bug fixes only to the latest minor branch. Security patches are available for the last 5 minor branches:
Elixir version | Support
:------------- | :-----------------------------
1.7 | Bug fixes and security patches
1.9 | Bug fixes and security patches
1.8 | Security patches only
1.7 | Security patches only
1.6 | Security patches only
1.5 | Security patches only
1.4 | Security patches only
1.3 | Security patches only
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
@@ -49,7 +49,9 @@ Elixir version | Supported Erlang/OTP versions
1.4 | 18 - 19 (and Erlang/OTP 20 from v1.4.5)
1.5 | 18 - 20
1.6 | 19 - 20 (and Erlang/OTP 21 from v1.6.6)
1.7 | 19 - 21
1.7 | 19 - 22
1.8 | 20 - 22
1.9 | 20 - 22
While Elixir often adds compatibility to new Erlang/OTP versions on released branches, such as support for Erlang/OTP 20 in v1.4.5, those releases usually contain the minimum changes for Elixir to run without errors. Only the next minor release, in this case v1.5.0, does effectively leverage the new features provided by the latest Erlang/OTP release.
@@ -61,85 +63,92 @@ Elixir deprecations happen in 3 steps:
1. The feature is soft-deprecated. It means both CHANGELOG and documentation must list the feature as deprecated but no warning is effectively emitted by running the code. There is no requirement to soft-deprecate a feature.
2. The feature is effectively deprecated by emitting warnings on usage. This is also known as hard-deprecation. In order to deprecate a feature, the proposed alternative MUST exist for AT LEAST two minor versions. For example, `Enum.uniq/2` was soft-deprecated in favor of `Enum.uniq_by/2` in Elixir v1.1. This means a deprecation warning may only be emitted by Elixir v1.3 or later.
2. The feature is effectively deprecated by emitting warnings on usage. This is also known as hard-deprecation. In order to deprecate a feature, the proposed alternative MUST exist for AT LEAST THREE minor versions. For example, `Enum.uniq/2` was soft-deprecated in favor of `Enum.uniq_by/2` in Elixir v1.1. This means a deprecation warning may only be emitted by Elixir v1.4 or later.
3. The feature is removed. This can only happen on major releases. This means deprecated features in Elixir v1.x shall only be removed by Elixir v2.x.
### Table of deprecations
Deprecated feature | Hard-deprecated in | Replaced by (available since)
:----------------------------------------------- | :----------------- | :----------------------------
Passing a non-empty list to `Enum.into/2` | [v1.8] | `Kernel.++/2` or `Keyword.merge/2` (v1.0)
Passing a non-empty list to `:into` in `for` | [v1.8] | `Kernel.++/2` or `Keyword.merge/2` (v1.0)
`:seconds`, `:milliseconds`, etc. as time units | [v1.8] | `:second`, `:millisecond`, etc. (v1.4)
`Inspect.Algebra.surround/3` | [v1.8] | `Inspect.Algebra.concat/2` and `Inspect.Algebra.nest/2` (v1.0)
`Inspect.Algebra.surround_many/6` | [v1.8] | `Inspect.Algebra.container_doc/6` (v1.6)
`Kernel.ParallelCompiler.files/2` | [v1.8] | `Kernel.ParallelCompiler.compile/2` (v1.6)
`Kernel.ParallelCompiler.files_to_path/2` | [v1.8] | `Kernel.ParallelCompiler.compile_to_path/2` (v1.6)
`Kernel.ParallelRequire.files/2` | [v1.8] | `Kernel.ParallelCompiler.require/2` (v1.6)
`System.cwd/0` and `System.cwd!/0` | [v1.8] | `File.cwd/0` and `File.cwd!/0` (v1.0)
`Code.get_docs/2` | [v1.7] | `Code.fetch_docs/1` (v1.7)
Calling `super/1` on GenServer callbacks | [v1.7] | Not calling `super/1` (v1.0)
`Enum.chunk/2`[`/3/4`](`Enum.chunk/4`) | [v1.7] | `Enum.chunk_every/2`[`/3/4`](`Enum.chunk_every/4`) (v1.5)
`not left in right` | [v1.7] | [`left not in right`](`Kernel.SpecialForms.in/2`) (v1.5)
`Registry.start_link/3` | [v1.7] | `Registry.start_link/1` (v1.5)
`Stream.chunk/2`[`/3/4`](`Stream.chunk/4`) | [v1.7] | `Stream.chunk_every/2`[`/3/4`](`Stream.chunk_every/4`) (v1.5)
`Enum.partition/2` | [v1.6] | `Enum.split_with/2` (v1.4)
`Keyword.replace/3` | [v1.6] | `Keyword.fetch/2` + `Keyword.put/3` (v1.0)
`Macro.unescape_tokens/1` and `Macro.unescape_tokens/2` | [v1.6] | Use `Enum.map/2` to traverse over the arguments (v1.0)
`Module.add_doc/6` | [v1.6] | `@doc` module attribute (v1.0)
`Map.replace/3` | [v1.6] | `Map.fetch/2` + `Map.put/3` (v1.0)
`Range.range?/1` | [v1.6] | Pattern match on `_.._` (v1.0)
`Atom.to_char_list/1` | [v1.5] | `Atom.to_charlist/1` (v1.3)
`Enum.filter_map/3` | [v1.5] | `Enum.filter/2` + `Enum.map/2` or [`for`](`Kernel.SpecialForms.for/1`) comprehensions (v1.0)
`Float.to_char_list/1` | [v1.5] | `Float.to_charlist/1` (v1.3)
`GenEvent` module | [v1.5] | `Supervisor` and `GenServer` (v1.0);<br/>[`GenStage`](https://hex.pm/packages/gen_stage) (v1.3);<br/>[`:gen_event`](http://www.erlang.org/doc/man/gen_event.html) (Erlang/OTP 17)
`Integer.to_char_list/1` and `Integer.to_char_list/2` | [v1.5] | `Integer.to_charlist/1` and `Integer.to_charlist/2` (v1.3)
`Kernel.to_char_list/1` | [v1.5] | `Kernel.to_charlist/1` (v1.3)
`List.Chars.to_char_list/1` | [v1.5] | `List.Chars.to_charlist/1` (v1.3)
`Stream.filter_map/3` | [v1.5] | `Stream.filter/2` + `Stream.map/2` (v1.0)
`String.ljust/3` and `String.rjust/3` | [v1.5] | Use `String.pad_leading/3` and `String.pad_trailing/3` with a binary padding (v1.3)
`String.strip/1` and `String.strip/2` | [v1.5] | `String.trim/1` and `String.trim/2` (v1.3)
`String.lstrip/1` and `String.rstrip/1` | [v1.5] | `String.trim_leading/1` and `String.trim_trailing/1` (v1.3)
`String.lstrip/2` and `String.rstrip/2` | [v1.5] | Use `String.trim_leading/2` and `String.trim_trailing/2` with a binary as second argument (v1.3)
`String.to_char_list/1` | [v1.5] | `String.to_charlist/1` (v1.3)
`()` to mean `nil` | [v1.5] | `nil` (v1.0)
`char_list/0` type | [v1.5] | `t:charlist/0` type (v1.3)
`:char_lists` key in `t:Inspect.Opts.t/0` type | [v1.5] | `:charlists` key (v1.3)
`:as_char_lists` value in `t:Inspect.Opts.t/0` type | [v1.5] | `:as_charlists` value (v1.3)
`@compile {:parse_transform, _}` in `Module` | [v1.5] | *None*
EEx: `<%=` in middle and end expressions | [v1.5] | Use `<%` (`<%=` is allowed only on start expressions) (v1.0)
`Access.key/1` | [v1.4] | `Access.key/2` (v1.3)
`Behaviour` module | [v1.4] | `@callback` module attribute (v1.0)
`Enum.uniq/2` | [v1.4] | `Enum.uniq_by/2` (v1.2)
`Float.to_char_list/2` | [v1.4] | `:erlang.float_to_list/2` (Erlang/OTP 17)
`Float.to_string/2` | [v1.4] | `:erlang.float_to_binary/2` (Erlang/OTP 17)
`HashDict` module | [v1.4] | `Map` (v1.2)
`HashSet` module | [v1.4] | `MapSet` (v1.1)
Multi-letter aliases in `OptionParser` | [v1.4] | Use single-letter aliases (v1.0)
`Set` module | [v1.4] | `MapSet` (v1.1)
`Stream.uniq/2` | [v1.4] | `Stream.uniq_by/2` (v1.2)
`IEx.Helpers.import_file/2` | [v1.4] | `IEx.Helpers.import_file_if_available/1` (v1.3)
`Mix.Utils.camelize/1` | [v1.4] | `Macro.camelize/1` (v1.2)
`Mix.Utils.underscore/1` | [v1.4] | `Macro.underscore/1` (v1.2)
Variable used as function call | [v1.4] | Use parentheses (v1.0)
Anonymous functions with no expression after `->` | [v1.4] | Use an expression or explicitly return `nil` (v1.0)
Support for making private functions overridable | [v1.4] | Use public functions (v1.0)
`Dict` module | [v1.3] | `Keyword` (v1.0) or `Map` (v1.2)
`Keyword.size/1` | [v1.3] | `Kernel.length/1` (v1.0)
`Map.size/1` | [v1.3] | `Kernel.map_size/1` (v1.0)
`Set` behaviour | [v1.3] | `MapSet` data structure (v1.1)
`String.valid_character?/1` | [v1.3] | `String.valid?/1` (v1.0)
`Task.find/2` | [v1.3] | Use direct message matching (v1.0)
`:append_first` option in `Kernel.defdelegate/2` | [v1.3] | Define the function explicitly (v1.0)
`/r` option in `Regex` | [v1.3] | `/U` (v1.1)
`\x{X*}` inside strings/sigils/charlists | [v1.3] | `\uXXXX` or `\u{X*}` (v1.1)
Map or dictionary as second argument in `Enum.group_by/3` | [v1.3] | `Enum.reduce/3` (v1.0)
Non-map as second argument in `URI.decode_query/2` | [v1.3] | Use a map (v1.0)
`Dict` behaviour | [v1.2] | `MapSet` data structure (v1.1)
`Access` protocol | [v1.1] | `Access` behaviour (v1.1)
`as: true \| false` in `alias/2` and `require/2` | [v1.1] | *None*
`?\xHEX` | [v1.1] | `0xHEX` (v1.0)
The first column is the version the feature was hard deprecated. The second column shortly describes the deprecated feature and the third column explains the replacement and from which the version the replacement is available from.
Version | Deprecated feature | Replaced by (available since)
:-------| :-------------------------------------------------- | :---------------------------------------------------------------
[v1.9] | Passing `:insert_replaced` to `String.replace/4` | Use `:binary.replace/4` (v1.0)
[v1.9] | Enumerable keys in `Map.drop/2`, `Map.split/2`, and `Map.take/2` | Call `Enum.to_list/1` on the second argument before hand (v1.0)
[v1.9] | `Mix.Project.load_paths/1` | `Mix.Project.compile_path/1` (v1.0)
[v1.9] | `--detached` in CLI | `--erl "-detached"` (v1.0)
[v1.8] | Passing a non-empty list to `Enum.into/2` | `Kernel.++/2` or `Keyword.merge/2` (v1.0)
[v1.8] | Passing a non-empty list to `:into` in `for` | `Kernel.++/2` or `Keyword.merge/2` (v1.0)
[v1.8] | `:seconds`, `:milliseconds`, etc. as time units | `:second`, `:millisecond`, etc. (v1.4)
[v1.8] | `Inspect.Algebra.surround/3` | `Inspect.Algebra.concat/2` and `Inspect.Algebra.nest/2` (v1.0)
[v1.8] | `Inspect.Algebra.surround_many/6` | `Inspect.Algebra.container_doc/6` (v1.6)
[v1.8] | `Kernel.ParallelCompiler.files/2` | `Kernel.ParallelCompiler.compile/2` (v1.6)
[v1.8] | `Kernel.ParallelCompiler.files_to_path/2` | `Kernel.ParallelCompiler.compile_to_path/2` (v1.6)
[v1.8] | `Kernel.ParallelRequire.files/2` | `Kernel.ParallelCompiler.require/2` (v1.6)
[v1.8] | `System.cwd/0` and `System.cwd!/0` | `File.cwd/0` and `File.cwd!/0` (v1.0)
[v1.8] | Returning `{:ok, contents}` or `:error` from `Mix.Compilers.Erlang.compile/6`'s callback | Return `{:ok, contents, warnings}` or `{:error, errors, warnings}` (v1.6)
[v1.7] | `Code.get_docs/2` | `Code.fetch_docs/1` (v1.7)
[v1.7] | Calling `super/1` on GenServer callbacks | Implenting the behaviour explicitly without calling `super/1` (v1.0)
[v1.7] | `Enum.chunk/2`[`/3/4`](`Enum.chunk/4`) | `Enum.chunk_every/2`[`/3/4`](`Enum.chunk_every/4`) (v1.5)
[v1.7] | `not left in right` | [`left not in right`](`Kernel.in/2`) (v1.5)
[v1.7] | `Registry.start_link/3` | `Registry.start_link/1` (v1.5)
[v1.7] | `Stream.chunk/2`[`/3/4`](`Stream.chunk/4`) | `Stream.chunk_every/2`[`/3/4`](`Stream.chunk_every/4`) (v1.5)
[v1.6] | `Enum.partition/2` | `Enum.split_with/2` (v1.4)
[v1.6] | `Keyword.replace/3` | `Keyword.fetch/2` + `Keyword.put/3` (v1.0)
[v1.6] | `Macro.unescape_tokens/1/2` | Use `Enum.map/2` to traverse over the arguments (v1.0)
[v1.6] | `Module.add_doc/6` | `@doc` module attribute (v1.0)
[v1.6] | `Map.replace/3` | `Map.fetch/2` + `Map.put/3` (v1.0)
[v1.6] | `Range.range?/1` | Pattern match on `_.._` (v1.0)
[v1.5] | `Atom.to_char_list/1` | `Atom.to_charlist/1` (v1.3)
[v1.5] | `Enum.filter_map/3` | `Enum.filter/2` + `Enum.map/2` or [`for`](`Kernel.SpecialForms.for/1`) comprehensions (v1.0)
[v1.5] | `Float.to_char_list/1` | `Float.to_charlist/1` (v1.3)
[v1.5] | `GenEvent` module | `Supervisor` and `GenServer` (v1.0);<br/>[`GenStage`](https://hex.pm/packages/gen_stage) (v1.3);<br/>[`:gen_event`](http://www.erlang.org/doc/man/gen_event.html) (Erlang/OTP 17)
[v1.5] | `Integer.to_char_list/1/2` | `Integer.to_charlist/1` and `Integer.to_charlist/2` (v1.3)
[v1.5] | `Kernel.to_char_list/1` | `Kernel.to_charlist/1` (v1.3)
[v1.5] | `List.Chars.to_char_list/1` | `List.Chars.to_charlist/1` (v1.3)
[v1.5] | `Stream.filter_map/3` | `Stream.filter/2` + `Stream.map/2` (v1.0)
[v1.5] | `String.ljust/3` and `String.rjust/3` | Use `String.pad_leading/3` and `String.pad_trailing/3` with a binary padding (v1.3)
[v1.5] | `String.strip/1` and `String.strip/2` | `String.trim/1` and `String.trim/2` (v1.3)
[v1.5] | `String.lstrip/1` and `String.rstrip/1` | `String.trim_leading/1` and `String.trim_trailing/1` (v1.3)
[v1.5] | `String.lstrip/2` and `String.rstrip/2` | Use `String.trim_leading/2` and `String.trim_trailing/2` with a binary as second argument (v1.3)
[v1.5] | `String.to_char_list/1` | `String.to_charlist/1` (v1.3)
[v1.5] | `()` to mean `nil` | `nil` (v1.0)
[v1.5] | `char_list/0` type | `t:charlist/0` type (v1.3)
[v1.5] | `:char_lists` key in `t:Inspect.Opts.t/0` type | `:charlists` key (v1.3)
[v1.5] | `:as_char_lists` value in `t:Inspect.Opts.t/0` type | `:as_charlists` value (v1.3)
[v1.5] | `@compile {:parse_transform, _}` in `Module` | *None*
[v1.5] | EEx: `<%=` in middle and end expressions | Use `<%` (`<%=` is allowed only on start expressions) (v1.0)
[v1.4] | `Access.key/1` | `Access.key/2` (v1.3)
[v1.4] | `Behaviour` module | `@callback` module attribute (v1.0)
[v1.4] | `Enum.uniq/2` | `Enum.uniq_by/2` (v1.2)
[v1.4] | `Float.to_char_list/2` | `:erlang.float_to_list/2` (Erlang/OTP 17)
[v1.4] | `Float.to_string/2` | `:erlang.float_to_binary/2` (Erlang/OTP 17)
[v1.4] | `HashDict` module | `Map` (v1.2)
[v1.4] | `HashSet` module | `MapSet` (v1.1)
[v1.4] | Multi-letter aliases in `OptionParser` | Use single-letter aliases (v1.0)
[v1.4] | `Set` module | `MapSet` (v1.1)
[v1.4] | `Stream.uniq/2` | `Stream.uniq_by/2` (v1.2)
[v1.4] | `IEx.Helpers.import_file/2` | `IEx.Helpers.import_file_if_available/1` (v1.3)
[v1.4] | `Mix.Utils.camelize/1` | `Macro.camelize/1` (v1.2)
[v1.4] | `Mix.Utils.underscore/1` | `Macro.underscore/1` (v1.2)
[v1.4] | Variable used as function call | Use parentheses (v1.0)
[v1.4] | Anonymous functions with no expression after `->` | Use an expression or explicitly return `nil` (v1.0)
[v1.4] | Support for making private functions overridable | Use public functions (v1.0)
[v1.3] | `Dict` module | `Keyword` (v1.0) or `Map` (v1.2)
[v1.3] | `Keyword.size/1` | `Kernel.length/1` (v1.0)
[v1.3] | `Map.size/1` | `Kernel.map_size/1` (v1.0)
[v1.3] | `Set` behaviour | `MapSet` data structure (v1.1)
[v1.3] | `String.valid_character?/1` | `String.valid?/1` (v1.0)
[v1.3] | `Task.find/2` | Use direct message matching (v1.0)
[v1.3] | `:append_first` option in `Kernel.defdelegate/2` | Define the function explicitly (v1.0)
[v1.3] | `/r` option in `Regex` | `/U` (v1.1)
[v1.3] | `\x{X*}` inside strings/sigils/charlists | `\uXXXX` or `\u{X*}` (v1.1)
[v1.3] | Map/dictionary as 2nd argument in `Enum.group_by/3` | `Enum.reduce/3` (v1.0)
[v1.3] | Non-map as 2nd argument in `URI.decode_query/2` | Use a map (v1.0)
[v1.2] | `Dict` behaviour | `MapSet` data structure (v1.1)
[v1.1] | `Access` protocol | `Access` behaviour (v1.1)
[v1.1] | `as: true \| false` in `alias/2` and `require/2` | *None*
[v1.1] | `?\xHEX` | `0xHEX` (v1.0)
[v1.1]: https://github.com/elixir-lang/elixir/blob/v1.1/CHANGELOG.md#4-deprecations
[v1.2]: https://github.com/elixir-lang/elixir/blob/v1.2/CHANGELOG.md#changelog-for-elixir-v12
@@ -148,4 +157,5 @@ Non-map as second argument in `URI.decode_query/2` | [v1.3] | Use a map (v1
[v1.5]: https://github.com/elixir-lang/elixir/blob/v1.5/CHANGELOG.md#4-deprecations
[v1.6]: https://github.com/elixir-lang/elixir/blob/v1.6/CHANGELOG.md#4-deprecations
[v1.7]: https://github.com/elixir-lang/elixir/blob/v1.7/CHANGELOG.md#4-hard-deprecations
[v1.8]: https://github.com/elixir-lang/elixir/blob/master/CHANGELOG.md#4-hard-deprecations
[v1.8]: https://github.com/elixir-lang/elixir/blob/v1.8/CHANGELOG.md#4-hard-deprecations
[v1.9]: https://github.com/elixir-lang/elixir/blob/v1.9/CHANGELOG.md#4-hard-deprecations
+3 -13
View File
@@ -106,15 +106,11 @@ def my_function(number) when is_integer(number) and rem(number, 2) == 0 do
end
```
This would be repetitive to write every time we need this check, so, as mentioned at the beginning of this section, we can abstract this away using a macro. Remember that defining a function that performs this check wouldn't work because we can't use custom functions in guards. Our macro would look like this:
This would be repetitive to write every time we need this check, so, as mentioned at the beginning of this section, we can abstract this away using a macro. Remember that defining a function that performs this check wouldn't work because we can't use custom functions in guards. Use `defguard` and `defguardp` to create guard macros. Here's an example:
```elixir
defmodule MyInteger do
defmacro is_even(number) do
quote do
is_integer(unquote(number)) and rem(unquote(number), 2) == 0
end
end
defguard is_even(value) when is_integer(value) and rem(value, 2) == 0
end
```
@@ -128,13 +124,7 @@ def my_function(number) when is_even(number) do
end
```
While it's possible to create custom guards with macros, it's recommended to define them using `defguard` and `defguardp` which perform additional compile-time checks. Here's an example:
```elixir
defmodule MyInteger do
defguard is_even(value) when is_integer(value) and rem(value, 2) == 0
end
```
While it's possible to create custom guards with macros, it's recommended to define them using `defguard` and `defguardp` which perform additional compile-time checks.
## Multiple guards in the same clause

Some files were not shown because too many files have changed in this diff Show More