Skip to content

Commit 4801d23

Browse files
brandjoncopybara-github
authored andcommitted
Add type checking of compiled expressions
This updates `Program#compileExpr` to perform type checking. It also adds a unit test, `eval/StaticTypeCheckTest.java`, to verify static type checking behavior at the `Program` level. This differs from the existing unit test `syntax/TypeCheckerTest`, which works at the level of the type checker itself and which doesn't know the production definitions of the universal types. Added `TypeTagger#tagExpr` to mirror `Resolver#resolveExpr`. (`TypeChecker` already has `inferTypeOf` to operate at the expression level.) Also added `TypeTagger#tagExprFunction` to set the type on the final resolved function synthesized for the expression by the resolver, since this function is not accessible from the AST. Work toward bazelbuild#28325. PiperOrigin-RevId: 864422737 Change-Id: I41b2599160a456bf157b554a51a36ac56b7b61e6
1 parent 945c6b5 commit 4801d23

File tree

5 files changed

+145
-2
lines changed

5 files changed

+145
-2
lines changed

src/main/java/net/starlark/java/syntax/Program.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,16 @@ public static Program compileFile(StarlarkFile file, Resolver.Module env)
162162
public static Program compileExpr(Expression expr, Resolver.Module module, FileOptions options)
163163
throws SyntaxError.Exception {
164164
Resolver.Function body = Resolver.resolveExpr(expr, module, options);
165-
// TODO: #27370 - This utility method should also have some form of type checking applied to it,
166-
// using whatever type definitions are available in the given module.
165+
166+
if (options.resolveTypeSyntax()) {
167+
TypeTagger.tagExpr(expr, module);
168+
}
169+
170+
if (options.staticTypeChecking()) {
171+
StarlarkType exprType = TypeChecker.inferTypeOf(expr);
172+
TypeTagger.tagExprFunction(body, exprType);
173+
}
174+
167175
return new Program(
168176
body,
169177
/* loads= */ ImmutableList.of(),

src/main/java/net/starlark/java/syntax/TypeTagger.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,4 +373,38 @@ public static void tagFile(StarlarkFile file, Module module) {
373373
TypeTagger r = new TypeTagger(file.errors, module);
374374
r.visit(file);
375375
}
376+
377+
/**
378+
* Same as {@link #tagFile}, but for an individual expression.
379+
*
380+
* <p>Any errors are thrown as a {@link SyntaxError.Exception}.
381+
*/
382+
public static void tagExpr(Expression expr, Module module) throws SyntaxError.Exception {
383+
List<SyntaxError> errors = new ArrayList<>();
384+
TypeTagger r = new TypeTagger(errors, module);
385+
386+
r.visit(expr);
387+
388+
if (!errors.isEmpty()) {
389+
throw new SyntaxError.Exception(errors);
390+
}
391+
}
392+
393+
/**
394+
* Sets the Starlark type on a {@link Resolver.Function} that the resolver generated to wrap an
395+
* expression.
396+
*/
397+
public static void tagExprFunction(Resolver.Function function, StarlarkType exprType) {
398+
Types.CallableType functionType =
399+
Types.callable(
400+
/* parameterNames= */ ImmutableList.of(),
401+
/* parameterTypes= */ ImmutableList.of(),
402+
/* numPositionalOnlyParameters= */ 0,
403+
/* numPositionalParameters= */ 0,
404+
/* mandatoryParams= */ ImmutableSet.of(),
405+
/* varargsType= */ null,
406+
/* kwargsType= */ null,
407+
/* returns= */ exprType);
408+
setType(function, functionType);
409+
}
376410
}

src/test/java/net/starlark/java/eval/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ java_test(
3232
"StarlarkMutableTest.java",
3333
"StarlarkThreadDebuggingTest.java",
3434
"StarlarkThreadTest.java",
35+
"StaticTypeCheckTest.java",
3536
"SymbolGeneratorTest.java",
3637
],
3738
jvm_flags = [

src/test/java/net/starlark/java/eval/EvalTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@
3535
StarlarkMutableTest.class,
3636
StarlarkThreadDebuggingTest.class,
3737
StarlarkThreadTest.class,
38+
StaticTypeCheckTest.class,
3839
})
3940
public class EvalTests {}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright 2026 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package net.starlark.java.eval;
16+
17+
import static net.starlark.java.syntax.TestUtils.assertContainsError;
18+
import static org.junit.Assert.assertThrows;
19+
20+
import net.starlark.java.syntax.Expression;
21+
import net.starlark.java.syntax.FileOptions;
22+
import net.starlark.java.syntax.ParserInput;
23+
import net.starlark.java.syntax.Program;
24+
import net.starlark.java.syntax.StarlarkFile;
25+
import net.starlark.java.syntax.StarlarkType;
26+
import net.starlark.java.syntax.SyntaxError;
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
import org.junit.runners.JUnit4;
30+
31+
/**
32+
* Integrated tests for static type checking of Starlark code.
33+
*
34+
* <p>For tests of the type checker logic in isolation, see syntax/TypeCheckerTest.java.
35+
*/
36+
@RunWith(JUnit4.class)
37+
public final class StaticTypeCheckTest {
38+
39+
@SuppressWarnings("FieldCanBeFinal")
40+
private FileOptions.Builder options =
41+
FileOptions.builder()
42+
.allowTypeSyntax(true)
43+
.resolveTypeSyntax(true)
44+
.staticTypeChecking(true)
45+
// This lets us construct simpler test cases without wrapper `def` statements.
46+
.allowToplevelRebinding(true);
47+
48+
@SuppressWarnings("FieldCanBeFinal")
49+
private Module module = Module.create();
50+
51+
private Program compile(String... lines) throws SyntaxError.Exception {
52+
ParserInput input = ParserInput.fromLines(lines);
53+
StarlarkFile file = StarlarkFile.parse(input, options.build());
54+
return Program.compileFile(file, module);
55+
}
56+
57+
private void assertValid(String... lines) {
58+
try {
59+
compile(lines);
60+
} catch (SyntaxError.Exception ex) {
61+
throw new AssertionError("Expected success, but got: " + ex.getMessage(), ex);
62+
}
63+
}
64+
65+
private void assertInvalid(String message, String... lines) {
66+
SyntaxError.Exception ex = assertThrows(SyntaxError.Exception.class, () -> compile(lines));
67+
assertContainsError(ex.errors(), message);
68+
}
69+
70+
private StarlarkType inferType(String expr) throws SyntaxError.Exception {
71+
ParserInput input = ParserInput.fromLines(expr);
72+
Expression expression = Expression.parse(input, options.build());
73+
Program program = Program.compileExpr(expression, module, options.build());
74+
return program.getResolvedFunction().getFunctionType();
75+
}
76+
77+
@Test
78+
public void typecheckSuccess() {
79+
assertValid("n = 123 + 123");
80+
}
81+
82+
@Test
83+
public void typecheckFailure() {
84+
assertInvalid(
85+
"operator '+' cannot be applied to types 'int' and 'str'",
86+
"""
87+
n = 123 + 'abc'
88+
""");
89+
}
90+
91+
@Test
92+
public void noneAsType() {
93+
assertInvalid(
94+
"cannot assign type 'int' to 'x' of type 'None'",
95+
"""
96+
x : None = 123
97+
""");
98+
}
99+
}

0 commit comments

Comments
 (0)