tala-frontend-0.2.0: src/Progmasoft/Tala/TypeChecker/Testing.hs
-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
{- | The test runtime in the type checker.
@#expect@ and the runner of a test build call methods of @$Test@, a name
that no source can spell. This module checks those calls and rewrites them
into calls of the runtime, as "Progmasoft.Tala.TypeChecker.Console" does
for the console:
* @$Test.Fail(header, text, value, ...)@ records a failed expectation. The
report is the header, and below it one line for each operand, its text
and its value: @count is 2@. An operand whose type has no text form is
left out of the report rather than refused, because the expectation
itself is well formed.
* @$Test.End(name)@ closes the test of that name.
* @$Test.Finish()@ writes the totals of the run.
-}
module Progmasoft.Tala.TypeChecker.Testing
( testReceiver
, checkTestCall
) where
import Progmasoft.Tala.AST
import Progmasoft.Tala.Diagnostic
import Progmasoft.Tala.RuntimeCall
import Progmasoft.Tala.TypeChecker.Console (CheckExpression, asText, runtimeCall)
type Checked = (Expression ResolvedName Type, Type, [Diagnostic])
-- | Whether the receiver of a member call is @$Test@.
testReceiver :: Expression ResolvedName () -> Bool
testReceiver receiver = case receiver of
NameExpression _ name _ -> resolvedSymbol name == builtinTestSymbol
_ -> False
-- | Check a call of a method of @$Test@ and rewrite it into runtime calls.
checkTestCall :: CheckExpression -> SourceSpan -> Identifier -> [Expression ResolvedName ()] -> Checked
checkTestCall check callSpan member arguments = case (identifierText member, arguments) of
("Fail", LiteralExpression headerSpan (StringLiteral header) () : operands) ->
case operandLines operands of
Right (pieces, problems) ->
let report = foldl join (text headerSpan header) pieces
in (runtimeCall callSpan TestFail [report], voidType, problems)
Left problems -> failed problems
("End", [LiteralExpression nameSpan (StringLiteral name) ()]) ->
(runtimeCall callSpan TestEnd [text nameSpan name], voidType, [])
("Finish", []) -> (runtimeCall callSpan TestFinish [], voidType, [])
(name, _) -> failed [problem ("the test runtime has no call " ++ name ++ " of this form")]
where
join whole part = runtimeCall callSpan TextConcat [whole, part]
-- Each operand as a line of the report: the line terminator, an
-- indent, the operand's text, and its value.
operandLines values = case values of
[] -> Right ([], [])
LiteralExpression textSpan (StringLiteral written) () : value : rest -> do
(later, laterProblems) <- operandLines rest
let (typed, valueType, problems) = check Nothing value
line = case asText (expressionSourceSpan typed) typed valueType of
Right shown ->
[ runtimeCall textSpan TextNewline []
, text textSpan (" " ++ written ++ " is ")
, shown
]
Left _ -> []
Right (line ++ later, problems ++ laterProblems)
_ -> Left [problem "the operands of a failed expectation come in pairs of text and value"]
text spanValue value = LiteralExpression spanValue (StringLiteral value) stringType
failed problems = (LiteralExpression callSpan UnitLiteral ErrorType, ErrorType, problems)
problem message = Diagnostic TypeCheckerStage Error "TAT0083" (Just callSpan) message