Documentación oficial

Sistema de tipos

Referencia técnica de Sistema de tipos — Joss language and Joss Red ecosystem.

Sistema de tipos, inferencia y conversiones

Antes: Proyecto web completo, Colecciones. Después: Referencia de sintaxis, Diagnósticos. Referencia técnica: Diagnósticos, Sintaxis.


¿Qué vas a aprender aquí?

Un sistema de tipos es el conjunto de reglas que gobierna cómo una computadora interpreta los unos y ceros en la memoria. Sin tipos, una secuencia de 64 bits en la memoria podría ser un número, una letra, una imagen o una instrucción de procesador; no habría forma de saberlo.

En Joss, el sistema de tipos cumple un doble propósito: 1. Seguridad y robustez: Detecta incoherencias (como intentar multiplicar un texto por una lista) antes de que el código se ejecute en producción. 2. Claridad documental: Ayuda a cualquier desarrollador a comprender de inmediato qué datos espera una función y qué resultado va a producir.

En esta guía aprenderás: 1. La lista canónica de tipos de datos en Joss y por qué ciertos nombres antiguos (aliases) ya no son válidos. 2. Cómo funciona la inferencia de tipos y las diferencias entre var, tipos explícitos, mixed y const. 3. El funcionamiento de los tipos unión (T|U), valores opcionales anulables (T?) y el tipo de retorno : void. 4. Cómo funciona la compatibilidad y la promoción numérica exacta (int → decimal) y por qué mezclar con float exige conversión explícita (JOSS-ARITH-003). 5. Colecciones tipadas con genéricos (array<T>, map<K, V>, channel<T>, Result<T, E>). 6. La regla de coerción de cadenas (typesystem.CoerceString) y las protecciones aritméticas de seguridad.


1. Inventario de tipos fuente canónicos

La fuente de verdad canónica del compilador (pkg/typesystem/types.go) reconoce los siguientes tipos válidos:

Tipo fuente Significado Representación física en el Runtime Ejemplo de uso
int Entero con signo de 64 bits int64 (−9,223,372,036,854,775,808 a 9,223,372,036,854,775,807) int $id = 101
float Punto flotante binario estándar IEEE-754 float64 de 64 bits float $ratio = 0.75
decimal Número decimal de coma fija en base diez shopspring/decimal.Decimal (alta precisión) decimal $precio = 99.99m
string Secuencia de caracteres UTF-8 string de Go con soporte para grafemas string $email = "[email protected]"
bool Valor de verdad lógico bool (true o false) bool $valido = true
array Secuencia dinámica de elementos []interface{} (slice de Go) array $items = [1, 2, 3]
map Tabla asociativa con claves de texto map[string]interface{} (hashmap de Go) map $datos = {"rol": "admin"}
object Instancia genérica de una clase Instancia de clase de usuario o nativa object $instancia = new Persona()
channel Canal de comunicación concurrente *core.Channel (canal Go de mensajes) channel $c = make_chan(1)
mixed Dinamismo explícito y polimorfismo Cualquier valor válido del runtime mixed $dato = "dinamico"
void Terminación sin valor (solo retorno de callable) Sin valor de retorno (nil) public func log(): void
null Ausencia de valor Representación nil null
Nombre de clase / interfaz / enum Tipo nominal definido por el usuario o nativo Instancia o caso correspondiente Persona $p = new Persona()

Los antiguos aliases eliminados

[!WARNING] En versiones antiguas del lenguaje existían nombres alternativos heredados de otros ecosistemas como integer, double, boolean, dynamic, any o list. Estos nombres fueron completamente eliminados de la gramática.

Si escribes integer $x = 10, el analizador no lo convertirá a int; buscará una clase de usuario llamada integer. Al no encontrarla, emitirá el error de diagnóstico JOSS-TYPE-009 (Tipo no resuelto). Utiliza siempre los nombres canónicos: int, float, bool, mixed y array.


2. Inferencia y formas de declarar variables

Joss combina la agilidad de la inferencia moderna con la seguridad de los lenguajes fuertemente tipados:

var $edad = 20
$edad = 30
string $nombre = "Ada"
mixed $resultado = 10
$resultado = "pendiente"
print($edad)
print($nombre)
print($resultado)

