Skip to content

Conversation

@gandalf-repo
Copy link

Cloned Pull Request

This PR is a clone of the original PR: n8n-io/n8n#17925

Original Author: mutdmour
Original PR Number: #17925
Original Repository: n8n-io/n8n


Original Description:

Summary

Add more resources and operations to Todoist node

Screen.Recording.2025-08-03.at.15.40.57.mov

Related Linear tickets, Github issues, and Community forum posts

Review / Merge checklist

  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with release/backport (if the PR is an urgent fix that needs to be backported)

Files Changed:

  • modified: packages/nodes-base/nodes/Todoist/GenericFunctions.ts
  • modified: packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts
  • modified: packages/nodes-base/nodes/Todoist/v2/Service.ts
  • modified: packages/nodes-base/nodes/Todoist/v2/TodoistV2.node.ts
  • added: packages/nodes-base/nodes/Todoist/v2/test/OperationHandler.test.ts
  • added: packages/nodes-base/nodes/Todoist/v2/test/TodoistV2.node.test.ts
  • added: packages/nodes-base/nodes/Todoist/v2/test/workflow.json
  • added: packages/nodes-base/utils/__tests__/types.test.ts
  • modified: packages/nodes-base/utils/types.ts

This PR was automatically cloned for AI code review benchmarking purposes.

Original PR: n8n-io/n8n#17925
Author: mutdmour

## Summary

Add more resources and operations to Todoist node

https://github.com/user-attachments/assets/4bbee79f-ea0e-4c20-9e34-e00add862cd9

<!--
Describe what the PR does and how to test.
Photos and videos are recommended.
-->

## Related Linear tickets, Github issues, and Community forum posts

<!--
Include links to **Linear ticket** or Github issue or Community forum post.
Important in order to close *automatically* and provide context to reviewers.
https://linear.app/n8n/issue/
-->
<!-- Use "closes #<issue-number>", "fixes #<issue-number>", or "resolves #<issue-number>" to automatically close issues when the PR is merged. -->

## Review / Merge checklist

- [ ] PR title and summary are descriptive. ([conventions](../blob/master/.github/pull_request_title_conventions.md)) <!--
   **Remember, the title automatically goes into the changelog.
   Use `(no-changelog)` otherwise.**
