packages feed

tala-compiler-0.1.0: test/SourceWarningTests.hs

-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1

{- | Tests for the source warnings, which today are the two spellings of the
removed decrement operator.

A warning must never change what compiles: every text below that the
compiler accepts is still accepted, and the warning only points at the place
where @--@ became a comment.
-}
module SourceWarningTests (sourceWarningTests) where

import Progmasoft.Tala.AST
import Progmasoft.Tala.Compiler
import Progmasoft.Tala.Diagnostic
import Progmasoft.Tala.SourceWarnings

sourceWarningTests :: [(String, Bool)]
sourceWarningTests =
    [ ("an attached double dash after a name is warned about", positions "count--;" == [(1, 6)])
    , ("an attached double dash after a parenthesis is warned about", positions "(count)--;" == [(1, 8)])
    , ("an attached double dash after an index is warned about", positions "items[i]--;" == [(1, 9)])
    , ("a leading double dash before a terminated name is warned about", positions "--count;" == [(1, 1)])
    , ("a leading double dash tolerates blanks around the terminator", positions "  --count ;  " == [(1, 3)])
    , ("every occurrence is reported in source order", positions "a--;\nb = 1;\n--c;" == [(1, 2), (3, 1)])
    , ("the warning is a lexer-stage warning with its own code", attachedShape)
    , ("the two spellings give different advice", differentAdvice)
    , ("a spaced comment after code is not warned about", quiet "count -- one less")
    , ("a comment on its own line is not warned about", quiet "-- count the items")
    , ("a comment that starts with a word and continues is not warned about", quiet "--count the items;")
    , ("commented-out code that is not a bare name is not warned about", quiet "--count = 5;")
    , ("an attached comment after an operator is not warned about", quiet "total = left +-- right\n1;")
    , ("a documentation comment is never a decrement spelling", quiet "value--| documented\n--!module;")
    , ("a long comment is never a decrement spelling", quiet "value--[[ long ]] + 1; --[[name;]]")
    , ("a double dash inside a string is not a comment at all", quiet "String text = \"count--;\";")
    , ("a double dash inside a raw string is not a comment at all", quiet "String text = [[--count;]];")
    , ("compound subtraction is the replacement and is not warned about", quiet "count -= 1; total = a - -b;")
    , ("a text with a lexical error has no warnings", quiet "count--;\n\"unterminated")
    , ("an empty text has no warnings", quiet "")
    , ("a warned program still compiles when it is otherwise valid", warnedProgramStillCompiles)
    , ("the warning does not turn a valid program into an error", null (lefts (compileToCorePrep validWarned)))
    ]

warnings :: String -> [Diagnostic]
warnings = sourceWarnings "warnings.tala"

-- | Line and column of each warning's start.
positions :: String -> [(Int, Int)]
positions text =
    [ (sourceLine start, sourceColumn start)
    | diagnostic <- warnings text
    , Just spanValue <- [diagnosticSpan diagnostic]
    , let start = sourceStart spanValue
    ]

quiet :: String -> Bool
quiet = null . warnings

attachedShape :: Bool
attachedShape = case warnings "count--;" of
    [Diagnostic LexerStage Warning code (Just spanValue) _] ->
        code == decrementSpellingCode
            && code == "TAL0009"
            && sourceFile spanValue == "warnings.tala"
            -- The span covers exactly the two characters of the sign.
            && sourceColumn (sourceEnd spanValue) - sourceColumn (sourceStart spanValue) == 2
            && sourceLine (sourceEnd spanValue) == sourceLine (sourceStart spanValue)
    _ -> False

differentAdvice :: Bool
differentAdvice = case (warnings "count--;", warnings "--count;") of
    ([attached], [leading]) ->
        diagnosticMessage attached /= diagnosticMessage leading
            && all (mentions "no decrement operator") [attached, leading]
            && mentions "space before" attached
            && mentions "space after" leading
    _ -> False
    where
        mentions needle diagnostic = needle `isInfixOfText` diagnosticMessage diagnostic

isInfixOfText :: String -> String -> Bool
isInfixOfText needle haystack = any (startsWith needle) (suffixes haystack)
    where
        startsWith prefix text = take (length prefix) text == prefix
        suffixes text = case text of
            [] -> [[]]
            _ : rest -> text : suffixes rest

-- `value--;` leaves the name as the final expression of its block, so the
-- program is accepted. The warning is the only sign that nothing is
-- subtracted.
validWarned :: CompilerInput
validWarned =
    CompilerInput
        "warnings.tala"
        ( unlines
            [ "class Program {"
            , "    public static int Evaluate(_ int limit) {"
            , "        int value = limit;"
            , "        value--;"
            , "    }"
            , "}"
            ]
        )

warnedProgramStillCompiles :: Bool
warnedProgramStillCompiles =
    length (sourceWarnings "warnings.tala" (compilerSourceText validWarned)) == 1

lefts :: Either [left] right -> [left]
lefts = either id (const [])