diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+1.15.1
+
+* Fix infinite loop when formatting expressions containing `?`
+    * See: https://github.com/dhall-lang/dhall-haskell/pull/491
+
 1.15.0
 
 * BREAKING CHANGE TO THE API: Support alternative imports using new `?` operator
diff --git a/dhall.cabal b/dhall.cabal
--- a/dhall.cabal
+++ b/dhall.cabal
@@ -1,5 +1,5 @@
 Name: dhall
-Version: 1.15.0
+Version: 1.15.1
 Cabal-Version: >=1.10
 Build-Type: Simple
 Tested-With: GHC == 8.0.1
@@ -173,7 +173,7 @@
         bytestring                                 < 0.11,
         case-insensitive                           < 1.3 ,
         containers                  >= 0.5.0.0  && < 0.6 ,
-        contravariant                              < 1.5 ,
+        contravariant                              < 1.6 ,
         cryptonite                  >= 0.23     && < 1.0 ,
         Diff                        >= 0.2      && < 0.4 ,
         directory                   >= 1.3      && < 1.4 ,
@@ -274,9 +274,11 @@
     Main-Is: Main.hs
     GHC-Options: -Wall
     Build-Depends:
-        base                      ,
-        Diff    >= 0.2   && < 0.4 ,
-        doctest >= 0.7.0 && < 0.16
+        base                          ,
+        directory >= 1.2.2.0 && < 1.4 ,
+        filepath                < 1.5 ,
+        mockery                 < 0.4 ,
+        doctest   >= 0.7.0   && < 0.17
     Default-Language: Haskell2010
 
 Benchmark dhall-parser
@@ -285,7 +287,7 @@
     Build-Depends:
         base                      >= 4        && < 5  ,
         containers                >= 0.5.0.0  && < 0.6,
-        criterion                 >= 1.1      && < 1.5,
+        criterion                 >= 1.1      && < 1.6,
         dhall                                         ,
         directory                 >= 1.3      && < 1.4,
         text                      >= 0.11.1.0 && < 1.3
@@ -297,6 +299,6 @@
     Build-Depends:
         base                      >= 4        && < 5  ,
         containers                >= 0.5.0.0  && < 0.6,
-        criterion                 >= 1.1      && < 1.5,
+        criterion                 >= 1.1      && < 1.6,
         dhall
     Default-Language: Haskell2010
diff --git a/doctest/Main.hs b/doctest/Main.hs
--- a/doctest/Main.hs
+++ b/doctest/Main.hs
@@ -1,6 +1,29 @@
 module Main where
 
+import Data.Monoid ((<>))
+import System.FilePath ((</>))
+
+import qualified System.Directory
+import qualified Test.Mockery.Directory
 import qualified Test.DocTest
 
 main :: IO ()
-main = Test.DocTest.doctest [ "-isrc", "src/Dhall.hs", "src/Dhall/Import.hs" ]
+main = do
+    pwd    <- System.Directory.getCurrentDirectory
+    prefix <- System.Directory.makeAbsolute pwd
+
+    Test.Mockery.Directory.inTempDirectory $ do
+        writeFile "makeBools" "λ(n : Bool) → [ n && True, n && False, n || True, n || False ]"
+        writeFile "bool1" "True"
+        writeFile "bool2" "False"
+        writeFile "both" "./bool1 && ./bool2"
+        writeFile "file2" "./file1"
+        writeFile "file1" "./file2"
+
+        Test.DocTest.doctest
+            [ "--fast"
+            , "-i" <> (prefix </> "src")
+            , prefix </> "src/Dhall.hs"
+            , prefix </> "src/Dhall/Import.hs"
+            , prefix </> "src/Dhall/Tutorial.hs"
+            ]
diff --git a/src/Dhall/Pretty/Internal.hs b/src/Dhall/Pretty/Internal.hs
--- a/src/Dhall/Pretty/Internal.hs
+++ b/src/Dhall/Pretty/Internal.hs
@@ -541,7 +541,19 @@
     prettyOperatorExpression a0
 
 prettyOperatorExpression :: Pretty a => Expr s a -> Doc Ann
