Why is int x{ y = 5 } possible?

The operator=() results in a value, which is the value assigned to the variable. Because of this, it is possible to chain assignments like this:

int x, y, z;
x = y = z = 1;

I will start from your last question

Also, why doesn't the compiler or IDE complain about main() not returning an int?

According to the C++ Standard (6.6.1 main function)

5 A return statement in main has the effect of leaving the main function (destroying any objects with automatic storage duration) and calling std::exit with the return value as the argument. If control flows off the end of the compound-statement of main, the effect is equivalent to a return with operand 0 (see also 18.3).

And relative to this question

How is this possible, since y = 5 is not a calculable expression?

From the C++ Standard (8.18 Assignment and compound assignment operators)

1 The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand.

Sp this declaration

int x{ y = 5 };

can be equivalently split into two statements

y = 5;
int x{ y };

Moreover in C++ you can even to make a reference to the variable y the following way

int &x{ y = 5 };

Here is a demonstrative program

#include <iostream>

int main() 
{
    int y;
    int &x{ y = 5 };    

    std::cout << "y = " << y << '\n';

    x = 10;

    std::cout << "y = " << y << '\n';
}

Its output is

y = 5
y = 10

You may this declaration

int x{ y = 5 };

rewrite also like

int x = { y = 5 };

However take into account that there is a difference between these (looking similarly as the above declarations) two declarations.

auto x{ y = 5 };

and

auto x = { y = 5 };

In the first declaration the variable x has the type int. In the second declaration the variable x has the type std::initializer_list<int>.

To make the difference more visible see how the values of the objects are outputted.

#include <iostream>

int main() 
{
    int y;
    auto x1 { y = 5 };  

    std::cout << "x1 = " << x1 << '\n';

    auto x2 = { y = 10 };   

    std::cout << "*x2.begin()= " << *x2.begin() << '\n';

    std::cout << "y = " << y << '\n';

    return 0;
}

The program output is

x1 = 5
*x2.begin()= 10
y = 10

How is this possible, since y = 5 is not a calculable expression?

It is an assignment, and assignments yield values, i.e. the "cv-unqualified type of the left operand", see [expr.ass/3]. Hence y = 5 results in y, which is 5, which is used to initialize x.

With respect to your second question, see cppreference on main (or [basic.start.main/5]):

The body of the main function does not need to contain the return statement: if control reaches the end of main without encountering a return statement, the effect is that of executing return 0;.

Hence, compiler or IDE warning you about a missing return statement at the end of main would be plain wrong. Admittedly, the fact that you should always return objects from non-void functions execpt main is kind of... well, for historical reason I guess.