diff --git a/mlir/include/mlir/Parser/AsmParserState.h b/mlir/include/mlir/Parser/AsmParserState.h --- a/mlir/include/mlir/Parser/AsmParserState.h +++ b/mlir/include/mlir/Parser/AsmParserState.h @@ -20,6 +20,8 @@ class BlockArgument; class FileLineColLoc; class Operation; +class OperationName; +class SymbolRefAttr; class Value; /// This class represents state from a parsed MLIR textual format string. It is @@ -61,6 +63,10 @@ /// Source definitions for any result groups of this operation. SmallVector> resultGroups; + + /// If this operation is a symbol operation, this vector contains symbol + /// uses of this operation. + SmallVector symbolUses; }; /// This class represents the information for a block definition within the @@ -108,10 +114,28 @@ // Populate State //===--------------------------------------------------------------------===// - /// Add a definition of the given operation. - void addDefinition( - Operation *op, llvm::SMRange location, + /// Initialize the state in preparation for populating more parser state under + /// the given top-level operation. + void initialize(Operation *topLevelOp); + + /// Finalize any in-progress parser state under the given top-level operation. + void finalize(Operation *topLevelOp); + + /// Start a definition for an operation with the given name. + void startOperationDefinition(const OperationName &opName); + + /// Finalize the most recently started operation definition. + void finalizeOperationDefinition( + Operation *op, llvm::SMRange nameLoc, ArrayRef> resultGroups = llvm::None); + + /// Start a definition for a region nested under the current operation. + void startRegionDefinition(); + + /// Finalize the most recently started region definition. + void finalizeRegionDefinition(); + + /// Add a definition of the given entity. void addDefinition(Block *block, llvm::SMLoc location); void addDefinition(BlockArgument blockArg, llvm::SMLoc location); @@ -119,6 +143,12 @@ void addUses(Value value, ArrayRef locations); void addUses(Block *block, ArrayRef locations); + /// Add source uses for all the references nested under `refAttr`. The + /// provided `locations` should match 1-1 with the number of references in + /// `refAttr`, i.e.: + /// nestedReferences.size() + /*leafReference=*/1 == refLocations.size() + void addUses(SymbolRefAttr refAttr, ArrayRef refLocations); + /// Refine the `oldValue` to the `newValue`. This is used to indicate that /// `oldValue` was a placeholder, and the uses of it should really refer to /// `newValue`. diff --git a/mlir/lib/Parser/AsmParserState.cpp b/mlir/lib/Parser/AsmParserState.cpp --- a/mlir/lib/Parser/AsmParserState.cpp +++ b/mlir/lib/Parser/AsmParserState.cpp @@ -8,6 +8,7 @@ #include "mlir/Parser/AsmParserState.h" #include "mlir/IR/Operation.h" +#include "mlir/IR/SymbolTable.h" using namespace mlir; @@ -33,6 +34,28 @@ //===----------------------------------------------------------------------===// struct AsmParserState::Impl { + /// A map from a SymbolRefAttr to a range of uses. + using SymbolUseMap = DenseMap>; + + struct PartialOpDef { + PartialOpDef() : symbolTable(std::make_unique()) {} + PartialOpDef(const OperationName &opName) { + const auto *abstractOp = opName.getAbstractOperation(); + if (abstractOp && abstractOp->hasTrait()) + symbolTable = std::make_unique(); + } + + /// Return if this operation is a symbol table. + bool isSymbolTable() const { return symbolTable.get(); } + + /// If this operation is a symbol table, the following contains symbol uses + /// within this operation. + std::unique_ptr symbolTable; + }; + + /// Resolve any symbol table uses under the given partial operation. + void resolveSymbolUses(Operation *op, PartialOpDef &opDef); + /// A mapping from operations in the input source file to their parser state. SmallVector> operations; DenseMap operationToIdx; @@ -44,8 +67,38 @@ /// A set of value definitions that are placeholders for forward references. /// This map should be empty if the parser finishes successfully. DenseMap> placeholderValueUses; + + /// A stack of partial operation definitions that have been started but not + /// yet finalized. + SmallVector partialOperations; + + /// A stack of symbol use scopes. This is used when collecting symbol table + /// uses during parsing. + SmallVector symbolUseScopes; + + /// A symbol table containing all of the symbol table operations in the IR. + SymbolTableCollection symbolTable; }; +void AsmParserState::Impl::resolveSymbolUses(Operation *op, + PartialOpDef &opDef) { + assert(opDef.isSymbolTable() && "expected op to be a symbol table"); + + SmallVector symbolOps; + for (auto &it : *opDef.symbolTable) { + symbolOps.clear(); + if (failed(symbolTable.lookupSymbolIn(op, it.first.cast(), + symbolOps))) + continue; + + for (const auto &symIt : llvm::zip(symbolOps, it.second)) { + auto opIt = operationToIdx.find(std::get<0>(symIt)); + if (opIt != operationToIdx.end()) + operations[opIt->second]->symbolUses.push_back(std::get<1>(symIt)); + } + } +} + //===----------------------------------------------------------------------===// // AsmParserState //===----------------------------------------------------------------------===// @@ -77,17 +130,70 @@ //===----------------------------------------------------------------------===// // Populate State -void AsmParserState::addDefinition( - Operation *op, llvm::SMRange location, +void AsmParserState::initialize(Operation *topLevelOp) { + startOperationDefinition(topLevelOp->getName()); + + // If the top-level operation is a symbol table, push a new symbol scope. + Impl::PartialOpDef &partialOpDef = impl->partialOperations.back(); + if (partialOpDef.isSymbolTable()) + impl->symbolUseScopes.push_back(partialOpDef.symbolTable.get()); +} + +void AsmParserState::finalize(Operation *topLevelOp) { + assert(!impl->partialOperations.empty() && + "expected valid partial operation definition"); + Impl::PartialOpDef partialOpDef = impl->partialOperations.pop_back_val(); + + // If this operation is a symbol table, resolve any symbol uses. + if (partialOpDef.isSymbolTable()) + impl->resolveSymbolUses(topLevelOp, partialOpDef); +} + +void AsmParserState::startOperationDefinition(const OperationName &opName) { + impl->partialOperations.push_back(opName); +} + +void AsmParserState::finalizeOperationDefinition( + Operation *op, llvm::SMRange nameLoc, ArrayRef> resultGroups) { + assert(!impl->partialOperations.empty() && + "expected valid partial operation definition"); + Impl::PartialOpDef partialOpDef = impl->partialOperations.pop_back_val(); + + // Build the full operation definition. std::unique_ptr def = - std::make_unique(op, location); + std::make_unique(op, nameLoc); for (auto &resultGroup : resultGroups) def->resultGroups.emplace_back(resultGroup.first, convertIdLocToRange(resultGroup.second)); - impl->operationToIdx.try_emplace(op, impl->operations.size()); impl->operations.emplace_back(std::move(def)); + + // If this operation is a symbol table, resolve any symbol uses. + if (partialOpDef.isSymbolTable()) + impl->resolveSymbolUses(op, partialOpDef); +} + +void AsmParserState::startRegionDefinition() { + assert(!impl->partialOperations.empty() && + "expected valid partial operation definition"); + + // If the parent operation of this region is a symbol table, we also push a + // new symbol scope. + Impl::PartialOpDef &partialOpDef = impl->partialOperations.back(); + if (partialOpDef.isSymbolTable()) + impl->symbolUseScopes.push_back(partialOpDef.symbolTable.get()); +} + +void AsmParserState::finalizeRegionDefinition() { + assert(!impl->partialOperations.empty() && + "expected valid partial operation definition"); + + // If the parent operation of this region is a symbol table, pop the symbol + // scope for this region. + Impl::PartialOpDef &partialOpDef = impl->partialOperations.back(); + if (partialOpDef.isSymbolTable()) + impl->symbolUseScopes.pop_back(); } void AsmParserState::addDefinition(Block *block, llvm::SMLoc location) { @@ -169,6 +275,18 @@ def.definition.uses.push_back(convertIdLocToRange(loc)); } +void AsmParserState::addUses(SymbolRefAttr refAttr, + ArrayRef locations) { + // Ignore this symbol if no scopes are active. + if (impl->symbolUseScopes.empty()) + return; + + assert((refAttr.getNestedReferences().size() + 1) == locations.size() && + "expected the same number of references as provided locations"); + (*impl->symbolUseScopes.back())[refAttr].append(locations.begin(), + locations.end()); +} + void AsmParserState::refineDefinition(Value oldValue, Value newValue) { auto it = impl->placeholderValueUses.find(oldValue); assert(it != impl->placeholderValueUses.end() && diff --git a/mlir/lib/Parser/AttributeParser.cpp b/mlir/lib/Parser/AttributeParser.cpp --- a/mlir/lib/Parser/AttributeParser.cpp +++ b/mlir/lib/Parser/AttributeParser.cpp @@ -15,6 +15,7 @@ #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Dialect.h" #include "mlir/IR/IntegerSet.h" +#include "mlir/Parser/AsmParserState.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Endian.h" @@ -153,6 +154,13 @@ // Parse a symbol reference attribute. case Token::at_identifier: { + // When populating the parser state, this is a list of locations for all of + // the nested references. + SmallVector referenceLocations; + if (state.asmState) + referenceLocations.push_back(getToken().getLocRange()); + + // Parse the top-level reference. std::string nameStr = getToken().getSymbolReference(); consumeToken(Token::at_identifier); @@ -174,12 +182,21 @@ return Attribute(); } + // If we are populating the assembly state, add the location for this + // reference. + if (state.asmState) + referenceLocations.push_back(getToken().getLocRange()); + std::string nameStr = getToken().getSymbolReference(); consumeToken(Token::at_identifier); nestedRefs.push_back(SymbolRefAttr::get(getContext(), nameStr)); } + SymbolRefAttr symbolRefAttr = builder.getSymbolRefAttr(nameStr, nestedRefs); - return builder.getSymbolRefAttr(nameStr, nestedRefs); + // If we are populating the assembly state, record this symbol reference. + if (state.asmState) + state.asmState->addUses(symbolRefAttr, referenceLocations); + return symbolRefAttr; } // Parse a 'unit' attribute. diff --git a/mlir/lib/Parser/Parser.cpp b/mlir/lib/Parser/Parser.cpp --- a/mlir/lib/Parser/Parser.cpp +++ b/mlir/lib/Parser/Parser.cpp @@ -166,18 +166,12 @@ /// operations. class OperationParser : public Parser { public: - OperationParser(ParserState &state, Operation *topLevelOp) - : Parser(state), opBuilder(topLevelOp->getRegion(0)), - topLevelOp(topLevelOp) { - // The top level operation starts a new name scope. - pushSSANameScope(/*isIsolated=*/true); - } - + OperationParser(ParserState &state, Operation *topLevelOp); ~OperationParser(); /// After parsing is finished, this function must be called to see if there /// are any remaining issues. - ParseResult finalize(); + ParseResult finalize(Operation *topLevelOp); //===--------------------------------------------------------------------===// // SSA Value Handling @@ -281,7 +275,10 @@ bool isIsolatedNameScope = false); /// Parse a region body into 'region'. - ParseResult parseRegionBody(Region ®ion); + ParseResult + parseRegionBody(Region ®ion, llvm::SMLoc startLoc, + ArrayRef> entryArguments, + bool isIsolatedNameScope); //===--------------------------------------------------------------------===// // Block Parsing @@ -402,6 +399,17 @@ }; } // end anonymous namespace +OperationParser::OperationParser(ParserState &state, Operation *topLevelOp) + : Parser(state), opBuilder(topLevelOp->getRegion(0)), + topLevelOp(topLevelOp) { + // The top level operation starts a new name scope. + pushSSANameScope(/*isIsolated=*/true); + + // If we are populating the parser state, prepare it for parsing. + if (state.asmState) + state.asmState->initialize(topLevelOp); +} + OperationParser::~OperationParser() { for (auto &fwd : forwardRefPlaceholders) { // Drop all uses of undefined forward declared reference and destroy @@ -421,7 +429,7 @@ /// After parsing is finished, this function must be called to see if there are /// any remaining issues. -ParseResult OperationParser::finalize() { +ParseResult OperationParser::finalize(Operation *topLevelOp) { // Check for any forward references that are left. If we find any, error // out. if (!forwardRefPlaceholders.empty()) { @@ -458,6 +466,10 @@ opOrArgument.get().setLoc(locAttr); } + // If we are populating the parser state, finalize the top-level operation. + if (state.asmState) + state.asmState->finalize(topLevelOp); + // Pop the top level name scope. return popSSANameScope(); } @@ -809,7 +821,8 @@ asmResultGroups.emplace_back(resultIt, std::get<2>(record)); resultIt += std::get<1>(record); } - state.asmState->addDefinition(op, nameTok.getLocRange(), asmResultGroups); + state.asmState->finalizeOperationDefinition(op, nameTok.getLocRange(), + asmResultGroups); } // Add definitions for each of the result groups. @@ -824,7 +837,7 @@ // Add this operation to the assembly state if it was provided to populate. } else if (state.asmState) { - state.asmState->addDefinition(op, nameTok.getLocRange()); + state.asmState->finalizeOperationDefinition(op, nameTok.getLocRange()); } return success(); @@ -903,6 +916,10 @@ } } + // If we are populating the parser state, start a new operation definition. + if (state.asmState) + state.asmState->startOperationDefinition(result.name); + // Parse the operand list. SmallVector operandInfos; if (parseToken(Token::l_paren, "expected '(' to start operand list") || @@ -981,9 +998,19 @@ Operation *OperationParser::parseGenericOperation(Block *insertBlock, Block::iterator insertPt) { + Token nameToken = getToken(); + OpBuilder::InsertionGuard restoreInsertionPoint(opBuilder); opBuilder.setInsertionPoint(insertBlock, insertPt); - return parseGenericOperation(); + Operation *op = parseGenericOperation(); + if (!op) + return nullptr; + + // If we are populating the parser asm state, finalize this operation + // definition. + if (state.asmState) + state.asmState->finalizeOperationDefinition(op, nameToken.getLocRange()); + return op; } namespace { @@ -1367,6 +1394,14 @@ result = getBuilder().getStringAttr(atToken.getSymbolReference()); attrs.push_back(getBuilder().getNamedAttr(attrName, result)); parser.consumeToken(); + + // If we are populating the assembly parser state, record this as a symbol + // reference. + if (parser.getState().asmState) { + parser.getState().asmState->addUses( + getBuilder().getSymbolRefAttr(result.getValue()), + atToken.getLocRange()); + } return success(); } @@ -1858,9 +1893,13 @@ // Get location information for the operation. auto srcLocation = getEncodedSourceLocation(opLoc); + OperationState opState(srcLocation, opName); + + // If we are populating the parser state, start a new operation definition. + if (state.asmState) + state.asmState->startOperationDefinition(opState.name); // Have the op implementation take a crack and parsing this. - OperationState opState(srcLocation, opName); CleanupOpStateRegions guard{opState}; CustomOpAsmParser opAsmParser(opLoc, resultIDs, parseAssemblyFn, isIsolatedFromAbove, opName, *this); @@ -1931,10 +1970,6 @@ // Region Parsing //===----------------------------------------------------------------------===// -/// Region. -/// -/// region ::= '{' region-body -/// ParseResult OperationParser::parseRegion( Region ®ion, ArrayRef> entryArguments, @@ -1944,9 +1979,29 @@ if (parseToken(Token::l_brace, "expected '{' to begin a region")) return failure(); - // Check for an empty region. - if (entryArguments.empty() && consumeIf(Token::r_brace)) - return success(); + // If we are populating the parser state, start a new region definition. + if (state.asmState) + state.asmState->startRegionDefinition(); + + // Parse the region body. + if ((!entryArguments.empty() || getToken().isNot(Token::r_brace)) && + parseRegionBody(region, lBraceTok.getLoc(), entryArguments, + isIsolatedNameScope)) { + return failure(); + } + + // If we are populating the parser state, finalize this region. + if (state.asmState) + state.asmState->finalizeRegionDefinition(); + consumeToken(Token::r_brace); + + return success(); +} + +ParseResult OperationParser::parseRegionBody( + Region ®ion, llvm::SMLoc startLoc, + ArrayRef> entryArguments, + bool isIsolatedNameScope) { auto currentPt = opBuilder.saveInsertionPoint(); // Push a new named value scope. @@ -1960,7 +2015,7 @@ // now in the assembly state. Blocks with a name will be defined when the name // is parsed. if (state.asmState && getToken().isNot(Token::caret_identifier)) - state.asmState->addDefinition(block, lBraceTok.getLoc()); + state.asmState->addDefinition(block, startLoc); // Add arguments to the entry block. if (!entryArguments.empty()) { @@ -2002,8 +2057,12 @@ // Parse the rest of the region. region.push_back(owning_block.release()); - if (parseRegionBody(region)) - return failure(); + while (getToken().isNot(Token::r_brace)) { + Block *newBlock = nullptr; + if (parseBlock(newBlock)) + return failure(); + region.push_back(newBlock); + } // Pop the SSA value scope for this region. if (popSSANameScope()) @@ -2014,21 +2073,6 @@ return success(); } -/// Region. -/// -/// region-body ::= block* '}' -/// -ParseResult OperationParser::parseRegionBody(Region ®ion) { - // Parse the list of blocks. - while (!consumeIf(Token::r_brace)) { - Block *newBlock = nullptr; - if (parseBlock(newBlock)) - return failure(); - region.push_back(newBlock); - } - return success(); -} - //===----------------------------------------------------------------------===// // Block Parsing //===----------------------------------------------------------------------===// @@ -2278,7 +2322,7 @@ // If we got to the end of the file, then we're done. case Token::eof: { - if (opParser.finalize()) + if (opParser.finalize(topLevelOp.get())) return failure(); // Verify that the parsed operations are valid. diff --git a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp --- a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp +++ b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp @@ -21,7 +21,7 @@ std::pair lineAndCol = mgr.getLineAndColumn(loc); lsp::Position pos; pos.line = lineAndCol.first - 1; - pos.character = lineAndCol.second; + pos.character = lineAndCol.second - 1; return pos; } @@ -33,10 +33,7 @@ /// Returns a language server range for the given source range. static lsp::Range getRangeFromLoc(llvm::SourceMgr &mgr, llvm::SMRange range) { - // lsp::Range is an inclusive range, SMRange is half-open. - llvm::SMLoc inclusiveEnd = - llvm::SMLoc::getFromPointer(range.End.getPointer() - 1); - return {getPosFromLoc(mgr, range.Start), getPosFromLoc(mgr, inclusiveEnd)}; + return {getPosFromLoc(mgr, range.Start), getPosFromLoc(mgr, range.End)}; } /// Returns a language server location from the given source range. @@ -350,6 +347,12 @@ for (const auto &result : op.resultGroups) if (containsPosition(result.second)) return collectLocationsFromLoc(op.op->getLoc(), locations, uri); + for (const auto &symUse : op.symbolUses) { + if (contains(symUse, posLoc)) { + locations.push_back(getLocationFromLoc(sourceMgr, op.loc, uri)); + return collectLocationsFromLoc(op.op->getLoc(), locations, uri); + } + } } // Check all definitions related to blocks. @@ -380,11 +383,21 @@ if (contains(op.loc, posLoc)) { for (const auto &result : op.resultGroups) appendSMDef(result.second); + for (const auto &symUse : op.symbolUses) + if (contains(symUse, posLoc)) + references.push_back(getLocationFromLoc(sourceMgr, symUse, uri)); return; } for (const auto &result : op.resultGroups) if (isDefOrUse(result.second, posLoc)) return appendSMDef(result.second); + for (const auto &symUse : op.symbolUses) { + if (!contains(symUse, posLoc)) + continue; + for (const auto &symUse : op.symbolUses) + references.push_back(getLocationFromLoc(sourceMgr, symUse, uri)); + return; + } } // Check all definitions related to blocks. diff --git a/mlir/test/mlir-lsp-server/definition-split-file.test b/mlir/test/mlir-lsp-server/definition-split-file.test --- a/mlir/test/mlir-lsp-server/definition-split-file.test +++ b/mlir/test/mlir-lsp-server/definition-split-file.test @@ -25,7 +25,7 @@ // CHECK-NEXT: "line": 3 // CHECK-NEXT: }, // CHECK-NEXT: "start": { -// CHECK-NEXT: "character": 1, +// CHECK-NEXT: "character": 0, // CHECK-NEXT: "line": 3 // CHECK-NEXT: } // CHECK-NEXT: }, diff --git a/mlir/test/mlir-lsp-server/definition.test b/mlir/test/mlir-lsp-server/definition.test --- a/mlir/test/mlir-lsp-server/definition.test +++ b/mlir/test/mlir-lsp-server/definition.test @@ -22,13 +22,34 @@ // CHECK-NEXT: "line": 1 // CHECK-NEXT: }, // CHECK-NEXT: "start": { -// CHECK-NEXT: "character": 1, +// CHECK-NEXT: "character": 0, // CHECK-NEXT: "line": 1 // CHECK-NEXT: } // CHECK-NEXT: }, // CHECK-NEXT: "uri": "{{.*}}/foo.mlir" // CHECK-NEXT: } // ----- +{"jsonrpc":"2.0","id":2,"method":"textDocument/definition","params":{ + "textDocument":{"uri":"test:///foo.mlir"}, + "position":{"line":0,"character":7} +}} +// CHECK: "id": 2 +// CHECK-NEXT: "jsonrpc": "2.0", +// CHECK-NEXT: "result": [ +// CHECK-NEXT: { +// CHECK-NEXT: "range": { +// CHECK-NEXT: "end": { +// CHECK-NEXT: "character": 4, +// CHECK-NEXT: "line": 0 +// CHECK-NEXT: }, +// CHECK-NEXT: "start": { +// CHECK-NEXT: "character": 0, +// CHECK-NEXT: "line": 0 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "uri": "{{.*}}/foo.mlir" +// CHECK-NEXT: } +// ----- {"jsonrpc":"2.0","id":3,"method":"shutdown"} // ----- {"jsonrpc":"2.0","method":"exit"} diff --git a/mlir/test/mlir-lsp-server/hover.test b/mlir/test/mlir-lsp-server/hover.test --- a/mlir/test/mlir-lsp-server/hover.test +++ b/mlir/test/mlir-lsp-server/hover.test @@ -26,7 +26,7 @@ // CHECK-NEXT: "line": 1 // CHECK-NEXT: }, // CHECK-NEXT: "start": { -// CHECK-NEXT: "character": 10, +// CHECK-NEXT: "character": 9, // CHECK-NEXT: "line": 1 // CHECK-NEXT: } // CHECK-NEXT: } @@ -50,7 +50,7 @@ // CHECK-NEXT: "line": 1 // CHECK-NEXT: }, // CHECK-NEXT: "start": { -// CHECK-NEXT: "character": 1, +// CHECK-NEXT: "character": 0, // CHECK-NEXT: "line": 1 // CHECK-NEXT: } // CHECK-NEXT: } @@ -74,7 +74,7 @@ // CHECK-NEXT: "line": 3 // CHECK-NEXT: }, // CHECK-NEXT: "start": { -// CHECK-NEXT: "character": 1, +// CHECK-NEXT: "character": 0, // CHECK-NEXT: "line": 3 // CHECK-NEXT: } // CHECK-NEXT: } @@ -98,7 +98,7 @@ // CHECK-NEXT: "line": 0 // CHECK-NEXT: }, // CHECK-NEXT: "start": { -// CHECK-NEXT: "character": 11, +// CHECK-NEXT: "character": 10, // CHECK-NEXT: "line": 0 // CHECK-NEXT: } // CHECK-NEXT: } diff --git a/mlir/test/mlir-lsp-server/references.test b/mlir/test/mlir-lsp-server/references.test --- a/mlir/test/mlir-lsp-server/references.test +++ b/mlir/test/mlir-lsp-server/references.test @@ -23,7 +23,7 @@ // CHECK-NEXT: "line": 1 // CHECK-NEXT: }, // CHECK-NEXT: "start": { -// CHECK-NEXT: "character": 1, +// CHECK-NEXT: "character": 0, // CHECK-NEXT: "line": 1 // CHECK-NEXT: } // CHECK-NEXT: }, @@ -36,7 +36,7 @@ // CHECK-NEXT: "line": 2 // CHECK-NEXT: }, // CHECK-NEXT: "start": { -// CHECK-NEXT: "character": 8, +// CHECK-NEXT: "character": 7, // CHECK-NEXT: "line": 2 // CHECK-NEXT: } // CHECK-NEXT: }, @@ -44,6 +44,29 @@ // CHECK-NEXT: } // CHECK-NEXT: ] // ----- +{"jsonrpc":"2.0","id":2,"method":"textDocument/references","params":{ + "textDocument":{"uri":"test:///foo.mlir"}, + "position":{"line":0,"character":7}, + "context":{"includeDeclaration": false} +}} +// CHECK: "id": 2 +// CHECK-NEXT: "jsonrpc": "2.0", +// CHECK-NEXT: "result": [ +// CHECK-NEXT: { +// CHECK-NEXT: "range": { +// CHECK-NEXT: "end": { +// CHECK-NEXT: "character": 9, +// CHECK-NEXT: "line": 0 +// CHECK-NEXT: }, +// CHECK-NEXT: "start": { +// CHECK-NEXT: "character": 5, +// CHECK-NEXT: "line": 0 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "uri": "{{.*}}/foo.mlir" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// ----- {"jsonrpc":"2.0","id":3,"method":"shutdown"} // ----- {"jsonrpc":"2.0","method":"exit"}