diff --git a/src/Elm.elm b/src/Elm.elm index 0240686..72f17c6 100644 --- a/src/Elm.elm +++ b/src/Elm.elm @@ -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 @@ -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 [] -> diff --git a/src/Internal/Compiler.elm b/src/Internal/Compiler.elm index 0521afa..f5cbc5c 100644 --- a/src/Internal/Compiler.elm +++ b/src/Internal/Compiler.elm @@ -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 diff --git a/tests/TypeChecking.elm b/tests/TypeChecking.elm index 64c7cda..3aac30a 100644 --- a/tests/TypeChecking.elm +++ b/tests/TypeChecking.elm @@ -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" <| \_ ->