-prettyOperatorExpression = prettyOrExpression
+prettyOperatorExpression = prettyImportAltExpression
+
+prettyImportAltExpression :: Pretty a => Expr s a -> Doc Ann
+prettyImportAltExpression a0@(ImportAlt _ _) =
+    enclose' "" "    " (space <> operator "?" <> space) (operator "?" <> "  ") (fmap duplicate (docs a0))
+  where
+    docs (ImportAlt a b) = prettyOrExpression a : docs b
+    docs (Note      _ b) = docs b
+    docs              b  = [ prettyOrExpression b ]
+prettyImportAltExpression (Note _ a) =
+    prettyImportAltExpression a
+prettyImportAltExpression a0 =
+    prettyOrExpression a0
 
 prettyOrExpression :: Pretty a => Expr s a -> Doc Ann
 prettyOrExpression a0@(BoolOr _ _) =
diff --git a/src/Dhall/Tutorial.hs b/src/Dhall/Tutorial.hs
--- a/src/Dhall/Tutorial.hs
+++ b/src/Dhall/Tutorial.hs
@@ -168,6 +168,10 @@
 import Data.Vector (Vector)
 import Dhall
 
+-- $setup
+--
+-- >>> :set -XOverloadedStrings
+
 -- $introduction
 --
 -- The simplest way to use Dhall is to ignore the programming language features
@@ -208,9 +212,9 @@
 -- You can also load some types directly into Haskell without having to define a
 -- record, like this:
 --
--- > >>> :set -XOverloadedStrings
--- > >>> input auto "True" :: IO Bool
--- > True
+-- >>> :set -XOverloadedStrings
+-- >>> input auto "True" :: IO Bool
+-- True
 --
 -- The `input` function can decode any value if we specify the value's expected
 -- `Type`:
@@ -227,20 +231,20 @@
 -- > input bool :: Text -> IO Bool
 -- >
 -- > input bool "True" :: IO Bool
--- >
--- > >>> input bool "True"
--- > True
 --
+-- >>> input bool "True"
+-- True
+--
 -- ... or we can use `auto` to let the compiler infer what type to decode from
 -- the expected return type:
 --
 -- > auto :: Interpret a => Type a
 -- >
 -- > input auto :: Interpret a => Text -> IO a
--- >
--- > >>> input auto "True" :: IO Bool
--- > True
 --
+-- >>> input auto "True" :: IO Bool
+-- True
+--
 -- You can see what types `auto` supports \"out-of-the-box\" by browsing the
 -- instances for the `Interpret` class.  For example, the following instance
 -- says that we can directly decode any Dhall expression that evaluates to a
@@ -259,14 +263,14 @@
 -- Therefore, since we can decode a @Bool@, we must also be able to decode a
 -- @List@ of @Bool@s, like this:
 --
--- > >>> input auto "[True, False]" :: IO (Vector Bool)
--- > [True,False]
+-- >>> input auto "[True, False]" :: IO (Vector Bool)
+-- [True,False]
 --
 -- We could also specify what type to decode by providing an explicit `Type`
 -- instead of using `auto` with a type annotation:
 --
--- > >>> input (vector bool) "[True, False]"
--- > [True, False]
+-- >>> input (vector bool) "[True, False]"
+-- [True,False]
 --
 -- __Exercise:__ Create a @./config@ file that the following program can decode:
 --
@@ -303,16 +307,17 @@
 --
 -- Suppose that we try to decode a value of the wrong type, like this:
 --
--- > >>> input auto "1" :: IO Bool
--- > *** Exception:
--- > Error: Expression doesn't match annotation
--- > 
--- > - Bool
--- > + Natural
--- > 
--- > 1 : Bool
--- > 
--- > (input):1:1
+-- >>> input auto "1" :: IO Bool
+-- *** Exception:
+-- ...Error...: Expression doesn't match annotation
+-- ...
+-- - Bool
+-- + Natural
+-- ...
+-- 1 : Bool
+-- ...
+-- (input):1:1
+-- ...
 --
 -- The interpreter complains because the string @\"1\"@ cannot be decoded into a
 -- Haskell value of type `Bool`.  This part of the type error:
@@ -364,14 +369,14 @@
 --
 -- You might wonder why in some cases we can decode a configuration file:
 --
--- > >>> writeFile "bool" "True"
--- > >>> input auto "./bool" :: IO Bool
--- > True
+-- >>> writeFile "bool" "True"
+-- >>> input auto "./bool" :: IO Bool
+-- True
 --
 -- ... and in other cases we can decode a value directly:
 --
--- > >>> input auto "True" :: IO Bool
--- > True
+-- >>> input auto "True" :: IO Bool
+-- True
 --
 -- This is because importing a configuration from a file is a special case of a
 -- more general language feature: Dhall expressions can reference other
@@ -385,8 +390,8 @@
 --
 -- ... and read in all three files in a single expression:
 -- 
--- > >>> input auto "[ ./bool1 , ./bool2 , ./both ]" :: IO (Vector Bool)
--- > [True,False,False]
+-- >>> input auto "[ ./bool1 , ./bool2 , ./both ]" :: IO (Vector Bool)
+-- [True,False,False]
 --
 -- Each file path is replaced with the Dhall expression contained within that
 -- file.  If that file contains references to other files then those references
@@ -415,12 +420,12 @@
 --
 -- ... then the interpreter will reject the import:
 --
--- > >>> input auto "./file1" :: IO Natural
--- > *** Exception: 
--- > ↳ ./file1
--- >   ↳ ./file2
--- >
--- > Cyclic import: ./file1
+-- >>> input auto "./file1" :: IO Natural
+-- *** Exception: 
+-- ↳ ./file1
+--   ↳ ./file2
+-- ...
+-- Cyclic import: ./file1
 --
 -- You can also import expressions by URL.  For example, you can find a Dhall
 -- expression hosted at this GitHub URL:
@@ -432,13 +437,13 @@
 --
 -- ... and you can reference that expression either directly:
 --
--- > >>> input auto "https://raw.githubusercontent.com/dhall-lang/dhall-haskell/18e4e9a18dc53271146df3ccf5b4177c3552236b/examples/True" :: IO Bool
--- > True
+-- >>> input auto "https://raw.githubusercontent.com/dhall-lang/dhall-haskell/18e4e9a18dc53271146df3ccf5b4177c3552236b/examples/True" :: IO Bool
+-- True
 -- 
 -- ... or inside of a larger expression:
 --
--- > >>> input auto "False == https://raw.githubusercontent.com/dhall-lang/dhall-haskell/18e4e9a18dc53271146df3ccf5b4177c3552236b/examples/True" :: IO Bool
--- > False
+-- >>> input auto "False == https://raw.githubusercontent.com/dhall-lang/dhall-haskell/18e4e9a18dc53271146df3ccf5b4177c3552236b/examples/True" :: IO Bool
+-- False
 --
 -- You're not limited to hosting Dhall expressions on GitHub.  You can host a
 -- Dhall expression anywhere that you can host UTF8-encoded text on the web,
@@ -446,9 +451,9 @@
 --
 -- You can also import Dhall expressions from environment variables, too:
 --
--- > >>> System.Environment.setEnv "FOO" "1"
--- > >>> input auto "env:FOO" :: IO Natural
--- > 1
+-- >>> System.Environment.setEnv "FOO" "1"
+-- >>> input auto "env:FOO" :: IO Natural
+-- 1
 --
 -- You can import types, too.  For example, we can change our @./bar@ file to:
 --
@@ -466,20 +471,21 @@
 -- Note that all imports must be terminated by whitespace or you will get either
 -- an import error or a parse error, like this:
 --
--- > >>> writeFile "baz" "2.0"
--- > >>> input auto "./baz: Double" :: IO Double
--- > *** Exception: 
--- > ↳ ./baz: 
--- > 
--- > Error: Missing file
+-- >>> writeFile "baz" "2.0"
+-- >>> input auto "./baz: Double" :: IO Double
+-- *** Exception: 
+-- ↳ ./baz: 
+-- ...
+-- ...Error...: Missing file .../baz:
+-- ...
 --
 -- This is because the parser thinks that @./baz:@ is a single token due to
 -- the missing whitespace before the colon and tries to import a file named
 -- @./baz:@, which does not exist.  To fix the problem we have to add a space
 -- after @./baz@:
 --
