The issue here is that the parameter of Future<T>.value has the type FutureOr<T>. It can be either a future or a value.
Also, Dart type inference works by "pushing down" a context type, then trying to make the expression work at that type, and finally pushing the final static type back up.
If an expression like Future.value(...) has a context type, the missing type argument is always inferred from the context type.
When you write
Future<Future<User>> fut2 = Future.value(Future.value(Student()));
the context type of the outer Future.value, the type we know it should have, is Future<Future<User>>. That makes its argument have context type FutureOr<Future<User>>.
The argument is Future.value(Student()), where we don't yet know anything about Student() because we haven't gotten to it in the type inference yet, we're still working out way down towards it.
A Future<X> can satisfy that FutureOr<Future<User>> in two ways, either by being a Future<User> or by being a Future<Future<User>>.
Type inference then guesses that it's the latter. It's wrong, but it can't see that yet. The way type inference works, it has to use the context type when there is one, but the context type is ambiguous, and it ends up choosing the wrong option.
You are hitting an edge case of the type inference where the context type can be satisfied in two different ways, and the downwards type inference chooses the wrong one. It's a good heuristic that if you have a FutureOr<...> context type, and you see a Future constructor, you want the Future-part of the FutureOr<...>. It breaks down when you have FutureOr<Future<...>>. So, don't do that!
My recommendation, in complete generality, is to never have a Future<Future<anything>> in your program. Not only does it avoid problems like this, but it's also a better model for your code.
A future which eventually completes to something which eventually completes to a value ... just make it eventually complete to that value directly. Waiting for the intermediate future is just needless busywork.