You’re right, I had entirely the wrong semantics of replace in mind, so the above issue doesn’t exist like that. However, there still is some weirdness with the code. While the object that is being created is a valid member of C[A], the actual call to __replace__ is wrong in a sense. The calling function assumes that it looks like __replace__(self, *, attr: A) -> Self so passing attr=A() looks totally fine. But the passed object actually is a C[B] with __replace__(self, *, attr: B) -> Self.
But you might be right that this is a purely theoretical concern. We know that the actual function body of the replace method of C[B] is fine with an A being passed since it just creates a new instance of C[A]. Taking this reasoning further, I’m not sure if there even is the need to restrict the argument passed in the replace method at all. Having it be synthesised as __replace__[S](self, *, attr: S) -> C[S] would also work fine. Doing that would also mean that there’s no real need to special case the method further than just synthesizing it, which has to happen regardless.