Skip to content

Add Haskell implementation of randstr#5

Draft
Copilot wants to merge 6 commits intomasterfrom
copilot/add-haskell-implementation
Draft

Add Haskell implementation of randstr#5
Copilot wants to merge 6 commits intomasterfrom
copilot/add-haskell-implementation

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 6, 2026

  • Explore existing Racket codebase to understand all features
  • Set up Haskell project structure with Cabal
  • Implement core types and token data structures (Types.hs)
  • Implement character class definitions (CharClasses.hs)
  • Implement configuration module (Config.hs)
  • Implement utility functions (Utils.hs)
  • Implement tokenizer/parser (Tokenizer.hs)
  • Implement string generator (Generator.hs)
  • Implement public API (Lib.hs)
  • Implement CLI interface (app/Main.hs)
  • Add 39 tests for core functionality (test/Spec.hs)
  • Update Justfile with Haskell build/test commands
  • Update .gitignore for Haskell build artifacts
  • Verify build passes with zero warnings
  • Verify all 39 tests pass
  • Verify CLI works correctly
  • Run code review and address feedback
  • Run CodeQL security check (Haskell not supported by CodeQL - no issues)
  • Address PR review comments round 1 (configRandom guard, empty CharClass, POSIX fallback, CLI validation, secure mode error, NormalRange fix)
  • Address PR review comments round 2:
    • Strengthen \W+ test to verify non-word characters
    • Strengthen \s* test to verify whitespace characters
    • Strengthen \S+ test to verify non-whitespace characters
    • Strengthen [[:blank:]]* test to verify blank characters
    • Strengthen [[:space:]]+ test to verify space characters
    • Fix envToPositiveInteger to allow 0 for RANDSTR_MAX_REPEAT consistency
    • Remove redundant Randstr.Types () import from Lib.hs

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Copilot AI and others added 3 commits March 6, 2026 21:08
Create directory layout (src/Randstr/, app/, test/) and randstr.cabal
with library, executable, and test-suite configurations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a Haskell port of the randstr library under haskell/ directory with:
- Core types (Token, Quantifier, TokenType)
- Pattern tokenizer supporting all escape sequences, POSIX classes,
  character classes, groups, named groups, backreferences, Unicode properties
- String generator with quantifiers (*, +, ?, {n}, normal distribution)
- Character class definitions matching the Racket implementation
- CLI with -n, -m, -s flags and env var support
- 39 comprehensive tests covering all features
- Updated Justfile with hs-build, hs-test, hs-clean targets

Co-authored-by: hugefiver <18693500+hugefiver@users.noreply.github.com>
Co-authored-by: hugefiver <18693500+hugefiver@users.noreply.github.com>
Copilot AI changed the title [WIP] Add Haskell implementation to replicate project features Add Haskell implementation of randstr Mar 6, 2026
@hugefiver hugefiver requested a review from Copilot March 6, 2026 21:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a full Haskell implementation of the randstr library/CLI under haskell/, mirroring the existing Racket pipeline (pattern → tokens → generated string) and wiring it into the repo’s developer workflow.

Changes:

  • Introduces Haskell library modules for tokenization, generation, character classes, configuration, and utilities.
  • Adds a Haskell CLI (randstr) plus a Cabal package definition and HUnit test suite.
  • Updates repo tooling (Justfile) and .gitignore for Haskell build artifacts.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
haskell/src/Randstr/Types.hs Adds core ADTs for tokens/quantifiers/content.
haskell/src/Randstr/Tokenizer.hs Implements regex-like parsing into tokens (classes, groups, quantifiers, properties).
haskell/src/Randstr/Generator.hs Generates strings from tokens, including groups/alternation and quantifiers.
haskell/src/Randstr/CharClasses.hs Defines character sets and random selection helpers; Unicode property ranges.
haskell/src/Randstr/Config.hs Defines generation config and RNG helpers.
haskell/src/Randstr/Utils.hs Adds ordered de-duplication helper.
haskell/src/Randstr/Lib.hs Public API surface for library consumers.
haskell/app/Main.hs CLI implementation and env-var integration.
haskell/test/Spec.hs Adds HUnit test coverage for core behaviors.
haskell/randstr.cabal Cabal package config for lib/exe/tests.
Justfile Adds hs-build/hs-test/hs-clean targets.
.gitignore Ignores Cabal/GHC build artifacts.

