UNPKG

jsii-pacmak

Version:

A code generation framework for jsii backend languages

2,270 lines 95 kB
"use strict";
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _TypeCheckingHelper_stubs, _a, _TypeCheckingStub_PREFIX, _TypeCheckingStub_arguments, _TypeCheckingStub_hash;
Object.defineProperty(exports, "__esModule", { value: true });
const spec = require("@jsii/spec");
const assert = require("assert");
const codemaker_1 = require("codemaker");
const crypto = require("crypto");
const escapeStringRegexp = require("escape-string-regexp");
const fs = require("fs-extra");
const jsii_rosetta_1 = require("jsii-rosetta");
const path = require("path");
const generator_1 = require("../generator");
const logging_1 = require("../logging");
const markdown_1 = require("../markdown");
const target_1 = require("../target");
const util_1 = require("../util");
const version_1 = require("../version");
const _utils_1 = require("./_utils");
const type_name_1 = require("./python/type-name");
const util_2 = require("./python/util");
const version_utils_1 = require("./version-utils");
const index_1 = require("./index");
// eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-require-imports
const spdxLicenseList = require('spdx-license-list');
const requirementsFile = path.resolve(__dirname, 'python', 'requirements-dev.txt');
// we use single-quotes for multi-line strings to allow examples within the
// docstrings themselves to include double-quotes (see https://github.com/aws/jsii/issues/2569)
const DOCSTRING_QUOTES = "'''";
const RAW_DOCSTRING_QUOTES = `r${DOCSTRING_QUOTES}`;
class Python extends target_1.Target {
    constructor(options) {
        super(options);
        this.generator = new PythonGenerator(options.rosetta, options);
    }
    async generateCode(outDir, tarball) {
        await super.generateCode(outDir, tarball);
    }
    async build(sourceDir, outDir) {
        // Create a fresh virtual env
        const venv = await fs.mkdtemp(path.join(sourceDir, '.env-'));
        const venvBin = path.join(venv, process.platform === 'win32' ? 'Scripts' : 'bin');
        // On Windows, there is usually no python3.exe (the GitHub action workers will have a python3
        // shim, but using this actually results in a WinError with Python 3.7 and 3.8 where venv will
        // fail to copy the python binary if it's not invoked as python.exe). More on this particular
        // issue can be read here: https://bugs.python.org/issue43749
        await (0, util_1.shell)(process.platform === 'win32' ? 'python' : 'python3', [
            '-m',
            'venv',
            '--system-site-packages',
            venv,
        ]);
        const env = {
            ...process.env,
            PATH: `${venvBin}:${process.env.PATH}`,
            VIRTUAL_ENV: venv,
        };
        const python = path.join(venvBin, 'python');
        // Install the necessary things
        await (0, util_1.shell)(python, ['-m', 'pip', 'install', '--no-input', '-r', requirementsFile], {
            cwd: sourceDir,
            env,
            retry: { maxAttempts: 5 },
        });
        // Actually package up our code, both as a sdist and a wheel for publishing.
        await (0, util_1.shell)(python, ['setup.py', 'sdist', '--dist-dir', outDir], {
            cwd: sourceDir,
            env,
        });
        await (0, util_1.shell)(python, ['-m', 'pip', 'wheel', '--no-deps', '--wheel-dir', outDir, sourceDir], {
            cwd: sourceDir,
            env,
            retry: { maxAttempts: 5 },
        });
        await (0, util_1.shell)(python, ['-m', 'twine', 'check', path.join(outDir, '*')], {
            cwd: sourceDir,
            env,
        });
    }
}
exports.default = Python;
class TypeCheckingHelper {
    constructor() {
        _TypeCheckingHelper_stubs.set(this, new Array());
    }
    getTypeHints(fqn, args) {
        const stub = new TypeCheckingStub(fqn, args);
        __classPrivateFieldGet(this, _TypeCheckingHelper_stubs, "f").push(stub);
        return `typing.get_type_hints(${stub.name})`;
    }
    /** Emits instructions that create the annotations data... */
    flushStubs(code) {
        for (const stub of __classPrivateFieldGet(this, _TypeCheckingHelper_stubs, "f")) {
            stub.emit(code);
        }
        // Reset the stubs list
        __classPrivateFieldSet(this, _TypeCheckingHelper_stubs, [], "f");
    }
}
_TypeCheckingHelper_stubs = new WeakMap();
class TypeCheckingStub {
    constructor(fqn, args) {
        _TypeCheckingStub_arguments.set(this, void 0);
        _TypeCheckingStub_hash.set(this, void 0);
        // Removing the quoted type names -- this will be emitted at the very end of the module.
        __classPrivateFieldSet(this, _TypeCheckingStub_arguments, args.map((arg) => arg.replace(/"/g, '')), "f");
        __classPrivateFieldSet(this, _TypeCheckingStub_hash, crypto
            .createHash('sha256')
            .update(__classPrivateFieldGet(TypeCheckingStub, _a, "f", _TypeCheckingStub_PREFIX))
            .update(fqn)
            .digest('hex'), "f");
    }
    get name() {
        return `${__classPrivateFieldGet(TypeCheckingStub, _a, "f", _TypeCheckingStub_PREFIX)}${__classPrivateFieldGet(this, _TypeCheckingStub_hash, "f")}`;
    }
    emit(code) {
        code.line();
        openSignature(code, 'def', this.name, __classPrivateFieldGet(this, _TypeCheckingStub_arguments, "f"), 'None');
        code.line(`"""Type checking stubs"""`);
        code.line('pass');
        code.closeBlock();
    }
}
_a = TypeCheckingStub, _TypeCheckingStub_arguments = new WeakMap(), _TypeCheckingStub_hash = new WeakMap();
_TypeCheckingStub_PREFIX = { value: '_typecheckingstub__' };
const pythonModuleNameToFilename = (name) => {
    return path.join(...name.split('.'));
};
const toPythonMethodName = (name, protectedItem = false) => {
    let value = (0, util_2.toPythonIdentifier)((0, codemaker_1.toSnakeCase)(name));
    if (protectedItem) {
        value = `_${value}`;
    }
    return value;
};
const toPythonPropertyName = (name, constant = false, protectedItem = false) => {
    let value = (0, util_2.toPythonIdentifier)((0, codemaker_1.toSnakeCase)(name));
    if (constant) {
        value = value.toUpperCase();
    }
    if (protectedItem) {
        value = `_${value}`;
    }
    return value;
};
/**
 * Converts a given signature's parameter name to what should be emitted in Python. It slugifies the
 * positional parameter names that collide with a lifted prop by appending trailing `_`. There is no
 * risk of conflicting with an other positional parameter that ends with a `_` character because
 * this is prohibited by the `jsii` compiler (parameter names MUST be camelCase, and only a single
 * `_` is permitted when it is on **leading** position)
 *
 * @param name              the name of the parameter that needs conversion.
 * @param liftedParamNames  the list of "lifted" keyword parameters in this signature. This must be
 *                          omitted when generating a name for a parameter that **is** lifted.
 */
function toPythonParameterName(name, liftedParamNames = new Set()) {
    let result = (0, util_2.toPythonIdentifier)((0, codemaker_1.toSnakeCase)(name));
    while (liftedParamNames.has(result)) {
        result += '_';
    }
    return result;
}
const setDifference = (setA, setB) => {
    const result = new Set();
    for (const item of setA) {
        if (!setB.has(item)) {
            result.add(item);
        }
    }
    return result;
};
/**
 * Prepare python members for emission.
 *
 * If there are multiple members of the same name, they will all map to the same python
 * name, so we will filter all deprecated members and expect that there will be only one
 * left.
 *
 * Returns the members in a sorted list.
 */
function prepareMembers(members, resolver) {
    // create a map from python name to list of members
    const map = {};
    for (const m of members) {
        let list = map[m.pythonName];
        if (!list) {
            list = map[m.pythonName] = [];
        }
        list.push(m);
    }
    // now return all the members
    const ret = new Array();
    for (const [name, list] of Object.entries(map)) {
        let member;
        if (list.length === 1) {
            // if we have a single member for this normalized name, then use it
            member = list[0];
        }
        else {
            // we found more than one member with the same python name, filter all
            // deprecated versions and check that we are left with exactly one.
            // otherwise, they will overwrite each other
            // see https://github.com/aws/jsii/issues/2508
            const nonDeprecated = list.filter((x) => !isDeprecated(x));
            if (nonDeprecated.length > 1) {
                throw new Error(`Multiple non-deprecated members which map to the Python name "${name}"`);
            }
            if (nonDeprecated.length === 0) {
                throw new Error(`Multiple members which map to the Python name "${name}", but all of them are deprecated`);
            }
            member = nonDeprecated[0];
        }
        ret.push(member);
    }
    return sortMembers(ret, resolver);
}
const sortMembers = (members, resolver) => {
    let sortable = new Array();
    const sorted = new Array();
    const seen = new Set();
    // The first thing we want to do, is push any item which is not sortable to the very
    // front of the list. This will be things like methods, properties, etc.
    for (const member of members) {
        if (!isSortableType(member)) {
            sorted.push(member);
            seen.add(member);
        }
        else {
            sortable.push({ member, dependsOn: new Set(member.dependsOn(resolver)) });
        }
    }
    // Now that we've pulled out everything that couldn't possibly have dependencies,
    // we will go through the remaining items, and pull off any items which have no
    // dependencies that we haven't already sorted.
    while (sortable.length > 0) {
        for (const { member, dependsOn } of sortable) {
            const diff = setDifference(dependsOn, seen);
            if ([...diff].find((dep) => !(dep instanceof PythonModule)) == null) {
                sorted.push(member);
                seen.add(member);
            }
        }
        const leftover = sortable.filter(({ member }) => !seen.has(member));
        if (leftover.length === sortable.length) {
            throw new Error(`Could not sort members (circular dependency?). Leftover: ${leftover
                .map((lo) => lo.member.pythonName)
                .join(', ')}`);
        }
        else {
            sortable = leftover;
        }
    }
    return sorted;
};
function isSortableType(arg) {
    return arg.dependsOn !== undefined;
}
class BasePythonClassType {
    constructor(generator, pythonName, spec, fqn, opts, docs) {
        this.generator = generator;
        this.pythonName = pythonName;
        this.spec = spec;
        this.fqn = fqn;
        this.docs = docs;
        this.separateMembers = true;
        const { bases = [] } = opts;
        this.bases = bases;
        this.members = [];
    }
    dependsOn(resolver) {
        const dependencies = new Array();
        const parent = resolver.getParent(this.fqn);
        // We need to return any bases that are in the same module at the same level of
        // nesting.
        const seen = new Set();
        for (const base of this.bases) {
            if (spec.isNamedTypeReference(base)) {
                if (resolver.isInModule(base)) {
                    // Given a base, we need to locate the base's parent that is the same as
                    // our parent, because we only care about dependencies that are at the
                    // same level of our own.
                    // TODO: We might need to recurse into our members to also find their
                    //       dependencies.
                    let baseItem = resolver.getType(base);
                    let baseParent = resolver.getParent(base);
                    while (baseParent !== parent) {
                        baseItem = baseParent;
                        baseParent = resolver.getParent(baseItem.fqn);
                    }
                    if (!seen.has(baseItem.fqn)) {
                        dependencies.push(baseItem);
                        seen.add(baseItem.fqn);
                    }
                }
            }
        }
        return dependencies;
    }
    requiredImports(context) {
        return (0, type_name_1.mergePythonImports)(...this.bases.map((base) => (0, type_name_1.toTypeName)(base).requiredImports(context)), ...this.members.map((mem) => mem.requiredImports(context)));
    }
    addMember(member) {
        this.members.push(member);
    }
    get apiLocation() {
        if (!this.fqn) {
            throw new Error(`Cannot make apiLocation for ${this.pythonName}, does not have FQN`);
        }
        return { api: 'type', fqn: this.fqn };
    }
    emit(code, context) {
        context = nestedContext(context, this.fqn);
        const classParams = this.getClassParams(context);
        openSignature(code, 'class', this.pythonName, classParams);
        this.generator.emitDocString(code, this.apiLocation, this.docs, {
            documentableItem: `class-${this.pythonName}`,
            trailingNewLine: true,
        });
        if (this.members.length > 0) {
            const resolver = this.boundResolver(context.resolver);
            let shouldSeparate = false;
            for (const member of prepareMembers(this.members, resolver)) {
                if (shouldSeparate) {
                    code.line();
                }
                shouldSeparate = this.separateMembers;
                member.emit(code, { ...context, resolver });
            }
        }
        else {
            code.line('pass');
        }
        code.closeBlock();
        if (this.fqn != null) {
            context.emittedTypes.add(this.fqn);
        }
    }
    boundResolver(resolver) {
        if (this.fqn == null) {
            return resolver;
        }
        return resolver.bind(this.fqn);
    }
}
class BaseMethod {
    constructor(generator, pythonName, jsName, parameters, returns, docs, isStatic, pythonParent, opts) {
        this.generator = generator;
        this.pythonName = pythonName;
        this.jsName = jsName;
        this.parameters = parameters;
        this.returns = returns;
        this.docs = docs;
        this.isStatic = isStatic;
        this.pythonParent = pythonParent;
        this.classAsFirstParameter = false;
        this.returnFromJSIIMethod = true;
        this.shouldEmitBody = true;
        this.abstract = !!opts.abstract;
        this.liftedProp = opts.liftedProp;
        this.parent = opts.parent;
    }
    get apiLocation() {
        return {
            api: 'member',
            fqn: this.parent.fqn,
            memberName: this.jsName ?? '',
        };
    }
    requiredImports(context) {
        return (0, type_name_1.mergePythonImports)((0, type_name_1.toTypeName)(this.returns).requiredImports(context), ...this.parameters.map((param) => (0, type_name_1.toTypeName)(param).requiredImports(context)), ...liftedProperties(this.liftedProp));
        function* liftedProperties(struct) {
            if (struct == null) {
                return;
            }
            for (const prop of struct.properties ?? []) {
                yield (0, type_name_1.toTypeName)(prop.type).requiredImports(context);
            }
            for (const base of struct.interfaces ?? []) {
                const iface = context.resolver.dereference(base);
                for (const imports of liftedProperties(iface)) {
                    yield imports;
                }
            }
        }
    }
    emit(code, context, opts) {
        const { renderAbstract = true, forceEmitBody = false } = opts ?? {};
        const returnType = (0, type_name_1.toTypeName)(this.returns).pythonType(context);
        // We cannot (currently?) blindly use the names given to us by the JSII for
        // initializers, because our keyword lifting will allow two names to clash.
        // This can hopefully be removed once we get https://github.com/aws/jsii/issues/288
        // resolved, so build up a list of all of the prop names so we can check against
        // them later.
        const liftedPropNames = new Set();
        if (this.liftedProp?.properties != null) {
            for (const prop of this.liftedProp.properties) {
                liftedPropNames.add(toPythonParameterName(prop.name));
            }
        }
        // We need to turn a list of JSII parameters, into Python style arguments with
        // gradual typing, so we'll have to iterate over the list of parameters, and
        // build the list, converting as we go.
        const pythonParams = [];
        for (const param of this.parameters) {
            // We cannot (currently?) blindly use the names given to us by the JSII for
            // initializers, because our keyword lifting will allow two names to clash.
            // This can hopefully be removed once we get https://github.com/aws/jsii/issues/288
            // resolved.
            const paramName = toPythonParameterName(param.name, liftedPropNames);
            const paramType = (0, type_name_1.toTypeName)(param).pythonType({
                ...context,
                parameterType: true,
            });
            const paramDefault = param.optional ? ' = None' : '';
            pythonParams.push(`${paramName}: ${paramType}${paramDefault}`);
        }
        const documentableArgs = this.parameters
            .map((p) => ({
            name: p.name,
            docs: p.docs,
            definingType: this.parent,
        }))
            // If there's liftedProps, the last argument is the struct and it won't be _actually_ emitted.
            .filter((_, index) => this.liftedProp != null ? index < this.parameters.length - 1 : true)
            .map((param) => ({
            ...param,
            name: toPythonParameterName(param.name, liftedPropNames),
        }));
        // If we have a lifted parameter, then we'll drop the last argument to our params
        // and then we'll lift all of the params of the lifted type as keyword arguments
        // to the function.
        if (this.liftedProp !== undefined) {
            // Remove our last item.
            pythonParams.pop();
            const liftedProperties = this.getLiftedProperties(context.resolver);
            if (liftedProperties.length >= 1) {
                // All of these parameters are keyword only arguments, so we'll mark them
                // as such.
                pythonParams.push('*');
                // Iterate over all of our props, and reflect them into our params.
                for (const prop of liftedProperties) {
                    const paramName = toPythonParameterName(prop.prop.name);
                    const paramType = (0, type_name_1.toTypeName)(prop.prop).pythonType({
                        ...context,
                        parameterType: true,
                        typeAnnotation: true,
                    });
                    const paramDefault = prop.prop.optional ? ' = None' : '';
                    pythonParams.push(`${paramName}: ${paramType}${paramDefault}`);
                }
            }
            // Document them as keyword arguments
            documentableArgs.push(...liftedProperties.map((p) => ({
                name: p.prop.name,
                docs: p.prop.docs,
                definingType: p.definingType,
            })));
        }
        else if (this.parameters.length >= 1 &&
            this.parameters[this.parameters.length - 1].variadic) {
            // Another situation we could be in, is that instead of having a plain parameter
            // we have a variadic parameter where we need to expand the last parameter as a
            // *args.
            pythonParams.pop();
            const lastParameter = this.parameters.slice(-1)[0];
            const paramName = toPythonParameterName(lastParameter.name);
            const paramType = (0, type_name_1.toTypeName)(lastParameter.type).pythonType(context);
            pythonParams.push(`*${paramName}: ${paramType}`);
        }
        const decorators = new Array();
        if (this.jsName !== undefined) {
            decorators.push(`@jsii.member(jsii_name="${this.jsName}")`);
        }
        if (this.decorator !== undefined) {
            decorators.push(`@${this.decorator}`);
        }
        if (renderAbstract && this.abstract) {
            decorators.push('@abc.abstractmethod');
        }
        if (decorators.length > 0) {
            for (const decorator of decorators) {
                code.line(decorator);
            }
        }
        pythonParams.unshift(slugifyAsNeeded(this.implicitParameter, pythonParams.map((param) => param.split(':')[0].trim())));
        openSignature(code, 'def', this.pythonName, pythonParams, returnType);
        this.generator.emitDocString(code, this.apiLocation, this.docs, {
            arguments: documentableArgs,
            documentableItem: `method-${this.pythonName}`,
        });
        if ((this.shouldEmitBody || forceEmitBody) &&
            (!renderAbstract || !this.abstract)) {
            emitParameterTypeChecks(code, context, pythonParams.slice(1), `${this.pythonParent.fqn ?? this.pythonParent.pythonName}#${this.pythonName}`);
        }
        this.emitBody(code, context, renderAbstract, forceEmitBody, liftedPropNames, pythonParams[0], returnType);
        code.closeBlock();
    }
    emitBody(code, context, renderAbstract, forceEmitBody, liftedPropNames, implicitParameter, returnType) {
        if ((!this.shouldEmitBody && !forceEmitBody) ||
            (renderAbstract && this.abstract)) {
            code.line('...');
        }
        else {
            if (this.liftedProp !== undefined) {
                this.emitAutoProps(code, context, liftedPropNames);
            }
            this.emitJsiiMethodCall(code, context, liftedPropNames, implicitParameter, returnType);
        }
    }
    emitAutoProps(code, context, liftedPropNames) {
        const lastParameter = this.parameters.slice(-1)[0];
        const argName = toPythonParameterName(lastParameter.name, liftedPropNames);
        const typeName = (0, type_name_1.toTypeName)(lastParameter.type).pythonType({
            ...context,
            typeAnnotation: false,
        });
        // We need to build up a list of properties, which are mandatory, these are the
        // ones we will specifiy to start with in our dictionary literal.
        const liftedProps = this.getLiftedProperties(context.resolver).map((p) => new StructField(this.generator, p.prop, p.definingType));
        const assignments = liftedProps
            .map((p) => p.pythonName)
            .map((v) => `${v}=${v}`);
        assignCallResult(code, argName, typeName, assignments);
        code.line();
    }
    emitJsiiMethodCall(code, context, liftedPropNames, implicitParameter, returnType) {
        const methodPrefix = this.returnFromJSIIMethod ? 'return ' : '';
        const jsiiMethodParams = [];
        if (this.classAsFirstParameter) {
            if (this.parent === undefined) {
                throw new Error('Parent not known.');
            }
            if (this.isStatic) {
                jsiiMethodParams.push((0, type_name_1.toTypeName)(this.parent).pythonType({
                    ...context,
                    typeAnnotation: false,
                }));
            }
            else {
                // Using the dynamic class of `self`.
                jsiiMethodParams.push(`${implicitParameter}.__class__`);
            }
        }
        jsiiMethodParams.push(implicitParameter);
        if (this.jsName !== undefined) {
            jsiiMethodParams.push(`"${this.jsName}"`);
        }
        // If the last arg is variadic, expand the tuple
        const params = [];
        for (const param of this.parameters) {
            let expr = toPythonParameterName(param.name, liftedPropNames);
            if (param.variadic) {
                expr = `*${expr}`;
            }
            params.push(expr);
        }
        const value = `jsii.${this.jsiiMethod}(${jsiiMethodParams.join(', ')}, [${params.join(', ')}])`;
        code.line(`${methodPrefix}${this.returnFromJSIIMethod && returnType
            ? `typing.cast(${returnType}, ${value})`
            : value}`);
    }
    getLiftedProperties(resolver) {
        const liftedProperties = [];
        const stack = [this.liftedProp];
        const knownIfaces = new Set();
        const knownProps = new Set();
        for (let current = stack.shift(); current != null; current = stack.shift()) {
            knownIfaces.add(current.fqn);
            // Add any interfaces that this interface depends on, to the list.
            if (current.interfaces !== undefined) {
                for (const iface of current.interfaces) {
                    if (knownIfaces.has(iface)) {
                        continue;
                    }
                    stack.push(resolver.dereference(iface));
                    knownIfaces.add(iface);
                }
            }
            // Add all of the properties of this interface to our list of properties.
            if (current.properties !== undefined) {
                for (const prop of current.properties) {
                    if (knownProps.has(prop.name)) {
                        continue;
                    }
                    liftedProperties.push({ prop, definingType: current });
                    knownProps.add(prop.name);
                }
            }
        }
        return liftedProperties;
    }
}
class BaseProperty {
    constructor(generator, pythonName, jsName, type, docs, pythonParent, opts) {
        this.generator = generator;
        this.pythonName = pythonName;
        this.jsName = jsName;
        this.type = type;
        this.docs = docs;
        this.pythonParent = pythonParent;
        this.shouldEmitBody = true;
        const { abstract = false, immutable = false, isStatic = false } = opts;
        this.abstract = abstract;
        this.immutable = immutable;
        this.isStatic = isStatic;
        this.parent = opts.parent;
    }
    get apiLocation() {
        return { api: 'member', fqn: this.parent.fqn, memberName: this.jsName };
    }
    requiredImports(context) {
        return (0, type_name_1.toTypeName)(this.type).requiredImports(context);
    }
    emit(code, context, opts) {
        const { renderAbstract = true, forceEmitBody = false } = opts ?? {};
        const pythonType = (0, type_name_1.toTypeName)(this.type).pythonType(context);
        code.line(`@${this.decorator}`);
        code.line(`@jsii.member(jsii_name="${this.jsName}")`);
        if (renderAbstract && this.abstract) {
            code.line('@abc.abstractmethod');
        }
        openSignature(code, 'def', this.pythonName, [this.implicitParameter], pythonType, 
        // PyRight and MyPY both special-case @property, but not custom implementations such as our @classproperty...
        // MyPY reports on the re-declaration, but PyRight reports on the initial declaration (duh!)
        this.isStatic && !this.immutable
            ? 'pyright: ignore [reportGeneralTypeIssues]'
            : undefined);
        this.generator.emitDocString(code, this.apiLocation, this.docs, {
            documentableItem: `prop-${this.pythonName}`,
        });
        // NOTE: No parameters to validate here, this is a getter...
        if ((this.shouldEmitBody || forceEmitBody) &&
            (!renderAbstract || !this.abstract)) {
            code.line(`return typing.cast(${pythonType}, jsii.${this.jsiiGetMethod}(${this.implicitParameter}, "${this.jsName}"))`);
        }
        else {
            code.line('...');
        }
        code.closeBlock();
        if (!this.immutable) {
            code.line();
            // PyRight and MyPY both special-case @property, but not custom implementations such as our @classproperty...
            // MyPY reports on the re-declaration, but PyRight reports on the initial declaration (duh!)
            code.line(`@${this.pythonName}.setter${this.isStatic ? ' # type: ignore[no-redef]' : ''}`);
            if (renderAbstract && this.abstract) {
                code.line('@abc.abstractmethod');
            }
            openSignature(code, 'def', this.pythonName, [this.implicitParameter, `value: ${pythonType}`], 'None');
            if ((this.shouldEmitBody || forceEmitBody) &&
                (!renderAbstract || !this.abstract)) {
                emitParameterTypeChecks(code, context, [`value: ${pythonType}`], `${this.pythonParent.fqn ?? this.pythonParent.pythonName}#${this.pythonName}`);
                code.line(`jsii.${this.jsiiSetMethod}(${this.implicitParameter}, "${this.jsName}", value)`);
            }
            else {
                code.line('...');
            }
            code.closeBlock();
        }
    }
}
class Interface extends BasePythonClassType {
    emit(code, context) {
        context = nestedContext(context, this.fqn);
        emitList(code, '@jsii.interface(', [`jsii_type="${this.fqn}"`], ')');
        // First we do our normal class logic for emitting our members.
        super.emit(code, context);
        code.line();
        code.line();
        // Then, we have to emit a Proxy class which implements our proxy interface.
        const proxyBases = this.bases.map((b) => 
        // "# type: ignore[misc]" because MyPy cannot check dynamic base classes (naturally)
        `jsii.proxy_for(${(0, type_name_1.toTypeName)(b).pythonType({
            ...context,
            typeAnnotation: false,
        })}) # type: ignore[misc]`);
        openSignature(code, 'class', this.proxyClassName, proxyBases);
        this.generator.emitDocString(code, this.apiLocation, this.docs, {
            documentableItem: `class-${this.pythonName}`,
            trailingNewLine: true,
        });
        code.line(`__jsii_type__: typing.ClassVar[str] = "${this.fqn}"`);
        if (this.members.length > 0) {
            for (const member of this.members) {
                if (this.separateMembers) {
                    code.line();
                }
                member.emit(code, context, { forceEmitBody: true });
            }
        }
        else {
            code.line('pass');
        }
        code.closeBlock();
        code.line();
        code.line('# Adding a "__jsii_proxy_class__(): typing.Type" function to the interface');
        code.line(`typing.cast(typing.Any, ${this.pythonName}).__jsii_proxy_class__ = lambda : ${this.proxyClassName}`);
        if (this.fqn != null) {
            context.emittedTypes.add(this.fqn);
        }
    }
    getClassParams(context) {
        const params = this.bases.map((b) => (0, type_name_1.toTypeName)(b).pythonType({ ...context, typeAnnotation: false }));
        params.push('typing_extensions.Protocol');
        return params;
    }
    get proxyClassName() {
        return `_${this.pythonName}Proxy`;
    }
}
class InterfaceMethod extends BaseMethod {
    constructor() {
        super(...arguments);
        this.implicitParameter = 'self';
        this.jsiiMethod = 'invoke';
        this.shouldEmitBody = false;
    }
}
class InterfaceProperty extends BaseProperty {
    constructor() {
        super(...arguments);
        this.decorator = 'builtins.property';
        this.implicitParameter = 'self';
        this.jsiiGetMethod = 'get';
        this.jsiiSetMethod = 'set';
        this.shouldEmitBody = false;
    }
}
class Struct extends BasePythonClassType {
    constructor() {
        super(...arguments);
        this.directMembers = new Array();
    }
    addMember(member) {
        if (!(member instanceof StructField)) {
            throw new Error('Must add StructField to Struct');
        }
        this.directMembers.push(member);
    }
    emit(code, context) {
        context = nestedContext(context, this.fqn);
        const baseInterfaces = this.getClassParams(context);
        code.indent('@jsii.data_type(');
        code.line(`jsii_type=${JSON.stringify(this.fqn)},`);
        emitList(code, 'jsii_struct_bases=[', baseInterfaces, '],');
        assignDictionary(code, 'name_mapping', this.propertyMap(), ',', true);
        code.unindent(')');
        openSignature(code, 'class', this.pythonName, baseInterfaces);
        this.emitConstructor(code, context);
        for (const member of this.allMembers) {
            code.line();
            this.emitGetter(member, code, context);
        }
        this.emitMagicMethods(code);
        code.closeBlock();
        if (this.fqn != null) {
            context.emittedTypes.add(this.fqn);
        }
    }
    requiredImports(context) {
        return (0, type_name_1.mergePythonImports)(super.requiredImports(context), ...this.allMembers.map((mem) => mem.requiredImports(context)));
    }
    getClassParams(context) {
        return this.bases.map((b) => (0, type_name_1.toTypeName)(b).pythonType({ ...context, typeAnnotation: false }));
    }
    /**
     * Find all fields (inherited as well)
     */
    get allMembers() {
        return this.thisInterface.allProperties.map((x) => new StructField(this.generator, x.spec, x.definingType.spec));
    }
    get thisInterface() {
        if (this.fqn == null) {
            throw new Error('FQN not set');
        }
        return this.generator.reflectAssembly.system.findInterface(this.fqn);
    }
    emitConstructor(code, context) {
        const members = this.allMembers;
        const kwargs = members.map((m) => m.constructorDecl(context));
        const implicitParameter = slugifyAsNeeded('self', members.map((m) => m.pythonName));
        const constructorArguments = kwargs.length > 0
            ? [implicitParameter, '*', ...kwargs]
            : [implicitParameter];
        openSignature(code, 'def', '__init__', constructorArguments, 'None');
        this.emitConstructorDocstring(code);
        // Re-type struct arguments that were passed as "dict". Do this before validating argument types...
        for (const member of members.filter((m) => m.isStruct(this.generator))) {
            // Note that "None" is NOT an instance of dict (that's convenient!)
            const typeName = (0, type_name_1.toTypeName)(member.type.type).pythonType({
                ...context,
                typeAnnotation: false,
            });
            code.openBlock(`if isinstance(${member.pythonName}, dict)`);
            code.line(`${member.pythonName} = ${typeName}(**${member.pythonName})`);
            code.closeBlock();
        }
        if (kwargs.length > 0) {
            emitParameterTypeChecks(code, 
            // Runtime type check keyword args as this is a struct __init__ function.
            { ...context, runtimeTypeCheckKwargs: true }, ['*', ...kwargs], `${this.fqn ?? this.pythonName}#__init__`);
        }
        // Required properties, those will always be put into the dict
        assignDictionary(code, `${implicitParameter}._values: typing.Dict[builtins.str, typing.Any]`, members
            .filter((m) => !m.optional)
            .map((member) => `${JSON.stringify(member.pythonName)}: ${member.pythonName}`));
        // Optional properties, will only be put into the dict if they're not None
        for (const member of members.filter((m) => m.optional)) {
            code.openBlock(`if ${member.pythonName} is not None`);
            code.line(`${implicitParameter}._values["${member.pythonName}"] = ${member.pythonName}`);
            code.closeBlock();
        }
        code.closeBlock();
    }
    emitConstructorDocstring(code) {
        const args = this.allMembers.map((m) => ({
            name: m.pythonName,
            docs: m.docs,
            definingType: this.spec,
        }));
        this.generator.emitDocString(code, this.apiLocation, this.docs, {
            arguments: args,
            documentableItem: `class-${this.pythonName}`,
        });
    }
    emitGetter(member, code, context) {
        const pythonType = member.typeAnnotation(context);
        code.line('@builtins.property');
        openSignature(code, 'def', member.pythonName, ['self'], pythonType);
        member.emitDocString(code);
        // NOTE: No parameter to validate here, this is a getter.
        code.line(`result = self._values.get(${JSON.stringify(member.pythonName)})`);
        if (!member.optional) {
            // Add an assertion to maye MyPY happy!
            code.line(`assert result is not None, "Required property '${member.pythonName}' is missing"`);
        }
        code.line(`return typing.cast(${pythonType}, result)`);
        code.closeBlock();
    }
    emitMagicMethods(code) {
        code.line();
        code.openBlock('def __eq__(self, rhs: typing.Any) -> builtins.bool');
        code.line('return isinstance(rhs, self.__class__) and rhs._values == self._values');
        code.closeBlock();
        code.line();
        code.openBlock('def __ne__(self, rhs: typing.Any) -> builtins.bool');
        code.line('return not (rhs == self)');
        code.closeBlock();
        code.line();
        code.openBlock('def __repr__(self) -> str');
        code.indent(`return "${this.pythonName}(%s)" % ", ".join(`);
        code.line('k + "=" + repr(v) for k, v in self._values.items()');
        code.unindent(')');
        code.closeBlock();
    }
    propertyMap() {
        const ret = new Array();
        for (const member of this.allMembers) {
            ret.push(`${JSON.stringify(member.pythonName)}: ${JSON.stringify(member.jsiiName)}`);
        }
        return ret;
    }
}
class StructField {
    constructor(generator, prop, definingType) {
        this.generator = generator;
        this.prop = prop;
        this.definingType = definingType;
        this.pythonName = toPythonPropertyName(prop.name);
        this.jsiiName = prop.name;
        this.type = prop;
        this.docs = prop.docs;
    }
    get apiLocation() {
        return {
            api: 'member',
            fqn: this.definingType.fqn,
            memberName: this.jsiiName,
        };
    }
    get optional() {
        return !!this.type.optional;
    }
    requiredImports(context) {
        return (0, type_name_1.toTypeName)(this.type).requiredImports(context);
    }
    isStruct(generator) {
        return isStruct(generator.reflectAssembly.system, this.type.type);
    }
    constructorDecl(context) {
        const opt = this.optional ? ' = None' : '';
        return `${this.pythonName}: ${this.typeAnnotation({
            ...context,
            parameterType: true,
        })}${opt}`;
    }
    /**
     * Return the Python type annotation for this type
     */
    typeAnnotation(context) {
        return (0, type_name_1.toTypeName)(this.type).pythonType(context);
    }
    emitDocString(code) {
        this.generator.emitDocString(code, this.apiLocation, this.docs, {
            documentableItem: `prop-${this.pythonName}`,
        });
    }
    emit(code, context) {
        const resolvedType = this.typeAnnotation(context);
        code.line(`${this.pythonName}: ${resolvedType}`);
        this.emitDocString(code);
    }
}
class Class extends BasePythonClassType {
    constructor(generator, name, spec, fqn, opts, docs) {
        super(generator, name, spec, fqn, opts, docs);
        const { abstract = false, interfaces = [], abstractBases = [] } = opts;
        this.abstract = abstract;
        this.interfaces = interfaces;
        this.abstractBases = abstractBases;
    }
    dependsOn(resolver) {
        const dependencies = super.dependsOn(resolver);
        const parent = resolver.getParent(this.fqn);
        // We need to return any ifaces that are in the same module at the same level of
        // nesting.
        const seen = new Set();
        for (const iface of this.interfaces) {
            if (resolver.isInModule(iface)) {
                // Given a iface, we need to locate the ifaces's parent that is the same
                // as our parent, because we only care about dependencies that are at the
                // same level of our own.
                // TODO: We might need to recurse into our members to also find their
                //       dependencies.
                let ifaceItem = resolver.getType(iface);
                let ifaceParent = resolver.getParent(iface);
                while (ifaceParent !== parent) {
                    ifaceItem = ifaceParent;
                    ifaceParent = resolver.getParent(ifaceItem.fqn);
                }
                if (!seen.has(ifaceItem.fqn)) {
                    dependencies.push(ifaceItem);
                    seen.add(ifaceItem.fqn);
                }
            }
        }
        return dependencies;
    }
    requiredImports(context) {
        return (0, type_name_1.mergePythonImports)(super.requiredImports(context), // Takes care of base & members
        ...this.interfaces.map((base) => (0, type_name_1.toTypeName)(base).requiredImports(context)));
    }
    emit(code, context) {
        // First we emit our implments decorator
        if (this.interfaces.length > 0) {
            const interfaces = this.interfaces.map((b) => (0, type_name_1.toTypeName)(b).pythonType({ ...context, typeAnnotation: false }));
            code.line(`@jsii.implements(${interfaces.join(', ')})`);
        }
        // Then we do our normal class logic for emitting our members.
        super.emit(code, context);
        // Then, if our class is Abstract, we have to go through and redo all of
        // this logic, except only emiting abstract methods and properties as non
        // abstract, and subclassing our initial class.
        if (this.abstract) {
            context = nestedContext(context, this.fqn);
            const proxyBases = [this.pythonName];
            for (const base of this.abstractBases) {
                // "# type: ignore[misc]" because MyPy cannot check dynamic base classes (naturally)
                proxyBases.push(`jsii.proxy_for(${(0, type_name_1.toTypeName)(base).pythonType({
                    ...context,
                    typeAnnotation: false,
                })}) # type: ignore[misc]`);
            }
            code.line();
            code.line();
            openSignature(code, 'class', this.proxyClassName, proxyBases);
            // Filter our list of members to *only* be abstract members, and not any
            // other types.
            const abstractMembers = this.members.filter((m) => (m instanceof BaseMethod || m instanceof BaseProperty) && m.abstract);
            if (abstractMembers.length > 0) {
                let first = true;
                for (const member of abstractMembers) {
                    if (this.separateMembers) {
                        if (first) {
                            first = false;
                        }
                        else {
                            code.line();
                        }
                    }
                    member.emit(code, context, { renderAbstract: false });
                }
            }
            else {
                code.line('pass');
            }
            code.closeBlock();
            code.line();
            code.line('# Adding a "__jsii_proxy_class__(): typing.Type" function to the abstract class');
            code.line(`typing.cast(typing.Any, ${this.pythonName}).__jsii_proxy_class__ = lambda : ${this.proxyClassName}`);
        }
    }
    getClassParams(context) {
        const params = this.bases.map((b) => (0, type_name_1.toTypeName)(b).pythonType({ ...context, typeAnnotation: false }));
        const metaclass = this.abstract ? 'JSIIAbstractClass' : 'JSIIMeta';
        params.push(`metaclass=jsii.${metaclass}`);
        params.push(`jsii_type="${this.fqn}"`);
        return params;
    }
    get proxyClassName() {
        return `_${this.pythonName}Proxy`;
    }
}
class StaticMethod extends BaseMethod {
    constructor() {
        super(...arguments);
        this.decorator = 'builtins.classmethod';
        this.implicitParameter = 'cls';
        this.jsiiMethod = 'sinvoke';
    }
}
class Initializer extends BaseMethod {
    constructor() {
        super(...arguments);
        this.implicitParameter = 'self';
        this.jsiiMethod = 'create';
        this.classAsFirstParameter = true;
        this.returnFromJSIIMethod = false;
    }
}
class Method extends BaseMethod {
    constructor() {
        super(...arguments);
        this.implicitParameter = 'self';
        this.jsiiMethod = 'invoke';
    }
}
class AsyncMethod extends BaseMethod {
    constructor() {
        super(...arguments);
        this.implicitParameter = 'self';
        this.jsiiMethod = 'ainvoke';
    }
}
class StaticProperty extends BaseProperty {
    constructor() {
        super(...arguments);
        this.decorator = 'jsii.python.classproperty';
        this.implicitParameter = 'cls';
        this.jsiiGetMethod = 'sget';
        this.jsiiSetMethod = 'sset';
    }
}
class Property extends BaseProperty {
    constructor() {
        super(...arguments);
        this.decorator = 'builtins.property';
        this.implicitParameter = 'self';
        this.jsiiGetMethod = 'get';
        this.jsiiSetMethod = 'set';
    }
}
class Enum extends BasePythonClassType {
    constructor() {
        super(...arguments);
        this.separateMembers = false;
    }
    emit(code, context) {
        context = nestedContext(context, this.fqn);
        emitList(code, '@jsii.enum(', [`jsii_type="${this.fqn}"`], ')');
        return super.emit(code, context);
    }
    getClassParams(_context) {
        return ['enum.Enum'];
    }
    requiredImports(context) {
        return super.requiredImports(context);
    }
}
class EnumMember {
    constructor(generator, pythonName, value, docs, parent) {
        this.generator = generator;
        this.pythonName = pythonName;
        this.value = value;
        this.docs = docs;
        this.parent = parent;
        this.pythonName = pythonName;
        this.value = value;
    }
    get apiLocation() {
        return { api: 'member', fqn: this.parent.fqn, memberName: this.value };
    }
    dependsOnModules() {
        return new Set();
    }
    emit(code, _context) {
        code.line(`${this.pythonName} = "${this.value}"`);
        this.generator.emitDocString(code, this.apiLocation, this.docs, {
            documentableItem: `enum-${this.pythonName}`,
        });
    }
    requiredImports(_context) {
        return {};
    }
}
/**
 * Python module
 *
 * Will be called for jsii submodules and namespaces.
 */
class PythonModule {
    constructor(pythonName, fqn, opts) {
        this.pythonName = pythonName;
        this.fqn = fqn;
        this.members = new Array();
        this.modules = new Array();
        this.assembly = opts.assembly;
        this.assemblyFilename = opts.assemblyFilename;
        this.loadAssembly = !!opts.loadAssembly;
        this.moduleDocumentation = opts.moduleDocumentation;
    }
    addMember(member) {
        this.members.push(member);
    }
    addPythonModule(pyMod) {
        assert(!this.loadAssembly, 'PythonModule.addPythonModule CANNOT be called on assembly-loading modules (it would cause a load cycle)!');
        assert(pyMod.pythonName.startsWith(`${this.pythonName}.`), `Attempted to register ${pyMod.pythonName} as a child module of ${this.pythonName}, but the names don't match!`);
        const [firstLevel, ...rest] = pyMod.pythonName
            .substring(this.pythonName.length + 1)
            .split('.');
        if (rest.length === 0) {
            // This is a direct child module...
            this.modules.push(pyMod);
        }
        else {
            // This is a nested child module, so we delegate to the directly nested module...
            const parent = this.modules.find((m) => m.pythonName === `${this.pythonName}.${firstLevel}`);
            if (!parent) {
                throw new Error(`Attempted to register ${pyMod.pythonName} within ${this.pythonName}, but ${this.pythonName}.${firstLevel} wasn't registered yet!`);
            }
            parent.addPythonModule(pyMod);
        }
    }
    requiredImports(context) {
        return (0, type_name_1.mergePythonImports)(...this.members.map((mem) => mem.requiredImports(context)));
    }
    emit(code, context) {
        this.emitModuleDocumentation(code);
        const resolver = this.fqn
            ? context.resolver.bind(this.fqn, this.pythonName)
            : context.resolver;
        context = {
            ...context,
            submodule: this.fqn ?? context.submodule,
            resolver,
        };
        // Before we write anything else, we need to write out our module headers, this
        // is where we handle stuff like imports, any required initialization, etc.
        // If multiple packages use the same namespace (in Python, a directory) it
        // depends on how they are laid out on disk if deep imports of multiple packages
        // will succeed. `pip` merges all packages into the same directory, and deep
        // imports work automatically. `bazel` puts packages into different directories,
        // and `import aws_cdk.subpackage` will fail if `aws_cdk/__init__.py` and
        // `aws_cdk/subpackage/__init__.py` are not in the same directory.
        //
        // We can get around this by using `pkgutil` to extend the search path for the
        // current module (`__path__`) with all packages found on `sys.path`.
        code.line('from pkgutil import extend_path');
        code.line('__path__ = extend_path(__path__, __name__)');
        code.line();
        code.line('import abc');
        code.line('import builtins');
        code.line('import datetime');
        code.line('import enum');
        code.line('import typing');
        code.line();
        code.line('import jsii');
        code.line('import publication');
        code.line('import typing_extensions');
        code.line();
        code.line('from typeguard import check_type');
        // Determine if we need to write out the kernel load line.
        if (this.loadAssembly) {
            this.emitDependencyImports(code);
            code.line();
            emitList(code, '__jsii_assembly__ = jsii.JSIIAssembly.load(', [
                JSON.stringify(this.assembly.name),
                JSON.stringify(this.assembly.version),
                '__name__[0:-6]',
                `${JSON.stringify(this.assemblyFilename)}`,
            ], ')');
        }
        else {
            // Then we must import the ._jsii subpackage.
            code.line();
            let distanceFromRoot = 0;
            for (let curr = this.fqn; curr !== this.assembly.name; curr = curr.substring(0, curr.lastIndexOf('.'))) {
                distanceFromRoot++;
            }
            code.line(`from ${'.'.repeat(distanceFromRoot + 1)}_jsii import *`);
            this.emitRequiredImports(code, context);
        }
        // Emit all of our members.
        for (const member of prepareMembers(this.members, resolver)) {
            code.line();
            code.line();
            member.emit(code, context);
        }
        // Whatever names we've exported, we'll write out our __all__ that lists them.
        //
        // __all__ is normally used for when users write `from library import *`, but we also
        // use it with the `publication` module to hide everything that's NOT in the list.
        //
        // Normally adding submodules to `__all__` has the (negative?) side-effect
        // that all submodules get loaded when the user does `import *`, but we
        // already load submodules anyway so it doesn't make a difference, and in combination
        // with the `publication` module NOT having them in this list hides any submodules
        // we import as part of typechecking.
        const exportedMembers = [
            ...this.members.map((m) => `"${m.pythonName}"`),
            ...this.modules
                .filter((m) => this.isDirectChild(m))
                .map((m) => `"${lastComponent(m.pythonName)}"`),
        ];
        if (this.loadAssembly) {
            exportedMembers.push('"__jsii_assembly__"');
        }
        // Declare the list of "public" members this module exports
        if (this.members.length > 0) {
            code.line();
        }
        code.line();
        if (exportedMembers.length > 0) {
            code.indent('__all__ = [');
            for (const member of exportedMembers.sort()) {
                // Writing one by line might be _a lot_ of lines, but it'll make reviewing changes to the list easier. Trust me.
                code.line(`${member},`);
            }
            code.unindent(']');
        }
        else {
            code.line('__all__: typing.List[typing.Any] = []');
        }
        // Next up, we'll use publication to ensure that all of the non-public names
        // get hidden from dir(), tab-complete, etc.
        code.line();
        code.line('publication.publish()');
        // Finally, we'll load all registered python modules
        if (this.modules.length > 0) {
            code.line();
            code.line('# Loading modules to ensure their types are registered with the jsii runtime library');
            for (const module of this.modules.sort((l, r) => l.pythonName.localeCompare(r.pythonName))) {
                // Rather than generating an absolute import like
                // "import jsii_calc.submodule" this builds a relative import like
                // "from . import submodule". This enables distributing python packages
                // and using the generated modules in the same codebase.
                const submodule = module.pythonName.substring(this.pythonName.length + 1);
                code.line(`from . import ${submodule}`);
            }
        }
    }
    /**
     * Emit the bin scripts if bin section defined.
     */
    emitBinScripts(code) {
        const scripts = new Array();
        if (this.loadAssembly) {
            if (this.assembly.bin != null) {
                for (const name of Object.keys(this.assembly.bin)) {
                    const script_file = path.join('src', pythonModuleNameToFilename(this.pythonName), 'bin', name);
                    code.openFile(script_file);
                    code.line('#!/usr/bin/env python');
                    code.line();
                    code.line('import jsii');
                    code.line('import sys');
                    code.line('import os');
                    code.line();
                    code.openBlock('if "JSII_RUNTIME_PACKAGE_CACHE" not in os.environ');
                    code.line('os.environ["JSII_RUNTIME_PACKAGE_CACHE"] = "disabled"');
                    code.closeBlock();
                    code.line();
                    emitList(code, '__jsii_assembly__ = jsii.JSIIAssembly.load(', [
                        JSON.stringify(this.assembly.name),
                        JSON.stringify(this.assembly.version),
                        JSON.stringify(this.pythonName.replace('._jsii', '')),
                        `${JSON.stringify(this.assemblyFilename)}`,
                    ], ')');
                    code.line();
                    emitList(code, 'exit_code = __jsii_assembly__.invokeBinScript(', [
                        JSON.stringify(this.assembly.name),
                        JSON.stringify(name),
                        'sys.argv[1:]',
                    ], ')');
                    code.line('exit(exit_code)');
                    code.closeFile(script_file);
                    scripts.push(script_file.replace(/\\/g, '/'));
                }
            }
        }
        return scripts;
    }
    isDirectChild(pyMod) {
        if (this.pythonName === pyMod.pythonName ||
            !pyMod.pythonName.startsWith(`${this.pythonName}.`)) {
            return false;
        }
        // Must include only one more component
        return !pyMod.pythonName
            .substring(this.pythonName.length + 1)
            .includes('.');
    }
    /**
     * Emit the README as module docstring if this is the entry point module (it loads the assembly)
     */
    emitModuleDocumentation(code) {
        if (this.moduleDocumentation) {
            code.line(RAW_DOCSTRING_QUOTES); // raw string so that python does not attempt to interpret invalid escapes that are valid in markdown
            code.line(this.moduleDocumentation);
            code.line(DOCSTRING_QUOTES);
        }
    }
    emitDependencyImports(code) {
        // Collect all the (direct) dependencies' ._jsii packages.
        const deps = Object.keys(this.assembly.dependencies ?? {})
            .map((dep) => this.assembly.dependencyClosure?.[dep]?.targets?.python?.module ??
            (0, util_2.die)(`No Python target was configrued for the dependency "${dep}".`))
            .map((mod) => `${mod}._jsii`)
            .sort();
        // Now actually write the import statements...
        if (deps.length > 0) {
            code.line();
            for (const moduleName of deps) {
                code.line(`import ${moduleName}`);
            }
        }
    }
    emitRequiredImports(code, context) {
        const requiredImports = this.requiredImports(context);
        const statements = Object.entries(requiredImports)
            .map(([sourcePackage, items]) => toImportStatements(sourcePackage, items))
            .reduce((acc, elt) => [...acc, ...elt], new Array())
            .sort(importComparator);
        if (statements.length > 0) {
            code.line();
        }
        for (const statement of statements) {
            statement.emit(code);
        }
        function toImportStatements(sourcePkg, items) {
            const result = new Array();
            if (items.has('')) {
                result.push({
                    comparisonBase: `import ${sourcePkg}`,
                    emit(code) {
                        code.line(this.comparisonBase);
                    },
                });
            }
            const pieceMeal = Array.from(items)
                .filter((i) => i !== '')
                .sort();
            if (pieceMeal.length > 0) {
                result.push({
                    comparisonBase: `from ${sourcePkg} import`,
                    emit: (code) => emitList(code, `from ${sourcePkg} import `, pieceMeal, '', {
                        ifMulti: ['(', ')'],
                    }),
                });
            }
            return result;
        }
        function importComparator(left, right) {
            if (left.comparisonBase.startsWith('import') ===
                right.comparisonBase.startsWith('import')) {
                return left.comparisonBase.localeCompare(right.comparisonBase);
            }
            // We want "from .foo import (...)" to be *after* "import bar"
            return right.comparisonBase.localeCompare(left.comparisonBase);
        }
    }
}
class Package {
    constructor(name, version, metadata) {
        this.modules = new Map();
        this.data = new Map();
        this.name = name;
        this.version = version;
        this.metadata = metadata;
    }
    addModule(module) {
        this.modules.set(module.pythonName, module);
        // This is the module that represents the assembly
        if (module.fqn === this.metadata.name) {
            this.rootModule = module;
        }
    }
    addData(module, filename, data) {
        if (!this.data.has(module.pythonName)) {
            this.data.set(module.pythonName, []);
        }
        this.data.get(module.pythonName).push({ filename, data });
    }
    write(code, context) {
        const modules = [...this.modules.values()].sort((a, b) => a.pythonName.localeCompare(b.pythonName));
        const scripts = new Array();
        // Iterate over all of our modules, and write them out to disk.
        for (const mod of modules) {
            const filename = path.join('src', pythonModuleNameToFilename(mod.pythonName), '__init__.py');
            code.openFile(filename);
            mod.emit(code, context);
            context.typeCheckingHelper.flushStubs(code);
            code.closeFile(filename);
            scripts.push(...mod.emitBinScripts(code));
        }
        // Handle our package data.
        const packageData = {};
        for (const [mod, pdata] of this.data) {
            for (const data of pdata) {
                if (data.data != null) {
                    const filepath = path.join('src', pythonModuleNameToFilename(mod), data.filename);
                    code.openFile(filepath);
                    code.line(data.data);
                    code.closeFile(filepath);
                }
            }
            packageData[mod] = pdata.map((pd) => pd.filename);
        }
        // Compute our list of dependencies
        const dependencies = [];
        for (const [depName, version] of Object.entries(this.metadata.dependencies ?? {})) {
            const depInfo = this.metadata.dependencyClosure[depName];
            dependencies.push(`${depInfo.targets.python.distName}${(0, version_utils_1.toPythonVersionRange)(version)}`);
        }
        // Need to always write this file as the build process depends on it.
        // Make up some contents if we don't have anything useful to say.
        code.openFile('README.md');
        code.line(this.rootModule?.moduleDocumentation ??
            `${this.name}\n${'='.repeat(this.name.length)}`);
        code.closeFile('README.md');
        const setupKwargs = {
            name: this.name,
            version: this.version,
            description: this.metadata.description,
            license: this.metadata.license,
            url: this.metadata.homepage,
            long_description_content_type: 'text/markdown',
            author: this.metadata.author.name +
                (this.metadata.author.email !== undefined
                    ? `<${this.metadata.author.email}>`
                    : ''),
            bdist_wheel: {
                universal: true,
            },
            project_urls: {
                Source: this.metadata.repository.url,
            },
            package_dir: { '': 'src' },
            packages: modules.map((m) => m.pythonName),
            package_data: packageData,
            python_requires: '~=3.8',
            install_requires: [
                `jsii${(0, version_utils_1.toPythonVersionRange)(`^${version_1.VERSION}`)}`,
                'publication>=0.0.3',
                'typeguard~=2.13.3',
            ]
                .concat(dependencies)
                .sort(),
            classifiers: [
                'Intended Audience :: Developers',
                'Operating System :: OS Independent',
                'Programming Language :: JavaScript',
                'Programming Language :: Python :: 3 :: Only',
                'Programming Language :: Python :: 3.8',
                'Programming Language :: Python :: 3.9',
                'Programming Language :: Python :: 3.10',
                'Programming Language :: Python :: 3.11',
                'Typing :: Typed',
            ],
            scripts,
        };
        // Packages w/ a deprecated message may have a non-deprecated stability (e.g: when EoL happens
        // for a stable package). We pretend it's deprecated for the purpose of trove classifiers when
        // this happens.
        switch (this.metadata.docs?.deprecated
            ? spec.Stability.Deprecated
            : this.metadata.docs?.stability) {
            case spec.Stability.Experimental:
                setupKwargs.classifiers.push('Development Status :: 4 - Beta');
                break;
            case spec.Stability.Stable:
                setupKwargs.classifiers.push('Development Status :: 5 - Production/Stable');
                break;
            case spec.Stability.Deprecated:
                setupKwargs.classifiers.push('Development Status :: 7 - Inactive');
                break;
            default:
            // No 'Development Status' trove classifier for you!
        }
        if (spdxLicenseList[this.metadata.license]?.osiApproved) {
            setupKwargs.classifiers.push('License :: OSI Approved');
        }
        const additionalClassifiers = this.metadata.targets?.python?.classifiers;
        if (additionalClassifiers != null) {
            if (!Array.isArray(additionalClassifiers)) {
                throw new Error(`The "jsii.targets.python.classifiers" value must be an array of strings if provided, but found ${JSON.stringify(additionalClassifiers, null, 2)}`);
            }
            // We discourage using those since we automatically set a value for them
            for (let classifier of additionalClassifiers.sort()) {
                if (typeof classifier !== 'string') {
                    throw new Error(`The "jsii.targets.python.classifiers" value can only contain strings, but found ${JSON.stringify(classifier, null, 2)}`);
                }
                // We'll split on `::` and re-join later so classifiers are "normalized" to a standard spacing
                const parts = classifier.split('::').map((part) => part.trim());
                const reservedClassifiers = [
                    'Development Status',
                    'License',
                    'Operating System',
                    'Typing',
                ];
                if (reservedClassifiers.includes(parts[0])) {
                    (0, logging_1.warn)(`Classifiers starting with ${reservedClassifiers
                        .map((x) => `"${x} ::"`)
                        .join(', ')} are automatically set and should not be manually configured`);
                }
                classifier = parts.join(' :: ');
                if (setupKwargs.classifiers.includes(classifier)) {
                    continue;
                }
                setupKwargs.classifiers.push(classifier);
            }
        }
        // We Need a setup.py to make this Package, actually a Package.
        code.openFile('setup.py');
        code.line('import json');
        code.line('import setuptools');
        code.line();
        code.line('kwargs = json.loads(');
        code.line('    """');
        code.line(JSON.stringify(setupKwargs, null, 4));
        code.line('"""');
        code.line(')');
        code.line();
        code.openBlock('with open("README.md", encoding="utf8") as fp');
        code.line('kwargs["long_description"] = fp.read()');
        code.closeBlock();
        code.line();
        code.line();
        code.line('setuptools.setup(**kwargs)');
        code.closeFile('setup.py');
        // Because we're good citizens, we're going to go ahead and support pyproject.toml
        // as well.
        // TODO: Might be easier to just use a TOML library to write this out.
        code.openFile('pyproject.toml');
        code.line('[build-system]');
        const buildTools = fs
            .readFileSync(requirementsFile, { encoding: 'utf-8' })
            .split('\n')
            .map((line) => /^\s*(.+)\s*#\s*build-system\s*$/.exec(line)?.[1]?.trim())
            .reduce((buildTools, entry) => (entry ? [...buildTools, entry] : buildTools), new Array());
        code.line(`requires = [${buildTools.map((x) => `"${x}"`).join(', ')}]`);
        code.line('build-backend = "setuptools.build_meta"');
        code.line();
        code.line('[tool.pyright]');
        code.line('defineConstant = { DEBUG = true }');
        code.line('pythonVersion = "3.8"');
        code.line('pythonPlatform = "All"');
        code.line('reportSelfClsParameterName = false');
        code.closeFile('pyproject.toml');
        // We also need to write out a MANIFEST.in to ensure that all of our required
        // files are included.
        code.openFile('MANIFEST.in');
        code.line('include pyproject.toml');
        code.closeFile('MANIFEST.in');
    }
}
class TypeResolver {
    constructor(types, findModule, findType, boundTo, moduleName) {
        this.types = types;
        this.findModule = findModule;
        this.findType = findType;
        this.moduleName = moduleName;
        this.boundTo = boundTo !== undefined ? this.toPythonFQN(boundTo) : boundTo;
        if (this.moduleName !== undefined) {
            this.moduleRe = new RegExp(`^(${escapeStringRegexp(this.moduleName)})\\.(.+)$`);
        }
        if (this.boundTo !== undefined) {
            this.boundRe = new RegExp(`^(${escapeStringRegexp(this.boundTo)})\\.(.+)$`);
        }
    }
    bind(fqn, moduleName) {
        return new TypeResolver(this.types, this.findModule, this.findType, fqn, moduleName !== undefined
            ? moduleName.startsWith('.')
                ? `${this.moduleName}${moduleName}`
                : moduleName
            : this.moduleName);
    }
    isInModule(typeRef) {
        const pythonType = typeof typeRef !== 'string' ? this.toPythonFQN(typeRef.fqn) : typeRef;
        return this.moduleRe.test(pythonType);
    }
    isInNamespace(typeRef) {
        const pythonType = typeof typeRef !== 'string' ? this.toPythonFQN(typeRef.fqn) : typeRef;
        return this.boundRe.test(pythonType);
    }
    getParent(typeRef) {
        const fqn = typeof typeRef !== 'string' ? typeRef.fqn : typeRef;
        const matches = /^(.+)\.[^.]+$/.exec(fqn);
        if (matches == null || !Array.isArray(matches)) {
            throw new Error(`Invalid FQN: ${fqn}`);
        }
        const [, parentFQN] = matches;
        const parent = this.types.get(parentFQN);
        if (parent === undefined) {
            throw new Error(`Could not find parent:  ${parentFQN}`);
        }
        return parent;
    }
    getDefiningPythonModule(typeRef) {
        const fqn = typeof typeRef !== 'string' ? typeRef.fqn : typeRef;
        const parent = this.types.get(fqn);
        if (parent) {
            let mod = parent;
            while (!(mod instanceof PythonModule)) {
                mod = this.getParent(mod.fqn);
            }
            return mod.pythonName;
        }
        const matches = /^([^.]+)\./.exec(fqn);
        if (matches == null || !Array.isArray(matches)) {
            throw new Error(`Invalid FQN: ${fqn}`);
        }
        const [, assm] = matches;
        return this.findModule(assm).targets.python.module;
    }
    getType(typeRef) {
        const type = this.types.get(typeRef.fqn);
        if (type === undefined) {
            throw new Error(`Could not locate type: "${typeRef.fqn}"`);
        }
        return type;
    }
    dereference(typeRef) {
        if (typeof typeRef !== 'string') {
            typeRef = typeRef.fqn;
        }
        return this.findType(typeRef);
    }
    toPythonFQN(fqn) {
        const [assemblyName, ...qualifiedIdentifiers] = fqn.split('.');
        const fqnParts = [
            this.findModule(assemblyName).targets.python.module,
        ];
        for (const part of qualifiedIdentifiers) {
            fqnParts.push((0, util_2.toPythonIdentifier)(part));
        }
        return fqnParts.join('.');
    }
}
class PythonGenerator extends generator_1.Generator {
    constructor(rosetta, options) {
        super(options);
        this.rosetta = rosetta;
        this.code.openBlockFormatter = (s) => `${s}:`;
        this.code.closeBlockFormatter = (_s) => false;
        this.types = new Map();
    }
    // eslint-disable-next-line complexity
    emitDocString(code, apiLocation, docs, options = {}) {
        if ((!docs || Object.keys(docs).length === 0) && !options.arguments) {
            return;
        }
        if (!docs) {
            docs = {};
        }
        const lines = new Array();
        if (docs.summary) {
            lines.push((0, markdown_1.md2rst)((0, _utils_1.renderSummary)(docs)));
            brk();
        }
        else {
            lines.push('');
        }
        function brk() {
            if (lines.length > 0 && lines[lines.length - 1].trim() !== '') {
                lines.push('');
            }
        }
        function block(heading, content, doBrk = true) {
            if (doBrk) {
                brk();
            }
            const contentLines = (0, markdown_1.md2rst)(content).split('\n');
            if (contentLines.length <= 1) {
                lines.push(`:${heading}: ${contentLines.join('')}`.trim());
            }
            else {
                lines.push(`:${heading}:`);
                brk();
                for (const line of contentLines) {
                    lines.push(line.trim());
                }
            }
            if (doBrk) {
                brk();
            }
        }
        if (docs.remarks) {
            brk();
            lines.push(...(0, markdown_1.md2rst)(this.convertMarkdown(docs.remarks ?? '', apiLocation)).split('\n'));
            brk();
        }
        if (options.arguments?.length ?? 0 > 0) {
            brk();
            for (const param of options.arguments) {
                // Add a line for every argument. Even if there is no description, we need
                // the docstring so that the Sphinx extension can add the type annotations.
                lines.push(`:param ${toPythonParameterName(param.name)}: ${onelineDescription(param.docs)}`);
            }
            brk();
        }
        if (docs.default) {
            block('default', docs.default);
        }
        if (docs.returns) {
            block('return', docs.returns);
        }
        if (docs.deprecated) {
            block('deprecated', docs.deprecated);
        }
        if (docs.see) {
            block('see', docs.see, false);
        }
        if (docs.stability && shouldMentionStability(docs.stability)) {
            block('stability', docs.stability, false);
        }
        if (docs.subclassable) {
            block('subclassable', 'Yes');
        }
        for (const [k, v] of Object.entries(docs.custom ?? {})) {
            block(k, v, false);
        }
        if (docs.example) {
            brk();
            lines.push('Example::');
            lines.push('');
            const exampleText = this.convertExample(docs.example, apiLocation);
            for (const line of exampleText.split('\n')) {
                lines.push(`    ${line}`);
            }
            brk();
        }
        while (lines.length > 0 && lines[lines.length - 1] === '') {
            lines.pop();
        }
        if (lines.length === 0) {
            return;
        }
        if (lines.length === 1) {
            code.line(`${DOCSTRING_QUOTES}${lines[0]}${DOCSTRING_QUOTES}`);
        }
        else {
            code.line(`${DOCSTRING_QUOTES}${lines[0]}`);
            lines.splice(0, 1);
            for (const line of lines) {
                code.line(line);
            }
            code.line(DOCSTRING_QUOTES);
        }
        if (options.trailingNewLine) {
            code.line();
        }
    }
    convertExample(example, apiLoc) {
        const translated = this.rosetta.translateExample(apiLoc, example, jsii_rosetta_1.TargetLanguage.PYTHON, (0, jsii_rosetta_1.enforcesStrictMode)(this.assembly));
        return translated.source;
    }
    convertMarkdown(markdown, apiLoc) {
        return this.rosetta.translateSnippetsInMarkdown(apiLoc, markdown, jsii_rosetta_1.TargetLanguage.PYTHON, (0, jsii_rosetta_1.enforcesStrictMode)(this.assembly));
    }
    getPythonType(fqn) {
        const type = this.types.get(fqn);
        if (type === undefined) {
            throw new Error(`Could not locate type: "${fqn}"`);
        }
        return type;
    }
    getAssemblyOutputDir(assm) {
        return path.join('src', pythonModuleNameToFilename(this.getAssemblyModuleName(assm)));
    }
    onBeginAssembly(assm, _fingerprint) {
        this.package = new Package(assm.targets.python.distName, (0, version_utils_1.toReleaseVersion)(assm.version, index_1.TargetName.PYTHON), assm);
        // This is the '<packagename>._jsii' module for this assembly
        const assemblyModule = new PythonModule(this.getAssemblyModuleName(assm), undefined, {
            assembly: assm,
            assemblyFilename: this.getAssemblyFileName(),
            loadAssembly: true,
            package: this.package,
        });
        this.package.addModule(assemblyModule);
        this.package.addData(assemblyModule, this.getAssemblyFileName(), undefined);
    }
    onEndAssembly(assm, _fingerprint) {
        const resolver = new TypeResolver(this.types, (fqn) => this.findModule(fqn), (fqn) => this.findType(fqn));
        this.package.write(this.code, {
            assembly: assm,
            emittedTypes: new Set(),
            resolver,
            runtimeTypeChecking: this.runtimeTypeChecking,
            submodule: assm.name,
            typeCheckingHelper: new TypeCheckingHelper(),
            typeResolver: (fqn) => resolver.dereference(fqn),
        });
    }
    /**
     * Will be called for assembly root, namespaces and submodules (anything that contains other types, based on its FQN)
     */
    onBeginNamespace(ns) {
        // 'ns' contains something like '@scope/jsii-calc-base-of-base'
        const submoduleLike = ns === this.assembly.name
            ? this.assembly
            : this.assembly.submodules?.[ns];
        const readmeLocation = { api: 'moduleReadme', moduleFqn: ns };
        const module = new PythonModule((0, type_name_1.toPackageName)(ns, this.assembly), ns, {
            assembly: this.assembly,
            assemblyFilename: this.getAssemblyFileName(),
            package: this.package,
            moduleDocumentation: submoduleLike?.readme
                ? this.convertMarkdown(submoduleLike.readme?.markdown, readmeLocation).trim()
                : undefined,
        });
        this.package.addModule(module);
        this.types.set(ns, module);
        if (ns === this.assembly.name) {
            // This applies recursively to submodules, so no need to duplicate!
            this.package.addData(module, 'py.typed', '');
        }
        if (ns === this.assembly.name) {
            this.rootModule = module;
        }
        else {
            this.rootModule.addPythonModule(module);
        }
    }
    onEndNamespace(ns) {
        if (ns === this.assembly.name) {
            delete this.rootModule;
        }
    }
    onBeginClass(cls, abstract) {
        const klass = new Class(this, (0, util_2.toPythonIdentifier)(cls.name), cls, cls.fqn, {
            abstract,
            bases: cls.base ? [this.findType(cls.base)] : undefined,
            interfaces: cls.interfaces?.map((base) => this.findType(base)),
            abstractBases: abstract ? this.getAbstractBases(cls) : [],
        }, cls.docs);
        if (cls.initializer !== undefined) {
            const { parameters = [] } = cls.initializer;
            klass.addMember(new Initializer(this, '__init__', undefined, parameters, undefined, cls.initializer.docs, false, // Never static
            klass, { liftedProp: this.getliftedProp(cls.initializer), parent: cls }));
        }
        this.addPythonType(klass);
    }
    onStaticMethod(cls, method) {
        const { parameters = [] } = method;
        const klass = this.getPythonType(cls.fqn);
        klass.addMember(new StaticMethod(this, toPythonMethodName(method.name), method.name, parameters, method.returns, method.docs, true, // Always static
        klass, {
            abstract: method.abstract,
            liftedProp: this.getliftedProp(method),
            parent: cls,
        }));
    }
    onStaticProperty(cls, prop) {
        const klass = this.getPythonType(cls.fqn);
        klass.addMember(new StaticProperty(this, toPythonPropertyName(prop.name, prop.const), prop.name, prop, prop.docs, klass, {
            abstract: prop.abstract,
            immutable: prop.immutable,
            isStatic: prop.static,
            parent: cls,
        }));
    }
    onMethod(cls, method) {
        const { parameters = [] } = method;
        const klass = this.getPythonType(cls.fqn);
        if (method.async) {
            klass.addMember(new AsyncMethod(this, toPythonMethodName(method.name, method.protected), method.name, parameters, method.returns, method.docs, !!method.static, klass, {
                abstract: method.abstract,
                liftedProp: this.getliftedProp(method),
                parent: cls,
            }));
        }
        else {
            klass.addMember(new Method(this, toPythonMethodName(method.name, method.protected), method.name, parameters, method.returns, method.docs, !!method.static, klass, {
                abstract: method.abstract,
                liftedProp: this.getliftedProp(method),
                parent: cls,
            }));
        }
    }
    onProperty(cls, prop) {
        const klass = this.getPythonType(cls.fqn);
        klass.addMember(new Property(this, toPythonPropertyName(prop.name, prop.const, prop.protected), prop.name, prop, prop.docs, klass, {
            abstract: prop.abstract,
            immutable: prop.immutable,
            isStatic: prop.static,
            parent: cls,
        }));
    }
    onUnionProperty(cls, prop, _union) {
        this.onProperty(cls, prop);
    }
    onBeginInterface(ifc) {
        let iface;
        if (ifc.datatype) {
            iface = new Struct(this, (0, util_2.toPythonIdentifier)(ifc.name), ifc, ifc.fqn, { bases: ifc.interfaces?.map((base) => this.findType(base)) }, ifc.docs);
        }
        else {
            iface = new Interface(this, (0, util_2.toPythonIdentifier)(ifc.name), ifc, ifc.fqn, { bases: ifc.interfaces?.map((base) => this.findType(base)) }, ifc.docs);
        }
        this.addPythonType(iface);
    }
    onEndInterface(_ifc) {
        return;
    }
    onInterfaceMethod(ifc, method) {
        const { parameters = [] } = method;
        const klass = this.getPythonType(ifc.fqn);
        klass.addMember(new InterfaceMethod(this, toPythonMethodName(method.name, method.protected), method.name, parameters, method.returns, method.docs, !!method.static, klass, { liftedProp: this.getliftedProp(method), parent: ifc }));
    }
    onInterfaceProperty(ifc, prop) {
        let ifaceProperty;
        const klass = this.getPythonType(ifc.fqn);
        if (ifc.datatype) {
            ifaceProperty = new StructField(this, prop, ifc);
        }
        else {
            ifaceProperty = new InterfaceProperty(this, toPythonPropertyName(prop.name, prop.const, prop.protected), prop.name, prop, prop.docs, klass, { immutable: prop.immutable, isStatic: prop.static, parent: ifc });
        }
        klass.addMember(ifaceProperty);
    }
    onBeginEnum(enm) {
        this.addPythonType(new Enum(this, (0, util_2.toPythonIdentifier)(enm.name), enm, enm.fqn, {}, enm.docs));
    }
    onEnumMember(enm, member) {
        this.getPythonType(enm.fqn).addMember(new EnumMember(this, (0, util_2.toPythonIdentifier)(member.name), member.name, member.docs, enm));
    }
    onInterfaceMethodOverload(_ifc, _overload, _originalMethod) {
        throw new Error('Unhandled Type: InterfaceMethodOverload');
    }
    onMethodOverload(_cls, _overload, _originalMethod) {
        throw new Error('Unhandled Type: MethodOverload');
    }
    onStaticMethodOverload(_cls, _overload, _originalMethod) {
        throw new Error('Unhandled Type: StaticMethodOverload');
    }
    getAssemblyModuleName(assm) {
        return `${assm.targets.python.module}._jsii`;
    }
    getParentFQN(fqn) {
        const m = /^(.+)\.[^.]+$/.exec(fqn);
        if (m == null) {
            throw new Error(`Could not determine parent FQN of: ${fqn}`);
        }
        return m[1];
    }
    getParent(fqn) {
        return this.getPythonType(this.getParentFQN(fqn));
    }
    addPythonType(type) {
        if (type.fqn == null) {
            throw new Error('Cannot add a Python type without a FQN.');
        }
        this.getParent(type.fqn).addMember(type);
        this.types.set(type.fqn, type);
    }
    getliftedProp(method) {
        // If there are parameters to this method, and if the last parameter's type is
        // a datatype interface, then we want to lift the members of that last paramter
        // as keyword arguments to this function.
        if (method.parameters?.length ?? 0 >= 1) {
            const lastParameter = method.parameters.slice(-1)[0];
            if (!lastParameter.variadic &&
                spec.isNamedTypeReference(lastParameter.type)) {
                const lastParameterType = this.findType(lastParameter.type.fqn);
                if (spec.isInterfaceType(lastParameterType) &&
                    lastParameterType.datatype) {
                    return lastParameterType;
                }
            }
        }
        return undefined;
    }
    getAbstractBases(cls) {
        const abstractBases = [];
        if (cls.base !== undefined) {
            const base = this.findType(cls.base);
            if (!spec.isClassType(base)) {
                throw new Error("Class inheritance that isn't a class?");
            }
            if (base.abstract) {
                abstractBases.push(base);
            }
        }
        return abstractBases;
    }
}
/**
 * Render a one-line description of the given docs, used for method arguments and inlined properties
 */
function onelineDescription(docs) {
    // Only consider a subset of fields here, we don't have a lot of formatting space
    if (!docs || Object.keys(docs).length === 0) {
        return '-';
    }
    const parts = [];
    if (docs.summary) {
        parts.push((0, markdown_1.md2rst)((0, _utils_1.renderSummary)(docs)));
    }
    if (docs.remarks) {
        parts.push((0, markdown_1.md2rst)(docs.remarks));
    }
    if (docs.default) {
        parts.push(`Default: ${(0, markdown_1.md2rst)(docs.default)}`);
    }
    return parts.join(' ').replace(/\s+/g, ' ');
}
function shouldMentionStability(s) {
    // Don't render "stable" or "external", those are both stable by implication.
    return s === spec.Stability.Deprecated || s === spec.Stability.Experimental;
}
function isStruct(typeSystem, ref) {
    if (!spec.isNamedTypeReference(ref)) {
        return false;
    }
    const type = typeSystem.tryFindFqn(ref.fqn);
    return !!(type?.isInterfaceType() && type?.isDataType());
}
/**
 * Appends `_` at the end of `name` until it no longer conflicts with any of the
 * entries in `inUse`.
 *
 * @param name  the name to be slugified.
 * @param inUse the names that are already being used.
 *
 * @returns the slugified name.
 */
function slugifyAsNeeded(name, inUse) {
    const inUseSet = new Set(inUse);
    while (inUseSet.has(name)) {
        name = `${name}_`;
    }
    return name;
}
////////////////////////////////////////////////////////////////////////////////
// BEHOLD: Helpers to output code that looks like what Black would format into...
//
// @see https://black.readthedocs.io/en/stable/the_black_code_style.html
const TARGET_LINE_LENGTH = 88;
function openSignature(code, keyword, name, params, returnType, lineComment) {
    const prefix = `${keyword} ${name}`;
    const suffix = returnType ? ` -> ${returnType}` : '';
    if (params.length === 0) {
        code.openBlock(`${prefix}${returnType ? '()' : ''}${suffix}`);
        return;
    }
    const join = ', ';
    const { elementsSize, joinSize } = totalSizeOf(params, join);
    const hasComments = params.some((param) => /#\s*.+$/.exec(param) != null);
    if (!hasComments &&
        TARGET_LINE_LENGTH >
            code.currentIndentLength +
                prefix.length +
                elementsSize +
                joinSize +
                suffix.length +
                2) {
        code.indent(`${prefix}(${params.join(join)})${suffix}:${lineComment ? `  # ${lineComment}` : ''}`);
        return;
    }
    code.indent(`${prefix}(`);
    for (const param of params) {
        code.line(param.replace(/(\s*# .+)?$/, ',$1'));
    }
    code.unindent(false);
    code.indent(`)${suffix}:${lineComment ? `  # ${lineComment}` : ''}`);
}
/**
 * Emits runtime type checking code for parameters.
 *
 * @param code        the CodeMaker to use for emitting code.
 * @param context     the emit context used when emitting this code.
 * @param params      the parameter signatures to be type-checked.
 * @params pythonName the name of the Python function being checked (qualified).
 */
function emitParameterTypeChecks(code, context, params, fqn) {
    if (!context.runtimeTypeChecking) {
        return false;
    }
    const paramInfo = params.map((param) => {
        const [name] = param.split(/\s*[:=#]\s*/, 1);
        if (name === '*') {
            return { kwargsMark: true };
        }
        else if (name.startsWith('*')) {
            return { name: name.slice(1), is_rest: true };
        }
        return { name };
    });
    const paramNames = paramInfo
        .filter((param) => param.name != null)
        .map((param) => param.name.split(/\s*:\s*/)[0]);
    const typesVar = slugifyAsNeeded('type_hints', paramNames);
    let openedBlock = false;
    for (const { is_rest, kwargsMark, name } of paramInfo) {
        if (kwargsMark) {
            if (!context.runtimeTypeCheckKwargs) {
                // This is the keyword-args separator, we won't check keyword arguments here because the kwargs will be rolled
                // up into a struct instance, and that struct's constructor will be checking again...
                break;
            }
            // Skip this (there is nothing to be checked as this is just a marker...)
            continue;
        }
        if (!openedBlock) {
            code.openBlock('if __debug__');
            code.line(`${typesVar} = ${context.typeCheckingHelper.getTypeHints(fqn, params)}`);
            openedBlock = true;
        }
        let expectedType = `${typesVar}[${JSON.stringify(name)}]`;
        let comment = '';
        if (is_rest) {
            // This is a vararg, so the value will appear as a tuple.
            expectedType = `typing.Tuple[${expectedType}, ...]`;
            // Need to ignore reportGeneralTypeIssues because pyright incorrectly parses that as a type annotation 😒
            comment = ' # pyright: ignore [reportGeneralTypeIssues]';
        }
        code.line(`check_type(argname=${JSON.stringify(`argument ${name}`)}, value=${name}, expected_type=${expectedType})${comment}`);
    }
    if (openedBlock) {
        code.closeBlock();
        return true;
    }
    // We did not reference type annotations data if we never opened a type-checking block.
    return false;
}
function assignCallResult(code, variable, funct, params) {
    const prefix = `${variable} = ${funct}(`;
    const suffix = ')';
    if (params.length === 0) {
        code.line(`${prefix}${suffix}`);
        return;
    }
    const join = ', ';
    const { elementsSize, joinSize } = totalSizeOf(params, join);
    if (TARGET_LINE_LENGTH >
        code.currentIndentLength +
            prefix.length +
            elementsSize +
            joinSize +
            suffix.length) {
        code.line(`${prefix}${params.join(join)}${suffix}`);
        return;
    }
    code.indent(prefix);
    if (TARGET_LINE_LENGTH > code.currentIndentLength + elementsSize + joinSize) {
        code.line(params.join(join));
    }
    else {
        for (const param of params) {
            code.line(`${param},`);
        }
    }
    code.unindent(suffix);
}
function assignDictionary(code, variable, elements, trailing, compact = false) {
    const space = compact ? '' : ' ';
    const prefix = `${variable}${space}=${space}{`;
    const suffix = `}${trailing ?? ''}`;
    if (elements.length === 0) {
        code.line(`${prefix}${suffix}`);
        return;
    }
    if (compact) {
        const join = ', ';
        const { elementsSize, joinSize } = totalSizeOf(elements, join);
        if (TARGET_LINE_LENGTH >
            prefix.length +
                code.currentIndentLength +
                elementsSize +
                joinSize +
                suffix.length) {
            code.line(`${prefix}${elements.join(join)}${suffix}`);
            return;
        }
    }
    code.indent(prefix);
    for (const elt of elements) {
        code.line(`${elt},`);
    }
    code.unindent(suffix);
}
function emitList(code, prefix, elements, suffix, opts) {
    if (elements.length === 0) {
        code.line(`${prefix}${suffix}`);
        return;
    }
    const join = ', ';
    const { elementsSize, joinSize } = totalSizeOf(elements, join);
    if (TARGET_LINE_LENGTH >
        code.currentIndentLength +
            prefix.length +
            elementsSize +
            joinSize +
            suffix.length) {
        code.line(`${prefix}${elements.join(join)}${suffix}`);
        return;
    }
    const [before, after] = opts?.ifMulti ?? ['', ''];
    code.indent(`${prefix}${before}`);
    if (elements.length === 1) {
        code.line(elements[0]);
    }
    else {
        if (TARGET_LINE_LENGTH >
            code.currentIndentLength + elementsSize + joinSize) {
            code.line(elements.join(join));
        }
        else {
            for (const elt of elements) {
                code.line(`${elt},`);
            }
        }
    }
    code.unindent(`${after}${suffix}`);
}
function totalSizeOf(strings, join) {
    return {
        elementsSize: strings
            .map((str) => str.length)
            .reduce((acc, elt) => acc + elt, 0),
        joinSize: strings.length > 1 ? join.length * (strings.length - 1) : 0,
    };
}
function nestedContext(context, fqn) {
    return {
        ...context,
        surroundingTypeFqns: fqn != null
            ? [...(context.surroundingTypeFqns ?? []), fqn]
            : context.surroundingTypeFqns,
    };
}
const isDeprecated = (x) => x.docs?.deprecated !== undefined;
/**
 * Last component of a .-separated name
 */
function lastComponent(n) {
    const parts = n.split('.');
    return parts[parts.length - 1];
}
//# sourceMappingURL=python.js.map