-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Stale symbol crash #17294
Comments
Hello,
dotty.tools.dotc.core.Denotations$StaleSymbol: stale symbol; method definition$lzyINIT2#52624 in class Generator, defined in Period(76..93, run = 2), is referred to in run Period(92..92, run = 3) | => root / Compile / compileIncremental 1s
at dotty.tools.dotc.core.Denotations$SingleDenotation.staleSymbolError(Denotations.scala:948)
at dotty.tools.dotc.core.Denotations$SingleDenotation.bringForward(Denotations.scala:752)
at dotty.tools.dotc.core.Denotations$SingleDenotation.toNewRun$1(Denotations.scala:799)
at dotty.tools.dotc.core.Denotations$SingleDenotation.current(Denotations.scala:870)
at dotty.tools.dotc.core.Symbols$Symbol.recomputeDenot(Symbols.scala:120)
at dotty.tools.dotc.core.Symbols$Symbol.computeDenot(Symbols.scala:114)
at dotty.tools.dotc.core.Symbols$Symbol.denot(Symbols.scala:107)
at dotty.tools.dotc.core.Symbols$Symbol.name(Symbols.scala:260)
at dotty.tools.dotc.core.SymDenotations$ClassDenotation.computeMemberNames$$anonfun$2(SymDenotations.scala:2328)
ObservationsNo metaprogrammingCode is difficult to minimize, however metaprogramming or quotes do not seem to be involved. Apparent workaroundThe faulty symbol object definition extends Definition:
... Changing the line for the declaration below acts as a work-around. val definition = new Definition:
... Caching IssueRunning the sbt build a second time succeeds after the crash.
Hope this helps. |
#18077) Closes #17152 Closes #17294 In general the issues stemmed from the fact that after suspending runs due to the found macros, when typing Ident of the called method, symbols from the previous run are found there. Some of them either are able to have their validity updated while returning the denotation (which those path dependent types are unable to do, since their owners now have updated decls, which do not include the stale symbol), or do not need denotation as part of a code path they rely on. The fixes simply check if the to-be-used symbol has a valid runID, and if not then recomputes it. The first commit fixes the minimizations from above GitHub issues. Both minimizations by design have to result in cyclic macro errors (which they now do), so they were placed in `neg-macros` tests. By some experimentation, I ended up with another, slightly different stale symbol crash, with the stale symbol trying to create a denotation in different place (in `withPrefix`), requiring an additional fix there, included in the second commit. This minimization, unlike the previous ones, does compile successfully, without cyclic macro errors, which shows the issue was not exclusive to those.
Still seeing this on 3.4.2 My repro is very complicated involving macros unfortunately, but I am seeing the same symptoms as #17294 (comment): it happens with a top level object and goes away when either making it a top level val or on the second compile. |
Hey, Here is the code of the macro: import scala.quoted.*
/**
* Scala 3 macro which finds all objects that inherit from a given type T as a compile-time / static metaprogramming operation.
*
* Example usage:
* import com.netflix.measures.library.macros.ObjectInheritanceFinder.findAllInheritingObjects
* val measureParsers: Set[MeasureParser] = findAllInheritingObjects[MeasureParser]
*/
object ObjectInheritanceFinder {
inline def findAllInheritingObjects[T]: Set[T] = ${ findAllInheritingObjectsImpl[T] }
private def findAllInheritingObjectsImpl[T: Type](using Quotes): Expr[Set[T]] = {
// Context-sensitive reflection import is tied to specific instance of Quotes that's created for each specific macro invocation
import quotes.reflect._
// Get the TypeRepr of the type parameter T to be used for inheritance checks
val baseType = TypeRepr.of[T]
/**
* Check if the symbol is an object and a subtype of baseType.
*
* @param sym The symbol to check.
* @return True if the symbol is an object and a subtype of baseType, otherwise false.
*/
def isObjectInheritingFromBaseType(sym: Symbol): Boolean =
// All symbols are terms or types, and some types throw when checking sym.flags.is, so this check is necessary before it is called
sym.isTerm &&
// flags.is(Flags.Module) is equivalent to checking if the symbol is an object
sym.flags.is(Flags.Module) &&
// termRef <:< baseType is equivalent to checking if the symbol is a subtype of baseType
sym.termRef <:< baseType
/**
* Recursively traverse a package and its subpackages to find objects that are subtypes of baseType.
*
* We convert to a Set because there are no guarantees that the same object won't be referenced in multiple packages.
*
* @param pkg The package symbol to start the search from.
* @return A sequence of fully qualified names representing the inheriting objects.
*/
def findAllInheritingObjectsInPackage(pkg: Symbol): Set[String] =
pkg.declarations.flatMap {
case sym if sym.isPackageDef => findAllInheritingObjectsInPackage(sym) // Recurse
case sym if isObjectInheritingFromBaseType(sym) => Some(sym.fullName) // Include
case _ => None // Skip
}.toSet
// Root package symbol
val packageSymbol: Symbol = Symbol.requiredPackage("com.netflix.measures.library")
// Find all objects that inherit from T starting from the root package
val inheritingObjects: Set[Expr[T]] = findAllInheritingObjectsInPackage(packageSymbol)
// Map fully qualified names to Expr[T]
.map(x => Ref(Symbol.requiredModule(x)).asExprOf[T])
// Convert Set[Expr[T]] to Expr[Set[T]]
'{ Set(${ Expr.ofSeq(inheritingObjects.toSeq) }*) }
}
} Anything wrong with it? |
If I add def isObjectInheritingFromBaseType(sym: Symbol): Boolean =
// All symbols are terms or types, and some types throw when checking sym.flags.is, so this check is necessary before it is called
sym.isTerm &&
// Check if the symbol is the object instance (as opposed to the type definition)
sym.isValDef &&
// Check if the symbol is an object
sym.flags.is(Flags.Module) &&
// Check if the symbol is a subtype of baseType
sym.termRef <:< baseType |
Hi @joan38. Usually with stale symbol crashes the actual macro implementation tends to not matter as much as the place where that macro is called. When we compile multiple files at once and one of them is found to call a simultaneously compiled macro method, we have to pause compilation of that file, finish compilation of the macro implementation, and return to compiling the previous file. This back and forth can cause stale symbol issues. For this reason I am not able to help You without a proper minimization, with the macro call included. The stack trace might help too - stale symbol crashes can have distinct causes and fixes, so I might be able to see this way if this is something that was being fixed before |
That explains why it was flaky and now works. Thanks for explanation! |
Could be related somehow to #17152, but this also occurs for older compiler versions and this minimization is far simpler.
It may be that this should just yield a cyclic dependency error instead of a crash. I'm not sure what the cross-file macro dependency limitations are.
Compiler version
v3.2.2
Also tested on the latest nightly: scala-3.3.1-RC1-bin-20230416-2f4cc4c-NIGHTLY
Minimized code
DFVal.scala
Width.scala
Output (click arrow to expand)
The text was updated successfully, but these errors were encountered: