Try using typ() instead of typ in the match line:
        class aaa():
            pass
        class bbb():
            pass
        def f1(typ):
            if typ is aaa:
                print("aaa")
            elif typ is bbb:
                print("bbb")
            else:
                print("???")
        def f2(typ):
            match typ():
                case aaa():
                    print("aaa")
                case bbb():
                    print("bbb")
                case _:
                    print("???")
        f1(aaa)
        f1(bbb)
        f2(aaa)
        f2(bbb)        
Output:
aaa
bbb
aaa
bbb
UPDATE:
Based on OP's comment asking for solution that works for classes more generally than the example classes in the question, here is an answer addressing this:
class aaa():
    pass
class bbb():
    pass
def f1(typ):
    if typ is aaa:
        print("aaa")
    elif typ is bbb:
        print("bbb")
    else:
        print("???")
def f2(typ):
    match typ.__qualname__:
        case aaa.__qualname__:
            print("aaa")
        case bbb.__qualname__:
            print("bbb")
        case _:
            print("???")
f1(aaa)
f1(bbb)
f2(aaa)
f2(bbb)
Output:
aaa
bbb
aaa
bbb
UPDATE #2:
Based on this post and some perusal of PEP 364 here, I have created an example showing how a few data types (a Python builtin, a class from the collections module, and a user defined class) can be used by match to determine an action to perform based on a class type (or more generally, a data type):
class bbb:
    pass
class namespacing_class:
    class aaa:
        pass
def f1(typ):
    if typ is aaa:
        print("aaa")
    elif typ is bbb:
        print("bbb")
    else:
        print("???")
def f2(typ):
    match typ.__qualname__:
        case aaa.__qualname__:
            print("aaa")
        case bbb.__qualname__:
            print("bbb")
        case _:
            print("???")
def f3(typ):
    import collections
    match typ:
        case namespacing_class.aaa:
            print("aaa")
        case __builtins__.str:
            print("str")
        case collections.Counter:
            print("Counter")
        case _:
            print("???")
'''
f1(aaa)
f1(bbb)
f2(aaa)
f2(bbb)
'''
f3(namespacing_class.aaa)
f3(str)
import collections
f3(collections.Counter)
Outputs:
aaa
str
Counter
As stated in this answer in another post:
A variable name in a case clause is treated as a name capture pattern. It always matches and tries to make an assignment to the variable name. ... We need to replace the name capture pattern with a non-capturing pattern such as a value pattern that uses the . operator for attribute lookup. The dot is the key to matching this a non-capturing pattern.
In other words, if we try to say case aaa: for example, aaa will be interpreted as a name to which we assign the subject (typ in your code) and will always match and block any attempts to match subsequent case lines.
To get around this, for class type names (or names generally) that can be specified using a dot (perhaps because they belong to a namespace or another class), we can use the dotted name as a pattern that will not be interpreted as a name capture.
For built-in type str, we can use case __builtins__.str:. For the Counter class in Python's collections module, we can use case collections.Counter:. If we define class aaa within another class named namespacing_class, we can use case namespacing_class.aaa:.
However, if we define class bbb at the top level within our Python code, it's not clear to me that there is any way to use a dotted name to refer to it and thereby avoid name capture.
It's possible there's a way to specify a user defined class type in a case line and I simply haven't figured it out yet. Otherwise, it seems rather arbitrary (and unfortunate) to be able to do this for dottable types and not for non-dottable ones.