diff --git a/.travis.yml b/.travis.yml
index f64813f711866a15db0e1bb43fd15ea2e0702ff5..835b621d0640ca510b1e545a75ff8841bf0d943c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ language: php
 php:
     - 7.2
     - 7.3
+    - 7.4
     - nightly
 
 cache:
diff --git a/composer.json b/composer.json
index 2b32574174f24a77d3572e40c1dd7f2e51a54583..8979e8f06e6c4839032193da917a958266915c37 100644
--- a/composer.json
+++ b/composer.json
@@ -20,17 +20,17 @@
         "ext-mbstring": "*",
         "paragonie/constant_time_encoding": "^2.0",
         "beberlei/assert": "^3.0",
-        "thecodingmachine/safe": "^0.1.14"
+        "thecodingmachine/safe": "^0.1.14|^1.0"
     },
     "require-dev": {
         "phpunit/phpunit": "^8.0",
         "php-coveralls/php-coveralls": "^2.0",
-        "phpstan/phpstan": "^0.11",
-        "phpstan/phpstan-beberlei-assert": "^0.11.0",
-        "phpstan/phpstan-deprecation-rules": "^0.11",
-        "phpstan/phpstan-phpunit": "^0.11",
-        "phpstan/phpstan-strict-rules": "^0.11",
-        "thecodingmachine/phpstan-safe-rule": "^0.1.0"
+        "phpstan/phpstan": "^0.12",
+        "phpstan/phpstan-beberlei-assert": "^0.12",
+        "phpstan/phpstan-deprecation-rules": "^0.12",
+        "phpstan/phpstan-phpunit": "^0.12",
+        "phpstan/phpstan-strict-rules": "^0.12",
+        "thecodingmachine/phpstan-safe-rule": "^1.0"
     },
     "suggest": {
     },
diff --git a/src/Factory.php b/src/Factory.php
index f5be85c0f8e457b2a9346d832b42ee2bdb6ee617..70df63945588b71f02959064dae491ad3b1b2588 100644
--- a/src/Factory.php
+++ b/src/Factory.php
@@ -41,6 +41,9 @@ final class Factory implements FactoryInterface
         return $otp;
     }
 
+    /**
+     * @param array<string, mixed> $data
+     */
     private static function populateParameters(OTPInterface &$otp, array $data): void
     {
         foreach ($data['query'] as $key => $value) {
@@ -48,6 +51,9 @@ final class Factory implements FactoryInterface
         }
     }
 
+    /**
+     * @param array<string, mixed> $data
+     */
     private static function populateOTP(OTPInterface &$otp, array $data): void
     {
         self::populateParameters($otp, $data);
@@ -66,6 +72,9 @@ final class Factory implements FactoryInterface
         $otp->setIssuer($result[0]);
     }
 
