All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ai.vespa.schemals.schemadocument.resolvers.InheritanceResolver Maven / Gradle / Ivy

There is a newer version: 8.441.21
Show newest version
package ai.vespa.schemals.schemadocument.resolvers;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;

import ai.vespa.schemals.common.FileUtils;
import ai.vespa.schemals.common.SchemaDiagnostic;
import ai.vespa.schemals.common.SchemaDiagnostic.DiagnosticCode;
import ai.vespa.schemals.context.ParseContext;
import ai.vespa.schemals.index.Symbol;
import ai.vespa.schemals.index.Symbol.SymbolStatus;
import ai.vespa.schemals.index.Symbol.SymbolType;
import ai.vespa.schemals.parser.ast.COMMA;
import ai.vespa.schemals.schemadocument.SchemaDocument;
import ai.vespa.schemals.tree.Node;
import ai.vespa.schemals.tree.SchemaNode;

/**
 * InheritanceResolver resolves all inheritance, and registers them in inheritance graphs
 */
public class InheritanceResolver {
    

    /**
     * Assuming first parsing step is done, use the list of unresolved inheritance
     * declarations to register the inheritance at index.
     * @return List of diagnostic found during inheritance handling
     */
    public static List resolveInheritances(ParseContext context) {
        List diagnostics = new ArrayList<>();
        Set documentInheritanceURIs = new HashSet<>();

        for (SchemaNode inheritanceNode : context.unresolvedInheritanceNodes()) {
            if (inheritanceNode.getSymbol().getType() == SymbolType.DOCUMENT) {
                resolveDocumentInheritance(inheritanceNode, context, diagnostics).ifPresent(
                    parentURI -> documentInheritanceURIs.add(parentURI)
                );
            }
        }

        for (SchemaNode inheritanceNode : context.unresolvedInheritanceNodes()) {
            if (inheritanceNode.getSymbol().getType() == SymbolType.STRUCT) {
                resolveStructInheritance(inheritanceNode, context, diagnostics);
            }

            if (inheritanceNode.getSymbol().getType() == SymbolType.RANK_PROFILE) {
                resolveRankProfileInheritance(inheritanceNode, context, diagnostics);
            }

            if (inheritanceNode.getSymbol().getType() == SymbolType.DOCUMENT_SUMMARY) {
                resolveDocumentSummaryInheritance(inheritanceNode, context, diagnostics);
            }
        }

        if (context.inheritsSchemaNode() != null) {
            String inheritsSchemaName = context.inheritsSchemaNode().getText();

            Optional schemaSymbol = context.schemaIndex().getSchemaDefinition(inheritsSchemaName);

            if (schemaSymbol.isPresent()) {
                SchemaDocument parent = context.scheduler().getSchemaDocument(schemaSymbol.get().getFileURI());

                if (parent != null) {
                    if (!documentInheritanceURIs.contains(parent.getFileURI())) {
                        diagnostics.add(new SchemaDiagnostic.Builder()
                                .setRange( context.inheritsSchemaNode().getRange())
                                .setMessage( "The schema document must explicitly inherit from " + inheritsSchemaName + " because the containing schema does so.")
                                .setSeverity( DiagnosticSeverity.Error)
                                .setCode(DiagnosticCode.EXPLICITLY_INHERIT_DOCUMENT)
                                .build() );
                        context.schemaIndex().getDocumentInheritanceGraph().addInherits(context.fileURI(), parent.getFileURI());
                    }

                    context.schemaIndex().insertSymbolReference(schemaSymbol.get(), context.inheritsSchemaNode().getSymbol());
                }
            }
        }

        context.clearUnresolvedInheritanceNodes();

        return diagnostics;
    }

