Abstract type annotations of method arguments aren’t a pattern for enabling a feature or solving a limitation in the first place, it’s just a part of method dispatch. foo(a::AbstractArray) isn’t inherently more correct or beneficial than foo(a::Array); it just depends on whether you want a method that works on AbstractArrays or just Arrays. You may need both methods because you need to do something specific to Arrays that you can’t or shouldn’t put into the AbstractArray method; you can find examples in other languages that must instead put both versions in type-checking branches e.g. if a isa Array _foo_Array(a) else _foo_AbstractArray(a) end. Note that Julia’s compiler has been able to optimize away type-checked branches for a long time, but a practical reason to avoid that is editing the method forces recompilation of the larger method and the summed methods that called it.
The distinction between method dispatch and compiled specializations is documented here.
Your assumption is mostly correct except abstract type annotations indeed open up method ambiguities, and trivially so. If we were only able to annotate concrete types in methods, it’s impossible for a function call’s concrete types to match multiple methods, let alone a set that lacks a most specific one. We can draw a parallel in ambiguity from a Tuple{...} type containing a function call’s concrete types with multiple Tuple{...} supertypes representing methods to a class with multiple parent classes. There are principles for gracefully avoiding method ambiguities; unless you’re weaving a particularly complex web of methods, it’s easier than it looks.