Reglas semánticas de declaración:

  1. Inferencia canónica con var (var $x = 20):
    • En la declaración, Joss inspecciona el valor inicial y fija el tipo concreto (en este caso int).
    • Las reasignaciones posteriores ($x = 30) deben ser compatibles con ese tipo. Si intentas meter un texto, el analizador rechazará el programa con JOSS-TYPE-001.
  2. Declaración explícita (string $nombre = "Ada"):
    • Fija el tipo de forma visible y documentada.
  3. Dinamismo voluntario (mixed $resultado = 10):
    • Le indica al analizador que esta variable cambiará de naturaleza a lo largo del tiempo. Puedes reasignarle un texto, un mapa o una clase sin errores.
    • let $resultado = 10 es una sintaxis histórica en transición (JOSS-DECL-006); prefiere siempre mixed $resultado = 10.
  4. Prohibición de variables no declaradas ($x = 20 directo):
    • Asignar por primera vez a una variable sin haberla declarado previamente con var, un tipo explícito, mixed o const emite JOSS-SYM-001 (y el aviso de transición JOSS-DECL-007).
  5. Inicialización con null:
    • Si escribes var $x = null, la inferencia se pospone hasta la primera asignación que contenga un valor concreto.

3. Tipos Unión (T|U) y tipos anulables (T?)

En el desarrollo real es muy común que una operación pueda devolver un dato concreto o bien null si no se encontró nada (por ejemplo, buscar un usuario en la base de datos).

Para estos casos, Joss ofrece tipos unión:

int|string $id = 10
print($id)
$id = "A-10"
print($id)
int? $cantidad = null
print($cantidad ?? "sin dato")

Reglas de los tipos unión:

  1. Sintaxis con barra vertical (|): int|string significa que la variable solo aceptará enteros o textos, pero rechazará booleanos o listas.
  2. El atajo de interrogación (?): Escribir int? es exactamente equivalente a escribir int|null. El AST del compilador lo normaliza automáticamente a una unión con null.
  3. Refinamiento de tipos (Smart Cast Narrowing): Si tienes una variable string? $nombre y la filtras con guard ($nombre != null) : { return "" } o con un ternario ($nombre != null) ? { ... }, el analizador estrecha automáticamente su tipo a string no nulo en la ruta validada.

4. Reglas de compatibilidad y promoción numérica

¿Cuándo puede un valor de tipo origen asignarse a una variable de tipo destino?

int ──────────► decimal
    promoción exacta (única implícita)
  1. Mismo tipo: Siempre permitido.
  2. int → decimal: Es la única promoción numérica implícita entre tipos distintos porque conserva el valor exacto en base diez sin pérdida de precisión.
  3. int → float y float → decimal: Requieren conversión explícita (floatval($entero) o decimal($flotante)). La primera puede perder precisión en enteros grandes (\(> 2^{53}\)) y la segunda sólo conserva la aproximación binaria previa. Intentar mezclarlos implícitamente emite JOSS-ARITH-003 o JOSS-TYPE-001/002.
  4. Clase → object: Cualquier instancia de clase es compatible con el tipo universal object.
  5. Subclase → Superclase: Una clase derivada que extiende a una clase base es aceptada donde se espere la clase base.
  6. Clase → Interfaz: Una clase que implementa una interfaz (implements) es compatible donde se declare dicha interfaz como tipo.
  7. mixed: Es universalmente compatible en ambas direcciones.

Las operaciones que mezclan int con float, o float con decimal, también requieren convertir deliberadamente uno de los operandos. La división de dos enteros produce float; si un operando no puede representarse exactamente en IEEE-754, el analyzer emite JOSS-ARITH-003 cuando lo conoce y el runtime mantiene la misma defensa. Para importes, porcentajes e impuestos usa literales decimal (19.99m) y evita pasar primero por float.


5. Colecciones tipadas (Genéricos de primer nivel)

Joss permite parametrizar colecciones y contenedores:

array<int> $cantidades = [2, 4, 6]
map<string, int> $inventario = {"pan": 2}
print($cantidades[2])
print($inventario["pan"])
  • array<T>: Un array donde todos los elementos deben ser de tipo T.
  • map<K, V>: Un mapa con claves de tipo K (deben ser string) y valores de tipo V.
  • channel<T>: Un canal de concurrencia tipado donde los mensajes transmitidos deben ser de tipo T (ej. channel<string>).
  • Result<T, E>: Tipo canónico para resultados de operaciones que pueden tener éxito con valor de tipo T o fallar con error de tipo E.
  • Clase<T>: Clases con parámetros formales de tipo genérico (ej. Caja<T>).

array<T>, map<K, V> y channel<T> son mutables y por ello sus parámetros son invariantes. Por ejemplo, array<int> no puede reutilizarse como array<decimal>, aunque un int individual pueda promoverse a decimal: permitir el alias dejaría insertar después un decimal fraccionario visible desde una referencia que promete contener sólo int. Las colecciones sin parámetro conservan compatibilidad por transición, pero al cruzar hacia una colección tipada emiten JOSS-TYPE-012 porque otro alias todavía podría mutarlas sin respetar el tipo.

Al indexar una colección parametrizada (por ejemplo $cantidades[0]), el analizador infiere de inmediato que el resultado es de tipo int, garantizando la seguridad en el resto del código.


6. Enumeraciones (enum)

