mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
[c] Add check to codegen for setter/getter pairs with inconsistens nullability (one nullable, the other not)
This commit is contained in:
parent
92022e9c76
commit
65b138411c
@ -1,5 +1,6 @@
|
|||||||
import { isMethodExcluded } from "./exclusions";
|
import { isMethodExcluded } from "./exclusions";
|
||||||
import { type ClassOrStruct, type Exclusion, type Field, isPrimitive, type Method, type Type, toSnakeCase } from "./types";
|
import { type ClassOrStruct, type Exclusion, type Field, isPrimitive, type Method, type Type, toSnakeCase } from "./types";
|
||||||
|
import type { CClassOrStruct, CMethod } from "./c-types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for methods that have both const and non-const versions with different return types.
|
* Checks for methods that have both const and non-const versions with different return types.
|
||||||
@ -341,6 +342,105 @@ export function checkMethodTypeNameConflicts(classes: ClassOrStruct[], allTypes:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for getter/setter pairs where the return type nullability of the getter
|
||||||
|
* doesn't match the parameter nullability of the setter. This creates inconsistent
|
||||||
|
* APIs in target languages (e.g., Dart) where such mismatches are forbidden.
|
||||||
|
*
|
||||||
|
* @param cTypes - Array of generated C types with their methods
|
||||||
|
*/
|
||||||
|
export function checkGetterSetterNullabilityMismatch(cTypes: CClassOrStruct[]): void {
|
||||||
|
const mismatches: Array<{
|
||||||
|
typeName: string,
|
||||||
|
fieldName: string,
|
||||||
|
getterNullable: boolean,
|
||||||
|
setterNullable: boolean
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
for (const cType of cTypes) {
|
||||||
|
if (!cType.methods) continue;
|
||||||
|
|
||||||
|
// Group methods by field name (extract from method names like spine_type_get_field, spine_type_set_field)
|
||||||
|
const fieldAccessors = new Map<string, { getter?: CMethod, setter?: CMethod }>();
|
||||||
|
|
||||||
|
for (const method of cType.methods) {
|
||||||
|
// Check if this is a getter method (ends with _get_<field_name> AND has exactly 1 parameter)
|
||||||
|
const getterMatch = method.name.match(/^(.+)_get_(.+)$/);
|
||||||
|
if (getterMatch && method.parameters?.length === 1) {
|
||||||
|
const fieldName = getterMatch[2];
|
||||||
|
if (!fieldAccessors.has(fieldName)) {
|
||||||
|
fieldAccessors.set(fieldName, {});
|
||||||
|
}
|
||||||
|
fieldAccessors.get(fieldName)!.getter = method;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is a setter method (ends with _set_<field_name> AND has exactly 2 parameters)
|
||||||
|
const setterMatch = method.name.match(/^(.+)_set_(.+)$/);
|
||||||
|
if (setterMatch && method.parameters?.length === 2) {
|
||||||
|
const fieldName = setterMatch[2];
|
||||||
|
if (!fieldAccessors.has(fieldName)) {
|
||||||
|
fieldAccessors.set(fieldName, {});
|
||||||
|
}
|
||||||
|
fieldAccessors.get(fieldName)!.setter = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each getter/setter pair for nullability mismatches
|
||||||
|
for (const [fieldName, accessors] of fieldAccessors) {
|
||||||
|
const { getter, setter } = accessors;
|
||||||
|
|
||||||
|
// Skip if we don't have both getter and setter
|
||||||
|
if (!getter || !setter) continue;
|
||||||
|
|
||||||
|
// Extract nullability information
|
||||||
|
const getterNullable = getter.returnTypeNullable || false;
|
||||||
|
|
||||||
|
// For setters, find the parameter that's not 'self' (should be the value parameter)
|
||||||
|
const valueParam = setter.parameters?.find(p => p.name !== 'self');
|
||||||
|
if (!valueParam) continue;
|
||||||
|
|
||||||
|
const setterNullable = valueParam.isNullable || false;
|
||||||
|
|
||||||
|
// Check for mismatch
|
||||||
|
if (getterNullable !== setterNullable) {
|
||||||
|
mismatches.push({
|
||||||
|
typeName: cType.name,
|
||||||
|
fieldName,
|
||||||
|
getterNullable,
|
||||||
|
setterNullable
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report mismatches
|
||||||
|
if (mismatches.length > 0) {
|
||||||
|
console.error("\n" + "=".repeat(80));
|
||||||
|
console.error("GETTER/SETTER NULLABILITY MISMATCHES");
|
||||||
|
console.error("=".repeat(80));
|
||||||
|
console.error(`\nFound ${mismatches.length} getter/setter pairs with mismatched nullability:\n`);
|
||||||
|
|
||||||
|
for (const mismatch of mismatches) {
|
||||||
|
const getterType = mismatch.getterNullable ? "nullable" : "non-nullable";
|
||||||
|
const setterType = mismatch.setterNullable ? "nullable" : "non-nullable";
|
||||||
|
console.error(` - ${mismatch.typeName}::${mismatch.fieldName}`);
|
||||||
|
console.error(` Getter returns: ${getterType}`);
|
||||||
|
console.error(` Setter expects: ${setterType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error("\nThese nullability mismatches cause compilation errors in some target");
|
||||||
|
console.error("languages (e.g., Dart). The getter and setter must have consistent nullability.");
|
||||||
|
console.error("You should either:");
|
||||||
|
console.error(" 1. Ensure the C++ field type has consistent nullability semantics");
|
||||||
|
console.error(" 2. Exclude problematic field getters or setters in exclusions.txt");
|
||||||
|
console.error(" 3. Override nullability analysis for specific field types");
|
||||||
|
console.error("=".repeat(80) + "\n");
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for methods that return non-primitive types by value.
|
* Checks for methods that return non-primitive types by value.
|
||||||
* These cannot be wrapped in C without heap allocation.
|
* These cannot be wrapped in C without heap allocation.
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { CWriter } from './c-writer';
|
import { CWriter } from './c-writer';
|
||||||
import { checkConstNonConstConflicts, checkFieldAccessorConflicts, checkMethodTypeNameConflicts, checkMultiLevelPointers, checkValueReturns } from './checks';
|
import { checkConstNonConstConflicts, checkFieldAccessorConflicts, checkGetterSetterNullabilityMismatch, checkMethodTypeNameConflicts, checkMultiLevelPointers, checkValueReturns } from './checks';
|
||||||
import { isTypeExcluded, loadExclusions } from './exclusions';
|
import { isTypeExcluded, loadExclusions } from './exclusions';
|
||||||
import { generateArrays, generateTypes } from './ir-generator';
|
import { generateArrays, generateTypes } from './ir-generator';
|
||||||
import { extractTypes } from './type-extractor';
|
import { extractTypes } from './type-extractor';
|
||||||
@ -71,6 +71,9 @@ export async function generate() {
|
|||||||
const { cTypes, cEnums } = await generateTypes(types, exclusions, allExtractedTypes);
|
const { cTypes, cEnums } = await generateTypes(types, exclusions, allExtractedTypes);
|
||||||
const cArrayTypes = await generateArrays(types, arrayType, exclusions);
|
const cArrayTypes = await generateArrays(types, arrayType, exclusions);
|
||||||
|
|
||||||
|
// Check for getter/setter nullability mismatches
|
||||||
|
checkGetterSetterNullabilityMismatch(cTypes);
|
||||||
|
|
||||||
// Build interface/pure type information first
|
// Build interface/pure type information first
|
||||||
const isInterface = buildInterfaceMap(allExtractedTypes.filter(t => t.kind !== 'enum') as ClassOrStruct[]);
|
const isInterface = buildInterfaceMap(allExtractedTypes.filter(t => t.kind !== 'enum') as ClassOrStruct[]);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user