implements PR025
This commit is contained in:
parent
0770bb869a
commit
52e8746bcc
@ -1,6 +1,7 @@
|
||||
package p.studio.compiler.pbs;
|
||||
|
||||
import p.studio.compiler.models.IRFunction;
|
||||
import p.studio.compiler.models.SourceKind;
|
||||
import p.studio.compiler.models.IRBackendFile;
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.pbs.lexer.PbsLexer;
|
||||
@ -21,10 +22,21 @@ public final class PbsFrontendCompiler {
|
||||
final FileId fileId,
|
||||
final String source,
|
||||
final DiagnosticSink diagnostics) {
|
||||
return compileFile(fileId, source, diagnostics, SourceKind.PROJECT);
|
||||
}
|
||||
|
||||
public IRBackendFile compileFile(
|
||||
final FileId fileId,
|
||||
final String source,
|
||||
final DiagnosticSink diagnostics,
|
||||
final SourceKind sourceKind) {
|
||||
final var admissionBaseline = diagnostics.errorCount();
|
||||
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
||||
final var ast = PbsParser.parse(tokens, fileId, diagnostics);
|
||||
final var irBackendFile = compileParsedFile(fileId, ast, diagnostics);
|
||||
final var parseMode = sourceKind == SourceKind.SDK_INTERFACE
|
||||
? PbsParser.ParseMode.INTERFACE_MODULE
|
||||
: PbsParser.ParseMode.ORDINARY;
|
||||
final var ast = PbsParser.parse(tokens, fileId, diagnostics, parseMode);
|
||||
final var irBackendFile = compileParsedFile(fileId, ast, diagnostics, sourceKind);
|
||||
if (diagnostics.errorCount() > admissionBaseline) {
|
||||
return IRBackendFile.empty(fileId);
|
||||
}
|
||||
@ -35,8 +47,16 @@ public final class PbsFrontendCompiler {
|
||||
final FileId fileId,
|
||||
final PbsAst.File ast,
|
||||
final DiagnosticSink diagnostics) {
|
||||
return compileParsedFile(fileId, ast, diagnostics, SourceKind.PROJECT);
|
||||
}
|
||||
|
||||
public IRBackendFile compileParsedFile(
|
||||
final FileId fileId,
|
||||
final PbsAst.File ast,
|
||||
final DiagnosticSink diagnostics,
|
||||
final SourceKind sourceKind) {
|
||||
final var semanticsErrorBaseline = diagnostics.errorCount();
|
||||
declarationSemanticsValidator.validate(ast, diagnostics);
|
||||
declarationSemanticsValidator.validate(ast, sourceKind, diagnostics);
|
||||
flowSemanticsValidator.validate(ast, diagnostics);
|
||||
if (diagnostics.errorCount() > semanticsErrorBaseline) {
|
||||
return IRBackendFile.empty(fileId);
|
||||
|
||||
@ -245,11 +245,12 @@ public final class PbsModuleVisibilityValidator {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO(pbs-interface-modules): include top-level host declarations when interface-module mode is added.
|
||||
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.STRUCT, structDecl.name(), structDecl.span(), nameTable);
|
||||
} else if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.CONTRACT, contractDecl.name(), contractDecl.span(), nameTable);
|
||||
} else if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.HOST, hostDecl.name(), hostDecl.span(), nameTable);
|
||||
} else if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.ERROR, errorDecl.name(), errorDecl.span(), nameTable);
|
||||
} else if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
||||
|
||||
@ -27,6 +27,14 @@ record PbsCallableScope(
|
||||
return new PbsCallableScope("contract signature scope", "contract", ownerNameId);
|
||||
}
|
||||
|
||||
static PbsCallableScope hostMethods(final NameId ownerNameId) {
|
||||
return new PbsCallableScope("host method scope", "host", ownerNameId);
|
||||
}
|
||||
|
||||
static PbsCallableScope builtinMethods(final NameId ownerNameId) {
|
||||
return new PbsCallableScope("builtin method scope", "builtin", ownerNameId);
|
||||
}
|
||||
|
||||
static PbsCallableScope implementsMethods(final NameId ownerNameId) {
|
||||
return new PbsCallableScope("implements method scope", "implements", ownerNameId);
|
||||
}
|
||||
|
||||
@ -133,6 +133,12 @@ final class PbsDeclarationRuleValidator {
|
||||
}
|
||||
|
||||
void validateConstDeclaration(final PbsAst.ConstDecl constDecl) {
|
||||
validateConstDeclaration(constDecl, true);
|
||||
}
|
||||
|
||||
void validateConstDeclaration(
|
||||
final PbsAst.ConstDecl constDecl,
|
||||
final boolean requireInitializer) {
|
||||
if (constDecl.explicitType() == null) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MISSING_CONST_TYPE_ANNOTATION.name(),
|
||||
@ -146,7 +152,7 @@ final class PbsDeclarationRuleValidator {
|
||||
diagnostics);
|
||||
}
|
||||
|
||||
if (constDecl.initializer() == null) {
|
||||
if (requireInitializer && constDecl.initializer() == null) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MISSING_CONST_INITIALIZER.name(),
|
||||
"Non-builtin const declaration '%s' must include an initializer".formatted(constDecl.name()),
|
||||
|
||||
@ -1,21 +1,56 @@
|
||||
package p.studio.compiler.pbs.semantics;
|
||||
|
||||
import p.studio.compiler.models.SourceKind;
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.source.Span;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.tables.NameTable;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public final class PbsDeclarationSemanticsValidator {
|
||||
private static final String ATTR_HOST = "Host";
|
||||
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
||||
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
||||
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
||||
private static final String ATTR_SLOT = "Slot";
|
||||
|
||||
private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
|
||||
ATTR_HOST,
|
||||
ATTR_BUILTIN_TYPE,
|
||||
ATTR_BUILTIN_CONST,
|
||||
ATTR_INTRINSIC_CALL,
|
||||
ATTR_SLOT);
|
||||
|
||||
private final NameTable nameTable = new NameTable();
|
||||
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
||||
|
||||
public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
|
||||
validate(ast, SourceKind.PROJECT, diagnostics);
|
||||
}
|
||||
|
||||
public void validate(
|
||||
final PbsAst.File ast,
|
||||
final SourceKind sourceKind,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var binder = new PbsNamespaceBinder(nameTable, diagnostics);
|
||||
final var rules = new PbsDeclarationRuleValidator(nameTable, diagnostics);
|
||||
final var interfaceModule = sourceKind == SourceKind.SDK_INTERFACE;
|
||||
|
||||
for (final var topDecl : ast.topDecls()) {
|
||||
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
||||
if (interfaceModule) {
|
||||
reportInterfaceNonDeclarativeDecl(
|
||||
functionDecl.span(),
|
||||
"Top-level functions are not allowed in interface modules",
|
||||
diagnostics);
|
||||
}
|
||||
validateCallable(
|
||||
PbsCallableScope.topLevelFunctions(),
|
||||
functionDecl.name(),
|
||||
@ -32,6 +67,12 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
|
||||
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
||||
binder.registerType(structDecl.name(), structDecl.span(), "struct");
|
||||
if (interfaceModule && (structDecl.hasBody() || !structDecl.methods().isEmpty() || !structDecl.ctors().isEmpty())) {
|
||||
reportInterfaceNonDeclarativeDecl(
|
||||
structDecl.span(),
|
||||
"Interface modules must not declare executable struct bodies",
|
||||
diagnostics);
|
||||
}
|
||||
validateStructDeclaration(structDecl, binder, rules, diagnostics);
|
||||
continue;
|
||||
}
|
||||
@ -39,16 +80,39 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||
binder.registerType(serviceDecl.name(), serviceDecl.span(), "service");
|
||||
binder.registerValue(serviceDecl.name(), serviceDecl.span(), "service singleton");
|
||||
if (interfaceModule) {
|
||||
reportInterfaceNonDeclarativeDecl(
|
||||
serviceDecl.span(),
|
||||
"Interface modules must not declare executable service bodies",
|
||||
diagnostics);
|
||||
}
|
||||
validateServiceDeclaration(serviceDecl, binder, rules);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||
binder.registerType(contractDecl.name(), contractDecl.span(), "contract");
|
||||
if (interfaceModule) {
|
||||
for (final var signature : contractDecl.signatures()) {
|
||||
validateReservedAttributeTarget(signature.attributes(), "contract signature", diagnostics);
|
||||
}
|
||||
}
|
||||
validateContractDeclaration(contractDecl, binder, rules);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||
binder.registerHostOwner(hostDecl.name(), hostDecl.span(), "host owner");
|
||||
validateHostDeclaration(hostDecl, binder, rules, interfaceModule, diagnostics);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
||||
binder.registerType(builtinTypeDecl.name(), builtinTypeDecl.span(), "builtin type");
|
||||
validateBuiltinTypeDeclaration(builtinTypeDecl, binder, rules, interfaceModule, diagnostics);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
||||
binder.registerType(errorDecl.name(), errorDecl.span(), "error");
|
||||
rules.validateErrorDeclaration(errorDecl);
|
||||
@ -69,11 +133,18 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
|
||||
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||
binder.registerValue(constDecl.name(), constDecl.span(), "const");
|
||||
rules.validateConstDeclaration(constDecl);
|
||||
final var allowMissingInitializer = validateConstDeclarationAttributes(constDecl, interfaceModule, diagnostics);
|
||||
rules.validateConstDeclaration(constDecl, !allowMissingInitializer);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (topDecl instanceof PbsAst.ImplementsDecl implementsDecl) {
|
||||
if (interfaceModule) {
|
||||
reportInterfaceNonDeclarativeDecl(
|
||||
implementsDecl.span(),
|
||||
"Interface modules must not declare executable 'implements' blocks",
|
||||
diagnostics);
|
||||
}
|
||||
validateImplementsDeclaration(implementsDecl, binder, rules);
|
||||
}
|
||||
}
|
||||
@ -116,7 +187,7 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
|
||||
binder.registerCtor(ctorScope, ctor.name(), PbsCallableShape.ctorShapeKey(ctor.parameters()), ctor.span());
|
||||
if (PbsCtorReturnScanner.containsReturnStatement(ctor.body())) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_INVALID_RETURN_INSIDE_CTOR.name(),
|
||||
"Constructors cannot contain 'return' statements",
|
||||
ctor.span());
|
||||
@ -164,6 +235,80 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateHostDeclaration(
|
||||
final PbsAst.HostDecl hostDecl,
|
||||
final PbsNamespaceBinder binder,
|
||||
final PbsDeclarationRuleValidator rules,
|
||||
final boolean interfaceModule,
|
||||
final DiagnosticSink diagnostics) {
|
||||
if (!interfaceModule) {
|
||||
reportInterfaceNonDeclarativeDecl(
|
||||
hostDecl.span(),
|
||||
"'declare host' is valid only in interface-module sources",
|
||||
diagnostics);
|
||||
return;
|
||||
}
|
||||
|
||||
final var signatureScope = PbsCallableScope.hostMethods(nameTable.register(hostDecl.name()));
|
||||
for (final var signature : hostDecl.signatures()) {
|
||||
validateCallable(
|
||||
signatureScope,
|
||||
signature.name(),
|
||||
signature.parameters(),
|
||||
signature.returnKind(),
|
||||
signature.returnType(),
|
||||
signature.resultErrorType(),
|
||||
signature.span(),
|
||||
false,
|
||||
binder,
|
||||
rules);
|
||||
validateHostSignatureAttributes(signature, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateBuiltinTypeDeclaration(
|
||||
final PbsAst.BuiltinTypeDecl builtinTypeDecl,
|
||||
final PbsNamespaceBinder binder,
|
||||
final PbsDeclarationRuleValidator rules,
|
||||
final boolean interfaceModule,
|
||||
final DiagnosticSink diagnostics) {
|
||||
if (!interfaceModule) {
|
||||
reportInterfaceNonDeclarativeDecl(
|
||||
builtinTypeDecl.span(),
|
||||
"'declare builtin type' is valid only in interface-module sources",
|
||||
diagnostics);
|
||||
return;
|
||||
}
|
||||
|
||||
validateBuiltinTypeAttribute(builtinTypeDecl, diagnostics);
|
||||
|
||||
final var fieldTypes = new ArrayList<PbsAst.TypeRef>(builtinTypeDecl.fields().size());
|
||||
for (final var field : builtinTypeDecl.fields()) {
|
||||
fieldTypes.add(field.typeRef());
|
||||
validateBuiltinFieldAttributes(field, diagnostics);
|
||||
}
|
||||
rules.validateTypeSurfaces(
|
||||
fieldTypes,
|
||||
"builtin type '%s'".formatted(builtinTypeDecl.name()),
|
||||
false);
|
||||
|
||||
final var methodScope = PbsCallableScope.builtinMethods(nameTable.register(builtinTypeDecl.name()));
|
||||
for (final var signature : builtinTypeDecl.signatures()) {
|
||||
validateCallable(
|
||||
methodScope,
|
||||
signature.name(),
|
||||
signature.parameters(),
|
||||
signature.returnKind(),
|
||||
signature.returnType(),
|
||||
signature.resultErrorType(),
|
||||
signature.span(),
|
||||
false,
|
||||
binder,
|
||||
rules);
|
||||
validateIntrinsicCallAttributes(signature, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateImplementsDeclaration(
|
||||
final PbsAst.ImplementsDecl implementsDecl,
|
||||
final PbsNamespaceBinder binder,
|
||||
@ -185,6 +330,404 @@ public final class PbsDeclarationSemanticsValidator {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validateConstDeclarationAttributes(
|
||||
final PbsAst.ConstDecl constDecl,
|
||||
final boolean interfaceModule,
|
||||
final DiagnosticSink 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())) {
|
||||
continue;
|
||||
}
|
||||
reportInvalidReservedAttributeTarget(
|
||||
attribute,
|
||||
"Attribute '%s' is not valid on const declarations".formatted(attribute.name()),
|
||||
diagnostics);
|
||||
}
|
||||
|
||||
if (!interfaceModule && !builtinConstAttributes.isEmpty()) {
|
||||
for (final var attribute : builtinConstAttributes) {
|
||||
reportInvalidReservedAttributeTarget(
|
||||
attribute,
|
||||
"'BuiltinConst' is valid only in interface-module const declarations",
|
||||
diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
if (builtinConstAttributes.size() > 1) {
|
||||
for (int i = 1; i < builtinConstAttributes.size(); i++) {
|
||||
reportDuplicateReservedAttribute(builtinConstAttributes.get(i), ATTR_BUILTIN_CONST, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
if (builtinConstAttributes.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
validateBuiltinConstAttributeShape(builtinConstAttributes.getFirst(), diagnostics);
|
||||
if (constDecl.initializer() != null) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||
"Const declaration '%s' with BuiltinConst metadata must omit initializer".formatted(constDecl.name()),
|
||||
constDecl.span());
|
||||
}
|
||||
return interfaceModule;
|
||||
}
|
||||
|
||||
private void validateHostSignatureAttributes(
|
||||
final PbsAst.FunctionSignature signature,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var hostAttributes = attributesNamed(signature.attributes(), ATTR_HOST);
|
||||
for (final var attribute : signature.attributes()) {
|
||||
if (!isReservedAttribute(attribute.name())) {
|
||||
continue;
|
||||
}
|
||||
if (ATTR_HOST.equals(attribute.name())) {
|
||||
continue;
|
||||
}
|
||||
reportInvalidReservedAttributeTarget(
|
||||
attribute,
|
||||
"Attribute '%s' is not valid on host signatures".formatted(attribute.name()),
|
||||
diagnostics);
|
||||
}
|
||||
|
||||
if (hostAttributes.isEmpty()) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MISSING_REQUIRED_RESERVED_ATTRIBUTE.name(),
|
||||
"Host signature '%s' must carry exactly one Host attribute".formatted(signature.name()),
|
||||
signature.span());
|
||||
return;
|
||||
}
|
||||
|
||||
if (hostAttributes.size() > 1) {
|
||||
for (int i = 1; i < hostAttributes.size(); i++) {
|
||||
reportDuplicateReservedAttribute(hostAttributes.get(i), ATTR_HOST, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
validateHostAttributeShape(hostAttributes.getFirst(), diagnostics);
|
||||
}
|
||||
|
||||
private void validateBuiltinTypeAttribute(
|
||||
final PbsAst.BuiltinTypeDecl builtinTypeDecl,
|
||||
final DiagnosticSink 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())) {
|
||||
continue;
|
||||
}
|
||||
reportInvalidReservedAttributeTarget(
|
||||
attribute,
|
||||
"Attribute '%s' is not valid on builtin type declarations".formatted(attribute.name()),
|
||||
diagnostics);
|
||||
}
|
||||
|
||||
if (builtinTypeAttributes.isEmpty()) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MISSING_REQUIRED_RESERVED_ATTRIBUTE.name(),
|
||||
"Builtin type '%s' must carry exactly one BuiltinType attribute".formatted(builtinTypeDecl.name()),
|
||||
builtinTypeDecl.span());
|
||||
return;
|
||||
}
|
||||
|
||||
if (builtinTypeAttributes.size() > 1) {
|
||||
for (int i = 1; i < builtinTypeAttributes.size(); i++) {
|
||||
reportDuplicateReservedAttribute(builtinTypeAttributes.get(i), ATTR_BUILTIN_TYPE, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
validateBuiltinTypeAttributeShape(builtinTypeAttributes.getFirst(), diagnostics);
|
||||
}
|
||||
|
||||
private void validateBuiltinFieldAttributes(
|
||||
final PbsAst.BuiltinFieldDecl field,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var slotAttributes = attributesNamed(field.attributes(), ATTR_SLOT);
|
||||
for (final var attribute : field.attributes()) {
|
||||
if (!isReservedAttribute(attribute.name())) {
|
||||
continue;
|
||||
}
|
||||
if (ATTR_SLOT.equals(attribute.name())) {
|
||||
continue;
|
||||
}
|
||||
reportInvalidReservedAttributeTarget(
|
||||
attribute,
|
||||
"Attribute '%s' is not valid on builtin fields".formatted(attribute.name()),
|
||||
diagnostics);
|
||||
}
|
||||
|
||||
if (slotAttributes.size() > 1) {
|
||||
for (int i = 1; i < slotAttributes.size(); i++) {
|
||||
reportDuplicateReservedAttribute(slotAttributes.get(i), ATTR_SLOT, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
if (!slotAttributes.isEmpty()) {
|
||||
validateSlotAttributeShape(slotAttributes.getFirst(), diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIntrinsicCallAttributes(
|
||||
final PbsAst.FunctionSignature signature,
|
||||
final DiagnosticSink 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())) {
|
||||
continue;
|
||||
}
|
||||
reportInvalidReservedAttributeTarget(
|
||||
attribute,
|
||||
"Attribute '%s' is not valid on builtin method signatures".formatted(attribute.name()),
|
||||
diagnostics);
|
||||
}
|
||||
|
||||
if (intrinsicAttributes.size() > 1) {
|
||||
for (int i = 1; i < intrinsicAttributes.size(); i++) {
|
||||
reportDuplicateReservedAttribute(intrinsicAttributes.get(i), ATTR_INTRINSIC_CALL, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
if (!intrinsicAttributes.isEmpty()) {
|
||||
validateIntrinsicCallAttributeShape(intrinsicAttributes.getFirst(), diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateHostAttributeShape(
|
||||
final PbsAst.Attribute attribute,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var args = validateNamedArguments(attribute, Set.of("module", "name", "version"), Set.of(), diagnostics);
|
||||
if (args == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateRequiredStringArgument(attribute, args, "module", false, diagnostics);
|
||||
validateRequiredStringArgument(attribute, args, "name", false, diagnostics);
|
||||
validateRequiredIntArgument(attribute, args, "version", true, diagnostics);
|
||||
}
|
||||
|
||||
private void validateBuiltinTypeAttributeShape(
|
||||
final PbsAst.Attribute attribute,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var args = validateNamedArguments(attribute, Set.of("name", "version"), Set.of(), diagnostics);
|
||||
if (args == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateRequiredStringArgument(attribute, args, "name", false, diagnostics);
|
||||
validateRequiredIntArgument(attribute, args, "version", true, diagnostics);
|
||||
}
|
||||
|
||||
private void validateBuiltinConstAttributeShape(
|
||||
final PbsAst.Attribute attribute,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var args = validateNamedArguments(attribute, Set.of("target", "name", "version"), Set.of(), diagnostics);
|
||||
if (args == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateRequiredStringArgument(attribute, args, "target", false, diagnostics);
|
||||
validateRequiredStringArgument(attribute, args, "name", false, diagnostics);
|
||||
validateRequiredIntArgument(attribute, args, "version", true, diagnostics);
|
||||
}
|
||||
|
||||
private void validateIntrinsicCallAttributeShape(
|
||||
final PbsAst.Attribute attribute,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var args = validateNamedArguments(attribute, Set.of("name"), Set.of("version"), diagnostics);
|
||||
if (args == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateRequiredStringArgument(attribute, args, "name", false, diagnostics);
|
||||
if (args.containsKey("version")) {
|
||||
validateRequiredIntArgument(attribute, args, "version", true, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateSlotAttributeShape(
|
||||
final PbsAst.Attribute attribute,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var args = validateNamedArguments(attribute, Set.of("index"), Set.of(), diagnostics);
|
||||
if (args == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateRequiredIntArgument(attribute, args, "index", false, diagnostics);
|
||||
}
|
||||
|
||||
private Map<String, PbsAst.AttributeValue> validateNamedArguments(
|
||||
final PbsAst.Attribute attribute,
|
||||
final Set<String> requiredNames,
|
||||
final Set<String> optionalNames,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var allowedNames = new HashSet<String>();
|
||||
allowedNames.addAll(requiredNames);
|
||||
allowedNames.addAll(optionalNames);
|
||||
|
||||
final var arguments = new HashMap<String, PbsAst.AttributeValue>();
|
||||
var malformed = false;
|
||||
for (final var argument : attribute.arguments()) {
|
||||
if (!allowedNames.contains(argument.name())) {
|
||||
malformed = true;
|
||||
continue;
|
||||
}
|
||||
if (arguments.putIfAbsent(argument.name(), argument.value()) != null) {
|
||||
malformed = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (final var requiredName : requiredNames) {
|
||||
if (!arguments.containsKey(requiredName)) {
|
||||
malformed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (malformed) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||
"Malformed %s attribute argument set".formatted(attribute.name()),
|
||||
attribute.span());
|
||||
return null;
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
private void validateRequiredStringArgument(
|
||||
final PbsAst.Attribute attribute,
|
||||
final Map<String, PbsAst.AttributeValue> arguments,
|
||||
final String argumentName,
|
||||
final boolean positiveOnly,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var value = arguments.get(argumentName);
|
||||
if (!(value instanceof PbsAst.AttributeStringValue stringValue)) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||
"Malformed %s.%s attribute argument".formatted(attribute.name(), argumentName),
|
||||
attribute.span());
|
||||
return;
|
||||
}
|
||||
|
||||
if (isStringLiteralEmpty(stringValue.value())) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||
"Attribute %s.%s must be a non-empty string".formatted(attribute.name(), argumentName),
|
||||
attribute.span());
|
||||
}
|
||||
|
||||
if (positiveOnly && stringValue.value().isBlank()) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||
"Attribute %s.%s must be a positive string literal".formatted(attribute.name(), argumentName),
|
||||
attribute.span());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRequiredIntArgument(
|
||||
final PbsAst.Attribute attribute,
|
||||
final Map<String, PbsAst.AttributeValue> arguments,
|
||||
final String argumentName,
|
||||
final boolean positive,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var value = arguments.get(argumentName);
|
||||
if (!(value instanceof PbsAst.AttributeIntValue intValue)) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||
"Malformed %s.%s attribute argument".formatted(attribute.name(), argumentName),
|
||||
attribute.span());
|
||||
return;
|
||||
}
|
||||
|
||||
final var numericValue = intValue.value();
|
||||
final boolean invalid = positive ? numericValue <= 0 : numericValue < 0;
|
||||
if (invalid) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||
"Attribute %s.%s has invalid numeric value".formatted(attribute.name(), argumentName),
|
||||
attribute.span());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStringLiteralEmpty(final String lexeme) {
|
||||
if (lexeme == null) {
|
||||
return true;
|
||||
}
|
||||
final var trimmed = lexeme.trim();
|
||||
if (trimmed.length() >= 2 && trimmed.startsWith("\"") && trimmed.endsWith("\"")) {
|
||||
return trimmed.length() == 2;
|
||||
}
|
||||
return trimmed.isEmpty();
|
||||
}
|
||||
|
||||
private List<PbsAst.Attribute> attributesNamed(
|
||||
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||
final String name) {
|
||||
final var matches = new ArrayList<PbsAst.Attribute>();
|
||||
for (final var attribute : attributes) {
|
||||
if (name.equals(attribute.name())) {
|
||||
matches.add(attribute);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
private boolean isReservedAttribute(final String name) {
|
||||
return RESERVED_ATTRIBUTES.contains(name);
|
||||
}
|
||||
|
||||
private void validateReservedAttributeTarget(
|
||||
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||
final String targetSurface,
|
||||
final DiagnosticSink diagnostics) {
|
||||
for (final var attribute : attributes) {
|
||||
if (!isReservedAttribute(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,
|
||||
final DiagnosticSink diagnostics) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET.name(),
|
||||
message,
|
||||
attribute.span());
|
||||
}
|
||||
|
||||
private void reportDuplicateReservedAttribute(
|
||||
final PbsAst.Attribute attribute,
|
||||
final String attributeName,
|
||||
final DiagnosticSink diagnostics) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_DUPLICATE_RESERVED_ATTRIBUTE.name(),
|
||||
"Duplicate %s attribute on the same declaration surface".formatted(attributeName),
|
||||
attribute.span());
|
||||
}
|
||||
|
||||
private void reportInterfaceNonDeclarativeDecl(
|
||||
final Span span,
|
||||
final String message,
|
||||
final DiagnosticSink diagnostics) {
|
||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION.name(),
|
||||
message,
|
||||
span);
|
||||
}
|
||||
|
||||
private void validateCallable(
|
||||
final PbsCallableScope scope,
|
||||
final String callableName,
|
||||
|
||||
@ -10,6 +10,11 @@ public enum PbsSemanticsErrors {
|
||||
E_SEM_DUPLICATE_ENUM_CASE_ID,
|
||||
E_SEM_INVALID_MIXED_OPTIONAL_RESULT_RETURN,
|
||||
E_SEM_INVALID_OPTIONAL_VOID_TYPE_SURFACE,
|
||||
E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION,
|
||||
E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET,
|
||||
E_SEM_MISSING_REQUIRED_RESERVED_ATTRIBUTE,
|
||||
E_SEM_DUPLICATE_RESERVED_ATTRIBUTE,
|
||||
E_SEM_MALFORMED_RESERVED_ATTRIBUTE,
|
||||
E_SEM_MISSING_CONST_TYPE_ANNOTATION,
|
||||
E_SEM_MISSING_CONST_INITIALIZER,
|
||||
E_SEM_CONST_NON_CONSTANT_INITIALIZER,
|
||||
|
||||
@ -134,7 +134,11 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
if (failedModuleKeys.contains(parsedSource.moduleKey())) {
|
||||
continue;
|
||||
}
|
||||
final var irBackendFile = frontendCompiler.compileParsedFile(parsedSource.fileId(), parsedSource.ast(), diagnostics);
|
||||
final var irBackendFile = frontendCompiler.compileParsedFile(
|
||||
parsedSource.fileId(),
|
||||
parsedSource.ast(),
|
||||
diagnostics,
|
||||
parsedSource.sourceKind());
|
||||
if (parsedSource.sourceKind() == SourceKind.SDK_INTERFACE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -215,6 +215,66 @@ class PbsModuleVisibilityTest {
|
||||
d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveHostBarrelEntryWhenHostDeclarationExistsInInterfaceModule() {
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var sourceFileId = new FileId(100);
|
||||
final var barrelFileId = new FileId(101);
|
||||
final var sourceAst = parseSource(
|
||||
"""
|
||||
declare host Gfx {
|
||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||
fn draw_pixel(x: int, y: int) -> void;
|
||||
}
|
||||
""",
|
||||
sourceFileId,
|
||||
diagnostics,
|
||||
PbsParser.ParseMode.INTERFACE_MODULE);
|
||||
final var barrelAst = parseBarrel(
|
||||
"""
|
||||
pub host Gfx;
|
||||
""",
|
||||
barrelFileId,
|
||||
diagnostics);
|
||||
final var module = new PbsModuleVisibilityValidator.ModuleUnit(
|
||||
new PbsModuleVisibilityValidator.ModuleCoordinates("sdk", ReadOnlyList.wrap(List.of("gfx"))),
|
||||
ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.SourceFile(sourceFileId, sourceAst))),
|
||||
ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.BarrelFile(barrelFileId, barrelAst))));
|
||||
|
||||
new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics);
|
||||
|
||||
assertTrue(diagnostics.stream().noneMatch(d ->
|
||||
d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectHostBarrelEntryWhenHostDeclarationIsMissing() {
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var sourceFileId = new FileId(110);
|
||||
final var barrelFileId = new FileId(111);
|
||||
final var sourceAst = parseSource(
|
||||
"""
|
||||
declare enum Color(Red);
|
||||
""",
|
||||
sourceFileId,
|
||||
diagnostics);
|
||||
final var barrelAst = parseBarrel(
|
||||
"""
|
||||
pub host Gfx;
|
||||
""",
|
||||
barrelFileId,
|
||||
diagnostics);
|
||||
final var module = new PbsModuleVisibilityValidator.ModuleUnit(
|
||||
new PbsModuleVisibilityValidator.ModuleCoordinates("sdk", ReadOnlyList.wrap(List.of("gfx"))),
|
||||
ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.SourceFile(sourceFileId, sourceAst))),
|
||||
ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.BarrelFile(barrelFileId, barrelAst))));
|
||||
|
||||
new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics);
|
||||
|
||||
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
|
||||
}
|
||||
|
||||
private PbsModuleVisibilityValidator.ModuleUnit module(
|
||||
final String project,
|
||||
final String modulePath,
|
||||
@ -257,7 +317,15 @@ class PbsModuleVisibilityTest {
|
||||
final String source,
|
||||
final FileId fileId,
|
||||
final DiagnosticSink diagnostics) {
|
||||
return PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||
return parseSource(source, fileId, diagnostics, PbsParser.ParseMode.ORDINARY);
|
||||
}
|
||||
|
||||
private PbsAst.File parseSource(
|
||||
final String source,
|
||||
final FileId fileId,
|
||||
final DiagnosticSink diagnostics,
|
||||
final PbsParser.ParseMode parseMode) {
|
||||
return PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics, parseMode);
|
||||
}
|
||||
|
||||
private PbsAst.BarrelFile parseBarrel(
|
||||
|
||||
@ -0,0 +1,101 @@
|
||||
package p.studio.compiler.pbs.semantics;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.compiler.models.SourceKind;
|
||||
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.identifiers.FileId;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PbsInterfaceModuleSemanticsTest {
|
||||
|
||||
@Test
|
||||
void shouldAcceptValidInterfaceModuleReservedMetadataShapes() {
|
||||
final var source = """
|
||||
[BuiltinType(name = "color", version = 1)]
|
||||
declare builtin type Color(
|
||||
[Slot(index = 0)] pub raw: int
|
||||
) {
|
||||
[IntrinsicCall(name = "pack", version = 1)]
|
||||
fn pack() -> int;
|
||||
}
|
||||
|
||||
declare host Gfx {
|
||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||
fn draw_pixel(x: int, y: int, c: Color) -> void;
|
||||
}
|
||||
|
||||
[BuiltinConst(target = "color", name = "white", version = 1)]
|
||||
declare const WHITE: Color;
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||
|
||||
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_MISSING_REQUIRED_RESERVED_ATTRIBUTE.name())));
|
||||
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET.name())));
|
||||
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectInvalidReservedAttributeTargetsAndShapesInInterfaceModule() {
|
||||
final var source = """
|
||||
declare host Gfx {
|
||||
fn draw_pixel(x: int, y: int) -> void;
|
||||
}
|
||||
|
||||
declare contract Api {
|
||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||
fn run() -> void;
|
||||
}
|
||||
|
||||
declare builtin type Color(pub raw: int) {
|
||||
[IntrinsicCall(version = 1)]
|
||||
fn pack() -> int;
|
||||
}
|
||||
|
||||
[BuiltinConst(target = "", name = "white", version = 0)]
|
||||
declare const WHITE: int = 1;
|
||||
""";
|
||||
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_MISSING_REQUIRED_RESERVED_ATTRIBUTE.name())));
|
||||
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET.name())));
|
||||
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectExecutableDeclarationsInInterfaceModule() {
|
||||
final var source = """
|
||||
fn run() -> int { return 1; }
|
||||
|
||||
declare service Game {
|
||||
fn tick() -> int { return 1; }
|
||||
}
|
||||
|
||||
declare contract C { fn run() -> int; }
|
||||
declare struct S(v: int);
|
||||
implements C for S using s {
|
||||
fn run() -> int { return 1; }
|
||||
}
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||
|
||||
final var nonDeclarativeCount = diagnostics.stream()
|
||||
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION.name()))
|
||||
.count();
|
||||
assertTrue(nonDeclarativeCount >= 3);
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,7 @@ import p.studio.compiler.pbs.stdlib.StdlibEnvironment;
|
||||
import p.studio.compiler.pbs.stdlib.StdlibEnvironmentResolver;
|
||||
import p.studio.compiler.pbs.stdlib.StdlibModuleSource;
|
||||
import p.studio.compiler.pbs.linking.PbsLinkErrors;
|
||||
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.identifiers.ProjectId;
|
||||
import p.studio.compiler.source.tables.FileTable;
|
||||
@ -219,7 +220,8 @@ class PBSFrontendPhaseServiceTest {
|
||||
LogAggregator.empty(),
|
||||
BuildingIssueSink.empty());
|
||||
|
||||
assertTrue(diagnostics.isEmpty());
|
||||
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION.name())));
|
||||
assertEquals(0, irBackend.getFunctions().size());
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user