Las enumeraciones permiten definir un tipo cerrado con un conjunto finito de casos posibles, evitando el uso de constantes dispersas o strings mágicos.

En Joss existen dos tipos de enumeraciones:

Enums puros (Unit Enums)

Cada caso representa un valor simbólico único con la propiedad ->name:

public enum Estado {
    case Pendiente
    case Aprobado
    case Rechazado
}

var $e = Estado::Pendiente
print($e->name)
var $e2 = Estado::Aprobado
print($e2->name)

Enums respaldados (Backed Enums)

Asocian cada caso a un valor escalar primitivo (string o int):

public enum Rol: string {
    case Admin = "admin"
    case Editor = "editor"
    case Lector = "lector"
}

var $r = Rol::Admin
print($r->value)

// Instanciar desde valor escalar con from() o tryFrom()
var $desdeValor = Rol::from("admin")
print($desdeValor->value)
print($desdeValor->name)

// Obtener todos los casos con cases()
var $todos = Rol::cases()
print(count($todos))

7. Operadores de comprobación de tipo: is e instanceof

El operador is (y su alias instanceof) permite consultar en tiempo de ejecución si un valor pertenece a un tipo primitivo (int, string, bool, etc.), una clase o una interfaz:

var $numero = 42
var $texto = "hola"
print($numero is int)
print($texto is string)
print($numero instanceof int)

8. Coerción textual tipada (typesystem.CoerceString)

En aplicaciones web, los datos que llegan desde formularios HTTP o peticiones JSON son cadenas de texto crudas (por ejemplo, "8080" o "true").

Joss implementa una política compartida de coerción textual (CoerceString) tanto en el analizador estático como en el runtime cuando asignas un literal o cadena a una variable con tipo explícito:

int $puerto = "9000"
decimal $precio = "49.99"
bool $activo = "yes"
print($puerto)
print($precio)
print($activo)

Tabla de entradas textuales aceptadas:

Tipo destino Cadenas de texto que Joss convierte automáticamente
int Textos con dígitos enteros ("9000", "-42"), o números flotantes sin parte fraccionaria ("100.0").
float Cualquier texto con notación decimal válida ("3.1416", "-0.05").
decimal Textos numéricos limpios ("49.99", "120.50m").
bool Acepta sin distinguir mayúsculas: "true", "1", "yes" (como true); y "false", "0", "no", "" (como false).

Si el texto no se puede convertir (por ejemplo int $x = "manzana"), el analizador emite un error estático JOSS-TYPE-002 o el runtime lo rechaza de forma defensiva.


9. Precisión numérica y defensas del runtime

Regla de seguridad Comportamiento en Joss Diagnóstico
Desbordamiento entero Las operaciones con enteros de 64 bits (+, -, *) se verifican contra overflow. Si superan los límites de 64 bits, la ejecución se interrumpe de inmediato. JOSS-ARITH-001
División por cero Dividir o calcular el resto de un número entre cero ($n / 0 o $n % 0) produce un error controlado en vez de valores NaN o Infinity inesperados. JOSS-ARITH-002
Pérdida de precisión implícita Mezclar int y float, float y decimal, o dividir enteros fuera de rango exacto IEEE-754 sin conversión explícita es rechazado. JOSS-ARITH-003
Índice fuera de rango Acceder a un índice negativo o superior a la longitud de una lista o cadena detiene el programa de forma estructurada. JOSS-INDEX-001

10. Diagnósticos comunes del sistema de tipos

Código Significado Solución habitual
JOSS-TYPE-001 Reasignación con tipo incompatible (ej. var $x = 1; $x = "hola"). Mantén el tipo homogéneo o declara la variable explícitamente como mixed $x.
JOSS-TYPE-002 Valor inicial incompatible con la anotación de tipo. Corrige el valor inicial para que coincida con el tipo declarado.
JOSS-TYPE-008 El valor retornado por una función no coincide con el tipo prometido en : Tipo. Revisa la expresión de return para que entregue el tipo prometido.
JOSS-TYPE-009 Nombre de tipo o clase inexistente (incluye aliases retirados como integer). Reemplaza integer, double, boolean, any por sus nombres canónicos: int, float, bool, mixed.
JOSS-TYPE-010 Una función tipada puede terminar sin ejecutar un return o throw. Asegúrate de que todas las ramas ternarias concluyan con un valor devuelto.
JOSS-TYPE-011 Se declaró un parámetro sin tipo (func($x)). Escribe el tipo del parámetro: func(int $x) o func(mixed $x).
JOSS-TYPE-014 Una función o método con nombre omitió su tipo de retorno. Añade : Tipo o : void en la firma de la función.

Siguiente paso

Para consultar todos los códigos de diagnóstico del analizador y sus ejemplos de corrección, continúa con: Catálogo y referencia de diagnósticos.