Compiladores (CC3001)
Aula 9: Análise de tipos
Pedro Vasconcelos
DCC/FCUP
Esta aula
Sistemas de Tipos Análise de Tipos Expressões Declarações e comandos Funções ExtrasRe-lembrar: fases dum compilador
texto do programa ↓ Análise lexical ↓ sequência de tokens ↓ Análise sintática ↓árvore sintática abstrata ↓
Análise semântica ↓
AST & tabela de símbolos
Geração de código ↓
código intermédio
Seleção de instruções ↓
código assembly simbólico ↓
Alocação de registos ↓
código assembly concreto ↓
Assembler & linker ↓
Sistemas de Tipos
Um sistema de tipos é um conjunto de regras lógicasda linguagem que todos os programas devem respeitar.
Exemplos de regras:
I +, -, *, / só operam sobre números I a condição de if deve ser um boleano
I numa atribuição var = expr a variável deve ser declarada com um tipo compatível com o da a expressão
Diferentes Sistemas de Tipos
Podemos classicar em dois eixos separados:
Estáticos vericação ocorreantes da execução do programa
vs.
Dinâmicos vericação ocorredurante a execuçãodo programa
Fortes operações com erros de tipos são proibidas
vs.
Diferentes Sistemas de Tipos (cont.)
Exemplos:
I Haskell, SML, Java: tipos estáticos e fortes
I Python, Scheme: tipos dinâmicos e fortes
I C: tipos estáticos e fracos
I Estas classicações são graduais e não absolutas
e.g. o sistema de tipos de Java é mais forte que o de C mas mais fraco que o de Haskell I A distinção estático/dinâmico deixa de fazer sentido para código máquina
Diferentes Sistemas de Tipos (cont.)
Porquê análise de tipos?
I Efetuar a análise de tipos durante a compilação (estática):
I evita vericações durante a execução do programa I permite gerar código mais eciente
Atributos
I A análise de tipos pode ser feita como uma (ou mais) travesia da AST
I Como as ASTs são estruturas recursivas, a travesia será implementada como funções recursivas
I As traversias constroem atributos; exemplos: I o tipo de cada sub-expressão;
I a tabela que associa cada identicador ao seu tipo (ambiente) I Atributos sintetizados: calculados das folhas da AST para a raiz
I Atributos herdados: passados da raiz da AST para as folhas
I Atributos sintetizados numa parte da AST podem ser herdados noutra
Expressões
I Uma linguagem com variáveis, expressões aritméticas e lógicas (comparações) I Dois tipos: int (números inteiros) e bool (valores lógicos)
Exemplos de expressões: 1+2*3
(1+2)<3
1+x*3 (válida se x for int)
(1+x)<y (válida se x for int e y for int)
Umerro de tipos(não podemos somar inteiros com valores lógicos): 1+(2<3)
Atributos sintetizados e herdados
Determinamos o tipo da expressão a partir dos tipos das sub-expressões (atributo sintetizado). < + 1 2 3 bool x atributo sintetizado
Passamos um tabela de símbolos com os tipos da variáveis (atributo herdado). < + 1 x y atributo herdado {x 7→int, y 7→ int} y
Implementação
data Expr = Num Int - 1, 2, 3 | Var Ident - x, y, z | Add Expr Expr - e1 + e2 | LessThan Expr Expr - e1 < e2 data Type = TyInt | TyBool
type TypeEnv = Map Ident Type - ambiente (tabela de símbolos) checkExpr :: TypeEnv -> Expr -> Type
- sintetizar o tipo de uma expressão
- argumentos: o ambiente (TypeEnv) e a expressão (Expr) - resultado: é um tipo (ou erro)
Atributos sintetizados vs. herdados
O tipo da expressão é propagado das folhas para a raiz (atributo sintetizado). checkExp env (Num n) = TyInt
...
checkExpr env (Add e1 e2) = let t1 = checkExpr env e1
t2 = checkExpr env e2
in if t1==TyInt && t2==TyInt then TyInt
Atributos sintetizados vs. herdados (cont.)
O ambiente (tabela de símbolos) é propagado da raiz para as folhas (atributo herdado). checkExpr env (Add e1 e2)
= let t1 = checkExpr env e1 t2 = checkExpr env e2
in if t1==TyInt && t2==TyInt then TyInt else error "type error in +"
Atributos sintetizados vs. herdados (cont.)
O ambiente será determinada na chamada de topo (raiz da AST):
> checkExpr (Map.fromList [("x",TyInt)]) (Add (Var "x") (Num 1)) TyInt
Esta expressão teria um erro de tipo num outro ambiente:
> checkExp (Map.fromList [("x", TyBool)] (Add (Var "x") (Num 1)))
type error in +
Denição completa
checkExpr env (Num n) = TyInt
checkExpr env (Var x) = case Map.lookup x env of Nothing -> error "undeclared var"
Just t -> t
checkExpr env (Add e1 e2) = let t1 = checkExpr env e1
t2 = checkExpr env e2
in if t1==TyInt && t2==TyInt then TyInt else error "type error in +"
checkExpr env (LessThan e1 e2) = let t1 = checkExpr env e1 t2 = checkExpr env e2
in if t1==TyInt && t2==TyInt then TyBool else error "type error in <"
Declarações e comandos
I Vamos acrescentar denições para declações de variáveisecomandos
I Acrescentamos novos denições na sintaxe abstrata e funções para fazer a análise de tipos
Declarações e comandos (cont.)
data Stm = Assign Ident Expr - var := expr
| If Expr Stm Stm - if expr then stm1 else stm2 | While Expr Stm - ciclo while
| Block [Decl] [Stm] - bloco: declarações e comandos
| Skip - comando vazio
deriving Show
Exemplos
if (x<y) x=y; else x=0; If (LessThan (Var "x") (Var "y"))(Assign "x" (Var "y")) (Assign "x" (Num 0))
Exemplos (cont.)
n = 1; while(n < 10) n = n+1; Block [] [ Assign "n" (Num 1), While (LessThan (Var "n") (Num 10)) (Assign "n" (Add (Var "n") (Num 1))) ]
Exemplos (cont.)
if (x<y) { int temp; temp = x; x = y; y = temp; }If (LessThan (Var "x") (Var "y")) (Block [("temp",TyInt)]
[ Assign "temp" (Var "x") , Assign "x" (Var "y") , Assign "y" (Var "temp") ])
Regras de tipos para comandos
Atribuição (var := expr) a variável e a expressão devem ter o mesmo tipo
Condição (if exp then stm1 else stm2)
1. a expressão deve ter tipo bool
2. os dois comandos devem estar corretamente tipados
Ciclo (while exp stm)
1. a expressão deve ter tipo bool
2. o corpo deve estar corretamente tipado
Bloco extendemos o ambiente com declarações e vericamos se todos os comandos estão bem tipados
Análise de tipos para comandos
checkStm :: TypeEnv -> Stm -> Bool
I Tal como para expressões vamos passar um ambiente como atributo herdado
I O resultado de analizar dum comando será um boleano I True se ocomando respeita as regras de tipos
I ou lança um erro1 com uma mensagem apropriada I Nos blocos: necessitamos deextender o ambiente
(Ver demonstração.)
Analisar if/else
checkStm env (If cond stm1 stm2) = let t0 = checkExpr env cond
in if t0 == TyBool then checkStm env stm1 && checkStm env stm2 else error "type error: condition must be bool"
Analisar blocos
checkStm env (Block decls stms) = let env' = extendEnv env decls
in checkAll env' stms
-- funções auxiliares: extender um ambiente extendEnv :: TypeEnv -> [Decl] -> TypeEnv extendEnv env [] = env
extendEnv env ((v,t):rest) = extendEnv (Map.insert v t env) rest -- verificar tipos de uma lista de comandos
checkAll env [] = True
Análise de tipos para funções
I Vamos extender a linguagem comdeclarações e chamada de funções
I Simplicação: denições comuma só expressão (e não comandos)
I Um programapode ter:
I declarações de uma ou mais funções;
Análise de tipos para funções (cont.)
data Expr = ...
| FunCall Ident [Expr] - chamada f(e1, e2, ...)
data FunDef = FunDef Ident Type [(Ident,Type)] Expr - t f(x1:t1,..., xn:tn) = expr
data Prog = Prog [FunDef] Stm
Exemplo
int incr(int x) { return x+1; } int main() { int x, y; x = 2; y = incr(x+1); } Prog
[FunDef "incr" TyInt [("x",TyInt)] (Add (Var "x") (Num 1))] [Block [("x", TyInt), ("y",TyInt)]
[ Assign "x" (Num 2)
, Assign "y" (FunCall "incr" [Add (Var "x") (Num 1)]) ]
Tipos de funções
(t1, t2, . . . , tn) → r
I t1, t2, . . . , tn são ostipos dos argumentos I r é otipo do resultado
data Type = ...
Vericação de chamadas
Para vericar uma chamada f (e1, . . . , en)
1. Procurar f no ambiente e obter um tipo (t1, . . . , tn) → r 2. Recursivamente vericar tipos de e1, . . . , en
3. Se os tipos são iguais a t1, . . . , tn então retornar o tipo do resultado (r); caso
Vericação de chamadas (cont.)
Uma nova equação na função checkExpr: checkExpr env (FunCall f args)
= case Map.lookup f env of
Just (TyFun argTypes result) ->
if map (checkExpr env) args == argTypes then result else error "invalid argument types"
Vericação de denições
Para vericar f (x1 : t1, . . . , xn: tn) : r = e
1. Extendemos o ambiente com {x1 7→ t1, . . . , xn7→ tn} 2. No ambiente extendido: vericamos o tipo de e
3. Se o tipo resultante for igual a r retornamos um novo ambiente com {f 7→ (t1, . . . , tn) → r }
Caso contrário: erro.
Vericação de denições (cont.)
checkFunDef :: TypeEnv -> FunDef -> TypeEnv checkFunDef env (FunDef f decls result expr)
= let env' = extendEnv decls env texpr = checkExpr env' expr in if texpr == result then
let args = map snd decls
in extendEnv [("f",TyFun args result)] env else error "wrong result type"
-- função auxiliar (já era usada para blocos) extendEnv :: TypeEnv -> [Decl] -> TypeEnv extendEnv env [] = env
Recursão
I A vericação de funções não permite usos recursivos da função, e.g.
int f(int x) = if x > 0 then x*f(x-1) else 1
I A extensão para recursão é simples: basta acresentar uma entrada para f ao ambiente usado para tipar o corpo da função2
I Mas só seria útil combinada com a extensão para comandos ou expressões if/else
(caso contrário não há forma de terminar a recursão. . .)
Overloading e coerção
Overloading utilizar o mesmo operador para operações em tipos diferentes (e.g. + em inteiros e fracionários)
1 + 2 -- int 1.0 + 2.5 -- float
Coercão conversão implícita ou explícita de tipos int x = ...;
(float)x + 2.5 -- conversão explícita x + 2.5 -- conversão implícita
Overloading e coerção (cont.)
I A vericação para tipos e operadores pré-denidos é simples (e.g. C, Pascal)
I Consideravelmente mais complexa se o programador pode denir operadores sobre tipos novos (e.g. C++, Haskell,. . . )
Polimorsmo
polimórco (adjetivo): que se apresenta sob diversas formas.
Dicionário Online Priberam Em programação: a possibilidade de escrevercódigo que opera sobre diferentes tipos.
polimorsmo paramétrico SML/Haskell ou generics em Java/C# reverse :: [t] -> [t] -- em Haskell void reverse(List<T> list); // em Java
polimorsmo ad-hoc overloading, interfaces, herança. . .
(Tópicos abordados em Fundamentos de Linguagens e Tópicos de Programação Funcional Avançada.)
Inferência
I Para alguns sistemas é possível inferir os tipos automaticamente em vez de apenas
vericar declarações
I As linguagens funcionais fortemente tipadas usam inferência baseada no algoritmo Damas-Milner (com extensões)