dev/pbs-symbol-documentation-and-hover-markdown #13

Merged
bquarkz merged 11 commits from dev/pbs-symbol-documentation-and-hover-markdown into master 2026-07-15 06:36:30 +00:00
9 changed files with 262 additions and 47 deletions
Showing only changes of commit aeea8ca900 - Show all commits

View File

@ -1,7 +1,7 @@
{"type":"meta","next_id":{"DSC":39,"AGD":42,"DEC":40,"PLN":103,"LSN":55,"CLSN":1}}
{"type":"discussion","id":"DSC-0038","status":"done","ticket":"studio-packer-rgba8888-asset-pipeline","title":"Studio and Packer RGBA8888 Asset Pipeline Alignment","created_at":"2026-05-23","updated_at":"2026-07-14","tags":["studio","packer","assets","glyph-bank","palette","rgba8888","runtime-alignment"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0053","file":"discussion/lessons/DSC-0038-studio-packer-rgba8888-asset-pipeline/LSN-0053-rgba8888-is-the-canonical-studio-packer-palette-contract.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14"}]}
{"type":"discussion","id":"DSC-0037","status":"done","ticket":"pbs-autocomplete-parameter-names","title":"PBS autocomplete parameter names for stdlib and method calls","created_at":"2026-05-08","updated_at":"2026-05-14","tags":["compiler-pbs","studio","lsp","autocomplete","signature-help","stdlib"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0052","file":"discussion/lessons/DSC-0037-pbs-autocomplete-parameter-names/LSN-0052-canonical-callable-parameter-names-through-pbs-editor-assistance.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]}
{"type":"discussion","id":"DSC-0036","status":"in_progress","ticket":"pbs-symbol-documentation-and-hover-markdown","title":"Modelo de documentacao de simbolos em PBS e consumo markdown no hover","created_at":"2026-05-08","updated_at":"2026-07-15","tags":["compiler","compiler-pbs","studio","lsp","vscode","editor","hover","documentation","markdown"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-pbs-symbol-documentation-and-hover-markdown.md","status":"accepted","created_at":"2026-05-08","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0039","file":"DEC-0039-pbs-symbol-documentation-with-doc-markdown-text-blocks.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0039"}],"plans":[{"id":"PLN-0092","file":"PLN-0092-specify-pbs-doc-attribute-and-markdown-text-block-syntax.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0093","file":"PLN-0093-implement-pbs-lexer-support-for-documentation-text-blocks.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0094","file":"PLN-0094-extend-pbs-attribute-parser-and-ast-for-doc-markdown-payloads.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0095","file":"PLN-0095-implement-documentation-text-block-normalization.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0096","file":"PLN-0096-validate-doc-attribute-semantics-and-diagnostics.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0097","file":"PLN-0097-attach-doc-metadata-to-pbs-semantic-symbols.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0098","file":"PLN-0098-expose-doc-metadata-through-pbs-editorial-and-lsp-surfaces.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0099","file":"PLN-0099-render-doc-markdown-in-hover-composition.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0100","file":"PLN-0100-author-doc-metadata-for-stdlib-sdk-and-interface-declarations.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0101","file":"PLN-0101-protect-runtime-artifacts-from-doc-metadata-lowering.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0102","file":"PLN-0102-add-end-to-end-doc-documentation-conformance-coverage.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0036","status":"in_progress","ticket":"pbs-symbol-documentation-and-hover-markdown","title":"Modelo de documentacao de simbolos em PBS e consumo markdown no hover","created_at":"2026-05-08","updated_at":"2026-07-15","tags":["compiler","compiler-pbs","studio","lsp","vscode","editor","hover","documentation","markdown"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-pbs-symbol-documentation-and-hover-markdown.md","status":"accepted","created_at":"2026-05-08","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0039","file":"DEC-0039-pbs-symbol-documentation-with-doc-markdown-text-blocks.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0039"}],"plans":[{"id":"PLN-0092","file":"PLN-0092-specify-pbs-doc-attribute-and-markdown-text-block-syntax.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0093","file":"PLN-0093-implement-pbs-lexer-support-for-documentation-text-blocks.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0094","file":"PLN-0094-extend-pbs-attribute-parser-and-ast-for-doc-markdown-payloads.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0095","file":"PLN-0095-implement-documentation-text-block-normalization.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0096","file":"PLN-0096-validate-doc-attribute-semantics-and-diagnostics.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0097","file":"PLN-0097-attach-doc-metadata-to-pbs-semantic-symbols.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0098","file":"PLN-0098-expose-doc-metadata-through-pbs-editorial-and-lsp-surfaces.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0099","file":"PLN-0099-render-doc-markdown-in-hover-composition.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0100","file":"PLN-0100-author-doc-metadata-for-stdlib-sdk-and-interface-declarations.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0101","file":"PLN-0101-protect-runtime-artifacts-from-doc-metadata-lowering.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0102","file":"PLN-0102-add-end-to-end-doc-documentation-conformance-coverage.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0035","status":"done","ticket":"pbs-lsp-editor-assistance-wave-1","title":"Wave 1 de assistencia editorial via LSP para PBS no VS Code","created_at":"2026-05-08","updated_at":"2026-05-08","tags":["studio","lsp","vscode","compiler","compiler-pbs","editor","completion","hover","signature-help"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0051","file":"discussion/lessons/DSC-0035-pbs-lsp-editor-assistance-wave-1/LSN-0051-compiler-backed-editor-assistance-for-pbs.md","status":"done","created_at":"2026-05-08","updated_at":"2026-05-08"}]}
{"type":"discussion","id":"DSC-0034","status":"done","ticket":"frontend-semantic-host-projection-flexibility","title":"Frontend semantic vocabulary flexibility and declarative host projection","created_at":"2026-05-06","updated_at":"2026-05-07","tags":["compiler","compiler-general","frontend","semantics","vscode","host-projection","lsp"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0048","file":"discussion/lessons/DSC-0034-frontend-semantic-host-projection-flexibility/LSN-0048-frontend-owned-semantic-vocabularies-with-declarative-host-projection.md","status":"done","created_at":"2026-05-07","updated_at":"2026-05-07"}]}
{"type":"discussion","id":"DSC-0033","status":"done","ticket":"frontend-visual-theme-spec-and-css-retirement","title":"Frontend visual theme spec and retirement of host-consumed semantic CSS","created_at":"2026-05-06","updated_at":"2026-05-08","tags":["compiler","compiler-general","frontend","presentation","theming","studio","vscode","lsp","pbs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0050","file":"discussion/lessons/DSC-0033-frontend-visual-theme-spec-and-css-retirement/LSN-0050-frontend-owned-visual-themes-with-structured-contract-and-host-adapters.md","status":"done","created_at":"2026-05-08","updated_at":"2026-05-08"}]}

View File

@ -2,7 +2,7 @@
id: PLN-0096
ticket: pbs-symbol-documentation-and-hover-markdown
title: Validate Doc attribute semantics and diagnostics
status: open
status: done
created: 2026-07-15
ref_decisions: [DEC-0039]
tags: [compiler, compiler-pbs, studio, lsp, vscode, editor, hover, documentation, markdown]

View File

@ -138,6 +138,7 @@ public final class PbsAst {
ReturnKind returnKind,
TypeRef returnType,
TypeRef resultErrorType,
ReadOnlyList<Attribute> attributes,
LifecycleMarker lifecycleMarker,
Block body,
Span span) implements TopDecl {
@ -149,18 +150,21 @@ public final class PbsAst {
ReadOnlyList<FunctionDecl> methods,
ReadOnlyList<CtorDecl> ctors,
boolean hasBody,
ReadOnlyList<Attribute> attributes,
Span span) implements TopDecl {
}
public record ContractDecl(
String name,
ReadOnlyList<FunctionSignature> signatures,
ReadOnlyList<Attribute> attributes,
Span span) implements TopDecl {
}
public record HostDecl(
String name,
ReadOnlyList<FunctionSignature> signatures,
ReadOnlyList<Attribute> attributes,
Span span) implements TopDecl {
}
@ -175,18 +179,21 @@ public final class PbsAst {
public record ServiceDecl(
String name,
ReadOnlyList<FunctionDecl> methods,
ReadOnlyList<Attribute> attributes,
Span span) implements TopDecl {
}
public record ErrorDecl(
String name,
ReadOnlyList<String> cases,
ReadOnlyList<Attribute> attributes,
Span span) implements TopDecl {
}
public record EnumDecl(
String name,
ReadOnlyList<EnumCase> cases,
ReadOnlyList<Attribute> attributes,
Span span) implements TopDecl {
}
@ -196,6 +203,7 @@ public final class PbsAst {
ReturnKind returnKind,
TypeRef returnType,
TypeRef resultErrorType,
ReadOnlyList<Attribute> attributes,
Span span) implements TopDecl {
}

View File

@ -108,28 +108,28 @@ final class PbsDeclarationParser {
final PbsToken declareToken,
final ReadOnlyList<PbsAst.Attribute> pendingAttributes) {
if (cursor.match(PbsTokenKind.STRUCT)) {
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "struct declarations");
return parseStructDeclaration(declareToken);
rejectUnsupportedDeclarationAttributes(pendingAttributes, "struct declarations");
return parseStructDeclaration(declareToken, pendingAttributes);
}
if (cursor.match(PbsTokenKind.CONTRACT)) {
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "contract declarations");
return parseContractDeclaration(declareToken);
rejectUnsupportedDeclarationAttributes(pendingAttributes, "contract declarations");
return parseContractDeclaration(declareToken, pendingAttributes);
}
if (cursor.match(PbsTokenKind.SERVICE)) {
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "service declarations");
return parseServiceDeclaration(declareToken);
rejectUnsupportedDeclarationAttributes(pendingAttributes, "service declarations");
return parseServiceDeclaration(declareToken, pendingAttributes);
}
if (cursor.match(PbsTokenKind.ERROR)) {
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "error declarations");
return parseErrorDeclaration(declareToken);
rejectUnsupportedDeclarationAttributes(pendingAttributes, "error declarations");
return parseErrorDeclaration(declareToken, pendingAttributes);
}
if (cursor.match(PbsTokenKind.ENUM)) {
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "enum declarations");
return parseEnumDeclaration(declareToken);
rejectUnsupportedDeclarationAttributes(pendingAttributes, "enum declarations");
return parseEnumDeclaration(declareToken, pendingAttributes);
}
if (cursor.match(PbsTokenKind.CALLBACK)) {
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "callback declarations");
return parseCallbackDeclaration(declareToken);
rejectUnsupportedDeclarationAttributes(pendingAttributes, "callback declarations");
return parseCallbackDeclaration(declareToken, pendingAttributes);
}
if (cursor.match(PbsTokenKind.GLOBAL)) {
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "global declarations");
@ -139,14 +139,14 @@ final class PbsDeclarationParser {
return parseConstDeclaration(declareToken, pendingAttributes);
}
if (cursor.match(PbsTokenKind.HOST)) {
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "host declarations");
rejectUnsupportedDeclarationAttributes(pendingAttributes, "host declarations");
if (isNotInterfaceMode()) {
final var end = declarationTerminatorDelegate.consume();
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));
}
return parseHostDeclaration(declareToken);
return parseHostDeclaration(declareToken, pendingAttributes);
}
if (cursor.match(PbsTokenKind.BUILTIN)) {
consume(PbsTokenKind.TYPE, "Expected 'type' in 'declare builtin type' declaration");
@ -211,14 +211,16 @@ final class PbsDeclarationParser {
span(implementsToken.start(), end));
}
private PbsAst.HostDecl parseHostDeclaration(final PbsToken declareToken) {
private PbsAst.HostDecl parseHostDeclaration(
final PbsToken declareToken,
final ReadOnlyList<PbsAst.Attribute> attributes) {
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected host declaration name");
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start host declaration body");
final var signatures = new ArrayList<PbsAst.FunctionSignature>();
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
var attributes = ReadOnlyList.<PbsAst.Attribute>empty();
var signatureAttributes = ReadOnlyList.<PbsAst.Attribute>empty();
if (cursor.check(PbsTokenKind.LEFT_BRACKET)) {
attributes = attributeParser.parseAttributeList();
signatureAttributes = attributeParser.parseAttributeList();
}
if (!cursor.match(PbsTokenKind.FN)) {
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
@ -226,12 +228,13 @@ final class PbsDeclarationParser {
cursor.advance();
continue;
}
signatures.add(parseFunctionSignature(cursor.previous(), attributes));
signatures.add(parseFunctionSignature(cursor.previous(), signatureAttributes));
}
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end host declaration body");
return new PbsAst.HostDecl(
name.lexeme(),
ReadOnlyList.wrap(signatures),
attributes,
span(declareToken.start(), rightBrace.end()));
}
@ -308,7 +311,9 @@ final class PbsDeclarationParser {
return fields;
}
private PbsAst.StructDecl parseStructDeclaration(final PbsToken declareToken) {
private PbsAst.StructDecl parseStructDeclaration(
final PbsToken declareToken,
final ReadOnlyList<PbsAst.Attribute> attributes) {
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected struct name");
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' in struct declaration");
final var fields = parseStructFields();
@ -340,6 +345,7 @@ final class PbsDeclarationParser {
ReadOnlyList.wrap(methods),
ReadOnlyList.wrap(ctors),
hasBody,
attributes,
span(declareToken.start(), end));
}
@ -411,17 +417,19 @@ final class PbsDeclarationParser {
span(ctorToken.start(), body.span().getEnd()));
}
private PbsAst.ContractDecl parseContractDeclaration(final PbsToken declareToken) {
private PbsAst.ContractDecl parseContractDeclaration(
final PbsToken declareToken,
final ReadOnlyList<PbsAst.Attribute> attributes) {
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()) {
var attributes = ReadOnlyList.<PbsAst.Attribute>empty();
var signatureAttributes = ReadOnlyList.<PbsAst.Attribute>empty();
if (cursor.check(PbsTokenKind.LEFT_BRACKET)) {
attributes = attributeParser.parseAttributeList();
signatureAttributes = attributeParser.parseAttributeList();
if (isOrdinaryMode()) {
reportAttributesNotAllowed(attributes, "Attributes are not allowed in ordinary .pbs source modules");
attributes = ReadOnlyList.empty();
reportAttributesNotAllowed(signatureAttributes, "Attributes are not allowed in ordinary .pbs source modules");
signatureAttributes = ReadOnlyList.empty();
}
}
if (!cursor.match(PbsTokenKind.FN)) {
@ -430,13 +438,15 @@ final class PbsDeclarationParser {
cursor.advance();
continue;
}
signatures.add(parseFunctionSignature(cursor.previous(), attributes));
signatures.add(parseFunctionSignature(cursor.previous(), signatureAttributes));
}
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()));
return new PbsAst.ContractDecl(name.lexeme(), ReadOnlyList.wrap(signatures), attributes, span(declareToken.start(), rightBrace.end()));
}
private PbsAst.ServiceDecl parseServiceDeclaration(final PbsToken declareToken) {
private PbsAst.ServiceDecl parseServiceDeclaration(
final PbsToken declareToken,
final ReadOnlyList<PbsAst.Attribute> attributes) {
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>();
@ -450,10 +460,12 @@ final class PbsDeclarationParser {
methods.add(parseFunctionLike(cursor.previous(), ReadOnlyList.empty()));
}
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()));
return new PbsAst.ServiceDecl(name.lexeme(), ReadOnlyList.wrap(methods), attributes, span(declareToken.start(), rightBrace.end()));
}
private PbsAst.ErrorDecl parseErrorDeclaration(final PbsToken declareToken) {
private PbsAst.ErrorDecl parseErrorDeclaration(
final PbsToken declareToken,
final ReadOnlyList<PbsAst.Attribute> attributes) {
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected error name");
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start error body");
final var cases = new ArrayList<String>();
@ -463,10 +475,12 @@ final class PbsDeclarationParser {
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()));
return new PbsAst.ErrorDecl(name.lexeme(), ReadOnlyList.wrap(cases), attributes, span(declareToken.start(), rightBrace.end()));
}
private PbsAst.EnumDecl parseEnumDeclaration(final PbsToken declareToken) {
private PbsAst.EnumDecl parseEnumDeclaration(
final PbsToken declareToken,
final ReadOnlyList<PbsAst.Attribute> attributes) {
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected enum name");
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after enum name");
@ -522,7 +536,7 @@ final class PbsDeclarationParser {
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())));
return new PbsAst.EnumDecl(name.lexeme(), ReadOnlyList.wrap(cases), attributes, span(declareToken.start(), Math.max(rightParen.end(), semicolon.end())));
}
private PbsAst.FunctionDecl parseFunctionLike(
@ -543,6 +557,7 @@ final class PbsDeclarationParser {
returnSpec.kind(),
returnSpec.returnType(),
returnSpec.resultErrorType(),
pendingAttributes,
lifecycleMarker,
body,
span(fnToken.start(), body.span().getEnd()));
@ -585,7 +600,9 @@ final class PbsDeclarationParser {
return token;
}
private PbsAst.CallbackDecl parseCallbackDeclaration(final PbsToken declareToken) {
private PbsAst.CallbackDecl parseCallbackDeclaration(
final PbsToken declareToken,
final ReadOnlyList<PbsAst.Attribute> attributes) {
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected callback name");
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after callback name");
final var parameters = typeParser.parseParametersUntilRightParen();
@ -600,6 +617,7 @@ final class PbsDeclarationParser {
returnSpec.kind(),
returnSpec.returnType(),
returnSpec.resultErrorType(),
attributes,
span(declareToken.start(), semicolon.end()));
}
@ -651,6 +669,20 @@ final class PbsDeclarationParser {
reportAttributesNotAllowed(attributes, "Attributes are not allowed before " + targetSurface);
}
private void rejectUnsupportedDeclarationAttributes(
final ReadOnlyList<PbsAst.Attribute> attributes,
final String targetSurface) {
for (final var attribute : attributes) {
if ("Doc".equals(attribute.name())) {
continue;
}
p.studio.compiler.source.diagnostics.Diagnostics.error(context.diagnostics(),
ParseErrors.E_PARSE_ATTRIBUTES_NOT_ALLOWED.name(),
"Only [Doc] is allowed before " + targetSurface,
attribute.span());
}
}
private void reportAttributesNotAllowed(
final ReadOnlyList<PbsAst.Attribute> attributes,
final String message) {
@ -684,6 +716,9 @@ final class PbsDeclarationParser {
PbsAst.LifecycleMarker marker = PbsAst.LifecycleMarker.NONE;
for (final var attribute : attributes) {
final PbsAst.LifecycleMarker nextMarker;
if ("Doc".equals(attribute.name())) {
continue;
}
if ("Init".equals(attribute.name())) {
nextMarker = PbsAst.LifecycleMarker.INIT;
} else if ("Frame".equals(attribute.name())) {

View File

@ -1,6 +1,7 @@
package p.studio.compiler.pbs.semantics;
import p.studio.compiler.models.SourceKind;
import p.studio.compiler.pbs.PbsDocumentationTextBlockNormalizer;
import p.studio.compiler.pbs.PbsFrontendCompiler;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.source.Span;
@ -19,6 +20,7 @@ public final class PbsDeclarationSemanticsValidator {
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
private static final String ATTR_INIT_ALLOWED = "InitAllowed";
private static final String ATTR_ASSET_LOWERING = "AssetLowering";
private static final String ATTR_DOC = "Doc";
private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
ATTR_HOST,
@ -27,7 +29,8 @@ public final class PbsDeclarationSemanticsValidator {
ATTR_BUILTIN_CONST,
ATTR_INTRINSIC_CALL,
ATTR_INIT_ALLOWED,
ATTR_ASSET_LOWERING);
ATTR_ASSET_LOWERING,
ATTR_DOC);
private final NameTable nameTable;
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
@ -60,6 +63,7 @@ public final class PbsDeclarationSemanticsValidator {
for (final var topDecl : ast.topDecls()) {
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
validateDocAttributes(functionDecl.attributes(), "function declarations", diagnostics);
if (interfaceModule) {
reportInterfaceNonDeclarativeDecl(
functionDecl.span(),
@ -82,6 +86,7 @@ public final class PbsDeclarationSemanticsValidator {
}
if (topDecl instanceof PbsAst.StructDecl structDecl) {
validateDocAttributes(structDecl.attributes(), "struct declarations", diagnostics);
binder.registerType(structDecl.name(), structDecl.span(), "struct");
if (interfaceModule && (structDecl.hasBody() || !structDecl.methods().isEmpty() || !structDecl.ctors().isEmpty())) {
reportInterfaceNonDeclarativeDecl(
@ -94,6 +99,7 @@ public final class PbsDeclarationSemanticsValidator {
}
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
validateDocAttributes(serviceDecl.attributes(), "service declarations", diagnostics);
binder.registerType(serviceDecl.name(), serviceDecl.span(), "service");
binder.registerValue(serviceDecl.name(), serviceDecl.span(), "service singleton");
binder.registerVisibleTopLevelSurface(serviceDecl.name(), serviceDecl.span(), "service");
@ -102,10 +108,12 @@ public final class PbsDeclarationSemanticsValidator {
}
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
validateDocAttributes(contractDecl.attributes(), "contract declarations", diagnostics);
binder.registerType(contractDecl.name(), contractDecl.span(), "contract");
if (interfaceModule) {
for (final var signature : contractDecl.signatures()) {
validateReservedAttributeTarget(signature.attributes(), "contract signature", diagnostics);
validateDocAttributes(signature.attributes(), "contract signatures", diagnostics);
validateReservedAttributeTargetExceptDoc(signature.attributes(), "contract signature", diagnostics);
}
}
validateContractDeclaration(contractDecl, binder, rules);
@ -113,6 +121,7 @@ public final class PbsDeclarationSemanticsValidator {
}
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
validateDocAttributes(hostDecl.attributes(), "host declarations", diagnostics);
binder.registerHostOwner(hostDecl.name(), hostDecl.span(), "host owner");
validateHostDeclaration(hostDecl, binder, rules, interfaceModule, diagnostics);
continue;
@ -125,18 +134,21 @@ public final class PbsDeclarationSemanticsValidator {
}
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
validateDocAttributes(errorDecl.attributes(), "error declarations", diagnostics);
binder.registerType(errorDecl.name(), errorDecl.span(), "error");
rules.validateErrorDeclaration(errorDecl);
continue;
}
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
validateDocAttributes(enumDecl.attributes(), "enum declarations", diagnostics);
binder.registerType(enumDecl.name(), enumDecl.span(), "enum");
rules.validateEnumDeclaration(enumDecl);
continue;
}
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
validateDocAttributes(callbackDecl.attributes(), "callback declarations", diagnostics);
binder.registerType(callbackDecl.name(), callbackDecl.span(), "callback");
rules.validateCallbackDeclaration(callbackDecl);
continue;
@ -494,12 +506,13 @@ public final class PbsDeclarationSemanticsValidator {
final PbsAst.ConstDecl constDecl,
final boolean interfaceModule,
final DiagnosticSink diagnostics) {
validateDocAttributes(constDecl.attributes(), "const declarations", diagnostics);
final var builtinConstAttributes = attributesNamed(constDecl.attributes(), ATTR_BUILTIN_CONST);
for (final var attribute : constDecl.attributes()) {
if (!isReservedAttribute(attribute.name())) {
continue;
}
if (ATTR_BUILTIN_CONST.equals(attribute.name())) {
if (ATTR_BUILTIN_CONST.equals(attribute.name()) || ATTR_DOC.equals(attribute.name())) {
continue;
}
reportInvalidReservedAttributeTarget(
@ -540,6 +553,7 @@ public final class PbsDeclarationSemanticsValidator {
private void validateHostSignatureAttributes(
final PbsAst.FunctionSignature signature,
final DiagnosticSink diagnostics) {
validateDocAttributes(signature.attributes(), "host signatures", diagnostics);
final var hostAttributes = attributesNamed(signature.attributes(), ATTR_HOST);
final var initAllowedAttributes = attributesNamed(signature.attributes(), ATTR_INIT_ALLOWED);
final var assetLoweringAttributes = attributesNamed(signature.attributes(), ATTR_ASSET_LOWERING);
@ -550,7 +564,8 @@ public final class PbsDeclarationSemanticsValidator {
if (ATTR_HOST.equals(attribute.name())
|| ATTR_CAPABILITY.equals(attribute.name())
|| ATTR_INIT_ALLOWED.equals(attribute.name())
|| ATTR_ASSET_LOWERING.equals(attribute.name())) {
|| ATTR_ASSET_LOWERING.equals(attribute.name())
|| ATTR_DOC.equals(attribute.name())) {
continue;
}
reportInvalidReservedAttributeTarget(
@ -597,12 +612,13 @@ public final class PbsDeclarationSemanticsValidator {
private void validateBuiltinTypeAttribute(
final PbsAst.BuiltinTypeDecl builtinTypeDecl,
final DiagnosticSink diagnostics) {
validateDocAttributes(builtinTypeDecl.attributes(), "builtin type declarations", diagnostics);
final var builtinTypeAttributes = attributesNamed(builtinTypeDecl.attributes(), ATTR_BUILTIN_TYPE);
for (final var attribute : builtinTypeDecl.attributes()) {
if (!isReservedAttribute(attribute.name())) {
continue;
}
if (ATTR_BUILTIN_TYPE.equals(attribute.name())) {
if (ATTR_BUILTIN_TYPE.equals(attribute.name()) || ATTR_DOC.equals(attribute.name())) {
continue;
}
reportInvalidReservedAttributeTarget(
@ -645,12 +661,13 @@ public final class PbsDeclarationSemanticsValidator {
private void validateIntrinsicCallAttributes(
final PbsAst.FunctionSignature signature,
final DiagnosticSink diagnostics) {
validateDocAttributes(signature.attributes(), "builtin method signatures", diagnostics);
final var intrinsicAttributes = attributesNamed(signature.attributes(), ATTR_INTRINSIC_CALL);
for (final var attribute : signature.attributes()) {
if (!isReservedAttribute(attribute.name())) {
continue;
}
if (ATTR_INTRINSIC_CALL.equals(attribute.name())) {
if (ATTR_INTRINSIC_CALL.equals(attribute.name()) || ATTR_DOC.equals(attribute.name())) {
continue;
}
reportInvalidReservedAttributeTarget(
@ -770,6 +787,47 @@ public final class PbsDeclarationSemanticsValidator {
}
}
private void validateDocAttributes(
final ReadOnlyList<PbsAst.Attribute> attributes,
final String targetSurface,
final DiagnosticSink diagnostics) {
final var docAttributes = attributesNamed(attributes, ATTR_DOC);
if (docAttributes.isEmpty()) {
return;
}
if (docAttributes.size() > 1) {
for (int i = 1; i < docAttributes.size(); i++) {
reportDuplicateReservedAttribute(docAttributes.get(i), ATTR_DOC, diagnostics);
}
}
validateDocAttributeShape(docAttributes.getFirst(), targetSurface, diagnostics);
}
private void validateDocAttributeShape(
final PbsAst.Attribute attribute,
final String targetSurface,
final DiagnosticSink diagnostics) {
final var args = validateNamedArguments(attribute, Set.of("markdown"), Set.of(), diagnostics);
if (args == null) {
return;
}
final var value = args.get("markdown");
if (!(value instanceof PbsAst.AttributeDocTextBlockValue docValue)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
"Doc.markdown on %s must be a documentation text block".formatted(targetSurface),
attribute.span());
return;
}
final var normalized = PbsDocumentationTextBlockNormalizer.normalizeLexeme(docValue.value());
if (normalized.isBlank()) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
"Doc.markdown on %s must not be empty".formatted(targetSurface),
attribute.span());
}
}
private Map<String, PbsAst.AttributeValue> validateNamedArguments(
final PbsAst.Attribute attribute,
final Set<String> requiredNames,
@ -911,6 +969,21 @@ public final class PbsDeclarationSemanticsValidator {
}
}
private void validateReservedAttributeTargetExceptDoc(
final ReadOnlyList<PbsAst.Attribute> attributes,
final String targetSurface,
final DiagnosticSink diagnostics) {
for (final var attribute : attributes) {
if (!isReservedAttribute(attribute.name()) || ATTR_DOC.equals(attribute.name())) {
continue;
}
reportInvalidReservedAttributeTarget(
attribute,
"Attribute '%s' is not valid on %s".formatted(attribute.name(), targetSurface),
diagnostics);
}
}
private void reportInvalidReservedAttributeTarget(
final PbsAst.Attribute attribute,
final String message,

View File

@ -1155,6 +1155,7 @@ public final class PbsEditorialSupportService {
functionDecl.returnKind(),
functionDecl.returnType(),
functionDecl.resultErrorType(),
functionDecl.attributes(),
functionDecl.lifecycleMarker(),
functionDecl.body(),
functionDecl.span());
@ -1166,6 +1167,7 @@ public final class PbsEditorialSupportService {
structDecl.methods(),
structDecl.ctors(),
structDecl.hasBody(),
structDecl.attributes(),
structDecl.span());
}
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
@ -1177,13 +1179,13 @@ public final class PbsEditorialSupportService {
builtinTypeDecl.span());
}
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
return new PbsAst.ServiceDecl(newName, serviceDecl.methods(), serviceDecl.span());
return new PbsAst.ServiceDecl(newName, serviceDecl.methods(), serviceDecl.attributes(), serviceDecl.span());
}
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
return new PbsAst.HostDecl(newName, hostDecl.signatures(), hostDecl.span());
return new PbsAst.HostDecl(newName, hostDecl.signatures(), hostDecl.attributes(), hostDecl.span());
}
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
return new PbsAst.ContractDecl(newName, contractDecl.signatures(), contractDecl.span());
return new PbsAst.ContractDecl(newName, contractDecl.signatures(), contractDecl.attributes(), contractDecl.span());
}
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
return new PbsAst.CallbackDecl(
@ -1192,13 +1194,14 @@ public final class PbsEditorialSupportService {
callbackDecl.returnKind(),
callbackDecl.returnType(),
callbackDecl.resultErrorType(),
callbackDecl.attributes(),
callbackDecl.span());
}
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
return new PbsAst.EnumDecl(newName, enumDecl.cases(), enumDecl.span());
return new PbsAst.EnumDecl(newName, enumDecl.cases(), enumDecl.attributes(), enumDecl.span());
}
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
return new PbsAst.ErrorDecl(newName, errorDecl.cases(), errorDecl.span());
return new PbsAst.ErrorDecl(newName, errorDecl.cases(), errorDecl.attributes(), errorDecl.span());
}
if (topDecl instanceof PbsAst.GlobalDecl globalDecl) {
return new PbsAst.GlobalDecl(newName, globalDecl.explicitType(), globalDecl.initializer(), globalDecl.span());

View File

@ -372,7 +372,8 @@ final class PbsFlowSemanticSupport {
}
if (topDecl instanceof PbsAst.CallbackDecl(
String name, ReadOnlyList<PbsAst.Parameter> parameters, PbsAst.ReturnKind returnKind,
PbsAst.TypeRef returnType, PbsAst.TypeRef resultErrorType, Span span
PbsAst.TypeRef returnType, PbsAst.TypeRef resultErrorType,
ReadOnlyList<PbsAst.Attribute> ignoredAttributes, Span span
)) {
final var symbol = callableFrom(
name,

View File

@ -14,15 +14,27 @@ class PbsInterfaceModuleSemanticsTest {
@Test
void shouldAcceptValidInterfaceModuleReservedMetadataShapes() {
final var source = """
[Doc(markdown = \"""
Packed color value.
\""")]
[BuiltinType(name = "color", version = 1)]
declare builtin type Color(
pub raw: int
) {
[Doc(markdown = \"""
Packs the color.
\""")]
[IntrinsicCall(name = "pack", version = 1)]
fn pack() -> int;
}
[Doc(markdown = \"""
Graphics host API.
\""")]
declare host Gfx {
[Doc(markdown = \"""
Draws one pixel.
\""")]
[Host(module = "gfx2d", name = "draw_pixel", version = 1)]
[Capability(name = "gfx2d")]
fn draw_pixel(x: int, y: int, c: Color) -> void;
@ -43,6 +55,26 @@ class PbsInterfaceModuleSemanticsTest {
d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name())));
}
@Test
void shouldRejectDocOnBuiltinFields() {
final var source = """
[BuiltinType(name = "color", version = 1)]
declare builtin type Color(
[Doc(markdown = \"""
Raw color bits.
\""")]
pub raw: int
) {
}
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics, SourceKind.SDK_INTERFACE);
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET.name())));
}
@Test
void shouldRejectInvalidReservedAttributeTargetsAndShapesInInterfaceModule() {
final var source = """

View File

@ -9,6 +9,69 @@ import static org.junit.jupiter.api.Assertions.*;
class PbsSemanticsDeclarationsTest {
@Test
void shouldAcceptDocOnNamedApiDeclarations() {
final var source = """
[Doc(markdown = \"""
Runs the program.
\""")]
fn run() -> void { return; }
[Doc(markdown = \"""
Holds player state.
\""")]
declare struct Player(x: int);
[Doc(markdown = \"""
Game services.
\""")]
declare service Game {
fn tick() -> void { return; }
}
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
assertFalse(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name())));
assertFalse(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET.name())));
}
@Test
void shouldRejectMalformedDuplicateAndEmptyDocAttributes() {
final var source = """
[Doc(markdown = \"""
First.
\""")]
[Doc(markdown = \"""
Second.
\""")]
fn duplicated() -> void { return; }
[Doc(text = \"""
Wrong argument.
\""")]
fn malformed() -> void { return; }
[Doc(markdown = \"""
\""")]
fn empty() -> void { return; }
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_RESERVED_ATTRIBUTE.name())));
final var malformedCount = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name()))
.count();
assertTrue(malformedCount >= 2);
}
@Test
void shouldAllowFunctionOverloadsWithDifferentShapes() {
final var source = """