-->
- [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created.
- [ ] Tests included. <!--
   A bug is not considered fixed, unless a test is added to prevent it from happening again.
   A feature is not complete without tests.
-->
- [ ] PR Labeled with `release/backport` (if the PR is an urgent fix that needs to be backported)

---
This is a cloned PR for AI code review benchmarking.
@refacto-bot
Copy link

refacto-bot bot commented Aug 5, 2025

🤖 Refacto.AI: PR Summary & Reviewer Guide

🎯 1. The Goal (Why was this PR raised?)

This PR aims to enhance the Todoist Node by adding more resources and operations to the existing functionality. The implementation expands the node's capabilities beyond basic task management to include projects, sections, comments, and labels.

🗺️ 2. The Changes (What was implemented?)

This section provides a walkthrough of the changes, connecting them to the PR's goals.

File Name Implementation Details Goal / Requirement Addressed
packages/nodes-base/utils/types.ts (Added) Added a new utility file with type assertion functions for validating parameters, including string, number, array types, and complex object validation. Add Parameter Validation
packages/nodes-base/nodes/Todoist/v2/Service.ts (Modified) Expanded service class to handle additional resource types (projects, sections, comments, labels) with new operation types and handler methods. Add Support for New Resources
packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts (Modified) Extended the existing handlers and added new handler classes for all new resource types (projects, sections, comments, labels). Added parameter validation using the new types utility. Implement New Operation Handlers
packages/nodes-base/nodes/Todoist/v2/test/TodoistV2.node.test.ts (Added) Added integration tests for the Todoist node with mock data for all resources and operations. Add Tests for New Functionality
packages/nodes-base/nodes/Todoist/v2/test/OperationHandler.test.ts (Added) Added comprehensive unit tests for all operation handlers including task, project, section, comment, and label operations. Add Unit Tests for Handlers
packages/nodes-base/nodes/Todoist/v2/test/workflow.json (Added) Added a test workflow configuration file to test all the new operations and resources. Add Test Workflow Configuration
packages/nodes-base/nodes/Todoist/v2/TodoistV2.node.ts (Modified) Updated the node definition to include new resources (project, section, comment, label) and their operations. Added UI parameters for all new operations. Update Node Definition
packages/nodes-base/nodes/Todoist/GenericFunctions.ts (Modified) Modified the todoistSyncRequest function to accept an endpoint parameter for more flexible API calls. Enhance API Request Functionality
packages/nodes-base/utils/tests/types.test.ts (Added) Added tests for the new type assertion utility functions. Add Tests for Type Utilities

🤔 3. Key Areas for Human Review

Here are the most important areas to focus your review on, including specific testing instructions.

Area of Concern: Type Validation Implementation

  • File: packages/nodes-base/utils/types.ts (All lines)
  • Why: This is a new utility file that provides core type validation functionality used throughout the implementation. Any issues here could affect parameter validation across all operations.
  • Testing Instruction: Test the validation functions with edge cases like null values, undefined values, and incorrect types to ensure they properly validate and throw appropriate errors.

Area of Concern: Service Class Expansion

  • File: packages/nodes-base/nodes/Todoist/v2/Service.ts (Lines 7-44, 60-163)
  • Why: The service class has been significantly expanded to handle multiple resource types. The new handler methods and type definitions are critical for the node's functionality.
  • Testing Instruction: Test each resource type (task, project, section, comment, label) with at least one operation to verify the service correctly routes operations to the appropriate handlers.

Area of Concern: Node Definition Updates

  • File: packages/nodes-base/nodes/Todoist/v2/TodoistV2.node.ts (Lines 101-1622)
  • Why: The node definition has been extensively updated with new UI parameters and operation options. This directly impacts the user experience when configuring the node.
  • Testing Instruction: In the n8n UI, create a workflow with the Todoist node and verify that all resources and their operations are available and properly configured with appropriate parameters.

Area of Concern: Operation Handler Implementation

  • File: packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts (Lines 45-980)
  • Why: This file contains complex implementation logic for all operations across different resources. The handlers include parameter validation and API call construction.
  • Testing Instruction: Test operations that involve complex parameter handling, such as the quickAdd operation and operations that accept multiple parameter types, to ensure they correctly process and validate inputs.

@gandalf-repo
Copy link
Author

/do-refacto

Please review this code for potential issues, security vulnerabilities, and improvements.

Review triggered at: 2025-08-05T10:44:23.121Z

@refacto-bot
Copy link

refacto-bot bot commented Aug 5, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

@refacto-bot
Copy link

refacto-bot bot commented Aug 5, 2025

Strong Type Safety Foundation - Security & Reliability Improvements Needed

Review Summary

This PR introduces valuable type validation utilities and extends the Todoist API with new resources (projects, sections, comments, labels) in a consistent way. The implementation shows good architectural thinking with proper separation of concerns. However, our analysis identified several important issues that need addressing before merging - particularly around type safety, error handling, and security validation. The most critical concerns involve potential type assertion bypasses and inconsistent error handling that could lead to runtime failures.

Well Done!!!

  • The introduction of strong type assertions significantly improves code safety and maintainability by ensuring parameters match expected types
  • The PR properly extends the Todoist API with well-structured handlers for new resources in a consistent way
  • The comprehensive test coverage with both unit tests and integration tests demonstrates good engineering practices

Files Processed

packages/nodes-base/utils/types.ts 1-167
packages/nodes-base/nodes/Todoist/v2/Service.ts 7-127
packages/nodes-base/nodes/Todoist/v2/TodoistV2.node.ts 1622-1643
packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts 1-139

📌 Additional Comments (5)

Security Enhancement

Insufficient Validation in assertIsResourceLocator Function

Explanation: The assertIsResourceLocator function checks for the presence of required properties but doesn't validate their types. An attacker could craft an object with the required properties but with unexpected types, potentially leading to type errors or security issues when these values are used elsewhere in the application.

function assertIsResourceLocator(parameterName: string, value: unknown): void {
	assert(
		typeof value === 'object' &&
			value !== null &&
			'__rl' in value &&
			'mode' in value &&
			'value' in value,
+ function assertIsResourceLocator(parameterName: string, value: unknown): void {
+ 	assert(
+ 		typeof value === 'object' &&
+ 			value !== null &&
+ 			'__rl' in value &&
+ 			typeof (value as any).__rl === 'boolean' &&
+ 			'mode' in value &&
+ 			typeof (value as any).mode === 'string' &&
+ 			'value' in value,

Reliability Enhancement

Missing Exhaustiveness Check in Type Guards

Explanation: The type guard functions for different operation types cast the input string to the expected type before checking if it's included in the operations array. This approach bypasses TypeScript's type checking and could lead to runtime errors if the operation arrays are modified without updating the type definitions. Additionally, there's no type guard for TaskOperationType, which is inconsistent with the other resource types and could lead to confusion and potential bugs.

export function isProjectOperationType(operation: string): operation is ProjectOperationType {
  return PROJECT_OPERATIONS.includes(operation as ProjectOperationType);
}

export function isSectionOperationType(operation: string): operation is SectionOperationType {
  return SECTION_OPERATIONS.includes(operation as SectionOperationType);
}

export function isCommentOperationType(operation: string): operation is CommentOperationType {
  return COMMENT_OPERATIONS.includes(operation as CommentOperationType);
}

export function isLabelOperationType(operation: string): operation is LabelOperationType {
  return LABEL_OPERATIONS.includes(operation as LabelOperationType);
}
+ export function isTaskOperationType(operation: string): operation is TaskOperationType {
+   return (TASK_OPERATIONS as readonly string[]).includes(operation);
+ }
+ 
+ // Helper function to ensure exhaustive checking in switch statements
+ export function assertNever(x: never): never {
+   throw new Error(`Unexpected object: ${x}`);
+ }

Performance Optimization

Redundant Type Checking in Validation Functions

Explanation: The validation functions perform redundant type checking with separate functions for each validation step. This creates unnecessary function call overhead, especially in the validateParameterAgainstTypes function which iterates through types and calls validateParameterType for each one. For large objects with many parameters, this can lead to hundreds of function calls that could be optimized with a more direct approach.

function assertIsValidObject(value: unknown): asserts value is Record<string, unknown> {
	assert(typeof value === 'object' && value !== null, 'Value is not a valid object');
}

function assertIsRequiredParameter(
	parameterName: string,
	value: unknown,
	isOptional: boolean,
): void {
	if (!isOptional && value === undefined) {
		assert(false, `Required parameter \"${parameterName}\" is missing`);
	}
}
+ function validateParameter(
+ 	parameterName: string,
+ 	value: unknown,
+ 	type: ParameterType | ParameterType[],
+ 	isOptional: boolean = false
+ ): boolean {
+ 	// Check required parameters
+ 	if (!isOptional && value === undefined) {
+ 		assert(false, `Required parameter \"${parameterName}\" is missing`);
+ 	}
+ 
+ 	// Skip validation for optional undefined parameters
+ 	if (value === undefined) return true;
+ 
+ 	// Handle multiple types
+ 	const types = Array.isArray(type) ? type : [type];
+ 	let isValid = false;
+ 
+ 	// Validate against each possible type
+ 	for (const singleType of types) {
+ 		// Resource locator validation
+ 		if (singleType === 'resource-locator') {
+ 			isValid = typeof value === 'object' && value !== null && '__rl' in value && 
+ 				'mode' in value && 'value' in value;
+ 		}
+ 		// Object validation
+ 		else if (singleType === 'object') {
+ 			isValid = typeof value === 'object' && value !== null;
+ 		}

Maintainability Enhancement

Complex Type Assertion Functions with Duplicated Logic

Explanation: The type validation system in types.ts has multiple functions with duplicated logic for type checking. The functions assertIsResourceLocator, assertIsObjectType, assertIsPrimitiveType, and assertIsArrayType all perform similar validation tasks with slightly different implementations. This approach increases code complexity and maintenance burden as changes to validation logic must be made in multiple places.

type ParameterType =
	| 'string'
	| 'boolean'
	| 'number'
	| 'resource-locator'
	| 'string[]'
	| 'number[]'
	| 'boolean[]'
	| 'object';

function assertIsValidObject(value: unknown): asserts value is Record<string, unknown> {
	assert(typeof value === 'object' && value !== null, 'Value is not a valid object');
}

function assertIsRequiredParameter(
	parameterName: string,
	value: unknown,
	isOptional: boolean,
): void {
	if (!isOptional && value === undefined) {
		assert(false, `Required parameter \"${parameterName}\" is missing`);
	}
}

function assertIsResourceLocator(parameterName: string, value: unknown): void {
	assert(
		typeof value === 'object' &&
			value !== null &&
			'__rl' in value &&
			'mode' in value &&
			'value' in value,
		`Parameter \"${parameterName}\" is not a valid resource locator object`,
	);
}
+ function validateParameterType(
+ 	parameterName: string,
+ 	value: unknown,
+ 	type: ParameterType,
+ ): boolean {
+ 	try {
+ 		switch (type) {
+ 			case 'resource-locator':
+ 				assert(
+ 					typeof value === 'object' &&
+ 						value !== null &&
+ 						'__rl' in value &&
+ 						'mode' in value &&
+ 						'value' in value,
+ 					`Parameter \"${parameterName}\" is not a valid resource locator object`,
+ 				);
+ 				break;
+ 			case 'object':
+ 				assert(
+ 					typeof value === 'object' && value !== null,
+ 					`Parameter \"${parameterName}\" is not a valid object`,
+ 				);
+ 				break;
+ 			case 'string[]':
+ 				assertIsArray(parameterName, value, (val): val is string => typeof val === 'string');
+ 				break;
+ 			case 'number[]':
+ 				assertIsArray(parameterName, value, (val): val is number => typeof val === 'number');
+ 				break;
+ 			case 'boolean[]':
+ 				assertIsArray(parameterName, value, (val): val is boolean => typeof val === 'boolean');
+ 				break;
+ 			default:
+ 				assert(typeof value === type, `Parameter \"${parameterName}\" is not a valid ${type}`);
+ 		}
+ 		return true;
+ 	} catch {
+ 		return false;
+ 	}
+ }

Redundant Type Definitions in Service.ts

Explanation: The Service.ts file defines separate arrays for operations of each resource type (TASK_OPERATIONS, PROJECT_OPERATIONS, etc.), but there's significant duplication between these arrays. The SECTION_OPERATIONS, COMMENT_OPERATIONS, and LABEL_OPERATIONS arrays all contain the exact same operations ('create', 'delete', 'get', 'getAll', 'update'). This duplication makes maintenance more difficult as adding a new common operation would require changes to multiple arrays.

// Define operations as const arrays - source of truth
const TASK_OPERATIONS = [
	'create',
	'close',
	'delete',
	'get',
	'getAll',
	'reopen',
	'update',
	'move',
	'sync',
	'quickAdd',
] as const;

const PROJECT_OPERATIONS = [
	'create',
	'delete',
	'get',
	'getAll',
	'update',
	'archive',
	'unarchive',
	'getCollaborators',
] as const;

const SECTION_OPERATIONS = ['create', 'delete', 'get', 'getAll', 'update'] as const;

const COMMENT_OPERATIONS = ['create', 'delete', 'get', 'getAll', 'update'] as const;

const LABEL_OPERATIONS = ['create', 'delete', 'get', 'getAll', 'update'] as const;

// Derive types from arrays
export type TaskOperationType = (typeof TASK_OPERATIONS)[number];
export type ProjectOperationType = (typeof PROJECT_OPERATIONS)[number];
export type SectionOperationType = (typeof SECTION_OPERATIONS)[number];
export type CommentOperationType = (typeof COMMENT_OPERATIONS)[number];
export type LabelOperationType = (typeof LABEL_OPERATIONS)[number];
+ // Define common operations as source of truth
+ const COMMON_OPERATIONS = ['create', 'delete', 'get', 'getAll', 'update'] as const;
+ 
+ // Define resource-specific operations by extending common operations
+ const TASK_OPERATIONS = [
+ 	...COMMON_OPERATIONS,
+ 	'close',
+ 	'reopen',
+ 	'move',
+ 	'sync',
+ 	'quickAdd',
+ ] as const;
+ 
+ const PROJECT_OPERATIONS = [
+ 	...COMMON_OPERATIONS,
+ 	'archive',
+ 	'unarchive',
+ 	'getCollaborators',
+ ] as const;
+ 
+ // These resources use only the common operations
+ const SECTION_OPERATIONS = COMMON_OPERATIONS;
+ const COMMENT_OPERATIONS = COMMON_OPERATIONS;
+ const LABEL_OPERATIONS = COMMON_OPERATIONS;

Comment on lines +46 to +51
| 'string[]'
| 'number[]'
| 'boolean[]'
| 'object';

function assertIsValidObject(value: unknown): asserts value is Record<string, unknown> {
Copy link

Choose a reason for hiding this comment

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

Type Assertion Bypass in assertIsValidObject Function

The assertIsValidObject function only checks that the value is an object and not null, but doesn't verify that it's actually a Record<string, unknown>. This allows arrays and other object types to pass validation. An attacker could exploit this by passing an array or a specially crafted object that would bypass validation but cause unexpected behavior or potentially code execution when used as a Record<string, unknown> elsewhere in the code.

function assertIsValidObject(value: unknown): asserts value is Record<string, unknown> {
	assert(typeof value === 'object' && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype, 'Value is not a valid plain object');

References

Standard: CWE-20: Improper Input Validation
Standard: OWASP Top 10 2021: A03 - Injection

Comment on lines +101 to +117
parameterName: string,
value: unknown,
type: ParameterType,
): boolean {
try {
if (type === 'resource-locator') {
assertIsResourceLocator(parameterName, value);
} else if (type === 'object') {
assertIsObjectType(parameterName, value);
} else if (type.endsWith('[]')) {
assertIsArrayType(parameterName, value, type);
} else {
assertIsPrimitiveType(parameterName, value, type);
}
return true;
} catch {
return false;
Copy link

Choose a reason for hiding this comment

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

Unsafe Type Assertion in validateParameterType Function

The validateParameterType function silently catches all exceptions and returns false, potentially hiding critical errors. This could allow malicious input to bypass validation if an unexpected error occurs during validation. Additionally, the function doesn't properly validate complex types, relying on separate functions that might have their own vulnerabilities.

	} catch (error) {
		// Only catch assertion errors, not other unexpected errors
		if (error instanceof Error && error.name === 'AssertionError') {
			return false;
		}
		throw error; // Re-throw unexpected errors
	}

References

Standard: CWE-703: Improper Check or Handling of Exceptional Conditions
Standard: OWASP Top 10 2021: A03 - Injection

Comment on lines +47 to +56
| 'number[]'
| 'boolean[]'
| 'object';

function assertIsValidObject(value: unknown): asserts value is Record<string, unknown> {
assert(typeof value === 'object' && value !== null, 'Value is not a valid object');
}

function assertIsRequiredParameter(
parameterName: string,
Copy link

Choose a reason for hiding this comment

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

Inconsistent Error Handling in Type Validation

The assertIsRequiredParameter function only checks for undefined values but not for null values. This inconsistency with assertIsValidObject (which checks for both null and undefined) creates a reliability issue where null values might pass the required parameter check but fail later in type-specific validations. This can lead to confusing error messages and potential runtime failures when null values are processed as if they were valid.

function assertIsRequiredParameter(
  parameterName: string,
  value: unknown,
  isOptional: boolean,
): void {
  if (!isOptional && (value === undefined || value === null)) {
    assert(
      false,
      `Required parameter "${parameterName}" is ${value === null ? 'null' : 'missing'}`
    );
  }
}

References

Standard: ISO/IEC 25010 Functional Correctness - Precondition Validation

Comment on lines 1622 to +1643
const service = new TodoistService();
let responseData;
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0) as OperationType;
const operation = this.getNodeParameter('operation', 0) as TaskOperationType;
for (let i = 0; i < length; i++) {
try {
if (resource === 'task') {
responseData = await service.execute(this, operation, i);
responseData = await service.executeTask(this, operation, i);
} else if (resource === 'project') {
if (!isProjectOperationType(operation)) {
throw new NodeOperationError(
this.getNode(),
`Invalid operation '${operation}' for project resource`,
);
}
responseData = await service.executeProject(this, operation, i);
} else if (resource === 'section') {
if (!isSectionOperationType(operation)) {
throw new NodeOperationError(
this.getNode(),
`Invalid operation '${operation}' for section resource`,
);
Copy link

Choose a reason for hiding this comment

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

Unhandled Promise Rejection in TodoistV2.execute

The execute method doesn't handle potential errors from the service execution methods for project, section, comment, and label resources. While task operations have error handling, the other resource operations lack try-catch blocks, which could lead to unhandled promise rejections if an error occurs. This violates ISO/IEC 25010 Reliability requirements for fault tolerance and could cause the workflow to terminate unexpectedly.

        try {
          if (resource === 'task') {
            responseData = await service.executeTask(this, operation, i);
          } else if (resource === 'project') {
            if (!isProjectOperationType(operation)) {
              throw new NodeOperationError(
                this.getNode(),
                `Invalid operation '${operation}' for project resource`,
              );
            }
            responseData = await service.executeProject(this, operation, i);
          } else if (resource === 'section') {
            if (!isSectionOperationType(operation)) {
              throw new NodeOperationError(
                this.getNode(),
                `Invalid operation '${operation}' for section resource`,
              );
            }
            responseData = await service.executeSection(this, operation, i);
          } else if (resource === 'comment') {
            if (!isCommentOperationType(operation)) {
              throw new NodeOperationError(
                this.getNode(),
                `Invalid operation '${operation}' for comment resource`,
              );
            }
            responseData = await service.executeComment(this, operation, i);
          } else if (resource === 'label') {
            if (!isLabelOperationType(operation)) {
              throw new NodeOperationError(
                this.getNode(),
                `Invalid operation '${operation}' for label resource`,
              );
            }
            responseData = await service.executeLabel(this, operation, i);
          } else {
            throw new NodeOperationError(this.getNode(), `Unsupported resource: ${resource}`);
          }
        } catch (error) {
          if (error instanceof NodeOperationError) {
            throw error;
          }
          throw new NodeOperationError(this.getNode(), `Error executing ${resource} ${operation}: ${error.message}`, {
            itemIndex: i,
          });
        }

References

Standard: ISO/IEC 25010 Reliability - Fault Tolerance

Comment on lines +112 to +126
itemIndex: number,
): Promise<TodoistResponse> {
return await this.sectionHandlers[operation].handleOperation(ctx, itemIndex);
}

async executeComment(
ctx: Context,
operation: CommentOperationType,
itemIndex: number,
): Promise<TodoistResponse> {
return await this.commentHandlers[operation].handleOperation(ctx, itemIndex);
}

async executeLabel(
ctx: Context,
Copy link

Choose a reason for hiding this comment

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

Incorrect Type Guards in Resource Operation Functions

All type guard functions (isProjectOperationType, isSectionOperationType, isCommentOperationType, and isLabelOperationType) have a logical flaw in their implementation. They attempt to check if an operation string is valid, but first cast the string to the target type before checking if it's included in the array. This defeats the purpose of the type guard, as it will accept any string that's not in the array but cast it to the type anyway, which could lead to runtime errors when using operations that passed the guard but aren't actually valid.

export function isProjectOperationType(operation: string): operation is ProjectOperationType {
	return (PROJECT_OPERATIONS as readonly string[]).includes(operation);
}

export function isSectionOperationType(operation: string): operation is SectionOperationType {
	return (SECTION_OPERATIONS as readonly string[]).includes(operation);
}

export function isCommentOperationType(operation: string): operation is CommentOperationType {
	return (COMMENT_OPERATIONS as readonly string[]).includes(operation);
}

export function isLabelOperationType(operation: string): operation is LabelOperationType {
	return (LABEL_OPERATIONS as readonly string[]).includes(operation);

References

Standard: TypeScript Type Safety - Type Guards and Type Narrowing

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.

2 participants