¿Te refieres a la comprobación de código dentro del compilador (lo que típicamente llamamos comprobación de tipos)? Te dejo un resumen ampliado y práctico de cómo se hace, con qué reglas, y cómo se implementa, enlazándolo con la tabla de símbolos y el análisis semántico.
Comprobación de código (comprobación de tipos)
Relacionado: Puntero. resumen. partes principales de un sistema robotico.
Qué es y por qué importa
La comprobación de tipos es el conjunto de reglas y mecanismos que verifican que cada parte del programa “tiene sentido” según el sistema de tipos del lenguaje. Es parte del análisis semántico y se apoya en la tabla de símbolos. Su objetivo: detectar incoherencias antes (estático) o durante (dinámico) la ejecución.
Estático vs. dinámico
-
Estática (en compilación): evita muchos errores y no sobrecarga el binario con comprobaciones en tiempo de ejecución (ej.: C, Java, Pascal).
-
Dinámica (en ejecución): más flexible para prototipos (ej.: Python), a costa de introducir checks en runtime. En la práctica, muchos lenguajes combinan ambos enfoques.
Piezas que intervienen
-
Sistema de tipos: define tipos base, constructores (arrays, registros, funciones, punteros…), y reglas de compatibilidad.
-
Expresiones de tipos: representación interna (a menudo con grafos acíclicos dirigidos) para comparar y reutilizar estructuras de tipos eficientemente.
-
Tabla de símbolos: guarda para cada identificador su tipo, clase (var, const, función…), ámbito, etc.; el verificador consulta aquí cada vez que aparece un nombre.
-
Árbol sintáctico + atributos: las reglas semánticas decoran el árbol con tipos (síntesis/herencia) y disparan errores cuando no cuadran.
Qué se comprueba (core)
-
Uso de identificadores
Declaración previa, clase correcta, y visibilidad según el ámbito. -
Asignaciones
El tipo RHS debe ser compatible con el LHS (conversión permitida o coerción definida). -
Operaciones
Operandos compatibles con el operador; resultado con tipo bien definido (promocionesint→float, etc.). -
Llamadas a funciones
Número/orden/tipos de argumentos vs. firma; tipo de retorno usado coherentemente. -
Estructuras compuestas
Índices de arrays del tipo esperado; campos destruct/recordexistentes y con el tipo correcto. (El rango del índice es análisis adicional, no estricto “tipo” en sí). -
Conversión de tipos
-
Explícita (cast): el programador la solicita.
-
Implícita (coerción): la hace el compilador si el lenguaje lo permite.
-
-
Equivalencia/compatibilidad de tipos
-
Estructural: misma forma (ej.: dos
array(0..5, real)son equivalentes). -
Nominal: los alias de tipo cuentan como tipos básicos distintos.
-
Funcional: pueden usarse indistintamente en un contexto sin transformar el valor.
-
-
Sobrecarga y polimorfismo
Resolver qué versión aplica según los tipos; mantener conjuntos de tipos por identificador.
Algoritmo práctico (esqueleto)
- Decoración de expresiones
-
Para un literal/identificador:
tipo(node) ← tipo_en_tabla_simbolos(name). -
Para
E1 op E2:-
t1 ← tipo(E1),t2 ← tipo(E2) -
si
compatible(op, t1, t2)entoncestipo(node) ← tipo_resultante(op, t1, t2); si se requiere, inserta coerciones en el IR. -
si no, error de tipos.
-
- Asignaciones
-
tL ← tipo(LHS),tR ← tipo(RHS) -
si
tRconvertible/compatible contL, ok (añadir conversión si procede); si no, error.
- Llamadas
- Busca firma en tabla; compara lista de argumentos (longitud y tipos). Error si no coincide; el tipo del nodo es el tipo de retorno.
Este esquema se implementa con gramáticas de atributos (S-atribuidas para análisis ascendente; L-atribuidas para pasar contexto), evaluando reglas cuando el parser reduce o expande.
Ejemplos rápidos
-
Asignación con promoción:
int x; float y; x = y + 3.14; // y+3.14 : float ⇒ requiere conversión a int o error según lenguaje(promociones/reglas definidas en el sistema de tipos).
-
Array index:
real a[1..10]; a[i] = 2.0; // tipo(i)==integer; a[i]: real(rango es verificación adicional; el tipo de
a[i]esreal). -
Equivalencia estructural vs. nominal:
Dosarray(0..5, array(0..5, real))pueden ser equivalentes estructuralmente; nominalmente no, si el lenguaje trata los alias como tipos básicos distintos.
Relación con la tabla de símbolos y el AST
-
La tabla de símbolos se actualiza en declaraciones (tipos, parámetros, forma de paso, etc.) y se consulta en usos; maneja ámbitos anidados para aplicar “anidación más próxima”.
-
El árbol se anota con atributos (
.tipo,.val, etc.) y se usa un grafo de dependencias para respetar el orden de cálculo.
Qué errores “de código” te pilla aquí
-
Usar una variable no declarada / fuera de ámbito.
-
Operar
string + int(si el lenguaje no lo define). -
Llamar
f(int, float)comof(3, "hola"). -
Escribir en una constante o asignar a un valor de tipo incompatible.
Si quieres, te preparo un mini-ejemplo de lenguaje (declaraciones, expresiones y funciones) y te muestro:
-
Cómo se llena la tabla de símbolos,
-
Cómo se anota el árbol con tipos, y
-
Dónde saltan exactamente los errores de tipos según tus reglas (estático vs. dinámico).