Skip to content

Commit 20b1831

Browse files
committed
add precise typing for regular and overloaded definitions.
precise indicator context is added to the proto. precise mode is activated when precise typing is required, and disabled when there in no-longer an expression context (like Block stats).
1 parent da5b035 commit 20b1831

File tree

2 files changed

+78
-18
lines changed

2 files changed

+78
-18
lines changed

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,10 @@ object ProtoTypes {
328328
case class FunProto(args: List[untpd.Tree], resType: Type)(
329329
typer: Typer,
330330
override val applyKind: ApplyKind,
331-
state: FunProtoState = new FunProtoState,
332-
val constrainResultDeep: Boolean = false)(using protoCtx: Context)
331+
private var state: FunProtoState = new FunProtoState,
332+
val constrainResultDeep: Boolean = false,
333+
val argsPrecises: List[Boolean] = Nil
334+
)(using protoCtx: Context)
333335
extends UncachedGroundType with ApplyingProto with FunOrPolyProto {
334336
override def resultType(using Context): Type = resType
335337

@@ -341,17 +343,23 @@ object ProtoTypes {
341343
typer.isApplicableType(tp, args, resultType, keepConstraint && !args.exists(isPoly))
342344
}
343345

346+
def snapshot: FunProtoState = state
347+
def resetTo(prevState: FunProtoState): Unit = state = prevState
348+
344349
def derivedFunProto(
345350
args: List[untpd.Tree] = this.args,
346351
resultType: Type = this.resultType,
347352
typer: Typer = this.typer,
348-
constrainResultDeep: Boolean = this.constrainResultDeep): FunProto =
353+
constrainResultDeep: Boolean = this.constrainResultDeep,
354+
argsPrecises: List[Boolean] = this.argsPrecises
355+
): FunProto =
349356
if (args eq this.args)
350357
&& (resultType eq this.resultType)
351358
&& (typer eq this.typer)
352359
&& constrainResultDeep == this.constrainResultDeep
360+
&& argsPrecises == this.argsPrecises
353361
then this
354-
else new FunProto(args, resultType)(typer, applyKind, constrainResultDeep = constrainResultDeep)
362+
else new FunProto(args, resultType)(typer, applyKind, constrainResultDeep = constrainResultDeep, argsPrecises)
355363

356364
/** @return True if all arguments have types.
357365
*/
@@ -434,7 +442,9 @@ object ProtoTypes {
434442
val protoTyperState = ctx.typerState
435443
val oldConstraint = protoTyperState.constraint
436444
val args1 = args.mapWithIndexConserve((arg, idx) =>
437-
cacheTypedArg(arg, arg => typer.typed(norm(arg, idx)), force = false))
445+
val precise = if (argsPrecises.nonEmpty && argsPrecises(idx)) Mode.Precise else Mode.None
446+
withMode(precise){cacheTypedArg(arg, arg => typer.typed(norm(arg, idx)), force = false)}
447+
)
438448
val newConstraint = protoTyperState.constraint
439449

