Skip to content

Instantly share code, notes, and snippets.

@LightAndLight
Last active July 3, 2017 09:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LightAndLight/67168114153cb3519c50185f3149b56c to your computer and use it in GitHub Desktop.
Save LightAndLight/67168114153cb3519c50185f3149b56c to your computer and use it in GitHub Desktop.
Bug dissection for #2803

purescript/purescript#2803

By the time it gets to the type checker, the % operator has been desugared to Var (Qualified (Just (ModuleName [ProperName "Main"])) (Ident "f")). The f introduced by the let binding is unqualified (Qualified Nothing (Ident "f")), and it seems that the qualified f is removed from the environment inside the scope of the let. I think that they should both be in scope, since they are different variables- one qualified, and one not.

It doesn't seem to be a general problem with let bindings and qualified names, because this compiles:

-- Test2.purs
module Test2 where

import Prelude ((+))

f :: Int -> Int -> Int
f = (+)
-- Test.purs
module Test where

import Prelude ((+), (-))
import Test2 as Test2

g :: Int -> Int -> Int
g a b = let f = (-) in Test2.f a b

In typeCheckAll (Language.PureScript.TypeChecker:223), main is checked, then g, but not f. I think there's going to be a dependency graph from which f is excluded.


Funny discovery- this works:

module Test where

import Prelude ((+), (-))

f :: Int -> Int -> Int
f = (+)

infixl 6 f as %

g :: Int -> Int -> Int
g a b = let f = (-) in asdf % b

asdf :: Int
asdf = 1

Addendum- this only worked coincidentally


typeCheckAll is called by typeCheckModule (Language.PureScript.TypeChecker:427), but it doesn't do any dependency analysis.

The related code might be in Language.PureScript.Sugar.BindingGroups.


Looking in the code for BindingGroups, it seems like it gets all the identifiers in a module, then finds where they're used to build a graph. I think maybe this process is happening before the operator desugaring stage, and it's not picking up the dependency of g on f.


Well, it seems that they already thought of this and createBindingsGroupsModule is the last thing in the desugar process.

Time to dive into createBindingGroups (Language.PureScript.Sugar.BindingGroups:57)


f does appear as a value declaration in values (BindingGroups:70) allIdents doesn't seem to be evaluating (BindingGroups:75) usedIdents is probably the identifiers used in the declaration, but it seems to be returning [] for all identifiers?


It seems to be putting g + main and f in different binding groups.


main doesn't even use g, so it's purely the fact that f is not recognised as being used in g. Maybe it's being deleted from the dependency set because of the shadowed definition. So it'll be a problem with usedIdents (BindingGroups:106)


It's not being deleted, but usedIdents' thinks it's already in scope because of the let binding


Resolution

The problem was in usedIdents, specifically the behaviour of the everywhereWithScope traversal. It was using a Set Ident to represent the scope, causing a shadowing issue because the compiler couldn't differentiate between the let-bound f and the globally bound one. It was fixed by changing the everywhereWithScope traversal to use Set (Qualified Ident) for the scope instead. This provides the information necessary to disambiguate the shadowed bindings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment