Hace años tomé una decisión que me daba cierta vergüenza: saqué el job de Psalm del hook de pre-commit y lo dejé solo en CI, porque nadie del equipo estaba dispuesto a esperar 50 segundos cada vez que guardaba la corrección de una errata. Sentí que estaba bajando el listón. Luego vi pasar exactamente lo mismo en otros tres equipos, y dejé de sentirme culpable para empezar a pensar que la herramienta y el flujo de trabajo, sencillamente, no encajaban. Esa es la espina que Jorg Sowa dice que quería quitarse cuando empezó mir en abril de 2026, y los números que ha publicado cuesta ignorarlos.
Primero los datos, porque importan. mir está escrito en Rust, tiene licencia MIT y va por la versión 0.88. En un mismo portátil analiza Symfony y Laravel en 0.7 a 2 segundos; Psalm tardó entre 37 y 54 segundos y llegó a picos de 3 a 4 GB de memoria sumando su proceso principal y nueve workers. Sowa habla de un rango de 19 a 82 veces más rápido, con una quinta parte de la memoria o menos. La prueba de Symfony usó el commit b47221ac con el psalm.xml original de Symfony, sin tocar, en nivel de error 5 y con su baseline; la de Laravel cubrió 1,742 archivos de la v13.36.0. Él mismo reconoce que el modo --diff de Psalm, que solo vuelve a revisar los archivos modificados, quedó fuera de la comparación, y me encantaría ver esa columna rellena antes de que alguien se tome el 82x como palabra sagrada.
Pero lo que no me quito de la cabeza no es el cronómetro. mir no trae su propio lenguaje de tipos. Lee psalm.xml cuando no hay mir.xml, detecta psalm-baseline.xml por su cuenta, respeta los errorLevel del 1 al 8 con el mapeo por issue de Psalm y acepta @psalm-pure, @psalm-immutable, @psalm-suppress y compañía, además de la sintaxis de PHPStan y @phpstan-ignore. Los nombres de issues como InvalidArgument y TaintedSql se mantienen allí donde ambas herramientas tienen la comprobación. Dicho de otra forma: lo que nació en Vimeo como el dialecto de docblocks de un proyecto se ha convertido en una interfaz que un motor completamente distinto puede implementar. Es un elogio enorme para la comunidad PHP. Construimos, en comentarios, un sistema de tipos más rico que el que ofrecía el propio lenguaje, y resultó lo bastante sólido como para sobrevivir a cualquier implementación concreta.
Y justo por eso creo que el siguiente paso es aburrido y llega tarde: necesitamos una especificación escrita del lenguaje de tipos en docblocks. Ahora mismo, lo que significan list<T>, non-empty-string o un array shape con tipos literales en un caso límite lo define lo que haga Psalm, con lo que PHPStan está mayormente de acuerdo y lo que, desde PhpStorm 2020.3, tu IDE entiende más o menos. Con un tercer intérprete serio en juego, «lo que haga Psalm» ya no basta. El propio Sowa avisa de que tu primera ejecución de mir dará una lista de issues distinta, que todavía no existen todas las reglas y que algunos nombres cambian. Parte de esas diferencias es simplemente trabajo pendiente. Otra parte serán desacuerdos reales sobre lo que significa un tipo, y sin una especificación nadie puede decir quién tiene razón.
La objeción más fuerte a tanto entusiasmo es una que me tomo en serio. Psalm está escrito en PHP, así que quienes lo usan pueden leerlo, depurarlo y mandar un parche un domingo por la tarde. Un analizador en Rust deja el motor fuera del alcance de la mayoría de sus usuarios. Los plugins muestran claramente el coste: mir tiene una API nativa de plugins en Rust inspirada en los event handlers de Psalm, y los plugins PHP existentes, como el de PHPUnit, solo funcionan a través de un puente que lanza un subproceso PHP, lo que sigue exigiendo tener PHP y Psalm instalados. Si las herramientas de análisis de la comunidad acaban en manos de un grupo más reducido de mantenedores que trabajan en otro lenguaje, eso supone una pérdida real de resiliencia, y no voy a fingir lo contrario.
Aun así, me quedo del lado de darle la bienvenida, por una razón práctica. Tu inversión nunca estuvo en el código fuente de Psalm. Está en las anotaciones que tu equipo ha ido escribiendo durante años, en la baseline que vas reduciendo poco a poco, en las supresiones con comentarios que explican por qué. Como mir trata todo eso como el contrato, probarlo te cuesta un composer require y un comando, y puedes ejecutar los dos analizadores en paralelo en CI hasta que te fíes del resultado. No hay que reescribir nada. Si mir se queda atascado en la 0.9 para siempre, lo desinstalas y has perdido una tarde. Los desarrolladores de Go consiguieron sus linters rápidos reescribiendo herramientas y pidiendo a todo el mundo que adoptara convenciones nuevas; nosotros conservamos las nuestras y cambiamos el motor por debajo, que, sinceramente, es mejor trato.
Lo que cambia cuando el análisis tarda dos segundos es el momento en que lo ejecutas. Con 50 segundos es una barrera que cruzas una vez antes de hacer push. Con menos de dos puede ejecutarse cada vez que guardas, y los errores de tipos aparecen cuando todavía recuerdas por qué escribiste esa línea. Ese cambio de hábito vale más que cualquier gráfico de benchmarks, y es justo lo que el diseño de Psalm nunca pudo darnos del todo, por bueno que sea su análisis. Cambiaría encantado unas cuantas reglas que faltan por eso, al menos en una rama de feature.
Así que tengo curiosidad por saber dónde te sitúas, sobre todo si mantienes librerías que publican tipos precisos en sus docblocks. Si dos motores no se ponen de acuerdo sobre una de tus firmas @template, ¿cuál consideras que se equivoca? ¿Pondrías tu nombre detrás de una especificación mantenida por la comunidad para este lenguaje de tipos, o crees que lo más sano es dejar que las herramientas converjan por su cuenta?




Comentarios
Aún no hay comentarios — escribe el primero.
Inicia la conversación
Sin cuenta ni contraseña — introduce tu correo y te enviamos un enlace de acceso de un solo uso. ¿Primera vez? Todo se configura automáticamente.
Tu valoración se aplicará automáticamente al iniciar sesión.
Revisa tu bandeja de entrada
Hemos enviado un enlace de acceso a …. Ábrelo en este dispositivo — esta pestaña te conectará automáticamente.
¿No llega nada? Mira en la carpeta de spam — y marca el correo como «No es spam» para que la próxima vez llegue directo.