Type Narrowing with Structural Pattern Matching

How do you type an untyped language? That's a question we are trying to solve from the beginning of time it seems!
Instead of focusing on implementing a static type system like every other language, why not try something different? I had this idea of using pattern matching as a means of specifying types as well as binding values for quite some time.
The below :point_down: is my take on this idea, it's a little rough and a little ugly syntax wise but I think it's a start to improve upon.

// reserved words or literal values: null, undefined, Infinity, NaN, 10, "hello", true, ...
function foo(null) { ... }

// boolean
function foo(!!a) { ... }

// number
function foo(+a, -b) { ... }

// string
function foo(`hello ${a}`) { ... }

// symbol
function  foo(Symbol`abc ${a}` as Bar) { ... }

// bigint
function foo(+a!n) { ... }

// map
function foo(Map[[+a, +b], ...c]) { ... }

// set
function foo(Set[+a, ...b]) { ... }

// array/iterable
function foo([+a, ...c]) { ... }

// object
function foo({ a: +a, b: !!b }) { ... }

// function
function foo((+#a, `${#b}`) => !!#c) { ... }

Many of the methods given above uses coercion if needed. For example if you supply a string to function foo(+a) { ... }, it'll try to convert the given value into a number and throws an error if the result is anything except a numeric literal. i.e it throws for Infinity, NaN, etc.
There should also be a way to get the whole value matched by the pattern and that's the job of the as binding.
Also an interesting thing about function pattern is that the named bindings in the parameter list return type begin # because it makes it so that those bindings are not accessible.
If we were to make the parameter and return type of a callback function accessible in the calling function, how would that work? Using promises maybe🤔

What do you think, extending functions with more patterns is a good idea or not?
Also please take the syntax shown here with a grain of salt, it's just for demonstrating the idea.