I don't know what to add other than the one data point that I use Stack exclusively to build Hadrian.
I get the same output (with quux2 = []
) for this program:
module NoInline (quux2) where
{-# OPAQUE quux1 #-}
quux1 :: [v]
quux1 = mempty
quux2 :: [String]
quux2 = quux1
FWIW I've noticed this on the 9.8 branch but haven't tried other branches yet.
Given the following program:
module NoInline (quux2) where
{-# NOINLINE quux1 #-}
quux1 :: [v]
quux1 = mempty
quux2 :: [String]
quux2 = quux1
Compiling this with -fno-ignore-interface-pragmas
, I see that quux1
is not inlined into quux2
up until Tidy
:
$ ./_build/ghc-stage1 -fforce-recomp -dno-typeable-binds input/NoInline.hs -ddump-prep -ddump-simpl -dverbose-core2core -fno-ignore-interface-pragmas
...
==================== Simplifier ====================
Max iterations = 4
SimplMode {Phase = FinalPhase [final],
inline,
no rules,
eta-expand,
cast-swizzle,
case-of-case}
Result size of Simplifier
= {terms: 4, types: 6, coercions: 0, joins: 0/0}
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
quux1 [InlPrag=NOINLINE] :: forall v. [v]
[LclId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=ALWAYS_IF(arity=0,unsat_ok=True,boring_ok=True)}]
quux1 = GHC.Types.[]
-- RHS size: {terms: 1, types: 1, coercions: 0, joins: 0/0}
quux2 :: [String]
[LclIdX,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=ALWAYS_IF(arity=0,unsat_ok=True,boring_ok=True)}]
quux2 = quux1 @String
==================== Tidy Core ====================
Result size of Tidy Core
= {terms: 4, types: 6, coercions: 0, joins: 0/0}
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
quux1_rh1 :: forall v. [v]
[GblId, Unf=OtherCon []]
quux1_rh1 = GHC.Types.[]
-- RHS size: {terms: 1, types: 1, coercions: 0, joins: 0/0}
quux2 :: [String]
[GblId, Unf=OtherCon []]
quux2 = quux1_rh1 @String
But then Prep
inlines quux1
into quux2
:
==================== CorePrep ====================
Result size of CorePrep
= {terms: 2, types: 3, coercions: 0, joins: 0/0}
-- RHS size: {terms: 1, types: 1, coercions: 0, joins: 0/0}
NoInline.quux2 :: [GHC.Base.String]
[GblId, Unf=OtherCon []]
NoInline.quux2 = GHC.Types.[] @GHC.Base.String
Note that the Tidy
output doesn't contain the NOINLINE
pragma on quux1
anymore, but also it doesn't have an unfolding...
Is this all intentional? As a library user who cares about the Prep
output and can observe the difference between inlining or not inlining quux1
, is there a way to avoid this behaviour?
unless (isDoExpansionGenerated origin) -- Generated code shouldn't emit overlapping warnings
IIUC one of the contributions of this MR is a complete story for marking generated parts of the AST for proper error messages during typechecking. So what does HsIf
bring to the table anymore after renaming, compared to just always expanding into a function, that function being either user-supplied (in the RebindableSyntax
case) or some wired-in ifThenElse :: Bool -> a -> a -> a
?
I would like to see an example here that is just a bit more complex, e.g. do { e1; e2; e3 }
so that I can see if there's only an ExpandedThingRn
for the whole triad, or whether the second e2; e3
part gets its own ExpandedThingRn
as well.
The 3 non-obvious points to consider are:
1. Wrap the expression with a `fail` block if the pattern match is not irrefutable.
See Part 1. below
2. Generate appropriate warnings for discarded results in a body statement
eg. say `do { .. ; (g p :: m Int) ; ... }`
See Part 2. below
3. Generating appropriate type error messages which blame the correct source spans
See Part 3. below
Gergő Érdi (0ff1c501) at 13 Oct 06:59
JS: remove js_broken(22576) in favour of the pre-existing wordsize(...
... and 282 more commits
A typeclass instance that uses a type family is currently rejected unconditionally by GHC. But I believe there are special cases where it should be fine to accept them.
The basic idea is to think about the simplest possible case: if the type family is closed and the application is completely monomorphic. In this case, writing something like instance C (F Int Bool) where ...
should really mean exactly the same as if F Int Bool
was manually expanded by the user.
But I think we can go further, by classifying each type family parameter as whether the type family is parametric in that parameter. So something like
type family F a b where
F Int b = Maybe b
F Bool b = [b]
is not parametric in a
, but is parametric in b
. And so an instance instance C (F Int x)
could, again, be expanded by the user manually into instance C (Maybe x)
, so why not offer to do it for them.
For open type families, we can't mark any parameter as parametric, but it should still be fine to allow the degenerate case of fully monomorphic applications; after all, GHC already rejects conflicting type family instance declarations.
Ping @RossPaterson
Given these two modules:
module A(Foo(..)) where
data Foo = Foo1 | Foo2 | Foo3
{-# LANGUAGE DataKinds, TypeData #-}
module B(Foo(Foo1, Foo2)) where
import qualified A
type data Foo = Foo1 | Foo2 | Foo3
There should be no confusion in what the Foo1
and Foo2
refers to in the export spec of B
, since A
is imported qualified. And yet as of b8ebf876 GHC produces the following error message:
input/type-data-export/B.hs:2:10: error: [GHC-88993]
• The type constructor ‘Foo’ is not the parent of the data constructor ‘Foo2’.
Data constructors can only be exported with their parent type constructor.
Parent: A.Foo
• In the export: Foo(Foo1, Foo2)
|
2 | module B(Foo(Foo1, Foo2)) where
| ^^^^^^^^^^^^^^^
input/type-data-export/B.hs:2:10: error: [GHC-88993]
• The type constructor ‘Foo’ is not the parent of the data constructor ‘Foo1’.
Data constructors can only be exported with their parent type constructor.
Parent: A.Foo
• In the export: Foo(Foo1, Foo2)
|
2 | module B(Foo(Foo1, Foo2)) where
| ^^^^^^^^^^^^^^^
The following defaulting plugin trys to default everything to Int
:
module GHC.Defaulting.INTerference (plugin) where
import GHC.Driver.Plugins
import GHC.Tc.Plugin
import GHC.Tc.Types
import GHC.Types.Var
import GHC.Tc.Utils.TcType
import GHC.Tc.Types.Constraint
import GHC.Core.Predicate
import GHC.Tc.Solver
import GHC.Core.Type
import GHC.Core.Class
import GHC.Data.Bag
import GHC.Builtin.Types (intTy)
plugin :: Plugin
plugin = defaultPlugin
{ defaultingPlugin = \_ -> Just DefaultingPlugin
{ dePluginInit = pure ()
, dePluginRun = \ _ -> fill
, dePluginStop = \ _ -> pure ()
}
}
fill :: WantedConstraints -> TcPluginM [DefaultingProposal]
fill wanteds = pure
[ DefaultingProposal tv [intTy] [ct]
| ct <- bagToList $ approximateWC True wanteds
, Just (cls, tys) <- pure $ getClassPredTys_maybe (ctPred ct)
, [ty] <- pure $ filterOutInvisibleTypes (classTyCon cls) tys
, Just tv <- pure $ getTyVar_maybe ty
, isMetaTyVar tv
]
We can try it out with a small example program:
{-# OPTIONS_GHC -fplugin GHC.Defaulting.INTerference #-}
{-# LANGUAGE ExtendedDefaultRules #-}
class IsColor a where
op :: a -> ()
instance IsColor (Int, Int, Int) where
op _ = ()
main :: IO ()
main = pure $ op (1, 2, 3)
What happens here is that the plugin is called on Num a, Num b, Num c => (a, b, c)
(the so-far inferred type of (1, 2, 3)
) and it proposes a ~ Int, b ~ Int, c ~ Int
(with the appropriate Num
constraints). Since the Num
constraints are trivally fulfillable, GHC then accepts these proposals, and thus writes a := Int, b := Int, c := Int
. However, it then still tries to run the built-in defaulting mechanism, which proposes a ~ Integer
, resulting in the double-write a := Integer
.
We can see this by building a -DDEBUG
version of GHC (e.g. --flavour=devel2
) and looking at the relevant parts of the -ddump-tc-trace
output:
defaultingPlugins {
WC {wc_simple =
[W] $dIsColor_aGj {0}:: IsColor
(a_aGl[tau:0], b_aGm[tau:0], c_aGn[tau:0]) (CDictCan)
[W] $dNum_aJL {0}:: Num a_aGl[tau:0] (CDictCan)
[W] $dNum_aJO {0}:: Num b_aGm[tau:0] (CDictCan)
[W] $dNum_aJR {0}:: Num c_aGn[tau:0] (CDictCan)}
...
writeMetaTyVar a_aGl[tau:0] := Int
...
writeMetaTyVar b_aGm[tau:0] := Int
...
writeMetaTyVar c_aGn[tau:0] := Int
...
defaultingPlugin
[DefaultingProposal a_aGl[tau:0] [Int] [[W] $dNum_aJL {0}:: Num
a_aGl[tau:0] (CDictCan)],
DefaultingProposal b_aGm[tau:0] [Int] [[W] $dNum_aJO {0}:: Num
b_aGm[tau:0] (CDictCan)],
DefaultingProposal c_aGn[tau:0] [Int] [[W] $dNum_aJR {0}:: Num
c_aGn[tau:0] (CDictCan)]]
defaultingPlugins } [True]
applyDefaultingRules {
wanteds = WC {wc_simple =
[W] $dIsColor_aGj {0}:: IsColor
(a_aGl[tau:0], b_aGm[tau:0], c_aGn[tau:0]) (CDictCan)
[W] $dNum_aJL {0}:: Num a_aGl[tau:0] (CDictCan)
[W] $dNum_aJO {0}:: Num b_aGm[tau:0] (CDictCan)
[W] $dNum_aJR {0}:: Num c_aGn[tau:0] (CDictCan)}
groups = [(a_aGl[tau:0],
[[W] $dNum_aJL {0}:: Num a_aGl[tau:0] (CDictCan)]),
(b_aGm[tau:0], [[W] $dNum_aJO {0}:: Num b_aGm[tau:0] (CDictCan)]),
(c_aGn[tau:0], [[W] $dNum_aJR {0}:: Num c_aGn[tau:0] (CDictCan)])]
info = ([(), [], Integer, Double], (False, True))
...
unifyTyVar a_aGl[tau:0] := Integer
writeMetaTyVar a_aGl[tau:0] := Integer
<no location info>: error:
panic! (the 'impossible' happened)
GHC version 9.7.20230620:
ASSERT failed!
Double update of meta tyvar
a_aGl[tau:0]
Indirect Int
Call stack:
CallStack (from HasCallStack):
massertPpr, called at compiler/GHC/Tc/Zonk/TcType.hs:156:10 in ghc-9.7-inplace:GHC.Tc.Zonk.TcType
writeMetaTyVarRef, called at compiler/GHC/Tc/Zonk/TcType.hs:115:5 in ghc-9.7-inplace:GHC.Tc.Zonk.TcType
writeMetaTyVar, called at compiler/GHC/Tc/Solver/Monad.hs:1306:26 in ghc-9.7-inplace:GHC.Tc.Solver.Monad
CallStack (from HasCallStack):
panic, called at compiler/GHC/Utils/Error.hs:503:29 in ghc-9.7-inplace:GHC.Utils.Error
Please report this as a GHC bug: https://www.haskell.org/ghc/reportabug
Gergő Érdi (5ff6e33d) at 05 Sep 05:37
Improvements to the documentation of defaulting plugins
... and 5 more commits
Currently, the defaulting plugin API matches exactly the signature of the built-in defaulting mechanism, where each type variable occuring in the Wanteds
can be proposed to be defaulted to a priority list of types, one by one, with some extra constraints that need to hold after defaulting.
Since each new constraint is associated with a single defaultable type variable, this makes it impossible to propose defaults for multi-parameter typeclasses C a b
, or even constraints with multiple as-yet-unsolved type variablesC (T a b)
.
However, it would not take much to extend the plugin API to support this use case. I have a prototype implementation at cactus/ghc@b73d407b that changes the API to this:
data DefaultingProposal
= DefaultingProposal
{ deProposalTyVars :: [TcTyVar]
-- ^ The type variable to default.
, deProposalCandidates :: [[Type]]
-- ^ Candidate types to default the type variable to.
, deProposalCts :: [Ct]
-- ^ The constraints against which defaults are checked.
}
The types could be a bit tighter, because basically we want a rectangle of proposal candidates (all (equalLength deProposalTyVars) deProposalCandidates
), but I don't know how that's usually done in the GHC codebase so for the prototype this shall do.
Gergő Érdi (8ddd852f) at 31 Aug 06:48
Improvements to the documentation of defaulting plugins
... and 6 more commits
Gergő Érdi (b75f401b) at 31 Aug 06:47
Improvements to the documentation of defaulting plugins
Gergő Érdi (81bb4d89) at 31 Aug 06:45
Apply 2 suggestion(s) to 1 file(s)