I have found a workaround that allows for upsert of an untyped List<SObject> as outlined on the Salesforce Stack Exchange
. The only problem is that there is one signature for Database.upsert which, instead of throwing a TypeException when using an untyped list, causes a compile fail, namely the signature that takes all three arguments:
Database.upsert(List<SObject> records, SObjectField externalIdField, Boolean allOrNone)
If this final signature is validated at run-time instead of compile-time, a complete implementation of dynamic upsert will finally be possible.