Skip to content

Commit 515083c

Browse files
tetrominocopybara-github
authored andcommitted
Add isinstance keyword, and allow isinstance(x,t) to be parsed - but not resolved
Note that we parse arbitrary type expressions on the RHS of isinstance, but will be planning to limit the allowed types when resolving (in a follow-up). Consider, for example, something like isinstance(x, list[int]) - we cannot allow it because x may subsequently get a non-int element added to it. By contrast, we may want to allow e.g. `isinstance(f, Callable[[str], bool])` to test if f is a predicate on a string (note that Python doesn't, but in Python this is an eval-time error, not a syntax error). And further consider the possibility of `isinstance(x, T)` - where `T` is a type alias (and which might well be an alias for something we don't want to allow). So we cannot take the simple path of syntactically allowing only identifiers. Also make a drive-by fix to `cast()`'s pretty-printer. Working towards bazelbuild#27372 and isinstance(). PiperOrigin-RevId: 829638719 Change-Id: Iad1c4307fb4da262031b5e664f846662a081c210
1 parent 11f7383 commit 515083c

File tree

14 files changed

+213
-12
lines changed

14 files changed

+213
-12
lines changed

src/main/java/net/starlark/java/eval/Eval.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,9 @@ private static Object eval(StarlarkThread.Frame fr, Expression expr)
562562
return evalCall(fr, (CallExpression) expr);
563563
case CAST:
564564
return eval(fr, ((CastExpression) expr).getValue());
565+
case ISINSTANCE:
566+
fr.setErrorLocation(expr.getStartLocation());
567+
throw new EvalException("isinstance() is not yet supported");
565568
case IDENTIFIER:
566569
return evalIdentifier(fr, (Identifier) expr);
567570
case INDEX:

