How to handle type-checking an already type-checked AST node?

For context: I'm currently trying to implement a crude prototype of a MacroExpansionContext.getType(of:) function that allows a macro expansion to get the type of an expression as a TypeSyntax (stay tuned for a pitch, I just wanted to try out some designs of the feature first).

During a macro expansion, I want to run TypeChecker::typeCheckExpression (with macro expansion disabled) on an expression to get its type, so that I can query types from within the macro expansion plugin. This works fine, however, I'm running into the problem that apparently "The constraint system does not support type checking an already-type-checked AST" (as is noted in this PR), which results in the type-checker crashing after the macro expansion when it tries to check the same expression again.

Why is the type-checker designed that way? How hard would it be to support re-type-checking an AST?

Assuming the answer is "very hard", what alternative solutions to this problem can you think of?
For my prototype, I got quite far simply by adding an isAlreadyTypeChecked field to Expr and skipping an expression during type-checking when this field is set to true, but that's obviously not a real solution, because the expression actually has to be type-checked again (with macro expansions enabled).
Another idea I had was to literally deep-clone the expression AST and to type-check the cloned expression instead. This could probably work for an initial prototype of this feature (although it would be a big hassle to implement, as the AST currently just doesn't support cloning at all, so one would have to implement a clone function for every single AST node class...), but isn't a real solution either, because it would mean that all the type-checking work has to be done twice for that (possibly arbitrarily large) expression.

As I'm quite new to Swift compiler development, it's possible (very likely actually) that I'm missing some obvious solution, so hopefully one of you more seasoned Swift compiler developers can enlighten me :)

Cheers,
Josef

If an expression has already been type checked, you can call the getType() method to get its type. There is no reason to type check it again. Type checking is a side effectful operation which rewrites the AST by replacing identifiers with declaration references, inserting nodes representing implicit conversions, forming auto closures, etc. So it doesn’t make sense to type check an expression twice, conceptually, because the first type checking operation effectively destroys the original data structure (and while this is not a very good design for a type checker because it’s quite error prone, in your scenario it would waste CPU time to repeat the computation even if you could).

Maybe I'm misunderstanding something, but if I'm type-checking an expression without expanding macros first (which is definitely what I want to do if I'm currently inside another macro expansion), I surely have to type-check that expression again with expanding macros, so that it is "properly" type-checked in the end, no?

If you’re in the middle of type checking an expression you can look at the type of a sub expression using ConstraintSystem::getType(). It is also possible to type check an expression and get its type without applying a solution (which is the side effectful part). Hopefully one of these will apply in your situation, or at least lead to another approach.