Note that you can observe this effect with not just do, but also let, if, \, case, the extensions mdo and proc…and the dread unary -. I cannot think of a case in which this is ambiguous except for unary -. Here’s how the grammar is defined in the Haskell 2010 Language Report, §3: Expressions.
exp
→ infixexp :: [context =>] type
| infixexp
infixexp
→ lexp qop infixexp
| - infixexp
| lexp
lexp
→ \ apat1 … apatn -> exp
| let decls in exp
| if exp [;] then exp [;] else exp
| case exp of { alts }
| do { stmts }
| fexp
fexp
→ [fexp] aexp
aexp
→ ( exp )
| …
There just happens to be no case defined in fexp (function application) or aexp (literal expression) that allows an unparenthesised lexp (lambda, let, etc.). I would consider this a bug in the grammar.
Fixing this would also remove the need for the $ typing hack.