Corrección de fallos de promoción de tipos
Soluciones para casos donde sabes más sobre el tipo de un campo de lo que Dart puede determinar.
La promoción de tipos ocurre cuando el análisis de flujo puede confirmar con certeza que una variable con un tipo anulable no es nula, y que no cambiará a partir de ese momento. Muchas circunstancias pueden debilitar la solidez del tipo, lo que hace que la promoción de tipos falle.
Esta página enumera las razones por las que ocurren fallos en la promoción de tipos, con consejos sobre cómo solucionarlos. Para obtener más información sobre el análisis de flujo y la promoción de tipos, consulta la página Entendiendo la seguridad de nulos (null safety).
Versión del lenguaje no compatible para la promoción de campos
#La causa: Estás intentando promover un campo, pero la promoción de campos depende de la versión del lenguaje, y tu código está configurado para una versión del lenguaje anterior a la 3.2.
Si ya estás utilizando una versión del SDK >= Dart 3.2, es posible que tu código todavía esté configurado explícitamente para una versión del lenguaje anterior. Esto puede ocurrir por una de las siguientes razones:
- Tu archivo
pubspec.yamldeclara una restricción de SDK con un límite inferior por debajo de 3.2, o - Tienes un comentario
// @dart=versionen la parte superior del archivo, donde laversiónes inferior a 3.2.
Example:
// @dart=3.1
class C {
final int? _i;
C(this._i);
void f() {
if (_i != null) {
int i = _i; // ERROR
}
}
}
Message:
'_i' refers to a field. It couldn't be promoted because field promotion is only available in Dart 3.2 and above.
Solution:
Asegúrate de que tu biblioteca no esté utilizando una versión del lenguaje anterior a la 3.2.
Busca en la parte superior de tu archivo un comentario // @dart=version desactualizado,
o en tu pubspec.yaml un límite inferior de la restricción del SDK desactualizado.
Solo las variables locales se pueden promover (antes de Dart 3.2)
#La causa: Estás intentando promover una propiedad, pero solo las variables locales se pueden promover en las versiones de Dart anteriores a la 3.2, y estás utilizando una versión anterior a la 3.2.
Example:
class C {
int? i;
void f() {
if (i == null) return;
print(i.isEven); // ERROR
}
}
Message:
'i' refers to a property so it couldn't be promoted.
Solution:
Si estás utilizando Dart 3.1 o anterior, actualiza a 3.2 o posterior.
Si necesitas seguir usando una versión anterior, lee la sección Otras causas y soluciones temporales.
Otras causas y soluciones temporales
#Los ejemplos restantes en esta página documentan las razones de los fallos de promoción no relacionados con inconsistencias de versión, tanto para fallos de campos como de variables locales, con ejemplos y soluciones temporales.
En general, las soluciones habituales para los fallos de promoción son una o más de las siguientes:
- Asignar el valor de la propiedad a una variable local con el tipo no anulable que necesitas.
- Agregar una comprobación de nulo explícita (por ejemplo,
i == null). - Usar
!oascomo una comprobación redundante si estás seguro de que una expresión no puede sernull.
Aquí tienes un ejemplo de cómo crear una variable local
(que puede llamarse i)
que contiene el valor de i:
class C {
int? i;
void f() {
final i = this.i;
if (i == null) return;
print(i.isEven);
}
}
Este ejemplo presenta un campo de instancia,
pero en su lugar podría usar un getter de instancia, un campo o getter estático,
una variable o getter de nivel superior, o this.
Y aquí tienes un ejemplo del uso de i!:
print(i!.isEven);
No se puede promover this
#
La causa:
Estás intentando promover this,
pero la promoción de tipos para this aún no es compatible.
Un escenario común de promoción de this es al escribir métodos de extensión (extension methods).
Si el tipo de extensión on
es un tipo anulable,
querrás hacer una comprobación de nulo para ver si this es null:
Example:
extension on int? {
int get valueOrZero {
return this == null ? 0 : this; // ERROR
}
}
Message:
`this` can't be promoted.
Solution:
Crea una variable local para contener el valor de this,
luego realiza la comprobación de nulo.
extension on int? {
int get valueOrZero {
final self = this;
return self == null ? 0 : self;
}
}
Solo los campos privados se pueden promover
#La causa: Estás intentando promover un campo, pero el campo no es privado.
Es posible que otras bibliotecas de tu programa sobreescriban campos públicos con un getter. Dado que es posible que los getters no devuelvan un valor estable, y el compilador no puede saber qué están haciendo otras bibliotecas, los campos no privados no se pueden promover.
Example:
class Example {
final int? value;
Example(this.value);
}
void test(Example x) {
if (x.value != null) {
print(x.value + 1); // ERROR
}
}
Message:
'value' refers to a public property so it couldn't be promoted.
Solution:
Hacer que el campo sea privado le permite al compilador estar seguro de que ninguna biblioteca externa podría sobreescribir su valor, por lo que es seguro promoverlo.
class Example {
final int? _value;
Example(this._value);
}
void test(Example x) {
if (x._value != null) {
print(x._value + 1);
}
}
Solo los campos final se pueden promover
#La causa: Estás intentando promover un campo, pero el campo no es final.
Para el compilador, los campos no finales podrían, en principio, ser modificados en cualquier momento entre el momento en que se prueban y el momento en que se usan. Por lo tanto, no es seguro para el compilador promover un tipo anulable no final a un tipo no anulable.
Example:
class Example {
int? _mutablePrivateField;
Example(this._mutablePrivateField);
void f() {
if (_mutablePrivateField != null) {
int i = _mutablePrivateField; // ERROR
}
}
}
Message:
'_mutablePrivateField' refers to a non-final field so it couldn't be promoted.
Solution:
Haz que el campo sea final:
class Example {
final int? _immutablePrivateField;
Example(this._immutablePrivateField);
void f() {
if (_immutablePrivateField != null) {
int i = _immutablePrivateField; // OK
}
}
}
Los getters no se pueden promover
#La causa: Estás intentando promover un getter, pero solo los campos de instancia se pueden promover, no los getters de instancia.
El compilador no tiene forma de garantizar que un getter devuelva el mismo resultado cada vez. Dado que su estabilidad no se puede confirmar, no es seguro promover los getters.
Example:
import 'dart:math';
abstract class Example {
int? get _value => Random().nextBool() ? 123 : null;
}
void f(Example x) {
if (x._value != null) {
print(x._value.isEven); // ERROR
}
}
Message:
'_value' refers to a getter so it couldn't be promoted.
Solution:
Asigna el getter a una variable local:
import 'dart:math';
abstract class Example {
int? get _value => Random().nextBool() ? 123 : null;
}
void f(Example x) {
final value = x._value;
if (value != null) {
print(value.isEven); // OK
}
}
Los campos externos (external) no se pueden promover
#La causa:
Estás intentando promover un campo, pero el campo está marcado como external.
Los campos externos no se promueven porque son esencialmente getters externos; su implementación es código externo a Dart, por lo que no hay garantía para el compilador de que un campo externo devuelva el mismo valor cada vez que se llama.
Example:
class Example {
external final int? _externalField;
void f() {
if (_externalField != null) {
print(_externalField.isEven); // ERROR
}
}
}
Message:
'_externalField' refers to an external field so it couldn't be promoted.
Solution:
Asigna el valor del campo externo a una variable local:
class Example {
external final int? _externalField;
void f() {
final i = _externalField;
if (i != null) {
print(i.isEven); // OK
}
}
}
Conflicto con un getter en otro lugar de la biblioteca
#La causa: Estás intentando promover un campo, pero otra clase en la misma biblioteca contiene un getter concreto con el mismo nombre.
Example:
import 'dart:math';
class Example {
final int? _overridden;
Example(this._overridden);
}
class Override implements Example {
@override
int? get _overridden => Random().nextBool() ? 1 : null;
}
void testParity(Example x) {
if (x._overridden != null) {
print(x._overridden.isEven); // ERROR
}
}
Message:
'_overridden' couldn't be promoted because there is a conflicting getter in class 'Override'.
Solution:
Si el getter y el campo están relacionados y necesitan compartir su nombre (como cuando uno de ellos sobreescribe al otro, como en el ejemplo de arriba), entonces puedes habilitar la promoción de tipos asignando el valor a una variable local:
import 'dart:math';
class Example {
final int? _overridden;
Example(this._overridden);
}
class Override implements Example {
@override
int? get _overridden => Random().nextBool() ? 1 : null;
}
void testParity(Example x) {
final i = x._overridden;
if (i != null) {
print(i.isEven); // OK
}
}
Nota sobre clases no relacionadas
#Ten en cuenta que en el ejemplo anterior está claro
por qué no es seguro promover el campo _overridden:
porque existe una relación de sobreescritura entre el campo y el getter.
Sin embargo, un getter en conflicto evitará la promoción del campo
incluso si las clases no están relacionadas. Por ejemplo:
import 'dart:math';
class Example {
final int? _i;
Example(this._i);
}
class Unrelated {
int? get _i => Random().nextBool() ? 1 : null;
}
void f(Example x) {
if (x._i != null) {
int i = x._i; // ERROR
}
}
Otra biblioteca podría contener una clase que combine las dos clases no relacionadas
en la misma jerarquía de clases,
lo que provocaría que la referencia en la función f a x._i se
despachara a Unrelated._i. Por ejemplo:
class Surprise extends Unrelated implements Example {}
void main() {
f(Surprise());
}
Solution:
Si el campo y la entidad en conflicto realmente no están relacionados, puedes solucionar el problema dándoles nombres diferentes:
class Example {
final int? _i;
Example(this._i);
}
class Unrelated {
int? get _j => Random().nextBool() ? 1 : null;
}
void f(Example x) {
if (x._i != null) {
int i = x._i; // OK
}
}
Conflicto con un campo no promovible en otro lugar de la biblioteca
#La causa: Estás intentando promover un campo, pero otra clase en la misma biblioteca contiene un campo con el mismo nombre que no es promovible (por cualquiera de las otras razones enumeradas en esta página).
Example:
class Example {
final int? _overridden;
Example(this._overridden);
}
class Override implements Example {
@override
int? _overridden;
}
void f(Example x) {
if (x._overridden != null) {
print(x._overridden.isEven); // ERROR
}
}
Este ejemplo falla porque en tiempo de ejecución, x podría ser en realidad una
instancia de Override, por lo que la promoción no sería sólida.
Message:
'overridden' couldn't be promoted because there is a conflicting non-promotable field in class 'Override'.
Solution:
Si los campos realmente están relacionados y necesitan compartir un nombre, entonces puedes habilitar la promoción de tipos asignando el valor a una variable local final para promover:
class Example {
final int? _overridden;
Example(this._overridden);
}
class Override implements Example {
@override
int? _overridden;
}
void f(Example x) {
final i = x._overridden;
if (i != null) {
print(i.isEven); // OK
}
}
Si los campos no están relacionados, entonces renombra uno de los campos para que no entren en conflicto. Lee la Nota sobre clases no relacionadas.
Conflicto con el reenviador implícito noSuchMethod
#
La causa:
Estás intentando promover un campo que es privado y final,
pero otra clase en la misma biblioteca contiene un
reenviador implícito noSuchMethod
con el mismo nombre que el campo.
Esto no es seguro porque no hay garantía de que noSuchMethod
devuelva un valor estable de una invocación a la siguiente.
Example:
import 'package:mockito/mockito.dart';
class Example {
final int? _i;
Example(this._i);
}
class MockExample extends Mock implements Example {}
void f(Example x) {
if (x._i != null) {
int i = x._i; // ERROR
}
}
En este ejemplo, _i no se puede promover porque podría
resolverse al reenviador implícito no seguro noSuchMethod (también llamado
_i) que
el compilador genera dentro de MockExample.
El compilador crea esta implementación implícita de _i porque
MockExample promete soportar un getter para _i cuando implementa
Example en su declaración, pero no cumple esa promesa.
Por lo tanto, la implementación del getter indefinido es manejada por la
definición noSuchMethod de Mock, la cual
crea un reenviador implícito noSuchMethod con el mismo nombre.
El fallo también puede ocurrir entre campos de clases no relacionadas.
Message:
'_i' couldn't be promoted because there is a conflicting noSuchMethod forwarder in class 'MockExample'.
Solution:
Define el getter en cuestión para que noSuchMethod no tenga
que manejar implícitamente su implementación:
import 'package:mockito/mockito.dart';
class Example {
final int? _i;
Example(this._i);
}
class MockExample extends Mock implements Example {
@override
late final int? _i;
}
void f(Example x) {
if (x._i != null) {
int i = x._i; // OK
}
}
El getter se declara como late para ser consistente con
cómo se usan generalmente los mocks; no es necesario
declarar el getter como late para resolver este fallo de promoción de tipos en
escenarios que no involucren mocks.
Posiblemente escrito después de la promoción
#La causa: Estás intentando promover una variable que podría haber sido escrita desde que fue promovida.
Example:
void f(bool b, int? i, int? j) {
if (i == null) return;
if (b) {
i = j; // (1)
}
if (!b) {
print(i.isEven); // (2) ERROR
}
}
Solution:
En este ejemplo, cuando el análisis de flujo llega a (1),
degrada i de int no anulable de vuelta a int? anulable.
Un humano puede notar que el acceso en (2) es seguro
porque no hay ninguna ruta de código que incluya tanto (1) como (2), pero
el análisis de flujo no es lo suficientemente inteligente como para ver eso,
porque no rastrea correlaciones entre
condiciones en declaraciones if separadas.
Podrías solucionar el problema combinando las dos declaraciones if:
void f(bool b, int? i, int? j) {
if (i == null) return;
if (b) {
i = j;
} else {
print(i.isEven);
}
}
En casos de flujo de control lineal como estos (sin bucles),
el análisis de flujo tiene en cuenta el lado derecho de la asignación
al decidir si degradar.
Como resultado, otra forma de corregir este código es
cambiar el tipo de j a int.
void f(bool b, int? i, int j) {
if (i == null) return;
if (b) {
i = j;
}
if (!b) {
print(i.isEven);
}
}
Posiblemente escrito en una iteración previa del bucle
#La causa: Estás intentando promover algo sobre lo que se podría haber escrito en una iteración anterior de un bucle, por lo que la promoción quedó invalidada.
Example:
void f(Link? p) {
if (p != null) return;
while (true) { // (1)
print(p.value); // (2) ERROR
var next = p.next;
if (next == null) break;
p = next; // (3)
}
}
Cuando el análisis de flujo llega a (1),
mira hacia adelante y ve la escritura en p en (3).
Pero debido a que está mirando hacia adelante,
aún no ha determinado el tipo del lado derecho de la asignación,
por lo que no sabe si es seguro mantener la promoción.
Para estar seguro, invalida la promoción.
Solution:
Puedes solucionar este problema moviendo la comprobación de null al principio del bucle:
void f(Link? p) {
while (p != null) {
print(p.value);
p = p.next;
}
}
Esta situación también puede surgir en declaraciones switch si
un bloque case tiene una etiqueta,
porque puedes usar declaraciones switch etiquetadas para construir bucles:
void f(int i, int? j, int? k) {
if (j == null) return;
switch (i) {
label:
case 0:
print(j.isEven); // ERROR
j = k;
continue label;
}
}
Nuevamente, puedes solucionar el problema moviendo la comprobación de null al principio del bucle:
void f(int i, int? j, int? k) {
switch (i) {
label:
case 0:
if (j == null) return;
print(j.isEven);
j = k;
continue label;
}
}
En catch después de una posible escritura en try
#La causa:
La variable podría haber sido escrita en un bloque try,
y la ejecución ahora se encuentra en un bloque catch.
Example:
void f(int? i, int? j) {
if (i == null) return;
try {
i = j; // (1)
// ... Additional code ...
if (i == null) return; // (2)
// ... Additional code ...
} catch (e) {
print(i.isEven); // (3) ERROR
}
}
En este caso, el análisis de flujo no considera seguro i.isEven (3),
porque no tiene forma de saber en qué momento del bloque try
podría haber ocurrido la excepción,
por lo que asume de manera conservadora que podría haber sucedido entre (1) y (2),
cuando i era potencialmente null.
Situaciones similares pueden ocurrir entre bloques try y finally, y
entre bloques catch y finally.
Debido a un artefacto histórico de cómo se realizó la implementación,
estas situaciones de try/catch/finally no tienen en cuenta
el lado derecho de la asignación,
de manera similar a lo que sucede en los bucles.
Solution:
Para solucionar el problema, asegúrate de que el bloque catch no
dependa de suposiciones sobre el estado de las variables que
cambian dentro del bloque try.
Recuerda, la excepción puede ocurrir en cualquier momento durante el bloque try,
posiblemente cuando i es null.
La solución más segura es agregar una comprobación de nulo dentro del bloque catch:
try {
// ···
} catch (e) {
if (i != null) {
print(i.isEven); // (3) OK due to the null check in the line above.
} else {
// Handle the case where i is null.
}
}
O, si estás seguro de que no puede ocurrir una excepción mientras i sea null,
simplemente usa el operador !:
try {
// ···
} catch (e) {
print(i!.isEven); // (3) OK because of the `!`.
}
Desajuste de subtipo (subtype mismatch)
#La causa: Estás intentando promover a un tipo que no es un subtipo del tipo promovido actual de la variable (o no era un subtipo en el momento del intento de promoción).
Example:
void f(Object o) {
if (o is Comparable /* (1) */ ) {
if (o is Pattern /* (2) */ ) {
print(o.matchAsPrefix('foo')); // (3) ERROR
}
}
}
En este ejemplo, o se promueve a Comparable en (1), pero
no se promueve a Pattern en (2),
porque Pattern no es un subtipo de Comparable.
(La lógica es que si se promoviera,
entonces no podrías usar métodos en Comparable).
Ten en cuenta que el hecho de que Pattern no sea un subtipo de Comparable
no significa que el código en (3) esté muerto;
o podría tener un tipo, como String, que
implemente tanto Comparable como Pattern.
Solution:
Una solución posible es crear una nueva variable local de modo que
la variable original se promueva a Comparable, y
la nueva variable se promueva a Pattern:
void f(Object o) {
if (o is Comparable /* (1) */ ) {
Object o2 = o;
if (o2 is Pattern /* (2) */ ) {
print(
o2.matchAsPrefix('foo'),
); // (3) OK; o2 was promoted to `Pattern`.
}
}
}
Sin embargo, alguien que edite el código más tarde podría verse tentado a
cambiar Object o2 por var o2.
Ese cambio le da a o2 el tipo Comparable,
lo que trae de vuelta el problema de que el objeto no sea promovible a Pattern.
Una comprobación de tipo redundante podría ser una mejor solución:
void f(Object o) {
if (o is Comparable /* (1) */ ) {
if (o is Pattern /* (2) */ ) {
print((o as Pattern).matchAsPrefix('foo')); // (3) OK
}
}
}
Otra solución que a veces funciona es cuando puedes usar un tipo más preciso.
Si la línea 3 solo se interesa por cadenas,
entonces puedes usar String en tu comprobación de tipo.
Como String es un subtipo de Comparable, la promoción funciona:
void f(Object o) {
if (o is Comparable /* (1) */ ) {
if (o is String /* (2) */ ) {
print(o.matchAsPrefix('foo')); // (3) OK
}
}
}
Escritura capturada por una función local
#La causa: La variable ha sido capturada para escritura (write-captured) por una función local o expresión de función.
Example:
void f(int? i, int? j) {
var foo = () {
i = j;
};
// ... Use foo ...
if (i == null) return; // (1)
// ... Additional code ...
print(i.isEven); // (2) ERROR
}
El análisis de flujo deduce que tan pronto como se alcanza la definición de foo,
esta podría ser llamada en cualquier momento,
por lo tanto, ya no es seguro promover i en absoluto.
Al igual que con los bucles, esta degradación ocurre independientemente del
tipo del lado derecho de la asignación.
Solution:
A veces es posible reestructurar la lógica de manera que la promoción se realice antes de la captura por escritura:
void f(int? i, int? j) {
if (i == null) return; // (1)
// ... Additional code ...
print(i.isEven); // (2) OK
var foo = () {
i = j;
};
// ... Use foo ...
}
Otra opción es crear una variable local, para que no sea capturada por escritura:
void f(int? i, int? j) {
var foo = () {
i = j;
};
// ... Use foo ...
var i2 = i;
if (i2 == null) return; // (1)
// ... Additional code ...
print(i2.isEven); // (2) OK because `i2` isn't write captured.
}
O puedes hacer una comprobación redundante:
void f(int? i, int? j) {
var foo = () {
i = j;
};
// ... Use foo ...
if (i == null) return; // (1)
// ... Additional code ...
print(i!.isEven); // (2) OK due to `!` check.
}
Escrito fuera de la clausura o expresión de función actual
#La causa: La variable se escribe fuera de una clausura (closure) o expresión de función, y la ubicación de la promoción de tipos está dentro de la clausura o expresión de función.
Example:
void f(int? i, int? j) {
if (i == null) return;
var foo = () {
print(i.isEven); // (1) ERROR
};
i = j; // (2)
}
El análisis de flujo deduce que no hay forma de determinar
cuándo podría llamarse a foo,
por lo que podría llamarse después de la asignación en (2),
y por lo tanto, la promoción podría dejar de ser válida.
Al igual que con los bucles, esta degradación ocurre independientemente del tipo del
lado derecho de la asignación.
Solution:
Una solución es crear una variable local:
void f(int? i, int? j) {
if (i == null) return;
var i2 = i;
var foo = () {
print(i2.isEven); // (1) OK because `i2` isn't changed later.
};
i = j; // (2)
}
Example:
Un caso particularmente desagradable se ve así:
void f(int? i) {
i ??= 0;
var foo = () {
print(i.isEven); // ERROR
};
}
En este caso, un humano puede ver que la promoción es segura porque
la única escritura en i utiliza un valor no nulo y
ocurre antes de que se cree foo.
Pero el análisis de flujo no es tan inteligente.
Solution:
Nuevamente, una solución es crear una variable local:
void f(int? i) {
var j = i ?? 0;
var foo = () {
print(j.isEven); // OK
};
}
Esta solución funciona porque se infiere que j tiene un tipo no anulable (int)
debido a su valor inicial (i ?? 0).
Debido a que j tiene un tipo no anulable,
independientemente de si se asigna más tarde o no,
j nunca puede tener un valor no nulo.
Escritura capturada fuera de la clausura o expresión de función actual
#La causa: La variable que estás intentando promover está capturada para escritura fuera de una clausura (closure) o expresión de función, pero este uso de la variable está dentro de la clausura o expresión de función que está intentando promoverla.
Example:
void f(int? i, int? j) {
var foo = () {
if (i == null) return;
print(i.isEven); // ERROR
};
var bar = () {
i = j;
};
}
El análisis de flujo deduce que no hay forma de saber
en qué orden podrían ejecutarse foo y bar;
de hecho, bar podría incluso ejecutarse a la mitad de la ejecución de foo
(debido a que foo llama a algo que llama a bar).
Por lo tanto, no es seguro promover i en absoluto dentro de foo.
Solution:
La mejor solución es probablemente crear una variable local:
void f(int? i, int? j) {
var foo = () {
var i2 = i;
if (i2 == null) return;
print(i2.isEven); // OK because i2 is local to this closure.
};
var bar = () {
i = j;
};
}
A menos que se indique lo contrario, la documentación en este sitio refleja Dart 3.12.2. Página actualizada por última vez el 2026-05-15. Ver fuente oreportar un problema.