I agree that the behavior of match in Typed Racket is less useful than people coming from languages like ML or Haskell hope it might be.
[I started writing before dinner, and meanwhile @jbclements has explained, but perhaps some of this is still useful.]
To understand the behavior of your program, it might help to think about match expanding it to code like this:
#lang typed/racket
(: foo (All [T] (-> (-> (Listof T)) Number)))
(define (foo l1)
(cond
[(null? l1)
0]
[(list? l1)
(define l2 (rest l1))
(+ 1 (foo l2))]
[else
(error "no match")]))
Typed Racket actually does note that the branches are impossible, or your program would fail to typecheck!
The type of list? is (-> Any Boolean : (Listof Any)), where the proposition at the end says what the typechecker can know about any value for which list? returns #t.
By hovering over the closing parenthesis in DrRacket, we can see what Typed Racket has conclusded the type of the expression (list? l1) is False.
I'll leave the room for improvement for others to discuss in more detail. It does seem like there could be some more useful warning about bottom cases, but I wouldn't be shocked if there's macro-generated code that relies on this behavior to typecheck. A pattern-matching form that does more useful things with types would certainly be nice to have, but I also know there are challenges to writing one.
The fact that match has an implicit else clause raising a runtime error surprises people who are used to pattern matching with an exhaustiveness checker. Interestingly, while in an untyped context match's behavior is far preferable, cond's implicit default [else (void)] is more useful for catching bugs in Typed Racket.
Maybe there are improvements that could be made to the pattern-matching caveats in the guide? 8 Caveats and Limitations
I tend to use match less in Typed Racket for these reasons. For convenient destructuring binding, you can get much of the same benefit using forms like match-define or match-let once you know the shape of the value you have.