Skip to content

Commit e1ff781

Browse files
committed
Fix deprecation warning on !left in right, closes #15001
1 parent 82983cf commit e1ff781

File tree

3 files changed

+74
-42
lines changed

3 files changed

+74
-42
lines changed

lib/elixir/pages/references/operators.md

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,6 @@
88

99
This document is a complete reference of operators in Elixir, how they are parsed, how they can be defined, and how they can be overridden.
1010

11-
## Operator precedence and associativity
12-
13-
The following is a list of all operators that Elixir is capable of parsing, ordered from higher to lower precedence, alongside their associativity:
14-
15-
Operator | Associativity
16-
---------------------------------------------- | -------------
17-
`@` | Unary
18-
`.` | Left
19-
`+` `-` `!` `^` `not` | Unary
20-
`**` | Left
21-
`*` `/` | Left
22-
`+` `-` | Left
23-
`++` `--` `+++` `---` `..` `<>` | Right
24-
`//` (valid only inside `..//`) | Right
25-
`in` `not in` | Left
26-
`\|>` `<<<` `>>>` `<<~` `~>>` `<~` `~>` `<~>` | Left
27-
`<` `>` `<=` `>=` | Left
28-
`==` `!=` `=~` `===` `!==` | Left
29-
`&&` `&&&` `and` | Left
30-
`\|\|` `\|\|\|` `or` | Left
31-
`=` | Right
32-
`&`, `...` | Unary
33-
`\|` | Right
34-
`::` | Right
35-
`when` | Right
36-
`<-` `\\` | Left
37-
`=>` (valid only inside `%{}`) | None
38-
39-
Elixir also has two ternary operators:
40-
41-
Operator | Associativity
42-
---------------------------------------------- | -------------
43-
`first..last//step` | Right
44-
`%{map \| key => value, ...}` | None
45-
4611
## General operators
4712

4813
Elixir provides the following built-in operators:
@@ -60,7 +25,7 @@ Elixir provides the following built-in operators:
6025
* [`|>`](`|>/2`) - pipeline
6126
* [`=~`](`=~/2`) - text-based match
6227

