Frases como "digitação estática" e "digitação dinâmica" são muito usadas, e as pessoas tendem a usar definições sutilmente diferentes, então vamos começar esclarecendo o que queremos dizer.
Considere uma linguagem que tenha tipos estáticos que são verificados em tempo de compilação. Mas digamos que um erro de tipo gere apenas um aviso não fatal e, em tempo de execução, tudo é tipificado por pato. Esses tipos estáticos são apenas para conveniência do programador e não afetam o codegen. Isso ilustra que a digitação estática por si só não impõe limitações e não é mutuamente exclusiva na digitação dinâmica. (Objective-C é muito parecido com isso.)
Mas a maioria dos sistemas do tipo estático não se comporta dessa maneira. Há duas propriedades comuns de sistemas de tipo estático que podem impor limitações:
O compilador pode rejeitar um programa que contém um erro de tipo estático.
Essa é uma limitação, porque muitos programas seguros de tipo necessariamente contêm um erro de tipo estático.
Por exemplo, eu tenho um script Python que precisa ser executado como Python 2 e Python 3. Algumas funções alteraram seus tipos de parâmetros entre Python 2 e 3, portanto, tenho um código como este:
if sys.version_info[0] == 2:
wfile.write(txt)
else:
wfile.write(bytes(txt, 'utf-8'))
Um verificador de tipo estático do Python 2 rejeitaria o código do Python 3 (e vice-versa), mesmo que nunca fosse executado. Meu programa de segurança de tipo contém um erro de tipo estático.
Como outro exemplo, considere um programa Mac que deseja executar no OS X 10.6, mas aproveite os novos recursos do 10.7. Os métodos 10.7 podem ou não existir em tempo de execução, e cabe a mim, o programador, detectá-los. Um verificador de tipo estático é forçado a rejeitar meu programa para garantir a segurança do tipo ou aceitar o programa, além da possibilidade de produzir um erro de tipo (falta de função) em tempo de execução.
A verificação de tipo estático pressupõe que o ambiente de tempo de execução seja descrito adequadamente pelas informações de tempo de compilação. Mas prever o futuro é arriscado!
Aqui está mais uma limitação:
O compilador pode gerar código que assume que o tipo de tempo de execução é o tipo estático.
Assumir que os tipos estáticos estão "corretos" oferece muitas oportunidades de otimização, mas essas otimizações podem ser limitativas. Um bom exemplo são objetos proxy, por exemplo, remoting. Digamos que você deseje ter um objeto proxy local que encaminhe invocações de métodos para um objeto real em outro processo. Seria bom se o proxy fosse genérico (para que ele possa se disfarçar como qualquer objeto) e transparente (para que o código existente não precise saber que está falando com um proxy). Mas, para fazer isso, o compilador não pode gerar código que pressupõe que os tipos estáticos estejam corretos, por exemplo, através de chamadas de método com estaticamente embutido, porque isso falhará se o objeto for realmente um proxy.
Exemplos dessa comunicação remota em ação incluem o NSXPCConnection da ObjC ou o TransparentProxy do C # (cuja implementação exigiu algumas pessimizações no tempo de execução - veja aqui para uma discussão).
Quando o codegen não depende dos tipos estáticos e você possui recursos como encaminhamento de mensagens, pode fazer muitas coisas legais com objetos proxy, depuração etc.
Portanto, é uma amostra de algumas das coisas que você pode fazer se não precisar satisfazer um verificador de tipos. As limitações não são impostas por tipos estáticos, mas pela verificação de tipo estático imposta.