Complex number class in Lua
Possible improvements:

localise functions of the math library, and sometimes, self.r and self.i. It will reduce the number of table lookups, improving the performance, and will make the expressions simpler,
define __call metamethod for Complex. It will allow you to replace Complex:new(x, y) with Complex(x, y),
the 'constructor' should correctly handle cases when it is called with one parameter (Complex(5) = 5 + 0i). It can be achieved with a 'nullsafe operator' emulated as local a = user_input or value_if_null,
in can also be mage more concise, using the fact that setmetatable returns the table,
__newindex metamethod is not needed here, unless you want to set the absolute value or phase with code like c.abs = 1 or code.arg = pi. In addition, the arguments to __newindex are the table, the absent key and value set to it,
you can emulate the ternary operator in Lua with and and or, which allows to write more concise code: local a = condition and on_success or on_failure,
your arithmetic metamethods should handle the cases when the first argument (self) is a number,
you can define conjugate method and __div metamethod for division,
to square a table element, I'd rather recommend using ^ rather than multiplying by self. It will save table lookups,
you can define methods for polar coordinates and __pow metamethod for Euler's and de Moivre's formulae,
the __lt metamethod can be simplified using boolean operations,
Complex:tostring() can be simplified using 'ternary' operators, also handling of negative imaginary parts can be improved,
whether to put the i before or after the imaginary part and whether to surround a complex number with parentheses can be customisable,
the expression tests can be automated somewhat with load() function.

The improved code:
local math = require 'math'
local pi, sin, cos, atan2 = math.pi, math.sin, math.cos, math.atan2
local sqrt, exp, ln = math.sqrt, math.exp, math.log
local floor, abs = math.floor, math.abs

Complex = {
    IMAGINARY_CHAR = 'i'
}

function Complex:new(r, i)
    self.__index = self
    return setmetatable({r = r, i = i or 0}, self)
end

setmetatable(Complex, {
    __call = function (tbl, r, i)
        return Complex:new(r, i)
    end}
)

function Complex:__unm()
    return Complex(-self.r, -self.i)
end

function Complex:__add(other)
    return type(self) == 'number'   and Complex(self + other.r, other.i)
        or type(other) == 'number'  and Complex(self.r + other, self.i)
                                     or Complex(self.r + other.r, self.i + other.i)
end

function Complex:__sub(other)
    return self + (-other);
end

function Complex:__mul(other)
    return type(self) == 'number'   and Complex(self * other.r, self * other.i)
        or type(other) == 'number'  and Complex(self.r * other, self.i * other)
                                     or Complex(self.r * other.r - self.i * other.i, self.i * other.r + self.r * other.i)
end

function Complex:conjugate()
    return Complex(self.r, -self.i)
end

function Complex:__div(denominator)
    -- https://www.mesacc.edu/~scotz47781/mat120/notes/complex/dividing/dividing_complex.html
    local conjugate = type(denominator) == 'number' and Complex(denominator) or denominator:conjugate()
    local new_numerator, new_denominator = self * conjugate, denominator * conjugate
    -- new_denominator is real.
    return Complex(new_numerator.r / new_denominator.r, new_numerator.i / new_denominator.r)
end

function Complex:abs2()
    return self.r ^ 2 + self.i ^ 2
end

function Complex:abs()
    return sqrt(self:abs2())
end

function Complex:polar(abs, arg)
    return Complex(abs * cos(arg), abs * sin(arg))
end

function Complex:arg()
    return atan2(self.i, self.r)
end

function Complex:exp()
    local abs = exp(self.r)
    return Complex(abs * cos(self.i), abs * sin(self.i))
end
    
function Complex:__pow(power)
    -- Euler:
    local x = type(self) == 'number' and self or self.i == 0 and self.r &gt; 0 and self.r or nil
    if x then
        return (power * ln(x)):exp()
    else
    -- de Moivre:
        local n = type(power) == 'number' and power or power.i == 0 and power.r or nil
        if n and floor(n) == n then
            local abs, arg = self:abs(), self:arg()
            return Complex:polar(abs ^ n, n * arg)
        end
    end
end

function Complex:__eq(other)
    return
        self.r == other.r and
        self.i == other.i
end

function Complex:__lt(other)
    -- incorrect but for sorting:
    return self.r &lt; other.r or self.r == other.r and self.i &lt; other.i
end

function Complex:tostring(i, prefix, parentheses)
    local im = type(i) == 'string' and i or 'i'
    local r, i = self.r, self.i
    
    local str = ((r ~= 0 or i == 0) and r or '')
             .. (r ~= 0 and i &gt; 0 and ' + ' or '')
             .. (i &lt; 0 and ' - ' or '')
             .. (i ~= 0 and (prefix and im .. abs(i) or abs(i) .. im) or '')
    if parentheses then
        str = '(' .. str .. ')'
    end
    return str
end

function Complex:__tostring(i)
    return self:tostring(self.IMAGINARY_CHAR, self.PREFIX, self.PARENTHESES)
end

-- ===================================================================

Complex.IMAGINARY_CHAR = 'j' -- electrical notation.
Complex.PREFIX = true
Complex.PARENTHESES = true

local context = {
    Complex = Complex,
    x = Complex(0, 10),
    y = Complex(-5, 5),
    z = Complex(5),
    pi = pi
}

local cases = {
    'x', 'y', 'z', 'x:abs()',
    'x + y', 'x - y', 'x + 12', 'x - 12', '12 + x', '12 - x',
    'x * y', 'y * 12', '2 * x',
    'y:conjugate()', 'x / y', 'y / y', 'x / 5', '5 / x', '5 / x * x',
    'y:abs()', 'y:arg() / pi * 180', 'Complex:polar(2, pi / 4)', 'y:exp()', 'x ^ 2', '2 ^ x',
    'x == y', 'x ~= y', 'x &lt; y', 'x &gt; y', 'x &lt;= y', 'x &gt;= y'
}
for _, expr in ipairs(cases) do
    print(expr, assert(load('return ' .. expr, nil, 't', context))())
end
```