63-
Many of those can be used in guards; consult the [list of allowed guard functions and operators](patterns-and-guards.md#list-of-allowed-functions-and-operators).
28+
Many of those can be used in guards. Consult the [list of allowed guard functions and operators](patterns-and-guards.md#list-of-allowed-functions-and-operators).
6429

6530
Additionally, there are a few other operators that Elixir parses but doesn't actually use.
6631
See [Custom and overridden operators](#custom-and-overridden-operators) below for a list and for guidelines about their use.
@@ -73,7 +38,7 @@ Some other operators are special forms and cannot be overridden:
7338
* [`&`](`&/1`) - capture operator
7439
* [`::`](`::/2`) - type operator
7540

76-
Finally, these operators appear in the precedence table above but are only meaningful within certain constructs:
41+
Finally, these operators appear in the precedence table below but are only meaningful within certain constructs:
7742

7843
* `=>` - see [`%{}`](`%{}/1`)
7944
* `when` - see [Guards](patterns-and-guards.md#guards)
@@ -104,9 +69,47 @@ false
10469

10570
[`!=`](`!=/2`) and [`!==`](`!==/2`) act as the negation of [`==`](`==/2`) and [`===`](`===/2`), respectively.
10671

107-
## Custom and overridden operators
10872

109-
### Defining custom operators
73+
## Operator precedence and associativity
74+
75+
The following is a list of all operators that Elixir is capable of parsing, ordered from higher to lower precedence, alongside their associativity:
76+
77+
Operator | Associativity
78+
---------------------------------------------- | -------------
79+
`@` | Unary
80+
`.` | Left
81+
`+` `-` `!` `^` `not` | Unary
82+
`**` | Left
83+
`*` `/` | Left
84+
`+` `-` | Left
85+
`++` `--` `+++` `---` `..` `<>` | Right
86+
`//` (valid only inside `..//`) | Right
87+
`in` `not in` | Left
88+
`\|>` `<<<` `>>>` `<<~` `~>>` `<~` `~>` `<~>` | Left
89+
`<` `>` `<=` `>=` | Left
90+
`==` `!=` `=~` `===` `!==` | Left
91+
`&&` `&&&` `and` | Left
92+
`\|\|` `\|\|\|` `or` | Left
93+
`=` | Right
94+
`&`, `...` | Unary
95+
`\|` | Right
96+
`::` | Right
97+
`when` | Right
98+
`<-` `\\` | Left
99+
`=>` (valid only inside `%{}`) | None
100+
101+
Elixir also has two ternary operators:
102+
103+
Operator | Associativity
104+
---------------------------------------------- | -------------
105+
`first..last//step` | Right
106+
`%{map \| key => value, ...}` | None
107+
108+
> #### Deprecated operator precedence {: .info}
109+
>
110+
> Elixir parses `not left in right` as `not(left in right)` and `!left in right` as `!(left in right)`, which mismatches the precedence table above, but such behaviour is deprecated and emits a warning. Both constructs must be written as `left not in right` instead. In future major versions, the parser will match the table above.
111+
112+
## Custom and overridden operators
110113

111114
Elixir is capable of parsing a predefined set of operators. It's not possible to define new operators (as supported by some languages). However, not all operators that Elixir can parse are *used* by Elixir: for example, `+` and `||` are used by Elixir for addition and boolean *or*, but `<~>` is not used (but valid).
112115

lib/elixir/src/elixir_parser.yrl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -745,11 +745,14 @@ build_op(AST, {_Kind, Location, '//'}, Right) ->
745745
return_error(Location, "the range step operator (//) must immediately follow the range definition operator (..), for example: 1..9//2. If you wanted to define a default argument, use (\\\\) instead. Syntax error before: ", "'//'")
746746
end;
747747

748-
build_op({UOp, _, [Left]}, {_Kind, {Line, Column, _} = Location, 'in'}, Right) when ?rearrange_uop(UOp) ->
748+
build_op({UOp, UMeta, [Left]}, {_Kind, {Line, Column, _} = Location, 'in'}, Right) when ?rearrange_uop(UOp) ->
749749
%% TODO: Remove "not left in right" rearrangement on v2.0
750-
warn({Line, Column}, "\"not expr1 in expr2\" is deprecated, use \"expr1 not in expr2\" instead"),
750+
warn({Line, Column}, case UOp of
751+
'!' -> "\"!expr1 in expr2\" is deprecated, use \"expr1 not in expr2\" instead";
752+
'not' -> "\"not expr1 in expr2\" is deprecated, use \"expr1 not in expr2\" instead"
753+
end),
751754
Meta = meta_from_location(Location),
752-
{UOp, Meta, [{'in', Meta, [Left, Right]}]};
755+
{UOp, UMeta, [{'in', Meta, [Left, Right]}]};
753756

754757
build_op(Left, {in_op, NotLocation, 'not in', InLocation}, Right) ->
755758
NotMeta = newlines_op(NotLocation) ++ meta_from_location(NotLocation),

lib/elixir/test/elixir/kernel/parser_test.exs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,32 @@ defmodule Kernel.ParserTest do
526526
]}
527527
end
528528

529+
test "deprecated not/in" do
530+
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
531+
assert Code.string_to_quoted!("not a in b", columns: true) ==
532+
{:not, [line: 1, column: 1],
533+
[
534+
{:in, [line: 1, column: 7],
535+
[
536+
{:a, [line: 1, column: 5], nil},
537+
{:b, [line: 1, column: 10], nil}
538+
]}
539+
]}
540+
end) =~ "not expr1 in expr2"
541+
542+
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
543+
assert Code.string_to_quoted!("!a in b", columns: true) ==
544+
{:!, [line: 1, column: 1],
545+
[
546+
{:in, [line: 1, column: 4],
547+
[
548+
{:a, [line: 1, column: 2], nil},
549+
{:b, [line: 1, column: 7], nil}
550+
]}
551+
]}
552+
end) =~ "!expr1 in expr2"
553+
end
554+
529555
test "handles maps and structs" do
530556
assert Code.string_to_quoted("%{}", columns: true) ==
531557
{:ok, {:%{}, [line: 1, column: 1], []}}

0 commit comments

Comments
 (0)