Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions src/Elm.elm
Original file line number Diff line number Diff line change
Expand Up @@ -558,10 +558,6 @@ unwrapper modName typename =
argVal : { name : String, typename : String, val : Compiler.Expression, index : Index.Index }
argVal =
Compiler.toVar index "val"

return : { name : String, typename : String, val : Compiler.Expression, index : Index.Index }
return =
Compiler.toVar argVal.index "unwrapped"
in
{ expression =
Exp.LambdaExpression
Expand All @@ -583,14 +579,18 @@ unwrapper modName typename =
)
}
, annotation =
Ok
{ type_ =
Annotation.FunctionTypeAnnotation
(Compiler.nodify (Annotation.GenericType argVal.typename))
(Compiler.nodify (Annotation.GenericType return.typename))
, inferences = Dict.empty
, aliases = Compiler.emptyAliases
}
-- We can't generate a valid type annotation for an
-- unwrapper lambda: the arg type is the named custom
-- type (e.g. Wrapper), but the return type is its
-- unknown inner type. Generating `Wrapper -> a` would
-- be wrong because the body extracts a concrete value,
-- not a polymorphic one, so Elm would reject it with
-- a TYPE MISMATCH.
--
-- Returning `Err []` causes the declaration to be
-- generated without a type annotation, letting Elm
-- infer it from the custom type definition.
Err []
, imports =
case modName of
[] ->
Expand Down
29 changes: 28 additions & 1 deletion src/Internal/Compiler.elm
Original file line number Diff line number Diff line change
Expand Up @@ -1573,9 +1573,36 @@ applyType :
-> Result (List InferenceError) Inference
applyType index annotation args =
case annotation of
Err err ->
Err ((_ :: _) as err) ->
Err err

Err [] ->
-- The function's type is intentionally unknown (e.g.
-- Elm.unwrapper can't derive an annotation for its lambda).
-- Synthesize a fresh generic return type so downstream
-- inference can still flow through the application.
--
-- Only synthesize when args is non-empty; `apply fn []`
-- with an unknown fn is still "unknown" (the result is
-- the fn itself, whose type we still don't know).
case ( Index.typecheck index, args ) of
( True, _ :: _ ) ->
case mergeArgInferences args [] Dict.empty of
Ok mergedArgs ->
Ok
{ type_ =
Annotation.GenericType
(Index.protectTypeName "result" index)
, inferences = mergedArgs.inferences
, aliases = emptyAliases
}

Err _ ->
Err []

_ ->
Err []

Ok fnAnnotation ->
if Index.typecheck index then
case mergeArgInferences args [] fnAnnotation.inferences of
Expand Down
60 changes: 60 additions & 0 deletions tests/TypeChecking.elm
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,66 @@ generatedCode =
( 1 + 2, x )
"""
]
, test "Elm.unwrapper omits annotation to let Elm infer it" <|
-- unwrapper creates `\(Wrapper val) -> val` but can't
-- derive a valid type annotation because it doesn't know
-- the inner type of Wrapper. Any annotation we could
-- generate (like `Wrapper -> a`) would be rejected by Elm
-- because the extracted value has a concrete type, not a
-- polymorphic one. So we omit the annotation and let Elm
-- infer it from the custom type definition.
\_ ->
Elm.declaration "extract"
(Elm.unwrapper [] "Wrapper")
|> Elm.Expect.declarationAs
"""
extract (Wrapper val) =
val
"""
, test "Elm.unwrapper respects withType when caller provides an annotation" <|
\_ ->
Elm.declaration "extract"
(Elm.unwrapper [] "Wrapper"
|> Elm.withType (Type.function [ Type.named [] "Wrapper" ] Type.string)
)
|> Elm.Expect.declarationAs
"""
extract : Wrapper -> String
extract (Wrapper val) =
val
"""
, test "Elm.unwrap result propagates through downstream inference" <|
-- Even though unwrapper itself can't produce a type
-- annotation, `apply` synthesizes a fresh generic return
-- type when the function's type is unknown, so outer
-- expressions can still unify and infer correctly.
\_ ->
Elm.declaration "foo"
(Elm.Op.plus (Elm.int 1)
(Elm.unwrap [] "Wrapper" (Elm.val "wrapped"))
)
|> Elm.Expect.declarationAs
"""
foo : Int
foo =
1 + (\\(Wrapper val) -> val) wrapped
"""
, test "Elm.apply with zero args on an unknown function stays unknown" <|
-- `apply fn []` with an unknown fn type should not fabricate
-- a return type out of thin air. Without this guard, the
-- outer Op.plus would unify the fabricated generic with
-- `number` and emit `foo : Int`, even though the body is
-- `1 + (\(Wrapper val) -> val)` (adding an int to a lambda).
\_ ->
Elm.declaration "foo"
(Elm.Op.plus (Elm.int 1)
(Elm.apply (Elm.unwrapper [] "Wrapper") [])
)
|> Elm.Expect.declarationAs
"""
foo =
1 + (\\(Wrapper val) -> val)
"""
, describe "aliasAs pattern type"
[ test "aliasAs on record pattern uses the underlying record type" <|
\_ ->
Expand Down
Loading