Type checker: Add list/dict literal support
This also adds a `Never` bottom type to the hierarchy, and allows unions to be empty (at least internally). The type of an empty list is list[Never], and likewise for dictionaries. (Mypy requires user disambiguation in these types of cases; we may change to that in the future.) `Never` is not made available to user annotations in this CL.
Work toward #28037.
PiperOrigin-RevId: 853386929
Change-Id: Ic5ded1bff44af88c3adb9295b81bf10d4b579d58
diff --git a/src/main/java/net/starlark/java/syntax/TypeChecker.java b/src/main/java/net/starlark/java/syntax/TypeChecker.java
index 2b5ce08..309e5dd 100644
--- a/src/main/java/net/starlark/java/syntax/TypeChecker.java
+++ b/src/main/java/net/starlark/java/syntax/TypeChecker.java
@@ -123,9 +123,27 @@
case INDEX -> {
return inferIndex((IndexExpression) expr);
}
+ case LIST_EXPR -> {
+ var list = (ListExpression) expr;
+ List<StarlarkType> elementTypes = new ArrayList<>();
+ for (Expression element : list.getElements()) {
+ elementTypes.add(infer(element));
+ }
+ return Types.list(Types.union(elementTypes));
+ }
+ case DICT_EXPR -> {
+ var dict = (DictExpression) expr;
+ List<StarlarkType> keyTypes = new ArrayList<>();
+ List<StarlarkType> valueTypes = new ArrayList<>();
+ for (var entry : dict.getEntries()) {
+ keyTypes.add(infer(entry.getKey()));
+ valueTypes.add(infer(entry.getValue()));
+ }
+ return Types.dict(Types.union(keyTypes), Types.union(valueTypes));
+ }
default -> {
- // TODO: #28037 - support binaryop, call, cast, comprehension, conditional, dict_expr,
- // lambda, list, slice, and unaryop expressions.
+ // TODO: #28037 - support binaryop, call, cast, comprehension, conditional,
+ // lambda, slice, and unaryop expressions.
throw new UnsupportedOperationException(
String.format("cannot typecheck %s expression", expr.kind()));
}
diff --git a/src/main/java/net/starlark/java/types/Types.java b/src/main/java/net/starlark/java/types/Types.java
index 09972f6..413385e 100644
--- a/src/main/java/net/starlark/java/types/Types.java
+++ b/src/main/java/net/starlark/java/types/Types.java
@@ -48,6 +48,9 @@
/** The top type of the type hierarchy. */
public static final StarlarkType OBJECT = new ObjectType();
+ /** The bottom type of the type hierarchy. */
+ public static final StarlarkType NEVER = new NeverType();
+
// Primitive types
public static final StarlarkType NONE = new None();
@@ -120,6 +123,23 @@
}
}
+ private static final class NeverType extends StarlarkType {
+ @Override
+ public String toString() {
+ return "Never";
+ }
+
+ @Override
+ public int hashCode() {
+ return NeverType.class.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof NeverType;
+ }
+ }
+
private static final class None extends StarlarkType {
@Override
public String toString() {
@@ -389,7 +409,7 @@
if (subtypes.size() == 1) {
return subtypes.iterator().next();
} else if (subtypes.isEmpty()) {
- throw new IllegalArgumentException("Empty union!");
+ return Types.NEVER;
}
return new AutoValue_Types_UnionType(subtypes);
}
diff --git a/src/test/java/net/starlark/java/syntax/TypeCheckerTest.java b/src/test/java/net/starlark/java/syntax/TypeCheckerTest.java
index 7eb685e6..f76c542 100644
--- a/src/test/java/net/starlark/java/syntax/TypeCheckerTest.java
+++ b/src/test/java/net/starlark/java/syntax/TypeCheckerTest.java
@@ -446,4 +446,30 @@
t[1] = 123
""");
}
+
+ @Test
+ public void infer_dict() throws Exception {
+ // Empty case.
+ assertTypeGivenDecls("{}", Types.dict(Types.NEVER, Types.NEVER));
+
+ // Homogeneous case.
+ assertTypeGivenDecls("{'a': 1, 'b': 2}", Types.dict(Types.STR, Types.INT));
+
+ // Heterogeneous case.
+ StarlarkType unionType = Types.union(Types.STR, Types.INT);
+ assertTypeGivenDecls("{'a': 'abc', 1: 123}", Types.dict(unionType, unionType));
+ }
+
+ @Test
+ public void infer_list() throws Exception {
+ // Empty case.
+ assertTypeGivenDecls("[]", Types.list(Types.NEVER));
+
+ // Homogeneous case.
+ assertTypeGivenDecls("[1, 2, 3]", Types.list(Types.INT));
+
+ // Heterogeneous case.
+ StarlarkType unionType = Types.union(Types.INT, Types.STR);
+ assertTypeGivenDecls("[1, 'a']", Types.list(unionType));
+ }
}