src/main/java/net/starlark/java/syntax/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ java_library(
4141
"IfStatement.java",
4242
"IndexExpression.java",
4343
"IntLiteral.java",
44+
"IsInstanceExpression.java",
4445
"LambdaExpression.java",
4546
"Lexer.java",
4647
"ListExpression.java",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public enum Kind {
4141
IDENTIFIER,
4242
INDEX,
4343
INT_LITERAL,
44+
ISINSTANCE,
4445
LAMBDA,
4546
LIST_EXPR,
4647
SLICE,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2025 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.syntax;
16+
17+
/** Syntax node for isinstance() expressions. */
18+
public final class IsInstanceExpression extends Expression {
19+
private final int startOffset;
20+
private final Expression value;
21+
private final Expression type;
22+
private final int rparenOffset;
23+
24+
IsInstanceExpression(
25+
FileLocations locs, int startOffset, Expression value, Expression type, int rparenOffset) {
26+
super(locs, Kind.ISINSTANCE);
27+
this.startOffset = startOffset;
28+
this.value = value;
29+
this.type = type;
30+
this.rparenOffset = rparenOffset;
31+
}
32+
33+
@Override
34+
public int getStartOffset() {
35+
return startOffset;
36+
}
37+
38+
@Override
39+
public int getEndOffset() {
40+
return rparenOffset + 1;
41+
}
42+
43+
public Expression getValue() {
44+
return value;
45+
}
46+
47+
public Expression getType() {
48+
return type;
49+
}
50+
51+
@Override
52+
public String toString() {
53+
StringBuilder buf = new StringBuilder();
54+
buf.append("isinstance(");
55+
buf.append(value);
56+
buf.append(", ");
57+
buf.append(type);
58+
buf.append(')');
59+
return buf.toString();
60+
}
61+
62+
@Override
63+
public void accept(NodeVisitor visitor) {
64+
visitor.visit(this);
65+
}
66+
}

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

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,9 @@ private void scanToNewline() {
562562

563563
private static final Map<String, TokenKind> keywordMap = new HashMap<>();
564564

565+
/** Additional keywords that are only recognized if --experimental_starlark_type_syntax is set. */
566+
private static final Map<String, TokenKind> typeSyntaxExtraKeywordMap = new HashMap<>();
567+
565568
static {
566569
keywordMap.put("and", TokenKind.AND);
567570
keywordMap.put("as", TokenKind.AS);
@@ -594,6 +597,9 @@ private void scanToNewline() {
594597
keywordMap.put("while", TokenKind.WHILE);
595598
keywordMap.put("with", TokenKind.WITH);
596599
keywordMap.put("yield", TokenKind.YIELD);
600+
601+
typeSyntaxExtraKeywordMap.put("cast", TokenKind.CAST);
602+
typeSyntaxExtraKeywordMap.put("isinstance", TokenKind.ISINSTANCE);
597603
}
598604

599605
/**
@@ -606,16 +612,14 @@ private void identifierOrKeyword() {
606612
int oldPos = pos - 1;
607613
String id = identInterner.intern(scanIdentifier());
608614
TokenKind kind = keywordMap.get(id);
615+
if (kind == null && options.allowTypeSyntax()) {
616+
kind = typeSyntaxExtraKeywordMap.get(id);
617+
}
609618
if (kind == null) {
610-
if (options.allowTypeSyntax() && id.equals("cast")) {
611-
// `cast` is treated as a keyword iff --experimental_starlark_type_syntax is enabled.
612-
setToken(TokenKind.CAST, oldPos, pos);
613-
} else {
614-
setToken(TokenKind.IDENTIFIER, oldPos, pos);
615-
// setValue allocates a new String for the raw text, but it's not retained so we don't
616-
// bother interning it.
617-
setValue(id);
618-
}
619+
setToken(TokenKind.IDENTIFIER, oldPos, pos);
620+
// setValue allocates a new String for the raw text, but it's not retained so we don't
621+
// bother interning it.
622+
setValue(id);
619623
} else {
620624
setToken(kind, oldPos, pos);
621625
}

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,9 @@ private void printExpr(Expression expr, boolean canSkipParenthesis) {
405405
{
406406
CastExpression cast = (CastExpression) expr;
407407
buf.append("cast(");
408-
printExpr(cast.getType());
408+
printExpr(cast.getType(), /* canSkipParenthesis= */ true);
409409
buf.append(", ");
410-
printExpr(cast.getValue());
410+
printExpr(cast.getValue(), /* canSkipParenthesis= */ true);
411411
buf.append(')');
412412
break;
413413
}
@@ -438,6 +438,17 @@ private void printExpr(Expression expr, boolean canSkipParenthesis) {
438438
break;
439439
}
440440

441+
case ISINSTANCE:
442+
{
443+
IsInstanceExpression isinstance = (IsInstanceExpression) expr;
444+
buf.append("isinstance(");
445+
printExpr(isinstance.getValue(), /* canSkipParenthesis= */ true);
446+
buf.append(", ");
447+
printExpr(isinstance.getType(), /* canSkipParenthesis= */ true);
448+
buf.append(')');
449+
break;
450+
}
451+
441452
case FLOAT_LITERAL:
442453
{
443454
buf.append(((FloatLiteral) expr).getValue());

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ public void visit(CastExpression node) {
5858
visit(node.getValue());
5959
}
6060

61+
public void visit(IsInstanceExpression node) {
62+
visit(node.getValue());
63+
}
64+
6165
public void visit(Ellipsis node) {}
6266

6367
public void visit(Identifier node) {}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,21 @@ private Expression parseCastExpression() {
569569
return new CastExpression(locs, startOffset, typeExpr, valueExpr, rparenOffset);
570570
}
571571

572+
// isinstance_expression = 'isinstance' '(' expr ',' TypeExpr [','] ')'
573+
private Expression parseIsInstanceExpression() {
574+
checkAllowTypeSyntax(token.start, token.kind, token.value);
575+
int startOffset = expect(TokenKind.ISINSTANCE);
576+
expect(TokenKind.LPAREN);
577+
Expression valueExpr = parseTest();
578+
expect(TokenKind.COMMA);
579+
Expression typeExpr = parseTypeExprWithFallback();
580+
if (token.kind == TokenKind.COMMA) {
581+
expect(TokenKind.COMMA);
582+
}
583+
int rparenOffset = expect(TokenKind.RPAREN);
584+
return new IsInstanceExpression(locs, startOffset, valueExpr, typeExpr, rparenOffset);
585+
}
586+
572587
// Parse a list of call arguments.
573588
//
574589
// arg_list = ( (arg ',')* arg ','? )?
@@ -754,6 +769,9 @@ private Expression parsePrimary() {
754769
case CAST:
755770
return parseCastExpression();
756771

772+
case ISINSTANCE:
773+
return parseIsInstanceExpression();
774+
757775
case ELLIPSIS:
758776
if (!insideTypeExpr) {
759777
syntaxError("ellipsis ('...') is not allowed outside type expressions");

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,14 @@ public void visit(VarStatement node) {
804804
assign(node.getIdentifier());
805805
}
806806

807+
@Override
808+
public void visit(IsInstanceExpression node) {
809+
// TODO(b/350661266): restrict the types that can be used on the RHS of isinstance(); e.g.
810+
// `list` or `list | tuple` (or aliases resolving to those!) are allowed, but `list[int]` isn't,
811+
// since a list can subsequently be mutated to add a non-int element.
812+
errorf(node, "isinstance() is not yet supported");
813+
}
814+
807815
@Override
808816
public void visit(TypeAliasStatement node) {
809817
if (!(locals.syntax instanceof StarlarkFile)) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public enum TokenKind {
6868
INDENT("indent"),
6969
INT("integer literal"),
7070
IS("is"),
71+
/** Emitted only if --experimental_starlark_type_syntax is enabled. */
72+
ISINSTANCE("isinstance"),
7173
LAMBDA("lambda"),
7274
LBRACE("{"),
7375
LBRACKET("["),

0 commit comments

Comments
 (0)