private void handleTensorTypeDefinitions(SchemaNode identifierNode, SchemaNode tensorTypeNode, List<Diagnostic> diagnostics) {
        Optional<Symbol> parentScope = CSTUtils.findScope(tensorTypeNode.getParent());
        if (parentScope.isEmpty()) return;

        if (!tensorTypeNode.hasSymbol()) {
            tensorTypeNode.setSymbol(SymbolType.TENSOR, context.fileURI(), parentScope.get(), "tensor_" + tensorTypeNode.hashCode());
            tensorTypeNode.setSymbolStatus(SymbolStatus.DEFINITION);
            context.schemaIndex().insertSymbolDefinition(tensorTypeNode.getSymbol());
        }

        Symbol scope = tensorTypeNode.getSymbol();
        // TODO: better check of indexed versus mapped dimension type based on existing tensor parsing?
        SymbolType dimensionType = SymbolType.TENSOR_DIMENSION_INDEXED;
        if (identifierNode.getNextSibling() != null && identifierNode.getNextSibling().isASTInstance(LCURLY.class)) {
            dimensionType = SymbolType.TENSOR_DIMENSION_MAPPED;
        }

        identifierNode.setSymbol(dimensionType, context.fileURI(), scope, identifierNode.getText());

        Optional<Symbol> existingSymbolMapped = context.schemaIndex().findSymbolInScope(scope, SymbolType.TENSOR_DIMENSION_MAPPED, identifierNode.getText());
        Optional<Symbol> existingSymbolIndexed = context.schemaIndex().findSymbolInScope(scope, SymbolType.TENSOR_DIMENSION_INDEXED, identifierNode.getText());

        if (existingSymbolMapped.isPresent() || existingSymbolIndexed.isPresent()) {
            identifierNode.setSymbolStatus(SymbolStatus.INVALID);
            diagnostics.add(new SchemaDiagnostic.Builder()
                    .setRange(identifierNode.getRange())
                    .setMessage("Duplicate tensor dimension " + identifierNode.getText())
                    .setSeverity(DiagnosticSeverity.Error)
                    .build());
            return;
        }

        identifierNode.setSymbolStatus(SymbolStatus.DEFINITION);
        context.schemaIndex().insertSymbolDefinition(identifierNode.getSymbol());
    }
For rank expressions.
Example:
tensor<float>(d0[1], d1[10])
d0, and d1 should be marked as definition because they can be referenced in the body.
For this to work, the tensor type itself should also be marked as a definition, in order to give the dimensions a scope.
We will use the hashCode trick to give the tensorType node an unique identifier.
integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/parser/schema/IdentifySymbolDefinition.java