440450
if !args1.exists(arg => isUndefined(arg.tpe)) then state.typedArgs = args1
@@ -474,15 +484,21 @@ object ProtoTypes {
474484
* used to avoid repeated typings of trees when backtracking.
475485
*/
476486
def typedArg(arg: untpd.Tree, formal: Type)(using Context): Tree = {
477-
val wideFormal = formal.widenExpr
478-
val argCtx =
479-
if wideFormal eq formal then ctx
480-
else ctx.withNotNullInfos(ctx.notNullInfos.retractMutables)
481-
val locked = ctx.typerState.ownedVars
482-
val targ = cacheTypedArg(arg,
483-
typer.typedUnadapted(_, wideFormal, locked)(using argCtx),
484-
force = true)
485-
typer.adapt(targ, wideFormal, locked)
487+
val precise = formal match
488+
case v if v.isPrecise => Mode.Precise
489+
case _ if argsPrecises.nonEmpty && argsPrecises(args.indexOf(arg)) => Mode.Precise
490+
case _ => Mode.None
491+
withMode(precise) {
492+
val wideFormal = formal.widenExpr
493+
val argCtx =
494+
if wideFormal eq formal then ctx
495+
else ctx.withNotNullInfos(ctx.notNullInfos.retractMutables)
496+
val locked = ctx.typerState.ownedVars
497+
val targ = cacheTypedArg(arg,
498+
typer.typedUnadapted(_, wideFormal, locked)(using argCtx),
499+
force = true)
500+
typer.adapt(targ, wideFormal, locked)
501+
}
486502
}
487503

488504
/** The type of the argument `arg`, or `NoType` if `arg` has not been typed before

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
10611061

10621062
def typedBlock(tree: untpd.Block, pt: Type)(using Context): Tree = {
10631063
val (stats1, exprCtx) = withoutMode(Mode.Pattern) {
1064-
typedBlockStats(tree.stats)
1064+
// in all cases except a closure block, we disable precise type enforcement
1065+
tree.expr match
1066+
case _ : untpd.Closure => typedBlockStats(tree.stats)
1067+
case _ => withoutMode(Mode.Precise){ typedBlockStats(tree.stats) }
10651068
}
10661069
var expr1 = typedExpr(tree.expr, pt.dropIfProto)(using exprCtx)
10671070

@@ -1913,7 +1916,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
19131916
}
19141917

19151918
def typedInlined(tree: untpd.Inlined, pt: Type)(using Context): Tree = {
1916-
val (bindings1, exprCtx) = typedBlockStats(tree.bindings)
1919+
val (bindings1, exprCtx) = withoutMode(Mode.Precise){ typedBlockStats(tree.bindings) }
19171920
val expansion1 = typed(tree.expansion, pt)(using inlineContext(tree.call)(using exprCtx))
19181921
assignType(cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1),
19191922
bindings1, expansion1)
@@ -2686,7 +2689,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
26862689
pkg.moduleClass.info.decls.lookup(topLevelClassName).ensureCompleted()
26872690
var stats1 = typedStats(tree.stats, pkg.moduleClass)._1
26882691
if (!ctx.isAfterTyper)
2689-
stats1 = stats1 ++ typedBlockStats(MainProxies.proxies(stats1))._1
2692+
stats1 = stats1 ++ withoutMode(Mode.Precise){ typedBlockStats(MainProxies.proxies(stats1))._1 }
26902693
cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef)
26912694
}
26922695
case _ =>
@@ -3047,7 +3050,48 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
30473050
typed(tree, pt, locked)(using ctx.withSource(tree.source))
30483051
else if ctx.run.nn.isCancelled then
30493052
tree.withType(WildcardType)
3050-
else adapt(typedUnadapted(tree, pt, locked), pt, locked)
3053+
else
3054+
val tu = typedUnadapted(tree, pt, locked)
3055+
pt match
3056+
// If we are already in precise mode, then the arguments are already typed precisely,
3057+
// so there is no need for any additional logic.
3058+
case pt : FunProto if !ctx.mode.is(Mode.Precise) =>
3059+
extension (tpe: Type) def getArgsPrecises: List[Boolean] = tpe.widen match
3060+
case mt: MethodType => mt.paramInfos.map(_.isPrecise)
3061+
case pt: PolyType => pt.resType.getArgsPrecises
3062+
case _ => Nil
3063+
val argsPrecises = tu.tpe.getArgsPrecises
3064+
// if the function arguments are known to be precise, then we update the
3065+
// proto with this information and later propagate its state back to the
3066+
// original proto.
3067+
if (argsPrecises.contains(true))
3068+
val ptPrecises = pt.derivedFunProto(argsPrecises = argsPrecises)
3069+
val adapted = adapt(tu, ptPrecises, locked)
3070+
pt.resetTo(ptPrecises.snapshot)
3071+
adapted
3072+
// otherwise, we need to check for overloaded function, because in that
3073+
// case we may not know if the final adapted function will be precise.
3074+
else tu.tpe match
3075+
// the function is overloaded, so we preserve the typer and proto state,
3076+
// adapt the tree, and then check if the arguments should be considered as
3077+
// precise. if so, then we need to recover the typer state and proto
3078+
// state, and re-adapt the tree with the precise enforcement.
3079+
case v: TermRef if v.isOverloaded =>
3080+
val savedTyperState = ctx.typerState.snapshot()
3081+
val savedProtoState = pt.snapshot
3082+
val adaptedMaybePrecise = adapt(tu, pt, locked)
3083+
val argsPrecises = adaptedMaybePrecise.tpe.getArgsPrecises
3084+
if (argsPrecises.contains(true))
3085+
val ptPrecises = pt.derivedFunProto(argsPrecises = argsPrecises)
3086+
ctx.typerState.resetTo(savedTyperState)
3087+
pt.resetTo(savedProtoState)
3088+
val adapted = adapt(tu, ptPrecises, locked)
3089+
pt.resetTo(ptPrecises.snapshot)
3090+
adapted
3091+
else adaptedMaybePrecise
3092+
// the function is not overloaded or has precise arguments
3093+
case _ => adapt(tu, pt, locked)
3094+
case _ => adapt(tu, pt, locked)
30513095
}
30523096

30533097
def typed(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree =

0 commit comments

Comments
 (0)