    private static void resolveStructInheritance(SchemaNode inheritanceNode, ParseContext context, List diagnostics) {
        if (inheritanceNode.getPreviousSibling() != null && inheritanceNode.getPreviousSibling().getASTClass() == COMMA.class) {
            diagnostics.add(new SchemaDiagnostic.Builder()
                             .setRange(inheritanceNode.getRange())
                             .setMessage("Inheriting multiple structs is not supported.")
                             .setSeverity(DiagnosticSeverity.Error)
                             .build());
            return;
        }
        Node myStructDefinitionNode = inheritanceNode.getParent().getPreviousSibling();

        if (myStructDefinitionNode == null) {
            return;
        }

        if (!myStructDefinitionNode.hasSymbol()) {
            return;
        }

        Optional parentSymbol = context.schemaIndex().findSymbol(inheritanceNode.getSymbol());
        if (parentSymbol.isEmpty()) {
            // Handled elsewhere
            return;
        }

        if (!context.schemaIndex().tryRegisterStructInheritance(myStructDefinitionNode.getSymbol(), parentSymbol.get())) {
            diagnostics.add(new SchemaDiagnostic.Builder()
                    .setRange( inheritanceNode.getRange())
                    .setMessage( "Cannot inherit from " + parentSymbol.get().getShortIdentifier() + " because " + parentSymbol.get().getShortIdentifier() + " inherits from this struct.")
                    .setSeverity( DiagnosticSeverity.Error)
                    .build() );
        }


        // Look for redeclarations

        Symbol structDefinitionSymbol = myStructDefinitionNode.getSymbol();

        Map inheritedFields = new HashMap<>();
        List myFields = new ArrayList<>();

        for (Symbol fieldSymbol : context.schemaIndex().listSymbolsInScope(structDefinitionSymbol, SymbolType.FIELD)) {
            if (fieldSymbol.getScope().equals(structDefinitionSymbol)) {
                myFields.add(fieldSymbol);
            } else {
                inheritedFields.put(fieldSymbol.getShortIdentifier(), fieldSymbol);
            }
        }

        for (Symbol fieldSymbol : myFields) {
            if (!inheritedFields.containsKey(fieldSymbol.getShortIdentifier())) continue;
            diagnostics.add(new SchemaDiagnostic.Builder()
                    .setRange(fieldSymbol.getNode().getRange())
                    .setMessage("struct " + myStructDefinitionNode.getText() + " cannot inherit from " + parentSymbol.get().getShortIdentifier() + " and redeclare field " + fieldSymbol.getShortIdentifier())
                    .setSeverity(DiagnosticSeverity.Error)
                    .setCode(DiagnosticCode.INHERITS_STRUCT_FIELD_REDECLARED)
                    .build());
        }
    }

    private static void resolveRankProfileInheritance(SchemaNode inheritanceNode, ParseContext context, List diagnostics) {
        Node myRankProfileDefinitionNode = inheritanceNode.getParent().getPreviousSibling();
        String inheritedIdentifier = inheritanceNode.getText();

        if (myRankProfileDefinitionNode == null) return;
        if (!myRankProfileDefinitionNode.hasSymbol() || myRankProfileDefinitionNode.getSymbol().getStatus() != SymbolStatus.DEFINITION) return;

        List parentSymbols = context.schemaIndex().findSymbols(inheritanceNode.getSymbol());

        if (parentSymbols.isEmpty() || 
            // prevents rank-profile default inherits default from causing cyclic inheritance
            (inheritedIdentifier.equals("default") && myRankProfileDefinitionNode.getSymbol().getShortIdentifier().equals("default"))
        ) {
            if (inheritedIdentifier.equals("default")) {
                inheritanceNode.setSymbolStatus(SymbolStatus.BUILTIN_REFERENCE);
                return;
            }
            // Otherwise handled in resolve symbol ref
            return;
        }

        if (parentSymbols.size() > 1 && !parentSymbols.get(0).fileURIEquals(context.fileURI())) {
            String note = "\nNote:";

            for (Symbol symbol : parentSymbols) {
                note += "\nDefined in " + FileUtils.fileNameFromPath(symbol.getFileURI());
            }

            diagnostics.add(new SchemaDiagnostic.Builder()
                    .setRange( inheritanceNode.getRange())
                    .setMessage( inheritedIdentifier + " is ambiguous in this context. " + note)
                    .setSeverity( DiagnosticSeverity.Warning)
                    .build());
        }

        // Choose last one, if more than one (undefined behaviour if ambiguous).
        Symbol parentSymbol = parentSymbols.get(0);
        Symbol definitionSymbol = myRankProfileDefinitionNode.getSymbol();

        if (!checkRankProfileInheritanceCollisions(context, definitionSymbol, parentSymbol, inheritanceNode, diagnostics)) return;

        if (!context.schemaIndex().tryRegisterRankProfileInheritance(definitionSymbol, parentSymbol)) {
            diagnostics.add(new SchemaDiagnostic.Builder()
                    .setRange(inheritanceNode.getRange())
                    .setMessage("Cannot inherit from " + parentSymbol.getShortIdentifier() + " because " + parentSymbol.getShortIdentifier() + " inherits from this rank profile. ")
                    .setSeverity(DiagnosticSeverity.Error)
                    .build());

            return;
        }
    }