+    /**
+     * @param array<string, mixed> $data
+     */
     private static function checkData(array &$data): void
     {
         foreach (['scheme', 'host', 'path', 'query'] as $key) {
@@ -76,6 +85,9 @@ final class Factory implements FactoryInterface
         Assertion::keyExists($data['query'], 'secret', 'Not a valid OTP provisioning URI');
     }
 
+    /**
+     * @param array<string, mixed> $parsed_url
+     */
     private static function createOTP(array $parsed_url): OTPInterface
     {
         switch ($parsed_url['host']) {
diff --git a/src/HOTP.php b/src/HOTP.php
index 6facca0a6f7311f376113894a5c8ec1eba3d9e21..a2f4a23951a1210886aa2e6460210eeb8d42f893 100644
--- a/src/HOTP.php
+++ b/src/HOTP.php
@@ -84,11 +84,14 @@ final class HOTP extends OTP implements HOTPInterface
         return false;
     }
 
+    /**
+     * @return array<string, mixed>
+     */
     protected function getParameterMap(): array
     {
         $v = array_merge(
             parent::getParameterMap(),
-            ['counter' => function ($value) {
+            ['counter' => function ($value): int {
                 Assertion::greaterOrEqualThan((int) $value, 0, 'Counter must be at least 0.');
 
                 return (int) $value;
diff --git a/src/OTP.php b/src/OTP.php
index d117ae6837bbe2dbc8732325b6138ecc24bb1f6d..932bcf97e0329ec8bb9d6f730d7b201df328cb01 100644
--- a/src/OTP.php
+++ b/src/OTP.php
@@ -58,6 +58,9 @@ abstract class OTP implements OTPInterface
         return $this->generateOTP($timestamp);
     }
 
+    /**
+     * @param array<string, mixed> $options
+     */
     protected function filterOptions(array &$options): void
     {
         foreach (['algorithm' => 'sha1', 'period' => 30, 'digits' => 6] as $key => $default) {
@@ -69,6 +72,9 @@ abstract class OTP implements OTPInterface
         ksort($options);
     }
 
+    /**
+     * @param array<string, mixed> $options
+     */
     protected function generateURI(string $type, array $options): string
     {
         $label = $this->getLabel();
diff --git a/src/OTPInterface.php b/src/OTPInterface.php
index 40dfd5bc6afff6ce4625f9032378b088f1c9e3b2..66e163d5d78a36dc3470ff0f355323998972048c 100644
--- a/src/OTPInterface.php
+++ b/src/OTPInterface.php
@@ -72,6 +72,9 @@ interface OTPInterface
 
     public function hasParameter(string $parameter): bool;
 
+    /**
+     * @return array<string, mixed>
+     */
     public function getParameters(): array;
 
     /**
diff --git a/src/ParameterTrait.php b/src/ParameterTrait.php
index a65b4b9426040874361b4e57c9c5f15a69976d09..69fa774db7c3c22d25875b07841819966a79f6f7 100644
--- a/src/ParameterTrait.php
+++ b/src/ParameterTrait.php
@@ -21,7 +21,7 @@ use function Safe\sprintf;
 trait ParameterTrait
 {
     /**
-     * @var array
+     * @var array<string, mixed>
      */
     private $parameters = [];
 
@@ -40,6 +40,9 @@ trait ParameterTrait
      */
     private $issuer_included_as_parameter = true;
 
+    /**
+     * @return array<string, mixed>
+     */
     public function getParameters(): array
     {
         $parameters = $this->parameters;
@@ -141,6 +144,9 @@ trait ParameterTrait
         }
     }
 
+    /**
+     * @return array<string, mixed>
+     */
     protected function getParameterMap(): array
     {
         return [
@@ -149,7 +155,7 @@ trait ParameterTrait
 
                 return $value;
             },
-            'secret' => function ($value) {
+            'secret' => function ($value): string {
                 if (null === $value) {
                     $value = Base32::encodeUpper(random_bytes(64));
                 }
@@ -157,13 +163,13 @@ trait ParameterTrait
 
                 return $value;
             },
-            'algorithm' => function ($value) {
+            'algorithm' => function ($value): string {
                 $value = mb_strtolower($value);
                 Assertion::inArray($value, hash_algos(), sprintf('The "%s" digest is not supported.', $value));
 
                 return $value;
             },
-            'digits' => function ($value) {
+            'digits' => function ($value): int {
                 Assertion::greaterThan($value, 0, 'Digits must be at least 1.');
 
                 return (int) $value;
diff --git a/src/TOTP.php b/src/TOTP.php
index 9c0db82645cb70fd1a3a34f1c96c9a747ead48af..588b37f17cb450a7f70d46d461519b12415a0b20 100644
--- a/src/TOTP.php
+++ b/src/TOTP.php
@@ -119,17 +119,20 @@ final class TOTP extends OTP implements TOTPInterface
         return (int) floor(($timestamp - $this->getEpoch()) / $this->getPeriod());
     }
 
+    /**
+     * @return array<string, mixed>
+     */
     protected function getParameterMap(): array
     {
         $v = array_merge(
             parent::getParameterMap(),
             [
-                'period' => function ($value) {
+                'period' => function ($value): int {
                     Assertion::greaterThan((int) $value, 0, 'Period must be at least 1.');
 
                     return (int) $value;
                 },
-                'epoch' => function ($value) {
+                'epoch' => function ($value): int {
                     Assertion::greaterOrEqualThan((int) $value, 0, 'Epoch must be greater than or equal to 0.');
 
                     return (int) $value;
@@ -140,6 +143,9 @@ final class TOTP extends OTP implements TOTPInterface
         return $v;
     }
 
+    /**
+     * @param array<string, mixed> $options
+     */
     protected function filterOptions(array &$options): void
     {
         parent::filterOptions($options);
diff --git a/tests/TOTPTest.php b/tests/TOTPTest.php
index 07d4c3de078c71cbdb89adcb6595deff313d0881..ce955ebd51d2626cf2cd5331f522fc454a821ced 100644
--- a/tests/TOTPTest.php
+++ b/tests/TOTPTest.php
@@ -218,6 +218,8 @@ final class TOTPTest extends TestCase
     /**
      * @see https://tools.ietf.org/html/rfc6238#appendix-B
      * @see http://www.rfc-editor.org/errata_search.php?rfc=6238
+     *
+     * @return array<int, mixed[]>
      */
     public function dataVectors(): array
     {