--- > >>> input auto "./baz : Double" :: IO Double
--- > 2.0
+-- >>> input auto "./baz : Double" :: IO Double
+-- 2.0
 --
 -- __Exercise:__ There is a @not@ function hosted online here:
 --
@@ -503,30 +509,32 @@
 -- required for empty lists but optional for non-empty lists.  You will get a
 -- type error if you provide an empty list without a type annotation:
 --
--- > >>> input auto "[]" :: IO (Vector Natural)
--- > *** Exception:
--- > Error: Invalid input
--- > 
--- > (input):1:3:
--- >   |
--- > 1 | []
--- >   |   ^
--- > unexpected end of input
--- > expecting ':' or whitespace
+-- >>> input auto "[]" :: IO (Vector Natural)
+-- *** Exception:
+-- ...Error...: Invalid input
+-- ...
+-- (input):1:3:
+--   |
+-- 1 | []
+--   |   ^
+-- unexpected end of input
+-- expecting ':' or whitespace
+-- ...
 --
 -- Also, list elements must all have the same type.  You will get an error if
 -- you try to store elements of different types in a list:
 --
--- > >>> input auto "[1, True, 3]" :: IO (Vector Natural)
--- > *** Exception:
--- > Error: List elements should all have the same type
--- > 
--- > - Natural
--- > + Bool
--- > 
--- > True
--- > 
--- > (input):1:5
+-- >>> input auto "[1, True, 3]" :: IO (Vector Natural)
+-- *** Exception:
+-- ...Error...: List elements should all have the same type
+-- ...
+-- - Natural
+-- + Bool
+-- ...
+-- True
+-- ...
+-- (input):1:5
+-- ...
 --
 -- __Exercise:__ What is the shortest @./config@ file that you can decode using
 -- this command:
@@ -550,10 +558,10 @@
 --
 -- An @Optional@ corresponds to Haskell's `Maybe` type for decoding purposes:
 --
--- > >>> input auto "[1] : Optional Natural" :: IO (Maybe Natural)
--- > Just 1
--- > >>> input auto "[] : Optional Natural" :: IO (Maybe Natural)
--- > Nothing
+-- >>> input auto "[1] : Optional Natural" :: IO (Maybe Natural)
+-- Just 1
+-- >>> input auto "[] : Optional Natural" :: IO (Maybe Natural)
+-- Nothing
 --
 -- You cannot omit the type annotation for @Optional@ values.  The type
 -- annotation is mandatory
@@ -612,8 +620,8 @@
 -- ... which means to access the value of the field named @fieldName@ from the
 -- @record@.  For example:
 --
--- > >>> input auto "{ foo = True, bar = 2, baz = 4.2 }.baz" :: IO Double
--- > 4.2
+-- >>> input auto "{ foo = True, bar = 2, baz = 4.2 }.baz" :: IO Double
+-- 4.2
 --
 -- ... and you can project out multiple fields into a new record using this
 -- syntax:
@@ -809,8 +817,8 @@
 -- Now that we've verified that our function type checks and works, we can use
 -- the same function within Haskell:
 --
--- > >>> input auto "./makeBools True" :: IO (Vector Bool)
--- > [True,False,True,True]
+-- >>> input auto "./makeBools True" :: IO (Vector Bool)
+-- [True,False,True,True]
 --
 -- __Exercise__: Create a file named @getFoo@ that is a function of the following
 -- type:
@@ -1226,10 +1234,10 @@
 -- The identity function is polymorphic, meaning that `id` works on values of
 -- different types:
 --
--- > >>> id 4
--- > 4
--- > >>> id True
--- > True
+-- >>> id 4
+-- 4
+-- >>> id True
+-- True
 --
 -- The equivalent function in Dhall is:
 --
