implements PR003
This commit is contained in:
parent
9ddfa19bf2
commit
96c5505c04
@ -1,35 +0,0 @@
|
||||
# PR-002 - PBS AST Contract V1
|
||||
|
||||
## Briefing
|
||||
Hoje o AST representa apenas funcoes, `let/return` e expressoes basicas.
|
||||
A spec exige familias obrigatorias de declaracoes e metadados estaveis para parser, diagnosticos e lowering.
|
||||
Este PR fecha o contrato estrutural do AST sem ainda resolver todas as regras semanticas.
|
||||
|
||||
## Target
|
||||
- Specs:
|
||||
- `docs/pbs/specs/11. AST Specification.md` (secoes 6, 7, 8, 9, 10)
|
||||
- `docs/pbs/specs/3. Core Syntax Specification.md` (secoes 6, 7, 8, 9, 10)
|
||||
- Codigo:
|
||||
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/ast/PbsAst.java`
|
||||
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java`
|
||||
|
||||
## Method
|
||||
1. Remodelar o root para conter imports e uma lista de `TopDecl` em ordem de fonte.
|
||||
2. Introduzir familias obrigatorias de declaracao (`fn`, `struct`, `contract`, `service`, `error`, `enum`, `callback`, `declare const`, `implements`).
|
||||
3. Adicionar estruturas de tipo (unit, simple, optional, tuple nomeada, `Self`) e retorno (`plain`, `result`).
|
||||
4. Garantir `file/start/end` em todos os nos consumidos por diagnostico/lowering.
|
||||
5. Definir nos de erro/recovery explicitos para evitar AST permissivo.
|
||||
|
||||
## Acceptance Criteria
|
||||
- Existe exatamente um root por arquivo com filhos em ordem deterministica.
|
||||
- AST preserva identidade de declaracoes sem colapsar overload cedo.
|
||||
- Cada declaracao obrigatoria possui nome, assinatura/superficie e span.
|
||||
- Parser recovery nao fabrica semantica valida; erros ficam marcados no AST.
|
||||
- Precedencia e associatividade continuam observaveis pelo shape da arvore.
|
||||
|
||||
## Tests
|
||||
- `PbsParserTest`:
|
||||
- validacao de shape do root (`imports + topDecls`);
|
||||
- cobertura de todas as familias de declaracao obrigatorias no AST;
|
||||
- asserts de spans (`file/start/end`) em nos principais;
|
||||
- fixture com erro e recovery garantindo estrutura coerente.
|
||||
@ -1,35 +0,0 @@
|
||||
# PR-003 - PBS Parser Declarations and Type Surfaces
|
||||
|
||||
## Briefing
|
||||
A sintaxe de declaracoes e tipos da spec e significativamente maior que o parser atual.
|
||||
Este PR implementa a fatia de parser para declaracoes top-level e superficies de tipo/retorno, mantendo rejeicao deterministica das formas reservadas fora do contexto permitido.
|
||||
|
||||
## Target
|
||||
- Specs:
|
||||
- `docs/pbs/specs/3. Core Syntax Specification.md` (secoes 6.2, 7.1-7.6, 8)
|
||||
- `docs/pbs/specs/11. AST Specification.md` (secao 8)
|
||||
- Codigo:
|
||||
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java`
|
||||
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/ParseErrors.java`
|
||||
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/ast/PbsAst.java`
|
||||
|
||||
## Method
|
||||
1. Implementar parser de `declare struct/service/contract/error/enum/callback/const` e `implements`.
|
||||
2. Trocar assinatura de funcao para retorno via `->` com normalizacao de unit e single-slot.
|
||||
3. Implementar parser de `TypeRef` com `optional`, tuple nomeada e `Self`.
|
||||
4. Rejeitar no parser formas reservadas de `declare host` e `declare builtin type` em modulos ordinarios.
|
||||
5. Melhorar sincronizacao de recovery por contexto de declaracao.
|
||||
|
||||
## Acceptance Criteria
|
||||
- Top-level parseia todas as declaracoes previstas em `TopDecl`.
|
||||
- Retornos seguem formato normativo (`->`, `result<Err>`, `void`/`()`).
|
||||
- `optional` e tuple nomeada em tipo sao parseados com shape correto.
|
||||
- Formas invalidas de declaracao geram erro deterministico e parser continua.
|
||||
- `declare host`/`declare builtin type` em codigo comum sao rejeitados explicitamente.
|
||||
|
||||
## Tests
|
||||
- `PbsParserDeclarationsTest` novo cobrindo:
|
||||
- cada declaracao valida (struct, service, contract, error, enum, callback, const, implements);
|
||||
- erros de shape (enum misto implicito/explicito, retorno mal formado, optional invalido);
|
||||
- rejeicao de `declare host` fora de superficie reservada;
|
||||
- spans e recovery em declaracoes quebradas.
|
||||
@ -49,7 +49,7 @@ public final class PbsFrontendCompiler {
|
||||
fileId,
|
||||
fn.name(),
|
||||
fn.parameters().size(),
|
||||
fn.returnType() != null,
|
||||
fn.returnKind() != PbsAst.ReturnKind.INFERRED_UNIT,
|
||||
fn.span()));
|
||||
}
|
||||
return ReadOnlyList.wrap(functions);
|
||||
|
||||
@ -9,6 +9,23 @@ public final class PbsAst {
|
||||
private PbsAst() {
|
||||
}
|
||||
|
||||
public enum ReturnKind {
|
||||
INFERRED_UNIT,
|
||||
EXPLICIT_UNIT,
|
||||
PLAIN,
|
||||
RESULT
|
||||
}
|
||||
|
||||
public enum TypeRefKind {
|
||||
SIMPLE,
|
||||
SELF,
|
||||
OPTIONAL,
|
||||
UNIT,
|
||||
GROUP,
|
||||
NAMED_TUPLE,
|
||||
ERROR
|
||||
}
|
||||
|
||||
public record File(
|
||||
ReadOnlyList<ImportDecl> imports,
|
||||
ReadOnlyList<TopDecl> topDecls,
|
||||
@ -59,7 +76,9 @@ public final class PbsAst {
|
||||
public record FunctionDecl(
|
||||
String name,
|
||||
ReadOnlyList<Parameter> parameters,
|
||||
ReturnKind returnKind,
|
||||
TypeRef returnType,
|
||||
TypeRef resultErrorType,
|
||||
Expression elseFallback,
|
||||
Block body,
|
||||
Span span) implements TopDecl {
|
||||
@ -67,33 +86,41 @@ public final class PbsAst {
|
||||
|
||||
public record StructDecl(
|
||||
String name,
|
||||
ReadOnlyList<StructField> fields,
|
||||
boolean hasBody,
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
|
||||
public record ContractDecl(
|
||||
String name,
|
||||
ReadOnlyList<FunctionSignature> signatures,
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
|
||||
public record ServiceDecl(
|
||||
String name,
|
||||
ReadOnlyList<FunctionDecl> methods,
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
|
||||
public record ErrorDecl(
|
||||
String name,
|
||||
ReadOnlyList<String> cases,
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
|
||||
public record EnumDecl(
|
||||
String name,
|
||||
ReadOnlyList<EnumCase> cases,
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
|
||||
public record CallbackDecl(
|
||||
String name,
|
||||
ReadOnlyList<Parameter> parameters,
|
||||
ReturnKind returnKind,
|
||||
TypeRef returnType,
|
||||
TypeRef resultErrorType,
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
|
||||
@ -123,7 +150,66 @@ public final class PbsAst {
|
||||
}
|
||||
|
||||
public record TypeRef(
|
||||
TypeRefKind kind,
|
||||
String name,
|
||||
ReadOnlyList<NamedTypeField> fields,
|
||||
TypeRef inner,
|
||||
Span span) {
|
||||
public static TypeRef simple(final String name, final Span span) {
|
||||
return new TypeRef(TypeRefKind.SIMPLE, name, ReadOnlyList.empty(), null, span);
|
||||
}
|
||||
|
||||
public static TypeRef self(final Span span) {
|
||||
return new TypeRef(TypeRefKind.SELF, "Self", ReadOnlyList.empty(), null, span);
|
||||
}
|
||||
|
||||
public static TypeRef optional(final TypeRef inner, final Span span) {
|
||||
return new TypeRef(TypeRefKind.OPTIONAL, "optional", ReadOnlyList.empty(), inner, span);
|
||||
}
|
||||
|
||||
public static TypeRef unit(final Span span) {
|
||||
return new TypeRef(TypeRefKind.UNIT, "void", ReadOnlyList.empty(), null, span);
|
||||
}
|
||||
|
||||
public static TypeRef group(final TypeRef inner, final Span span) {
|
||||
return new TypeRef(TypeRefKind.GROUP, "group", ReadOnlyList.empty(), inner, span);
|
||||
}
|
||||
|
||||
public static TypeRef namedTuple(final ReadOnlyList<NamedTypeField> fields, final Span span) {
|
||||
return new TypeRef(TypeRefKind.NAMED_TUPLE, "tuple", fields, null, span);
|
||||
}
|
||||
|
||||
public static TypeRef error(final Span span) {
|
||||
return new TypeRef(TypeRefKind.ERROR, "<error>", ReadOnlyList.empty(), null, span);
|
||||
}
|
||||
}
|
||||
|
||||
public record NamedTypeField(
|
||||
String label,
|
||||
TypeRef typeRef,
|
||||
Span span) {
|
||||
}
|
||||
|
||||
public record StructField(
|
||||
String name,
|
||||
TypeRef typeRef,
|
||||
boolean isPublic,
|
||||
boolean isMutable,
|
||||
Span span) {
|
||||
}
|
||||
|
||||
public record FunctionSignature(
|
||||
String name,
|
||||
ReadOnlyList<Parameter> parameters,
|
||||
ReturnKind returnKind,
|
||||
TypeRef returnType,
|
||||
TypeRef resultErrorType,
|
||||
Span span) {
|
||||
}
|
||||
|
||||
public record EnumCase(
|
||||
String name,
|
||||
Long explicitValue,
|
||||
Span span) {
|
||||
}
|
||||
|
||||
|
||||
@ -5,4 +5,8 @@ public enum ParseErrors {
|
||||
E_PARSE_UNEXPECTED_TOKEN,
|
||||
E_PARSE_VISIBILITY_IN_SOURCE,
|
||||
E_PARSE_NON_ASSOC,
|
||||
E_PARSE_INVALID_DECL_SHAPE,
|
||||
E_PARSE_INVALID_RETURN_ANNOTATION,
|
||||
E_PARSE_INVALID_TYPE_SURFACE,
|
||||
E_PARSE_RESERVED_DECLARATION,
|
||||
}
|
||||
|
||||
@ -157,19 +157,19 @@ public final class PbsParser {
|
||||
*/
|
||||
private PbsAst.TopDecl parseDeclareTopDecl(final PbsToken declareToken) {
|
||||
if (cursor.match(PbsTokenKind.STRUCT)) {
|
||||
return parseSimpleNamedDecl(declareToken, "struct", PbsAst.StructDecl::new);
|
||||
return parseStructDeclaration(declareToken);
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.CONTRACT)) {
|
||||
return parseSimpleNamedDecl(declareToken, "contract", PbsAst.ContractDecl::new);
|
||||
return parseContractDeclaration(declareToken);
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.SERVICE)) {
|
||||
return parseSimpleNamedDecl(declareToken, "service", PbsAst.ServiceDecl::new);
|
||||
return parseServiceDeclaration(declareToken);
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.ERROR)) {
|
||||
return parseSimpleNamedDecl(declareToken, "error", PbsAst.ErrorDecl::new);
|
||||
return parseErrorDeclaration(declareToken);
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.ENUM)) {
|
||||
return parseSimpleNamedDecl(declareToken, "enum", PbsAst.EnumDecl::new);
|
||||
return parseEnumDeclaration(declareToken);
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.CALLBACK)) {
|
||||
return parseCallbackDeclaration(declareToken);
|
||||
@ -179,14 +179,14 @@ public final class PbsParser {
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.HOST)) {
|
||||
final var end = consumeDeclarationTerminator();
|
||||
report(cursor.previous(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN,
|
||||
report(cursor.previous(), ParseErrors.E_PARSE_RESERVED_DECLARATION,
|
||||
"'declare host' is reserved and not supported in ordinary source modules");
|
||||
return new PbsAst.InvalidDecl("reserved declare host", span(declareToken.start(), end));
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.BUILTIN)) {
|
||||
cursor.match(PbsTokenKind.TYPE);
|
||||
consume(PbsTokenKind.TYPE, "Expected 'type' in 'declare builtin type' declaration");
|
||||
final var end = consumeDeclarationTerminator();
|
||||
report(cursor.previous(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN,
|
||||
report(cursor.previous(), ParseErrors.E_PARSE_RESERVED_DECLARATION,
|
||||
"'declare builtin type' is reserved and not supported in ordinary source modules");
|
||||
return new PbsAst.InvalidDecl("reserved declare builtin type", span(declareToken.start(), end));
|
||||
}
|
||||
@ -198,22 +198,177 @@ public final class PbsParser {
|
||||
return new PbsAst.InvalidDecl("invalid declare form", span(declareToken.start(), token.end()));
|
||||
}
|
||||
|
||||
private PbsAst.StructDecl parseStructDeclaration(final PbsToken declareToken) {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected struct name");
|
||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' in struct declaration");
|
||||
final var fields = parseStructFields();
|
||||
consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after struct fields");
|
||||
|
||||
final boolean hasBody;
|
||||
long end;
|
||||
if (cursor.match(PbsTokenKind.SEMICOLON)) {
|
||||
hasBody = false;
|
||||
end = cursor.previous().end();
|
||||
} else if (cursor.match(PbsTokenKind.LEFT_BRACE)) {
|
||||
hasBody = true;
|
||||
end = parseStructBodyAndConsumeRightBrace(cursor.previous());
|
||||
} else {
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||
"Struct declaration must end with ';' or contain a '{...}' body");
|
||||
hasBody = false;
|
||||
end = consumeDeclarationTerminator();
|
||||
}
|
||||
|
||||
return new PbsAst.StructDecl(name.lexeme(), ReadOnlyList.wrap(fields), hasBody, span(declareToken.start(), end));
|
||||
}
|
||||
|
||||
private ArrayList<PbsAst.StructField> parseStructFields() {
|
||||
final var fields = new ArrayList<PbsAst.StructField>();
|
||||
if (cursor.check(PbsTokenKind.RIGHT_PAREN)) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
do {
|
||||
final var start = cursor.peek();
|
||||
var isPublic = false;
|
||||
var isMutable = false;
|
||||
if (cursor.match(PbsTokenKind.PUB)) {
|
||||
isPublic = true;
|
||||
if (cursor.match(PbsTokenKind.MUT)) {
|
||||
isMutable = true;
|
||||
}
|
||||
} else if (cursor.match(PbsTokenKind.MUT)) {
|
||||
final var mutToken = cursor.previous();
|
||||
report(mutToken, ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||
"'mut' is only valid immediately after 'pub' in struct fields");
|
||||
}
|
||||
|
||||
final var fieldName = consume(PbsTokenKind.IDENTIFIER, "Expected struct field name");
|
||||
consume(PbsTokenKind.COLON, "Expected ':' after struct field name");
|
||||
final var typeRef = parseTypeRef();
|
||||
fields.add(new PbsAst.StructField(
|
||||
fieldName.lexeme(),
|
||||
typeRef,
|
||||
isPublic,
|
||||
isMutable,
|
||||
span(start.start(), typeRef.span().getEnd())));
|
||||
} while (cursor.match(PbsTokenKind.COMMA));
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private long parseStructBodyAndConsumeRightBrace(final PbsToken leftBrace) {
|
||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||
if (cursor.match(PbsTokenKind.FN)) {
|
||||
parseMethodDeclarationInBody(cursor.previous());
|
||||
continue;
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.CTOR)) {
|
||||
parseCtorDeclarationInBody(cursor.previous());
|
||||
continue;
|
||||
}
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||
"Struct body accepts only 'fn' methods and 'ctor' declarations");
|
||||
cursor.advance();
|
||||
}
|
||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end struct body");
|
||||
return rightBrace.end();
|
||||
}
|
||||
|
||||
private void parseCtorDeclarationInBody(final PbsToken ctorToken) {
|
||||
consume(PbsTokenKind.IDENTIFIER, "Expected constructor name");
|
||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after constructor name");
|
||||
parseParametersUntilRightParen();
|
||||
consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after constructor parameters");
|
||||
consumeBlockForMember("constructor");
|
||||
}
|
||||
|
||||
private PbsAst.ContractDecl parseContractDeclaration(final PbsToken declareToken) {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected contract name");
|
||||
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start contract body");
|
||||
final var signatures = new ArrayList<PbsAst.FunctionSignature>();
|
||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||
if (!cursor.match(PbsTokenKind.FN)) {
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||
"Contract body accepts only function signatures");
|
||||
cursor.advance();
|
||||
continue;
|
||||
}
|
||||
signatures.add(parseFunctionSignature(cursor.previous(), true));
|
||||
}
|
||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end contract body");
|
||||
return new PbsAst.ContractDecl(name.lexeme(), ReadOnlyList.wrap(signatures), span(declareToken.start(), rightBrace.end()));
|
||||
}
|
||||
|
||||
private PbsAst.ServiceDecl parseServiceDeclaration(final PbsToken declareToken) {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected service name");
|
||||
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start service body");
|
||||
final var methods = new ArrayList<PbsAst.FunctionDecl>();
|
||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||
if (!cursor.match(PbsTokenKind.FN)) {
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||
"Service body accepts only method declarations");
|
||||
cursor.advance();
|
||||
continue;
|
||||
}
|
||||
methods.add(parseFunctionLike(cursor.previous(), false));
|
||||
}
|
||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end service body");
|
||||
return new PbsAst.ServiceDecl(name.lexeme(), ReadOnlyList.wrap(methods), span(declareToken.start(), rightBrace.end()));
|
||||
}
|
||||
|
||||
private PbsAst.ErrorDecl parseErrorDeclaration(final PbsToken declareToken) {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected error name");
|
||||
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start error body");
|
||||
final var cases = new ArrayList<String>();
|
||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||
final var caseToken = consume(PbsTokenKind.IDENTIFIER, "Expected error case label");
|
||||
cases.add(caseToken.lexeme());
|
||||
consume(PbsTokenKind.SEMICOLON, "Expected ';' after error case label");
|
||||
}
|
||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end error body");
|
||||
return new PbsAst.ErrorDecl(name.lexeme(), ReadOnlyList.wrap(cases), span(declareToken.start(), rightBrace.end()));
|
||||
}
|
||||
|
||||
private PbsAst.EnumDecl parseEnumDeclaration(final PbsToken declareToken) {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected enum name");
|
||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after enum name");
|
||||
|
||||
final var cases = new ArrayList<PbsAst.EnumCase>();
|
||||
if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) {
|
||||
do {
|
||||
final var caseName = consume(PbsTokenKind.IDENTIFIER, "Expected enum case label");
|
||||
Long explicitValue = null;
|
||||
if (cursor.match(PbsTokenKind.EQUAL)) {
|
||||
final var intToken = consume(PbsTokenKind.INT_LITERAL, "Expected integer literal after '=' in enum case");
|
||||
explicitValue = parseLongOrNull(intToken.lexeme());
|
||||
}
|
||||
cases.add(new PbsAst.EnumCase(caseName.lexeme(), explicitValue, span(caseName.start(), cursor.previous().end())));
|
||||
} while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN));
|
||||
}
|
||||
|
||||
final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after enum cases");
|
||||
final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after enum declaration");
|
||||
return new PbsAst.EnumDecl(name.lexeme(), ReadOnlyList.wrap(cases), span(declareToken.start(), Math.max(rightParen.end(), semicolon.end())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a top-level function declaration.
|
||||
*/
|
||||
private PbsAst.FunctionDecl parseFunction(final PbsToken fnToken) {
|
||||
return parseFunctionLike(fnToken, true);
|
||||
}
|
||||
|
||||
private PbsAst.FunctionDecl parseFunctionLike(final PbsToken fnToken, final boolean allowElseFallback) {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected function name");
|
||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
|
||||
final var parameters = parseParametersUntilRightParen();
|
||||
consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list");
|
||||
final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list");
|
||||
|
||||
PbsAst.TypeRef returnType = null;
|
||||
if (cursor.match(PbsTokenKind.COLON, PbsTokenKind.ARROW)) {
|
||||
returnType = parseTypeRef();
|
||||
}
|
||||
final var returnSpec = parseCallableReturnSpec(rightParen);
|
||||
|
||||
PbsAst.Expression elseFallback = null;
|
||||
if (cursor.match(PbsTokenKind.ELSE)) {
|
||||
if (allowElseFallback && cursor.match(PbsTokenKind.ELSE)) {
|
||||
elseFallback = exprParser.parseExpression();
|
||||
}
|
||||
|
||||
@ -221,12 +376,40 @@ public final class PbsParser {
|
||||
return new PbsAst.FunctionDecl(
|
||||
name.lexeme(),
|
||||
ReadOnlyList.wrap(parameters),
|
||||
returnType,
|
||||
returnSpec.kind,
|
||||
returnSpec.returnType,
|
||||
returnSpec.resultErrorType,
|
||||
elseFallback,
|
||||
body,
|
||||
span(fnToken.start(), body.span().getEnd()));
|
||||
}
|
||||
|
||||
private PbsAst.FunctionSignature parseFunctionSignature(final PbsToken fnToken, final boolean requireSemicolon) {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected function name");
|
||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
|
||||
final var parameters = parseParametersUntilRightParen();
|
||||
final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list");
|
||||
|
||||
final var returnSpec = parseCallableReturnSpec(rightParen);
|
||||
|
||||
long end = returnSpec.returnType.span().getEnd();
|
||||
if (requireSemicolon) {
|
||||
end = consume(PbsTokenKind.SEMICOLON, "Expected ';' after function signature").end();
|
||||
}
|
||||
|
||||
return new PbsAst.FunctionSignature(
|
||||
name.lexeme(),
|
||||
ReadOnlyList.wrap(parameters),
|
||||
returnSpec.kind,
|
||||
returnSpec.returnType,
|
||||
returnSpec.resultErrorType,
|
||||
span(fnToken.start(), end));
|
||||
}
|
||||
|
||||
private void parseMethodDeclarationInBody(final PbsToken fnToken) {
|
||||
parseFunctionLike(fnToken, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses `declare callback` declaration.
|
||||
*/
|
||||
@ -234,18 +417,17 @@ public final class PbsParser {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected callback name");
|
||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after callback name");
|
||||
final var parameters = parseParametersUntilRightParen();
|
||||
consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after callback parameter list");
|
||||
final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after callback parameter list");
|
||||
|
||||
PbsAst.TypeRef returnType = null;
|
||||
if (cursor.match(PbsTokenKind.COLON, PbsTokenKind.ARROW)) {
|
||||
returnType = parseTypeRef();
|
||||
}
|
||||
final var returnSpec = parseCallableReturnSpec(rightParen);
|
||||
|
||||
final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after callback declaration");
|
||||
return new PbsAst.CallbackDecl(
|
||||
name.lexeme(),
|
||||
ReadOnlyList.wrap(parameters),
|
||||
returnType,
|
||||
returnSpec.kind,
|
||||
returnSpec.returnType,
|
||||
returnSpec.resultErrorType,
|
||||
span(declareToken.start(), semicolon.end()));
|
||||
}
|
||||
|
||||
@ -296,15 +478,6 @@ public final class PbsParser {
|
||||
span(implementsToken.start(), end));
|
||||
}
|
||||
|
||||
private PbsAst.TopDecl parseSimpleNamedDecl(
|
||||
final PbsToken declareToken,
|
||||
final String kind,
|
||||
final NamedDeclFactory factory) {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected " + kind + " name");
|
||||
final var end = consumeDeclarationTerminator();
|
||||
return factory.build(name.lexeme(), span(declareToken.start(), end));
|
||||
}
|
||||
|
||||
private ArrayList<PbsAst.Parameter> parseParametersUntilRightParen() {
|
||||
final var parameters = new ArrayList<PbsAst.Parameter>();
|
||||
if (cursor.check(PbsTokenKind.RIGHT_PAREN)) {
|
||||
@ -325,21 +498,125 @@ public final class PbsParser {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private ParsedReturnSpec parseCallableReturnSpec(final PbsToken anchorToken) {
|
||||
if (cursor.match(PbsTokenKind.ARROW)) {
|
||||
return parseReturnSpecFromMarker(cursor.previous());
|
||||
}
|
||||
|
||||
if (cursor.match(PbsTokenKind.COLON)) {
|
||||
report(cursor.previous(), ParseErrors.E_PARSE_INVALID_RETURN_ANNOTATION,
|
||||
"Return annotations must use '->' syntax");
|
||||
return parseReturnSpecFromMarker(cursor.previous());
|
||||
}
|
||||
|
||||
final var inferredSpan = span(anchorToken.end(), anchorToken.end());
|
||||
return new ParsedReturnSpec(
|
||||
PbsAst.ReturnKind.INFERRED_UNIT,
|
||||
PbsAst.TypeRef.unit(inferredSpan),
|
||||
null);
|
||||
}
|
||||
|
||||
private ParsedReturnSpec parseReturnSpecFromMarker(final PbsToken markerToken) {
|
||||
if (cursor.match(PbsTokenKind.RESULT)) {
|
||||
final var resultToken = cursor.previous();
|
||||
consume(PbsTokenKind.LESS, "Expected '<' after 'result'");
|
||||
final var errorType = parseTypeRef();
|
||||
consume(PbsTokenKind.GREATER, "Expected '>' after result error type");
|
||||
|
||||
final PbsAst.TypeRef payloadType;
|
||||
if (isTypeStart(cursor.peek().kind())) {
|
||||
payloadType = parseTypeRef();
|
||||
} else {
|
||||
payloadType = PbsAst.TypeRef.unit(span(resultToken.end(), resultToken.end()));
|
||||
}
|
||||
|
||||
return new ParsedReturnSpec(
|
||||
PbsAst.ReturnKind.RESULT,
|
||||
payloadType,
|
||||
errorType);
|
||||
}
|
||||
|
||||
final var typeRef = parseTypeRef();
|
||||
if (typeRef.kind() == PbsAst.TypeRefKind.UNIT) {
|
||||
return new ParsedReturnSpec(PbsAst.ReturnKind.EXPLICIT_UNIT, typeRef, null);
|
||||
}
|
||||
return new ParsedReturnSpec(PbsAst.ReturnKind.PLAIN, typeRef, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a simple identifier-based type reference such as {@code int} or {@code Vector}.
|
||||
* Parses type references including optional, unit, grouped, and named-tuple forms.
|
||||
*/
|
||||
private PbsAst.TypeRef parseTypeRef() {
|
||||
if (cursor.match(PbsTokenKind.IDENTIFIER, PbsTokenKind.SELF)) {
|
||||
final var identifier = cursor.previous();
|
||||
return new PbsAst.TypeRef(identifier.lexeme(), span(identifier.start(), identifier.end()));
|
||||
if (cursor.match(PbsTokenKind.OPTIONAL)) {
|
||||
final var optionalToken = cursor.previous();
|
||||
final var inner = parseTypeRef();
|
||||
if (inner.kind() == PbsAst.TypeRefKind.UNIT) {
|
||||
report(optionalToken, ParseErrors.E_PARSE_INVALID_TYPE_SURFACE,
|
||||
"'optional void' is not a valid type surface");
|
||||
}
|
||||
return PbsAst.TypeRef.optional(inner, span(optionalToken.start(), inner.span().getEnd()));
|
||||
}
|
||||
|
||||
if (cursor.match(PbsTokenKind.VOID)) {
|
||||
final var token = cursor.previous();
|
||||
return PbsAst.TypeRef.unit(span(token.start(), token.end()));
|
||||
}
|
||||
|
||||
if (cursor.match(PbsTokenKind.SELF)) {
|
||||
final var token = cursor.previous();
|
||||
return PbsAst.TypeRef.self(span(token.start(), token.end()));
|
||||
}
|
||||
|
||||
if (cursor.match(PbsTokenKind.IDENTIFIER)) {
|
||||
final var token = cursor.previous();
|
||||
return PbsAst.TypeRef.simple(token.lexeme(), span(token.start(), token.end()));
|
||||
}
|
||||
|
||||
if (cursor.match(PbsTokenKind.LEFT_PAREN)) {
|
||||
final var open = cursor.previous();
|
||||
if (cursor.match(PbsTokenKind.RIGHT_PAREN)) {
|
||||
return PbsAst.TypeRef.unit(span(open.start(), cursor.previous().end()));
|
||||
}
|
||||
|
||||
if (cursor.check(PbsTokenKind.IDENTIFIER) && cursor.checkNext(PbsTokenKind.COLON)) {
|
||||
final var fields = parseNamedTupleTypeFields();
|
||||
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after named tuple type");
|
||||
return PbsAst.TypeRef.namedTuple(ReadOnlyList.wrap(fields), span(open.start(), close.end()));
|
||||
}
|
||||
|
||||
final var inner = parseTypeRef();
|
||||
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after grouped type");
|
||||
return PbsAst.TypeRef.group(inner, span(open.start(), close.end()));
|
||||
}
|
||||
|
||||
final var token = cursor.peek();
|
||||
report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected type name");
|
||||
report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected type surface");
|
||||
if (!cursor.isAtEnd()) {
|
||||
cursor.advance();
|
||||
}
|
||||
return new PbsAst.TypeRef("<error>", span(token.start(), token.end()));
|
||||
return PbsAst.TypeRef.error(span(token.start(), token.end()));
|
||||
}
|
||||
|
||||
private ArrayList<PbsAst.NamedTypeField> parseNamedTupleTypeFields() {
|
||||
final var fields = new ArrayList<PbsAst.NamedTypeField>();
|
||||
do {
|
||||
final var label = consume(PbsTokenKind.IDENTIFIER, "Expected tuple field label");
|
||||
consume(PbsTokenKind.COLON, "Expected ':' after tuple field label");
|
||||
final var typeRef = parseTypeRef();
|
||||
fields.add(new PbsAst.NamedTypeField(
|
||||
label.lexeme(),
|
||||
typeRef,
|
||||
span(label.start(), typeRef.span().getEnd())));
|
||||
} while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN));
|
||||
return fields;
|
||||
}
|
||||
|
||||
private boolean isTypeStart(final PbsTokenKind kind) {
|
||||
return kind == PbsTokenKind.IDENTIFIER
|
||||
|| kind == PbsTokenKind.SELF
|
||||
|| kind == PbsTokenKind.OPTIONAL
|
||||
|| kind == PbsTokenKind.VOID
|
||||
|| kind == PbsTokenKind.LEFT_PAREN;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -412,6 +689,16 @@ public final class PbsParser {
|
||||
return new PbsAst.ExpressionStatement(expression, span(expression.span().getStart(), semicolon.end()));
|
||||
}
|
||||
|
||||
private void consumeBlockForMember(final String memberKind) {
|
||||
if (!cursor.match(PbsTokenKind.LEFT_BRACE)) {
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||
"Expected '{' to start " + memberKind + " body");
|
||||
consumeDeclarationTerminator();
|
||||
return;
|
||||
}
|
||||
consumeBalancedBraces(cursor.previous());
|
||||
}
|
||||
|
||||
private long consumeDeclarationTerminator() {
|
||||
var end = cursor.previous().end();
|
||||
var parenDepth = 0;
|
||||
@ -486,6 +773,14 @@ public final class PbsParser {
|
||||
return token;
|
||||
}
|
||||
|
||||
private Long parseLongOrNull(final String text) {
|
||||
try {
|
||||
return Long.parseLong(text);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a source span for the current file.
|
||||
*/
|
||||
@ -500,8 +795,9 @@ public final class PbsParser {
|
||||
diagnostics.error(parseErrors.name(), message, new Span(fileId, token.start(), token.end()));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface NamedDeclFactory {
|
||||
PbsAst.TopDecl build(String name, Span declarationSpan);
|
||||
private record ParsedReturnSpec(
|
||||
PbsAst.ReturnKind kind,
|
||||
PbsAst.TypeRef returnType,
|
||||
PbsAst.TypeRef resultErrorType) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,4 +74,19 @@ final class PbsTokenCursor {
|
||||
PbsToken previous() {
|
||||
return tokens.get(Math.max(current - 1, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the token at a positive lookahead offset from current token.
|
||||
*/
|
||||
PbsToken peek(final int offset) {
|
||||
final var index = Math.min(current + Math.max(offset, 0), tokens.size() - 1);
|
||||
return tokens.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the token at lookahead offset 1 matches the expected kind.
|
||||
*/
|
||||
boolean checkNext(final PbsTokenKind kind) {
|
||||
return peek(1).kind() == kind;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,11 +12,11 @@ class PbsFrontendCompilerTest {
|
||||
@Test
|
||||
void shouldLowerFunctionsToIr() {
|
||||
final var source = """
|
||||
fn a(): int {
|
||||
fn a() -> int {
|
||||
return 1;
|
||||
}
|
||||
|
||||
fn b(x: int): int {
|
||||
fn b(x: int) -> int {
|
||||
return x;
|
||||
}
|
||||
""";
|
||||
@ -37,8 +37,8 @@ class PbsFrontendCompilerTest {
|
||||
@Test
|
||||
void shouldReportDuplicateFunctionNames() {
|
||||
final var source = """
|
||||
fn a(): int { return 1; }
|
||||
fn a(): int { return 2; }
|
||||
fn a() -> int { return 1; }
|
||||
fn a() -> int { return 2; }
|
||||
""";
|
||||
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
@ -8,14 +8,15 @@ import p.studio.compiler.source.identifiers.FileId;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PbsParserTest {
|
||||
|
||||
@Test
|
||||
void shouldParseSingleFunction() {
|
||||
void shouldParseSingleFunctionWithArrowReturn() {
|
||||
final var source = """
|
||||
fn sum(a: int, b: int): int
|
||||
fn sum(a: int, b: int) -> int
|
||||
{
|
||||
return a + b;
|
||||
}
|
||||
@ -34,7 +35,9 @@ class PbsParserTest {
|
||||
final var fn = ast.functions().getFirst();
|
||||
assertEquals("sum", fn.name());
|
||||
assertEquals(2, fn.parameters().size());
|
||||
assertEquals(PbsAst.ReturnKind.PLAIN, fn.returnKind());
|
||||
assertEquals("int", fn.returnType().name());
|
||||
assertNull(fn.resultErrorType());
|
||||
assertEquals(1, fn.body().statements().size());
|
||||
assertInstanceOf(PbsAst.ReturnStatement.class, fn.body().statements().getFirst());
|
||||
|
||||
@ -43,13 +46,13 @@ class PbsParserTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPreserveImportsAndTopDeclOrdering() {
|
||||
void shouldParseImportsAndTopDeclOrdering() {
|
||||
final var source = """
|
||||
import { Vec2 as V2 } from @core:math;
|
||||
|
||||
fn first(): int { return 1; }
|
||||
fn first() -> int { return 1; }
|
||||
declare struct Point(x: int, y: int);
|
||||
declare callback Tick(dt: int): int;
|
||||
declare callback Tick(dt: int) -> int;
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var fileId = new FileId(0);
|
||||
@ -70,17 +73,17 @@ class PbsParserTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRepresentMandatoryDeclarationFamilies() {
|
||||
void shouldParseDeclarationFamiliesWithShape() {
|
||||
final var source = """
|
||||
fn f(): int { return 1; }
|
||||
declare struct S(x: int);
|
||||
declare contract C { fn run(x: int): int; }
|
||||
declare service Game { fn tick(x: int): int { return x; } }
|
||||
declare error Err { Fail; }
|
||||
declare enum Mode(Idle, Run);
|
||||
declare callback TickCb(x: int): int;
|
||||
fn f() -> int { return 1; }
|
||||
declare struct S(pub mut x: int, y: optional int) { fn run() -> void { return; } ctor make(x: int) { return; } }
|
||||
declare contract C { fn run(x: int) -> int; }
|
||||
declare service Game { fn tick(x: int) -> int { return x; } }
|
||||
declare error Err { Fail; Crash; }
|
||||
declare enum Mode(Idle = 0, Run = 1);
|
||||
declare callback TickCb(x: int) -> result<Err> int;
|
||||
declare const LIMIT: int = 10;
|
||||
implements C for S using s { fn run(x: int): int { return x; } }
|
||||
implements C for S using s { fn run(x: int) -> int { return x; } }
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var fileId = new FileId(0);
|
||||
@ -88,24 +91,84 @@ class PbsParserTest {
|
||||
|
||||
final PbsAst.File ast = PbsParser.parse(tokens, fileId, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.isEmpty(), "Parser should represent all mandatory declaration families");
|
||||
assertTrue(diagnostics.isEmpty(), "Parser should represent declaration families with expected shapes");
|
||||
assertEquals(9, ast.topDecls().size());
|
||||
assertInstanceOf(PbsAst.FunctionDecl.class, ast.topDecls().get(0));
|
||||
assertInstanceOf(PbsAst.StructDecl.class, ast.topDecls().get(1));
|
||||
assertInstanceOf(PbsAst.ContractDecl.class, ast.topDecls().get(2));
|
||||
assertInstanceOf(PbsAst.ServiceDecl.class, ast.topDecls().get(3));
|
||||
assertInstanceOf(PbsAst.ErrorDecl.class, ast.topDecls().get(4));
|
||||
assertInstanceOf(PbsAst.EnumDecl.class, ast.topDecls().get(5));
|
||||
assertInstanceOf(PbsAst.CallbackDecl.class, ast.topDecls().get(6));
|
||||
|
||||
final var structDecl = assertInstanceOf(PbsAst.StructDecl.class, ast.topDecls().get(1));
|
||||
assertEquals(2, structDecl.fields().size());
|
||||
assertTrue(structDecl.hasBody());
|
||||
|
||||
final var contractDecl = assertInstanceOf(PbsAst.ContractDecl.class, ast.topDecls().get(2));
|
||||
assertEquals(1, contractDecl.signatures().size());
|
||||
|
||||
final var serviceDecl = assertInstanceOf(PbsAst.ServiceDecl.class, ast.topDecls().get(3));
|
||||
assertEquals(1, serviceDecl.methods().size());
|
||||
|
||||
final var errorDecl = assertInstanceOf(PbsAst.ErrorDecl.class, ast.topDecls().get(4));
|
||||
assertEquals(2, errorDecl.cases().size());
|
||||
|
||||
final var enumDecl = assertInstanceOf(PbsAst.EnumDecl.class, ast.topDecls().get(5));
|
||||
assertEquals(2, enumDecl.cases().size());
|
||||
|
||||
final var callbackDecl = assertInstanceOf(PbsAst.CallbackDecl.class, ast.topDecls().get(6));
|
||||
assertEquals(PbsAst.ReturnKind.RESULT, callbackDecl.returnKind());
|
||||
assertEquals("Err", callbackDecl.resultErrorType().name());
|
||||
|
||||
assertInstanceOf(PbsAst.ConstDecl.class, ast.topDecls().get(7));
|
||||
assertInstanceOf(PbsAst.ImplementsDecl.class, ast.topDecls().get(8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseOptionalAndNamedTupleTypes() {
|
||||
final var source = """
|
||||
fn shape(a: optional int, b: (x: int, y: optional float)) -> (value: optional int, meta: (k: int, v: float)) {
|
||||
return a;
|
||||
}
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var fileId = new FileId(0);
|
||||
|
||||
final PbsAst.File ast = PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.isEmpty(), "Parser should accept optional and named tuple type surfaces");
|
||||
|
||||
final var fn = ast.functions().getFirst();
|
||||
final var p0 = fn.parameters().get(0).typeRef();
|
||||
assertEquals(PbsAst.TypeRefKind.OPTIONAL, p0.kind());
|
||||
assertEquals(PbsAst.TypeRefKind.SIMPLE, p0.inner().kind());
|
||||
|
||||
final var p1 = fn.parameters().get(1).typeRef();
|
||||
assertEquals(PbsAst.TypeRefKind.NAMED_TUPLE, p1.kind());
|
||||
assertEquals(2, p1.fields().size());
|
||||
|
||||
final var returnType = fn.returnType();
|
||||
assertEquals(PbsAst.TypeRefKind.NAMED_TUPLE, returnType.kind());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectReservedDeclareHostAndBuiltinType() {
|
||||
final var source = """
|
||||
declare host HostApi { fn run() -> int; }
|
||||
declare builtin type Vec(x: int) { fn mag() -> float; }
|
||||
fn ok() -> int { return 1; }
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var fileId = new FileId(0);
|
||||
|
||||
final PbsAst.File ast = PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.hasErrors(), "Parser should reject reserved declare forms in ordinary source");
|
||||
assertEquals(3, ast.topDecls().size());
|
||||
assertInstanceOf(PbsAst.InvalidDecl.class, ast.topDecls().get(0));
|
||||
assertInstanceOf(PbsAst.InvalidDecl.class, ast.topDecls().get(1));
|
||||
assertInstanceOf(PbsAst.FunctionDecl.class, ast.topDecls().get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecoverWithInvalidDeclarationNode() {
|
||||
final var source = """
|
||||
declare ;
|
||||
fn ok(): int { return 1; }
|
||||
fn ok() -> int { return 1; }
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var fileId = new FileId(0);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user