    private static boolean checkRankProfileInheritanceCollisions(ParseContext context, Symbol rankProfileDefinitionSymbol, Symbol inheritanceCandidate, SchemaNode inheritanceNode, List diagnostics) {
        Map functionDefinitionsBeforeInheritance = new HashMap<>();
        for (Symbol symbol : context.schemaIndex().listSymbolsInScope(rankProfileDefinitionSymbol, SymbolType.FUNCTION)) {
            // Skip shadowing inside the current rank-profile
            if (symbol.getScope().equals(rankProfileDefinitionSymbol)) continue;

            functionDefinitionsBeforeInheritance.put(symbol.getShortIdentifier(), symbol.getScope());
        }

        List newInheritedFunctions = context.schemaIndex().listSymbolsInScope(inheritanceCandidate, SymbolType.FUNCTION);

        boolean success = true;
        for (Symbol parentFunction : newInheritedFunctions) {
            if (functionDefinitionsBeforeInheritance.containsKey(parentFunction.getShortIdentifier())) {
                diagnostics.add(new SchemaDiagnostic.Builder()
                    .setRange(inheritanceNode.getRange())
                    .setMessage(
                        "Cannot inherit from " + inheritanceNode.getText() + " because " + inheritanceNode.getText() + 
                        " defines function " + parentFunction.getShortIdentifier() + " which is already defined in " + 
                        functionDefinitionsBeforeInheritance.get(parentFunction.getShortIdentifier()).getShortIdentifier()
                    )
                    .setSeverity(DiagnosticSeverity.Error)
                    .build()
                );
                success = false;
            }
        }
        return success;
    }

    private static Optional resolveDocumentInheritance(SchemaNode inheritanceNode, ParseContext context, List diagnostics) {
        String schemaDocumentName = inheritanceNode.getText();
        Optional symbol = context.schemaIndex().getSchemaDefinition(schemaDocumentName);

        if (symbol.isEmpty()) {
            // Handled in resolve symbol references
            return Optional.empty();
        }

        if (!context.schemaIndex().tryRegisterDocumentInheritance(context.fileURI(), symbol.get().getFileURI())) {
            // Inheritance cycle
            diagnostics.add(new SchemaDiagnostic.Builder()
                    .setRange( inheritanceNode.getRange())
                    .setMessage( "Cannot inherit from " + schemaDocumentName + " because " + schemaDocumentName + " inherits from this document.")
                    .setSeverity( DiagnosticSeverity.Error)
                    .build() );
            return Optional.empty();
        }

        inheritanceNode.setSymbolStatus(SymbolStatus.REFERENCE);
        context.schemaIndex().insertSymbolReference(symbol.get(), inheritanceNode.getSymbol());
        return Optional.of(symbol.get().getFileURI());
    }

    private static void resolveDocumentSummaryInheritance(SchemaNode inheritanceNode, ParseContext context, List diagnostics) {
        Node myDocSummaryDefinitionNode = inheritanceNode.getParent().getPreviousSibling();

        if (myDocSummaryDefinitionNode == null) {
            return;
        }

        if (!myDocSummaryDefinitionNode.hasSymbol()) {
            return;
        }

        Optional parentSymbol = context.schemaIndex().findSymbol(inheritanceNode.getSymbol());
        if (parentSymbol.isEmpty()) {
            // Handled elsewhere
            return;
        }

        if (!context.schemaIndex().tryRegisterDocumentSummaryInheritance(myDocSummaryDefinitionNode.getSymbol(), parentSymbol.get())) {
            diagnostics.add(new SchemaDiagnostic.Builder()
                    .setRange( inheritanceNode.getRange())
                    .setMessage( "Cannot inherit from " + parentSymbol.get().getShortIdentifier() + " because " + parentSymbol.get().getShortIdentifier() + " inherits from this document summary.")
                    .setSeverity( DiagnosticSeverity.Error)
                    .build() );
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy