diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 92019ccb..54b2d66b 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -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"}]} diff --git a/discussion/workflow/plans/PLN-0096-validate-doc-attribute-semantics-and-diagnostics.md b/discussion/workflow/plans/PLN-0096-validate-doc-attribute-semantics-and-diagnostics.md index 77536cf0..196cafe1 100644 --- a/discussion/workflow/plans/PLN-0096-validate-doc-attribute-semantics-and-diagnostics.md +++ b/discussion/workflow/plans/PLN-0096-validate-doc-attribute-semantics-and-diagnostics.md @@ -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] diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/ast/PbsAst.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/ast/PbsAst.java index 34e9bf76..85f5b523 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/ast/PbsAst.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/ast/PbsAst.java @@ -138,6 +138,7 @@ public final class PbsAst { ReturnKind returnKind, TypeRef returnType, TypeRef resultErrorType, + ReadOnlyList attributes, LifecycleMarker lifecycleMarker, Block body, Span span) implements TopDecl { @@ -149,18 +150,21 @@ public final class PbsAst { ReadOnlyList methods, ReadOnlyList ctors, boolean hasBody, + ReadOnlyList attributes, Span span) implements TopDecl { } public record ContractDecl( String name, ReadOnlyList signatures, + ReadOnlyList attributes, Span span) implements TopDecl { } public record HostDecl( String name, ReadOnlyList signatures, + ReadOnlyList attributes, Span span) implements TopDecl { } @@ -175,18 +179,21 @@ public final class PbsAst { public record ServiceDecl( String name, ReadOnlyList methods, + ReadOnlyList attributes, Span span) implements TopDecl { } public record ErrorDecl( String name, ReadOnlyList cases, + ReadOnlyList attributes, Span span) implements TopDecl { } public record EnumDecl( String name, ReadOnlyList cases, + ReadOnlyList attributes, Span span) implements TopDecl { } @@ -196,6 +203,7 @@ public final class PbsAst { ReturnKind returnKind, TypeRef returnType, TypeRef resultErrorType, + ReadOnlyList attributes, Span span) implements TopDecl { } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsDeclarationParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsDeclarationParser.java index 11fc83ee..9bef5713 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsDeclarationParser.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsDeclarationParser.java @@ -108,28 +108,28 @@ final class PbsDeclarationParser { final PbsToken declareToken, final ReadOnlyList 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 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(); while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { - var attributes = ReadOnlyList.empty(); + var signatureAttributes = ReadOnlyList.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 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 attributes) { final var name = consume(PbsTokenKind.IDENTIFIER, "Expected contract name"); consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start contract body"); final var signatures = new ArrayList(); while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { - var attributes = ReadOnlyList.empty(); + var signatureAttributes = ReadOnlyList.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 attributes) { final var name = consume(PbsTokenKind.IDENTIFIER, "Expected service name"); consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start service body"); final var methods = new ArrayList(); @@ -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 attributes) { final var name = consume(PbsTokenKind.IDENTIFIER, "Expected error name"); consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start error body"); final var cases = new ArrayList(); @@ -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 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 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 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 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())) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java index a632ad42..7cc9e63b 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java @@ -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 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 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 validateNamedArguments( final PbsAst.Attribute attribute, final Set requiredNames, @@ -911,6 +969,21 @@ public final class PbsDeclarationSemanticsValidator { } } + private void validateReservedAttributeTargetExceptDoc( + final ReadOnlyList 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, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsEditorialSupportService.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsEditorialSupportService.java index f87572f7..fc4e65ac 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsEditorialSupportService.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsEditorialSupportService.java @@ -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()); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java index 668f6b74..60132df1 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java @@ -372,7 +372,8 @@ final class PbsFlowSemanticSupport { } if (topDecl instanceof PbsAst.CallbackDecl( String name, ReadOnlyList parameters, PbsAst.ReturnKind returnKind, - PbsAst.TypeRef returnType, PbsAst.TypeRef resultErrorType, Span span + PbsAst.TypeRef returnType, PbsAst.TypeRef resultErrorType, + ReadOnlyList ignoredAttributes, Span span )) { final var symbol = callableFrom( name, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsInterfaceModuleSemanticsTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsInterfaceModuleSemanticsTest.java index c0b6ef79..7aa8679e 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsInterfaceModuleSemanticsTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsInterfaceModuleSemanticsTest.java @@ -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 = """ diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java index 36745223..2677908d 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java @@ -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 = """