Comment on lines +38 to +41
let vec = V.fromList options
charFunc cfg0 = CC.vectorRandomRef vec cfg0
(chars, cfg') = applyQuantifierFunc charFunc (tokenQuantifier tok) cfg
in (chars, env, cfg')
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CharClass tokens can have an empty options list (e.g., unknown POSIX class like [[:foo:]] or other parsing edge-cases). When options is empty, vectorRandomRef ultimately calls configRandom 0, which will crash. Handle the empty case explicitly (e.g., treat it as a literal error, fall back to a default char, or ensure the tokenizer never returns empty classes).

Suggested change
let vec = V.fromList options
charFunc cfg0 = CC.vectorRandomRef vec cfg0
(chars, cfg') = applyQuantifierFunc charFunc (tokenQuantifier tok) cfg
in (chars, env, cfg')
case options of
[] ->
-- Defensive: avoid crashing on empty character classes (e.g., unknown POSIX classes).
("", env, cfg)
_ ->
let vec = V.fromList options
charFunc cfg0 = CC.vectorRandomRef vec cfg0
(chars, cfg') = applyQuantifierFunc charFunc (tokenQuantifier tok) cfg
in (chars, env, cfg')

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +64
-- | Generate n strings, threading the config through.
generateN :: String -> Int -> Config -> [String]
generateN _ 0 _ = []
generateN pattern n cfg =
let tokens = tokenizePattern pattern
(result, cfg') = generateFromTokens tokens cfg
in result : generateN pattern (n - 1) cfg'
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generateN only stops at n == 0; if callers pass a negative n, this will recurse forever. Add a guard/base case for n <= 0 and consider validating n in randstrN/randstrNWith to return an error for invalid counts.

Copilot uses AI. Check for mistakes.
posixClassToChars "print" = printableChars
posixClassToChars "punct" = punctuationChars
posixClassToChars "xdigit" = hexDigitChars
posixClassToChars _ = [] -- Unknown POSIX class
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

posixClassToChars _ = [] means unknown POSIX classes produce an empty character class, which later can crash generation (empty options) and also differs from the Racket implementation (which falls back to treating an invalid POSIX class start as literals). Consider changing POSIX parsing to fall back to literal characters when the class name is unknown/invalid, or otherwise ensure the resulting class is never empty.

Suggested change
posixClassToChars _ = [] -- Unknown POSIX class
-- Fallback for unknown POSIX classes: use all ASCII characters to avoid empty classes.
posixClassToChars _ = asciiChars

Copilot uses AI. Check for mistakes.
-- Returns (randomValue, newConfig).
configRandom :: Int -> Config -> (Int, Config)
configRandom n cfg =
let (val, newGen) = uniformR (0, n - 1) (cfgGen cfg)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

configRandom will throw when n <= 0 (it calls uniformR (0, n-1)). Several call sites can pass 0 (e.g., random selection from an empty list/vector) and CLI allows negative cfgMaxRepeat which can make maxR + 1 be 0. Guard against non-positive n (either clamp to 1, return 0 deterministically, or change the API to return Maybe/Either and propagate errors).

Suggested change
let (val, newGen) = uniformR (0, n - 1) (cfgGen cfg)
let safeN = if n <= 0 then 1 else n
(val, newGen) = uniformR (0, safeN - 1) (cfgGen cfg)

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +51
((v, ""):_) -> go (opts { optCount = v }) rest
_ -> Left $ "Invalid count: " ++ n
go opts ("--count":n:rest) = case reads n :: [(Int, String)] of
((v, ""):_) -> go (opts { optCount = v }) rest
_ -> Left $ "Invalid count: " ++ n
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-n/--count accepts 0 or negative values. With count <= 0, randstrNWith calls generateN with a negative n, which never hits the n == 0 base case and will recurse indefinitely. Validate optCount as >= 1 during argument parsing and return a clear error when invalid.

Suggested change
((v, ""):_) -> go (opts { optCount = v }) rest
_ -> Left $ "Invalid count: " ++ n
go opts ("--count":n:rest) = case reads n :: [(Int, String)] of
((v, ""):_) -> go (opts { optCount = v }) rest
_ -> Left $ "Invalid count: " ++ n
((v, ""):_) | v >= 1 -> go (opts { optCount = v }) rest
_ -> Left $ "Invalid count (must be >= 1): " ++ n
go opts ("--count":n:rest) = case reads n :: [(Int, String)] of
((v, ""):_) | v >= 1 -> go (opts { optCount = v }) rest
_ -> Left $ "Invalid count (must be >= 1): " ++ n

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +56
((v, ""):_) -> go (opts { optMaxRepeat = Just v }) rest
_ -> Left $ "Invalid max-repeat: " ++ n
go opts ("--max-repeat":n:rest) = case reads n :: [(Int, String)] of
((v, ""):_) -> go (opts { optMaxRepeat = Just v }) rest
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-m/--max-repeat accepts negative values. A negative cfgMaxRepeat can make configRandom (maxR + 1) evaluate to configRandom 0, which crashes. Validate max-repeat as >= 0 (or clamp it) in argument parsing.

Suggested change
((v, ""):_) -> go (opts { optMaxRepeat = Just v }) rest
_ -> Left $ "Invalid max-repeat: " ++ n
go opts ("--max-repeat":n:rest) = case reads n :: [(Int, String)] of
((v, ""):_) -> go (opts { optMaxRepeat = Just v }) rest
((v, ""):_) | v >= 0 -> go (opts { optMaxRepeat = Just v }) rest
_ -> Left $ "Invalid max-repeat: " ++ n
go opts ("--max-repeat":n:rest) = case reads n :: [(Int, String)] of
((v, ""):_) | v >= 0 -> go (opts { optMaxRepeat = Just v }) rest

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +36
, " -n, --count N Generate N strings (default: 1)"
, " -m, --max-repeat N Maximum repetition for * and + (default: env RANDSTR_MAX_REPEAT or 5)"
, " -s, --secure Use cryptographically secure random number generator"
, " -h, --help Show this help message"
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI help and config field claim “cryptographically secure random”, but cfgSecureRandom is never used and all randomness comes from StdGen/uniformR. Either implement a secure RNG path (and thread it through Config), or remove/rename the option and documentation to avoid misleading users.

Copilot uses AI. Check for mistakes.
Comment on lines +216 to +226
in (NormalRange (max 0 n1) (max 1 n2) (plusCount + 1), [])
parseSecondNumber digits1 digits2 ('}':rest) plusCount =
let n1 = readInt (reverse digits1)
n2 = readInt (reverse digits2)
in (NormalRange (max 0 n1) (max 1 n2) (plusCount + 1), rest)
parseSecondNumber digits1 digits2 (c:rest) plusCount
| c >= '0' && c <= '9' = parseSecondNumber digits1 (c : digits2) rest plusCount
| otherwise =
let n1 = readInt (reverse digits1)
n2 = readInt (reverse digits2)
in (NormalRange (max 0 n1) (max 1 n2) (plusCount + 1), c:rest)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normal-range quantifier parsing clamps n2 with max 1 n2, which changes semantics vs the Racket implementation: an explicit upper bound of 0 (e.g., {+0} / {n1+0}) should remain 0, not be forced to 1. Only a missing second number should default to 1.

Suggested change
in (NormalRange (max 0 n1) (max 1 n2) (plusCount + 1), [])
parseSecondNumber digits1 digits2 ('}':rest) plusCount =
let n1 = readInt (reverse digits1)
n2 = readInt (reverse digits2)
in (NormalRange (max 0 n1) (max 1 n2) (plusCount + 1), rest)
parseSecondNumber digits1 digits2 (c:rest) plusCount
| c >= '0' && c <= '9' = parseSecondNumber digits1 (c : digits2) rest plusCount
| otherwise =
let n1 = readInt (reverse digits1)
n2 = readInt (reverse digits2)
in (NormalRange (max 0 n1) (max 1 n2) (plusCount + 1), c:rest)
in (NormalRange (max 0 n1) (max 0 n2) (plusCount + 1), [])
parseSecondNumber digits1 digits2 ('}':rest) plusCount =
let n1 = readInt (reverse digits1)
n2 = readInt (reverse digits2)
in (NormalRange (max 0 n1) (max 0 n2) (plusCount + 1), rest)
parseSecondNumber digits1 digits2 (c:rest) plusCount
| c >= '0' && c <= '9' = parseSecondNumber digits1 (c : digits2) rest plusCount
| otherwise =
let n1 = readInt (reverse digits1)
n2 = readInt (reverse digits2)
in (NormalRange (max 0 n1) (max 0 n2) (plusCount + 1), c:rest)

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +40
configRandom n cfg =
let (val, newGen) = uniformR (0, n - 1) (cfgGen cfg)
in (val, cfg { cfgGen = newGen })

-- | Generate a random floating point number in [0, 1).
-- Returns (randomValue, newConfig).
-- The upper bound ensures we never reach exactly 1.0.
configRandomReal :: Config -> (Double, Config)
configRandomReal cfg =
let upperBound = 1.0 - 2.220446049250313e-16 -- 1.0 - machine epsilon for Double
(val, newGen) = uniformR (0.0 :: Double, upperBound) (cfgGen cfg)
in (val, cfg { cfgGen = newGen })
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cfgSecureRandom flag (and the CLI --secure/RANDSTR_SECURE options that set it) advertise a cryptographically secure mode, but both configRandom and configRandomReal always use StdGen/uniformR and never branch on cfgSecureRandom, so all randomness remains non-cryptographic. If callers rely on this mode to generate secrets such as passwords, tokens, or API keys, an attacker who can observe outputs can feasibly brute-force or predict the PRNG state and derive other generated values. Implement a separate CSPRNG-backed random source that is selected whenever cfgSecureRandom is true (and threaded through all generators), or remove the secure option to avoid giving a false sense of security.

Suggested change
configRandom n cfg =
let (val, newGen) = uniformR (0, n - 1) (cfgGen cfg)
in (val, cfg { cfgGen = newGen })
-- | Generate a random floating point number in [0, 1).
-- Returns (randomValue, newConfig).
-- The upper bound ensures we never reach exactly 1.0.
configRandomReal :: Config -> (Double, Config)
configRandomReal cfg =
let upperBound = 1.0 - 2.220446049250313e-16 -- 1.0 - machine epsilon for Double
(val, newGen) = uniformR (0.0 :: Double, upperBound) (cfgGen cfg)
in (val, cfg { cfgGen = newGen })
configRandom n cfg
| cfgSecureRandom cfg =
error "Secure random mode (cfgSecureRandom=True) is not implemented in this build; cryptographically secure random generation is unavailable."
| otherwise =
let (val, newGen) = uniformR (0, n - 1) (cfgGen cfg)
in (val, cfg { cfgGen = newGen })
-- | Generate a random floating point number in [0, 1).
-- Returns (randomValue, newConfig).
-- The upper bound ensures we never reach exactly 1.0.
configRandomReal :: Config -> (Double, Config)
configRandomReal cfg
| cfgSecureRandom cfg =
error "Secure random mode (cfgSecureRandom=True) is not implemented in this build; cryptographically secure random generation is unavailable."
| otherwise =
let upperBound = 1.0 - 2.220446049250313e-16 -- 1.0 - machine epsilon for Double
(val, newGen) = uniformR (0.0 :: Double, upperBound) (cfgGen cfg)
in (val, cfg { cfgGen = newGen })

Copilot uses AI. Check for mistakes.
@hugefiver
Copy link
Copy Markdown
Owner

@copilot apply changes based on the comments in this thread

…har class, secure mode error, fix NormalRange clamping

- Config.hs: Guard configRandom against n<=0 (clamp to 1); error on cfgSecureRandom=True
- Generator.hs: Handle empty options in CharClass to avoid crash
- Tokenizer.hs: Unknown POSIX classes fall back to asciiChars; NormalRange uses max 0 n2
- Lib.hs: generateN handles n<=0 safely
- Main.hs: Validate -n>=1, -m>=0; update help text about secure mode

Co-authored-by: hugefiver <18693500+hugefiver@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 7, 2026

@copilot apply changes based on the comments in this thread

Applied all 9 review comments in commit 358ab89:

  • Config.hs: configRandom now clamps n <= 0 to 1; both configRandom and configRandomReal error when cfgSecureRandom = True (since CSPRNG is not implemented)
  • Generator.hs: Empty CharClass options return "" instead of crashing
  • Tokenizer.hs: Unknown POSIX classes fall back to asciiChars; NormalRange uses max 0 n2 instead of max 1 n2
  • Lib.hs: generateN handles n <= 0 safely
  • Main.hs: -n/--count validated >= 1, -m/--max-repeat validated >= 0, help text updated for --secure

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 7 comments.

Comment on lines +61 to +65
testNonWhitespaceChar :: Test
testNonWhitespaceChar = TestCase $ do
let result = genWith "\\S+"
assertBool "\\S+ produces non-empty string" (not (null result))

Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only checks for non-emptiness, but it doesn't verify that \\S+ generates non-whitespace characters. Please assert that all characters in result are not in the whitespace set (space/tab/newline/CR).

Copilot uses AI. Check for mistakes.
Comment on lines +115 to +118
testPosixBlank :: Test
testPosixBlank = TestCase $
let _result = genWith "[[:blank:]]*"
in assertBool "[:blank:]* produces string" True
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't assert any property of the output besides "True", so it won't catch regressions in [[:blank:]]*. Please assert that the result contains only blank characters (space/tab) (and optionally that its length is within expected bounds for *).

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +72
-- | Read a positive integer from an environment variable.
envToPositiveInteger :: String -> IO (Maybe Int)
envToPositiveInteger name = do
val <- lookupEnv name
return $ case val of
Nothing -> Nothing
Just "" -> Nothing
Just v -> case reads v :: [(Int, String)] of
((n, ""):_) | n > 0 -> Just n
_ -> Nothing
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--max-repeat accepts 0, but RANDSTR_MAX_REPEAT is parsed via envToPositiveInteger which requires n > 0. If a user sets RANDSTR_MAX_REPEAT=0 to disallow repetition, it'll be ignored and the default (5) will be used instead. Consider allowing 0 here (and updating the helper name/comment accordingly) to keep env var behavior consistent with CLI parsing.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +25
import Randstr.Types ()
import Randstr.Config (Config(..), defaultConfig)
import Randstr.Tokenizer
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Randstr.Types () appears to be a redundant import (nothing is referenced from it, and it doesn't look like it's only being imported for instances). With -Wall, this is likely to produce an unused-import warning; please remove it or import the specific identifiers you actually need.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +60
testWhitespaceChar :: Test
testWhitespaceChar = TestCase $
let _result = genWith "\\s*"
in assertBool "\\s* produces string" True -- may be empty due to *

Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't assert any property of the output besides "True", so it won't catch regressions (e.g., if \\s* started producing non-whitespace). Please assert something meaningful, such as that the result consists only of whitespace characters (and optionally that its length is within the configured max repeat bounds).

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +55
testNonWordChar :: Test
testNonWordChar = TestCase $ do
let result = genWith "\\W+"
assertBool "\\W+ produces non-empty string" (not (null result))

Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test currently only checks for non-emptiness, but it doesn't verify that \\W+ actually generates non-word characters. To make the test effective, assert that every character is neither alphanumeric nor underscore.

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +124
testPosixSpace :: Test
testPosixSpace = TestCase $ do
let result = genWith "[[:space:]]+"
assertBool "[:space:]+ produces non-empty string" (not (null result))

Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test checks that [[:space:]]+ is non-empty, but it doesn't verify the generated characters are actually whitespace. Please assert that every character is in the expected whitespace set (space/tab/newline/CR) or use Data.Char.isSpace if that matches the library semantics.

Copilot uses AI. Check for mistakes.
@hugefiver
Copy link
Copy Markdown
Owner

@copilot apply changes based on the comments in this thread

…stency, remove redundant import

- Spec.hs: \W+ test verifies non-word chars, \s* verifies whitespace only,
  \S+ verifies non-whitespace, [[:blank:]]* verifies blank chars,
  [[:space:]]+ verifies space chars
- Main.hs: Rename envToPositiveInteger to envToNonNegativeInteger (allows 0)
  for RANDSTR_MAX_REPEAT consistency with CLI -m flag
- Lib.hs: Remove redundant Randstr.Types () import

Co-authored-by: hugefiver <18693500+hugefiver@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 9, 2026

@copilot apply changes based on the comments in this thread

Applied all 7 review comments in commit 64d1509:

  • Spec.hs: \W+ now verifies non-word chars, \s* verifies whitespace only, \S+ verifies non-whitespace, [[:blank:]]* verifies blank chars (space/tab), [[:space:]]+ verifies space chars
  • Main.hs: Renamed envToPositiveIntegerenvToNonNegativeInteger (allows RANDSTR_MAX_REPEAT=0, consistent with CLI -m 0)
  • Lib.hs: Removed redundant Randstr.Types () import

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants