-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[clang][AST] Do not try to handle irrelevant cases in writeBareSourceLocation #166588
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
@llvm/pr-subscribers-clang Author: SKill (SergejSalnikov) ChangesPatch is 89.62 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/166588.diff 2 Files Affected:
diff --git a/clang/include/clang/AST/JSONNodeDumper.h b/clang/include/clang/AST/JSONNodeDumper.h
index 427a9c51ece1b..d364795a05811 100644
--- a/clang/include/clang/AST/JSONNodeDumper.h
+++ b/clang/include/clang/AST/JSONNodeDumper.h
@@ -149,7 +149,7 @@ class JSONNodeDumper
void writeIncludeStack(PresumedLoc Loc, bool JustFirst = false);
// Writes the attributes of a SourceLocation object without.
- void writeBareSourceLocation(SourceLocation Loc, bool IsSpelling);
+ void writeBareSourceLocation(SourceLocation Loc);
// Writes the attributes of a SourceLocation to JSON based on its presumed
// spelling location. If the given location represents a macro invocation,
diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp
index 9f4dba9f14fa6..d364795a05811 100644
--- a/clang/lib/AST/JSONNodeDumper.cpp
+++ b/clang/lib/AST/JSONNodeDumper.cpp
@@ -1,1894 +1,464 @@
-#include "clang/AST/JSONNodeDumper.h"
+//===--- JSONNodeDumper.h - Printing of AST nodes to JSON -----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements AST dumping of components of individual AST nodes to
+// a JSON.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_AST_JSONNODEDUMPER_H
+#define LLVM_CLANG_AST_JSONNODEDUMPER_H
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTDumperUtils.h"
+#include "clang/AST/ASTNodeTraverser.h"
+#include "clang/AST/AttrVisitor.h"
+#include "clang/AST/CommentCommandTraits.h"
+#include "clang/AST/CommentVisitor.h"
+#include "clang/AST/ExprConcepts.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/Mangle.h"
#include "clang/AST/Type.h"
-#include "clang/Basic/SourceManager.h"
-#include "clang/Basic/Specifiers.h"
-#include "clang/Lex/Lexer.h"
-#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/JSON.h"
-using namespace clang;
+namespace clang {
-void JSONNodeDumper::addPreviousDeclaration(const Decl *D) {
- switch (D->getKind()) {
-#define DECL(DERIVED, BASE) \
- case Decl::DERIVED: \
- return writePreviousDeclImpl(cast<DERIVED##Decl>(D));
-#define ABSTRACT_DECL(DECL)
-#include "clang/AST/DeclNodes.inc"
-#undef ABSTRACT_DECL
-#undef DECL
- }
- llvm_unreachable("Decl that isn't part of DeclNodes.inc!");
-}
-
-void JSONNodeDumper::Visit(const Attr *A) {
- const char *AttrName = nullptr;
- switch (A->getKind()) {
-#define ATTR(X) \
- case attr::X: \
- AttrName = #X"Attr"; \
- break;
-#include "clang/Basic/AttrList.inc"
-#undef ATTR
- }
- JOS.attribute("id", createPointerRepresentation(A));
- JOS.attribute("kind", AttrName);
- JOS.attributeObject("range", [A, this] { writeSourceRange(A->getRange()); });
- attributeOnlyIfTrue("inherited", A->isInherited());
- attributeOnlyIfTrue("implicit", A->isImplicit());
-
- // FIXME: it would be useful for us to output the spelling kind as well as
- // the actual spelling. This would allow us to distinguish between the
- // various attribute syntaxes, but we don't currently track that information
- // within the AST.
- //JOS.attribute("spelling", A->getSpelling());
-
- InnerAttrVisitor::Visit(A);
-}
-
-void JSONNodeDumper::Visit(const Stmt *S) {
- if (!S)
- return;
-
- JOS.attribute("id", createPointerRepresentation(S));
- JOS.attribute("kind", S->getStmtClassName());
- JOS.attributeObject("range",
- [S, this] { writeSourceRange(S->getSourceRange()); });
-
- if (const auto *E = dyn_cast<Expr>(S)) {
- JOS.attribute("type", createQualType(E->getType()));
- const char *Category = nullptr;
- switch (E->getValueKind()) {
- case VK_LValue: Category = "lvalue"; break;
- case VK_XValue: Category = "xvalue"; break;
- case VK_PRValue:
- Category = "prvalue";
- break;
- }
- JOS.attribute("valueCategory", Category);
- }
- InnerStmtVisitor::Visit(S);
-}
-
-void JSONNodeDumper::Visit(const Type *T) {
- JOS.attribute("id", createPointerRepresentation(T));
-
- if (!T)
- return;
-
- JOS.attribute("kind", (llvm::Twine(T->getTypeClassName()) + "Type").str());
- JOS.attribute("type", createQualType(QualType(T, 0), /*Desugar=*/false));
- attributeOnlyIfTrue("containsErrors", T->containsErrors());
- attributeOnlyIfTrue("isDependent", T->isDependentType());
- attributeOnlyIfTrue("isInstantiationDependent",
- T->isInstantiationDependentType());
- attributeOnlyIfTrue("isVariablyModified", T->isVariablyModifiedType());
- attributeOnlyIfTrue("containsUnexpandedPack",
- T->containsUnexpandedParameterPack());
- attributeOnlyIfTrue("isImported", T->isFromAST());
- InnerTypeVisitor::Visit(T);
-}
-
-void JSONNodeDumper::Visit(QualType T) {
- JOS.attribute("id", createPointerRepresentation(T.getAsOpaquePtr()));
- JOS.attribute("kind", "QualType");
- JOS.attribute("type", createQualType(T));
- JOS.attribute("qualifiers", T.split().Quals.getAsString());
-}
-
-void JSONNodeDumper::Visit(TypeLoc TL) {
- if (TL.isNull())
- return;
- JOS.attribute("kind",
- (llvm::Twine(TL.getTypeLocClass() == TypeLoc::Qualified
- ? "Qualified"
- : TL.getTypePtr()->getTypeClassName()) +
- "TypeLoc")
- .str());
- JOS.attribute("type",
- createQualType(QualType(TL.getType()), /*Desugar=*/false));
- JOS.attributeObject("range",
- [TL, this] { writeSourceRange(TL.getSourceRange()); });
-}
-
-void JSONNodeDumper::Visit(const Decl *D) {
- JOS.attribute("id", createPointerRepresentation(D));
-
- if (!D)
- return;
-
- JOS.attribute("kind", (llvm::Twine(D->getDeclKindName()) + "Decl").str());
- JOS.attributeObject("loc",
- [D, this] { writeSourceLocation(D->getLocation()); });
- JOS.attributeObject("range",
- [D, this] { writeSourceRange(D->getSourceRange()); });
- attributeOnlyIfTrue("isImplicit", D->isImplicit());
- attributeOnlyIfTrue("isInvalid", D->isInvalidDecl());
-
- if (D->isUsed())
- JOS.attribute("isUsed", true);
- else if (D->isThisDeclarationReferenced())
- JOS.attribute("isReferenced", true);
-
- if (const auto *ND = dyn_cast<NamedDecl>(D))
- attributeOnlyIfTrue("isHidden", !ND->isUnconditionallyVisible());
-
- if (D->getLexicalDeclContext() != D->getDeclContext()) {
- // Because of multiple inheritance, a DeclContext pointer does not produce
- // the same pointer representation as a Decl pointer that references the
- // same AST Node.
- const auto *ParentDeclContextDecl = dyn_cast<Decl>(D->getDeclContext());
- JOS.attribute("parentDeclContextId",
- createPointerRepresentation(ParentDeclContextDecl));
- }
-
- addPreviousDeclaration(D);
- InnerDeclVisitor::Visit(D);
-}
-
-void JSONNodeDumper::Visit(const comments::Comment *C,
- const comments::FullComment *FC) {
- if (!C)
- return;
-
- JOS.attribute("id", createPointerRepresentation(C));
- JOS.attribute("kind", C->getCommentKindName());
- JOS.attributeObject("loc",
- [C, this] { writeSourceLocation(C->getLocation()); });
- JOS.attributeObject("range",
- [C, this] { writeSourceRange(C->getSourceRange()); });
-
- InnerCommentVisitor::visit(C, FC);
-}
-
-void JSONNodeDumper::Visit(const TemplateArgument &TA, SourceRange R,
- const Decl *From, StringRef Label) {
- JOS.attribute("kind", "TemplateArgument");
- if (R.isValid())
- JOS.attributeObject("range", [R, this] { writeSourceRange(R); });
-
- if (From)
- JOS.attribute(Label.empty() ? "fromDecl" : Label, createBareDeclRef(From));
-
- InnerTemplateArgVisitor::Visit(TA);
-}
-
-void JSONNodeDumper::Visit(const CXXCtorInitializer *Init) {
- JOS.attribute("kind", "CXXCtorInitializer");
- if (Init->isAnyMemberInitializer())
- JOS.attribute("anyInit", createBareDeclRef(Init->getAnyMember()));
- else if (Init->isBaseInitializer())
- JOS.attribute("baseInit",
- createQualType(QualType(Init->getBaseClass(), 0)));
- else if (Init->isDelegatingInitializer())
- JOS.attribute("delegatingInit",
- createQualType(Init->getTypeSourceInfo()->getType()));
- else
- llvm_unreachable("Unknown initializer type");
-}
-
-void JSONNodeDumper::Visit(const OpenACCClause *C) {}
-
-void JSONNodeDumper::Visit(const OMPClause *C) {}
-
-void JSONNodeDumper::Visit(const BlockDecl::Capture &C) {
- JOS.attribute("kind", "Capture");
- attributeOnlyIfTrue("byref", C.isByRef());
- attributeOnlyIfTrue("nested", C.isNested());
- if (C.getVariable())
- JOS.attribute("var", createBareDeclRef(C.getVariable()));
-}
-
-void JSONNodeDumper::Visit(const GenericSelectionExpr::ConstAssociation &A) {
- JOS.attribute("associationKind", A.getTypeSourceInfo() ? "case" : "default");
- attributeOnlyIfTrue("selected", A.isSelected());
-}
-
-void JSONNodeDumper::Visit(const concepts::Requirement *R) {
- if (!R)
- return;
-
- switch (R->getKind()) {
- case concepts::Requirement::RK_Type:
- JOS.attribute("kind", "TypeRequirement");
- break;
- case concepts::Requirement::RK_Simple:
- JOS.attribute("kind", "SimpleRequirement");
- break;
- case concepts::Requirement::RK_Compound:
- JOS.attribute("kind", "CompoundRequirement");
- break;
- case concepts::Requirement::RK_Nested:
- JOS.attribute("kind", "NestedRequirement");
- break;
- }
-
- if (auto *ER = dyn_cast<concepts::ExprRequirement>(R))
- attributeOnlyIfTrue("noexcept", ER->hasNoexceptRequirement());
-
- attributeOnlyIfTrue("isDependent", R->isDependent());
- if (!R->isDependent())
- JOS.attribute("satisfied", R->isSatisfied());
- attributeOnlyIfTrue("containsUnexpandedPack",
- R->containsUnexpandedParameterPack());
-}
-
-void JSONNodeDumper::Visit(const APValue &Value, QualType Ty) {
- std::string Str;
- llvm::raw_string_ostream OS(Str);
- Value.printPretty(OS, Ctx, Ty);
- JOS.attribute("value", Str);
-}
-
-void JSONNodeDumper::Visit(const ConceptReference *CR) {
- JOS.attribute("kind", "ConceptReference");
- JOS.attribute("id", createPointerRepresentation(CR->getNamedConcept()));
- if (const auto *Args = CR->getTemplateArgsAsWritten()) {
- JOS.attributeArray("templateArgsAsWritten", [Args, this] {
- for (const TemplateArgumentLoc &TAL : Args->arguments())
- JOS.object(
- [&TAL, this] { Visit(TAL.getArgument(), TAL.getSourceRange()); });
- });
- }
- JOS.attributeObject("loc",
- [CR, this] { writeSourceLocation(CR->getLocation()); });
- JOS.attributeObject("range",
- [CR, this] { writeSourceRange(CR->getSourceRange()); });
-}
-
-void JSONNodeDumper::writeIncludeStack(PresumedLoc Loc, bool JustFirst) {
- if (Loc.isInvalid())
- return;
-
- JOS.attributeBegin("includedFrom");
- JOS.objectBegin();
-
- if (!JustFirst) {
- // Walk the stack recursively, then print out the presumed location.
- writeIncludeStack(SM.getPresumedLoc(Loc.getIncludeLoc()));
- }
-
- JOS.attribute("file", Loc.getFilename());
- JOS.objectEnd();
- JOS.attributeEnd();
-}
-
-void JSONNodeDumper::writeBareSourceLocation(SourceLocation Loc,
- bool IsSpelling) {
- PresumedLoc Presumed = SM.getPresumedLoc(Loc);
- unsigned ActualLine = IsSpelling ? SM.getSpellingLineNumber(Loc)
- : SM.getExpansionLineNumber(Loc);
- StringRef ActualFile = SM.getBufferName(Loc);
-
- if (Presumed.isValid()) {
- JOS.attribute("offset", SM.getDecomposedLoc(Loc).second);
- if (LastLocFilename != ActualFile) {
- JOS.attribute("file", ActualFile);
- JOS.attribute("line", ActualLine);
- } else if (LastLocLine != ActualLine)
- JOS.attribute("line", ActualLine);
-
- StringRef PresumedFile = Presumed.getFilename();
- if (PresumedFile != ActualFile && LastLocPresumedFilename != PresumedFile)
- JOS.attribute("presumedFile", PresumedFile);
-
- unsigned PresumedLine = Presumed.getLine();
- if (ActualLine != PresumedLine && LastLocPresumedLine != PresumedLine)
- JOS.attribute("presumedLine", PresumedLine);
-
- JOS.attribute("col", Presumed.getColumn());
- JOS.attribute("tokLen",
- Lexer::MeasureTokenLength(Loc, SM, Ctx.getLangOpts()));
- LastLocFilename = ActualFile;
- LastLocPresumedFilename = PresumedFile;
- LastLocPresumedLine = PresumedLine;
- LastLocLine = ActualLine;
-
- // Orthogonal to the file, line, and column de-duplication is whether the
- // given location was a result of an include. If so, print where the
- // include location came from.
- writeIncludeStack(SM.getPresumedLoc(Presumed.getIncludeLoc()),
- /*JustFirst*/ true);
- }
-}
-
-void JSONNodeDumper::writeSourceLocation(SourceLocation Loc) {
- SourceLocation Spelling = SM.getSpellingLoc(Loc);
- SourceLocation Expansion = SM.getExpansionLoc(Loc);
-
- if (Expansion != Spelling) {
- // If the expansion and the spelling are different, output subobjects
- // describing both locations.
- JOS.attributeObject("spellingLoc", [Spelling, this] {
- writeBareSourceLocation(Spelling, /*IsSpelling*/ true);
- });
- JOS.attributeObject("expansionLoc", [Expansion, Loc, this] {
- writeBareSourceLocation(Expansion, /*IsSpelling*/ false);
- // If there is a macro expansion, add extra information if the interesting
- // bit is the macro arg expansion.
- if (SM.isMacroArgExpansion(Loc))
- JOS.attribute("isMacroArgExpansion", true);
- });
- } else
- writeBareSourceLocation(Spelling, /*IsSpelling*/ true);
-}
-
-void JSONNodeDumper::writeSourceRange(SourceRange R) {
- JOS.attributeObject("begin",
- [R, this] { writeSourceLocation(R.getBegin()); });
- JOS.attributeObject("end", [R, this] { writeSourceLocation(R.getEnd()); });
-}
-
-std::string JSONNodeDumper::createPointerRepresentation(const void *Ptr) {
- // Because JSON stores integer values as signed 64-bit integers, trying to
- // represent them as such makes for very ugly pointer values in the resulting
- // output. Instead, we convert the value to hex and treat it as a string.
- return "0x" + llvm::utohexstr(reinterpret_cast<uint64_t>(Ptr), true);
-}
-
-llvm::json::Object JSONNodeDumper::createQualType(QualType QT, bool Desugar) {
- SplitQualType SQT = QT.split();
- std::string SQTS = QualType::getAsString(SQT, PrintPolicy);
- llvm::json::Object Ret{{"qualType", SQTS}};
-
- if (Desugar && !QT.isNull()) {
- SplitQualType DSQT = QT.getSplitDesugaredType();
- if (DSQT != SQT) {
- std::string DSQTS = QualType::getAsString(DSQT, PrintPolicy);
- if (DSQTS != SQTS)
- Ret["desugaredQualType"] = DSQTS;
- }
- if (const auto *TT = QT->getAs<TypedefType>())
- Ret["typeAliasDeclId"] = createPointerRepresentation(TT->getDecl());
- }
- return Ret;
-}
-
-void JSONNodeDumper::writeBareDeclRef(const Decl *D) {
- JOS.attribute("id", createPointerRepresentation(D));
- if (!D)
- return;
-
- JOS.attribute("kind", (llvm::Twine(D->getDeclKindName()) + "Decl").str());
- if (const auto *ND = dyn_cast<NamedDecl>(D))
- JOS.attribute("name", ND->getDeclName().getAsString());
- if (const auto *VD = dyn_cast<ValueDecl>(D))
- JOS.attribute("type", createQualType(VD->getType()));
-}
-
-llvm::json::Object JSONNodeDumper::createBareDeclRef(const Decl *D) {
- llvm::json::Object Ret{{"id", createPointerRepresentation(D)}};
- if (!D)
- return Ret;
-
- Ret["kind"] = (llvm::Twine(D->getDeclKindName()) + "Decl").str();
- if (const auto *ND = dyn_cast<NamedDecl>(D))
- Ret["name"] = ND->getDeclName().getAsString();
- if (const auto *VD = dyn_cast<ValueDecl>(D))
- Ret["type"] = createQualType(VD->getType());
- return Ret;
-}
-
-llvm::json::Array JSONNodeDumper::createCastPath(const CastExpr *C) {
- llvm::json::Array Ret;
- if (C->path_empty())
- return Ret;
-
- for (auto I = C->path_begin(), E = C->path_end(); I != E; ++I) {
- const CXXBaseSpecifier *Base = *I;
- const auto *RD = cast<CXXRecordDecl>(
- Base->getType()->castAsCanonical<RecordType>()->getDecl());
-
- llvm::json::Object Val{{"name", RD->getName()}};
- if (Base->isVirtual())
- Val["isVirtual"] = true;
- Ret.push_back(std::move(Val));
- }
- return Ret;
-}
+class APValue;
-#define FIELD2(Name, Flag) if (RD->Flag()) Ret[Name] = true
-#define FIELD1(Flag) FIELD2(#Flag, Flag)
+class NodeStreamer {
+ bool FirstChild = true;
+ bool TopLevel = true;
+ llvm::SmallVector<std::function<void(bool IsLastChild)>, 32> Pending;
-static llvm::json::Object
-createDefaultConstructorDefinitionData(const CXXRecordDecl *RD) {
- llvm::json::Object Ret;
+protected:
+ llvm::json::OStream JOS;
- FIELD2("exists", hasDefaultConstructor);
- FIELD2("trivial", hasTrivialDefaultConstructor);
- FIELD2("nonTrivial", hasNonTrivialDefaultConstructor);
- FIELD2("userProvided", hasUserProvidedDefaultConstructor);
- FIELD2("isConstexpr", hasConstexprDefaultConstructor);
- FIELD2("needsImplicit", needsImplicitDefaultConstructor);
- FIELD2("defaultedIsConstexpr", defaultedDefaultConstructorIsConstexpr);
-
- return Ret;
-}
-
-static llvm::json::Object
-createCopyConstructorDefinitionData(const CXXRecordDecl *RD) {
- llvm::json::Object Ret;
-
- FIELD2("simple", hasSimpleCopyConstructor);
- FIELD2("trivial", hasTrivialCopyConstructor);
- FIELD2("nonTrivial", hasNonTrivialCopyConstructor);
- FIELD2("userDeclared", hasUserDeclaredCopyConstructor);
- FIELD2("hasConstParam", hasCopyConstructorWithConstParam);
- FIELD2("implicitHasConstParam", implicitCopyConstructorHasConstParam);
- FIELD2("needsImplicit", needsImplicitCopyConstructor);
- FIELD2("needsOverloadResolution", needsOverloadResolutionForCopyConstructor);
- if (!RD->needsOverloadResolutionForCopyConstructor())
- FIELD2("defaultedIsDeleted", defaultedCopyConstructorIsDeleted);
-
- return Ret;
-}
-
-static llvm::json::Object
-createMoveConstructorDefinitionData(const CXXRecordDecl *RD) {
- llvm::json::Object Ret;
-
- FIELD2("exists", hasMoveConstructor);
- FIELD2("simple", hasSimpleMoveConstructor);
- FIELD2("trivial", hasTrivialMoveConstructor);
- FIELD2("nonTrivial", hasNonTrivialMoveConstructor);
- FIELD2("userDeclared", hasUserDeclaredMoveConstructor);
- FIELD2("needsImplicit", needsImplicitMoveConstructor);
- FIELD2("needsOverloadResolution", needsOverloadResolutionForMoveConstructor);
- if (!RD->needsOverloadResolutionForMoveConstructor())
- FIELD2("defaultedIsDeleted", defaultedMoveConstructorIsDeleted);
-
- return Ret;
-}
-
-static llvm::json::Object
-createCopyAssignmentDefinitionData(const CXXRecordDecl *RD) {
- llvm::json::Object Ret;
-
- FIELD2("simple", hasSimpleCopyAssignment);
- FIELD2("trivial", hasTrivialCopyAssignment);
- FIELD2("nonTrivial", hasNonTrivialCopyAssignment);
- FIELD2("hasConstParam", hasCopyAssignmentWithConstParam);
- FIELD2("implicitHasConstParam", implicitCopyAssignmentHasConstParam);
- FIELD2("userDeclared", hasUserDeclaredCopyAssignment);
- FIELD2("needsImplicit", needsImplicitCopyAssignment);
- FIELD2("needsOverloadResolution", needsOverloadResolutionForCopyAssignment);
-
- return Ret;
-}
-
-static llvm::json::Object
-createMoveAssignmentDefinitionData(const CXXRecordDecl *RD) {
- llvm::json::Object Ret;
-
- FIELD2("exists", hasMoveAssignment);
- FIELD2("simple", hasSimpleMoveAssignment);
- FIELD2("trivial", hasTrivialMoveAssignment);
- FIELD2("nonTrivial", hasNonTrivialMoveAssignment);
- FIELD2("userDeclared", hasUserDeclaredMoveAssignment);
- FIELD2("needsImplicit", needsImplicitMoveAssignment);
- FIELD2("needsOverloadResolution", needsOverloadResolutionForMoveAssignment);
-
- return Ret;
-}
-
-static llvm::json::Object
-createDestructorDefinitionData(const C...
[truncated]
|
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, could you please give a bit more context, i.e. perhaps explain what is going on and why the cases are impossible? It would be great to add this info to the PR description.
PTAL. |
AaronBallman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, good catch!
…Location (llvm#166588) `writeBareSourceLocation` is always called on either `Expanded` or `Spelling` location, in any on those cases the `SM.getSpellingLineNumber(Loc) == SM.getExpansionLineNumber(Loc) == SM.getLineNumber(Loc)`.
writeBareSourceLocationis always called on eitherExpandedorSpellinglocation, in any on those cases theSM.getSpellingLineNumber(Loc) == SM.getExpansionLineNumber(Loc) == SM.getLineNumber(Loc).