Análisis semántico: Comprobación de tipos Expresiones de tipos, sistemas de comprobación de tipos, equivalencia, resolución de sobrecargas y unificación. Introducción Objetivo de comprobación estática: Establecer en tiempo de compilación si el programa fuente sigue las convenciones sintácticas y semánticas del lenguaje. Ejemplos de comprobación estática: © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Comprobaciones Comprobaciones Comprobaciones Comprobaciones de tipos del flujo de control de unicidad relacionadas con nombres Sistemas de tipos Cada construcción de un lenguaje tiene asociado un tipo: Expresiones. Funciones. Sentencias. El problema básico: Decidir un tipo único para cada uno de los elementos © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Estrategia: Reglas de tipos: “Si ambos operandos de los operadores aritméticos de suma, sustracción y multiplicación son de tipo entero, entonces el resultado es de tipo entero”. “El resultado del operador unario & es un apuntador hacia el objeto al que se refiere el operando. Si el tipo del operando es ‘…’, el tipo del resultado es ‘puntero a …’”. Construir expresiones de tipos, separando: Tipos básicos. Tipos construidos. Expresiones de tipos El tipo de una construcción del lenguaje se denotará mediante expresiones de tipo Definición de expresión de tipo: Un tipo básico es una expresión de tipo. © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID boolean, char, integer, real, error_tipo, vacío. El nombre de un tipo es una expresión de tipo Un constructor de tipos aplicado a una expresión de tipo es una expresión de tipo Las expresiones de tipo pueden contener variables cuyos valores son expresiones de tipo © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Constructores de tipos Matrices: Si T es una expresión de tipo, array(I,T) es una expresión de tipo, donde I es un conjunto de índices, y T el tipo de los elementos Productos: Si T1 y T2 son expresiones de tipo, su producto cartesiano, T1×T2 es una expresión de tipo Registros: el constructor record se aplicará a una tupla formada con nombres de campos y tipos de campos. Punteros: Si T es una expresión de tipo, entonces pointer(T) es una expresión de tipo Funciones: transformaciones de un dominio de tipo D a un rango de tipo R. La expresión de tipo D→R indicará el tipo de la función Sistemas de tipos: Reglas Definición Sistema de Tipos: Serie de reglas para asignar expresiones de tipo a las distintas partes de un programa. Un comprobador de tipos implanta un sistema de tipos Comprobación estática vs. Dinámica: © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Cualquier verificación puede realizarse dinámicamente Un sistema de tipos seguro elimina la necesidad de comprobar dinámicamente errores de tipo Un lenguaje es fuertemente tipado si su compilador puede garantizar que los programas que acepte se ejecutarán sin errores de tipo Errores de tipos: El compilador debe informar de la naturaleza y posición del error Es conveniente que se recupere de los errores para comprobar el resto de la entrada Comprobador de tipos sencillo Un lenguaje simple y prototípico para representar el problema de especificación de tipos: P→D ; E D→D ; D | id : T © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID T→char | integer | array [ num ] of T | ↑T E→literal | num | id | E mod E | E [ E ] | E↑ Comprobador de tipos sencillo Parte de un esquema de traducción que guarda el tipo de un identificador: P→D ; E © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID D→D ; D D→id : T {añadetipo(id.entrada, T.tipo)} T→char {T.tipo:=char} T→integer {T.tipo:=integer} T→↑T1 {T.tipo:=pointer(T1.tipo)} T→array [ num ] of T1 {T.tipo:=array(1..num.val, T1.tipo)} Comprobador de tipos sencillo Comprobación de tipos en las expresiones © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID E→literal E→num E→id E→E1 mod E2 {E.tipo:=char} {E.tipo:=integer} {E.tipo:=busca(id.entrada)} {E.tipo:=if E1.tipo = integer and E2.tipo = integer then integer else error_tipo} {E.tipo:=if E2.tipo = integer and E1.tipo = array(s,t) then t else error_tipo} {E.tipo:=if E1.tipo = pointer(t) then t else error_tipo} E→E1 [ E2 ] E→E1↑ Comprobador de tipos sencillo Comprobación de tipos en las proposiciones T→boolean (gramática aumentada) S→id := E © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID S→if E then S1 {S.tipo := if id.tipo = E.tipo then vacío else error_tipo} {S.tipo:=if E.tipo = boolean then S1.tipo else error_tipo} S→while E do S1 {S.tipo:=if E.tipo = boolean then S1.tipo else error_tipo} S→S1 ; S2 {S.tipo:= if S1.tipo = vacío and S2.tipo = vacío then vacío else error_tipo} Comprobador de tipos sencillo Comprobación de tipos de funciones E→E ( E ) (gramática aumentada) E→E1 ( E2 ) {E.tipo:= if E2.tipo = s and © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID E1.tipo = s → t then t else error_tipo} Equivalencia de expresiones de tipos Equivalencia: Identidad (estructural sin constructores) Estructural De nombre (nombres para tipos) Equivalencia estructural en las expresiones de tipos: © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Dos expresiones son, o bien el mismo tipo básico, o están formadas aplicando el mismo constructor a tipos estructuralmente equivalentes Dos expresiones de tipos son estructuralmente equivalentes, si y sólo si, son idénticas Equivalencia de expresiones de tipos © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Comprobación de la equivalencia estructural de dos expresiones de tipos s y t. function equivest(s,t): boolean; begin if s y t son el mismo tipo básico then return true else if s=array(s1, s2) and t=array(t1, t2) then return equivest(s1,t1) and equivest(s2,t2) else if s=s1×s2 and t=t1×t2 then return equivest(s1,t1) and equivest(s2,t2) else if s=pointer(s1) and t=pointer(t1) then return equivest(s1,t1) else if s=s1→s2 and t=t1→t2 then return equivest(s1,t1) and equivest(s2,t2) else return false end Equivalencia de expresiones de tipos Nombres en expresiones de tipos: © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Se permite dar nombres a las expresiones de tipo y que estos nombres aparezcan en expresiones de tipos donde previamente sólo existían tipos básicos Equivalencia de nombre: dos expresiones de tipo tienen equivalencia de nombre, si y sólo si, son idénticas Equivalencia estructural: dos expresiones de tipos son estructuralmente equivalentes si representan dos expresiones de tipos estructuralmente equivalentes cuando todos los nombres han sido sustituidos Equivalencia de expresiones de tipos Ciclos en las representaciones de tipos © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Los nombres de tipo definidos recursivamente se pueden sustituir si se quieren introducir ciclos en el grafo de tipos Ejemplo: type enlace = ↑nodo nodo = record info:integer siguiente:enlace end; Equivalencia de expresiones de tipos Ciclos en las representaciones de tipos © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID nodo=record × × info × integer siguiente pointer nodo Equivalencia de expresiones de tipos Ciclos en las representaciones de tipos © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID nodo=record × × info × integer siguiente pointer Conversiones de tipos Coerciones: © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID La conversión de un tipo a otro es implícita si el compilador la va a realizar automáticamente. Estas conversiones se llaman coerciones La conversión es explícita si el programador debe escribir algo para motivar la conversión (operador). Para un comprobador de tipos, las conversiones explícitas se tratan como aplicaciones de función, por lo que no presentan problemas nuevos. Sobrecarga de funciones y operadores © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Símbolo sobrecargado: es aquél que tiene distintos significados dependiendo de su contexto La sobrecarga se resuelve cuando se determina un significado único para un caso de símbolo sobrecargado La solución de sobrecarga se denomina identificación de operadores Sobrecarga de funciones y operadores © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID La sobrecarga “implícita” (operadores aritméticos, generalmente) es posible resolverla mirando a los operandos La sobrecarga “explícita” se resolverá (si se puede) al observar el contexto en el que se encuentra Sobrecarga de funciones y operadores Ejemplo de sobrecarga (operador de multiplicación): integer × integer → integer © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID integer × integer → complex complex × complex → complex • La expresión 3*5 podrá tener tipo integer o complex • En 2*(3*5), 3*5 sólo podrá tener tipo integer • En z*(3*5), 3*5 sólo podrá tener tipo complex Sobrecarga de funciones y operadores © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Cada subexpresión de una expresión tendrá un conjunto posible de tipos (cada uno de los tipos se denomina tipo factible) Se resuelve la sobrecarga (en una expresión) si puede determinarse un tipo único para la expresión y cada una de sus subexpresiones Sobrecarga de funciones y operadores Solución de la sobrecarga (2 pasos): Se determina un tipo único para la expresión completa Se comprueba cada subexpresión comprobando si a partir del tipo único, el tipo de la subexpresión es o no único © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Sobrecarga de funciones y operadores Sobrecarga de operadores no resuelta 2*(3*5) con la sobrecarga anterior en la multiplicación © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID E {i,c} E{i} *:{i×i→i, E{i,c} i×i→c, 2{i} c×c→c} E{i} *:{i×i→i, E{i} i×i→c, 3{i} c×c→c} 5{i} Sobrecarga de funciones y operadores Sobrecarga de operadores resuelta 2+(3*5) con la sobrecarga anterior en la multiplicación E {i} © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID {i} E{i} 2{i} +:{i×i→i} E{i,c} E{i} *:{i×i→i, E{i} i×i→c, 3{i} c×c→c} 5{i} Funciones polimórficas Una función polimórfica es aquella que puede ejecutarse con argumentos de tipos distintos fun longitud(aplista)= © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID if null(aplista) then 0 else longitud(tl(aplista))+1; longitud([“dom”, “lun”, “mar”])=3 longitud([10, 9, 8])=3 Funciones polimórficas Las variables que representan expresiones de tipos permiten considerar tipos desconocidos en: funciones polimórficas comprobación del uso consistente de identificadores en un lenguaje que no exija que se declaren antes de ser utilizados (si siempre se usa igual, se garantiza un uso consistente y puede inferirse su tipo). © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Funciones polimórficas © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Inferencia de tipos: problema de determinar el tipo de una construcción del lenguaje a partir del modo en que se usa Las técnicas para inferencia de tipos pueden aplicarse a programas en lenguajes como C y Pascal para completar información sobre los tipos que faltan en el momento de la compilación Funciones polimórficas Notación para tipos polimórficos function desref(p); © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID begin return p↑ end; • El tipo de la función es ∀α.pointer(α) →α • Una expresión de tipo que contenga un símbolo ∀ se considerará un “tipo polimórfico” Funciones polimórficas Un lenguaje con funciones polimórficas P→D;E D → D ; D | id : Q © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Q → ∀ variable_de_tipo.Q | T T→ T ‘→’ T |T×T | constructor_unario ( T ) | tipo_basico | variable_de_tipo |(T) E → E ( E ) | E , E | id Funciones polimórficas ‘Programa’ generado por la gramática desref: ∀α.pointer(α) →α q: pointer(pointer(integer) © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID desref(desref(q)) ¿Es correcta la expresión a partir de la declaración de tipos? Funciones polimórficas Diferencias para comprobación de tipos entre funciones polimórficas y ordinarias: Casos diferentes de una función polimórfica en la misma expresión no deben tener necesariamente argumentos del mismo tipo Ejemplo: © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID desrefe y desrefi en desrefe(desrefi(q)) Funciones polimórficas Diferencias para comprobación de tipos entre funciones polimórficas y ordinarias : © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Reconsideración de la equivalencia de tipos (unificación: de manera informal, determinación de si s y t pueden ser estructuralmente equivalentes sustituyendo las variables en s y t por expresiones de tipo) Ejemplo: pointer(ai)=pointer(pointer(integer)), si ai se sustituye por pointer(integer) Funciones polimórficas Diferencias para comprobación de tipos entre funciones polimórficas y ordinarias: © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Se necesita un mecanismo para registrar el efecto de la unificación de dos expresiones. Si una variable a representa al tipo t, entonces a debe continuar representando a t durante toda la comprobación de tipos Funciones polimórficas © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Sustitución: transfomación de variables de tipo a expresiones de tipo Unificación: Dos expresiones de tipo t1 y t2 se unifican si existe alguna sustitución S tal que S(t1)=S(t2). En la práctica, interesa el unificador más general, que es la sustitución que menos limitaciones exige a las variables dentro de las expresiones Funciones polimórficas © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Ejemplo de comprobación de tipos para desref(desref(q)) Expresión Tipo q pointer(pointer(integer)) desrefi pointer(αi)Æαi desrefi(q) pointer(integer) desrefe pointer(αe)Æαe desrefe(desrefi(q)) integer Sustitución αi=pointer(integer) αe=integer Funciones polimórficas © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Relación entre comprobación e inferencia de tipos function desref(p) begin return p↑ end; • Inferir el tipo de desref a partir del cuerpo de la función • Unificación de desref(p) con return p↑ Funciones polimórficas © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Relación entre comprobación e inferencia de tipos Conocimiento previo: EXPRESIÓN TIPO ↑ pointer( χ )Æχ return δÆδ Método: Evaluación ascendente de declaración y expresión (de las subexpresiones a la expresión) Funciones polimórficas © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Relación entre la comprobación y la inferencia de tipos Expresión Tipo p α desref β desref(p) χ p α ↑ pointer(δ)Æδ p↑ δ Sustitución β=αÆχ α=pointer(δ) Funciones polimórficas © Valentín Cardeñoso Payo. Departamento de Informática UNIVERSIDAD DE VALLADOLID Relación entre la comprobación y la inferencia de tipos Expresión Tipo Sustitución return σÆσ return p↑ σ δ=σ desref (p) con return p↑ χ σ=χ Realizando las sustituciones tenemos que el tipo inferido es: ∀χ.pointer(χ)→χ