Pular para o conteúdo

A superfície de um módulo

Como o analisador lê o que outro arquivo oferece, sem executá-lo — e quando ele prefere calar.

P.naoExiste() e P.criar(1, 2, 3) são acusados antes de rodar, mesmo quando P vem de outro arquivo. É a checagem que mais importa em sistema grande: num arquivo de 40 linhas o erro aparece na primeira execução; num de 200 arquivos, a maioria das chamadas cruza módulo, e todas elas eram invisíveis.

Como ele sabe, sem executar#

superficie.py lê o outro arquivo com o lexer e o parser, e não com o interpretador. Do que encontra, guarda o que atravessa a fronteira:

GuardaPara conferir
os nomes exportadosP.naoExiste — o membro que não existe
a aridade de cada açãoP.criar(1, 2, 3) com dois parâmetros
o tipo de cada parâmetroD.valor_de(texto) onde se espera Integer
o tipo de retornoP.criar(…).clientte — o campo errado, do outro lado
os campos de cada recordo mesmo, um nível adiante

O tipo de retorno atravessar é o que mais rende. Sem ele, uma ação que declara -> Pedido virava um valor sem tipo no outro arquivo, e o campo com nome quase certo passava no check.

A tradução de vocabulário#

O -> Pedido declarado lá é P.Pedido aqui. Devolver o nome nu faria o analisador procurar um record que este arquivo não declara — e a primeira versão fazia isso, acusando o código certo:

text
Parameter 'p' of 'P.com_total' expects Pedido but got P.Pedido

Um falso alarme no caminho mais comum de um projeto modular ensina a desligar a verificação inteira. Hoje a tradução acontece nos dois lados, e quando não dá para concluir o analisador cala.

Quando ele cala#

Cala quandoPorque
o outro arquivo não compilauma superfície lida de código quebrado é palpite
há ciclo de importnão há ordem em que a leitura termine
a profundidade (4) acabaseguir a cadeia inteira levaria o check a minutos
o relay nomeia algo que só existe em execuçãoo nome pode ser qualquer coisa
o módulo não exporta aquele tipoo tipo é interno, e o nome daqui não o alcança

Em todos esses casos a superfície devolve aberta, e a conferência volta a ficar em silêncio. Um falso alarme é pior que um silêncio.

O escopo de quem chama#

A inferência usa o escopo de quem chama, e não o global. Parece detalhe e não é: D.valor_de(n) dentro de action f(n) virou "Undefined name 'n'" — 649 falsos alarmes num projeto gerado de 252 arquivos, um por uso de parâmetro numa chamada entre módulos.

E a suíte passava: os primeiros testes chamavam no nível de topo, onde o escopo global é o certo. O bug só aparecia dentro de uma ação, que é onde quase todo código vive. Quem pegou foi rodar o check no projeto grande.

O cache#

A superfície é guardada por (caminho, mtime). Sem isso, 200 arquivos importando três vizinhos cada levariam o check de 0,7 s a mais de um minuto — e um analisador que demora um minuto não roda a cada salvar, que é quando ele vale.

Por onde seguir#