feat: Template types #45

Merged
glen merged 10 commits from template_types into main 2022-08-05 12:48:57 +00:00
4 changed files with 110 additions and 45 deletions
Showing only changes of commit e82bcf5a9c - Show all commits

View File

@ -1,8 +1,8 @@
/* Core of pocomath: create an instance */ /* Core of pocomath: create an instance */
import typed from 'typed-function' import typed from 'typed-function'
import dependencyExtractor from './dependencyExtractor.mjs' import {dependencyExtractor, generateTypeExtractor} from './extractors.mjs'
import {makeChain} from './Chain.mjs' import {makeChain} from './Chain.mjs'
import {subsetOfKeys, typesOfSignature} from './utils.mjs' import {typesOfSignature, subsetOfKeys} from './utils.mjs'
const anySpec = {} // fixed dummy specification of 'any' type const anySpec = {} // fixed dummy specification of 'any' type
@ -461,9 +461,12 @@ export default class PocomathInstance {
this._addAffect(depname, name) this._addAffect(depname, name)
} }
for (const type of typesOfSignature(signature)) { for (const type of typesOfSignature(signature)) {
if (this._templateParam(type)) continue for (const word of type.split(/[<>]/)) {
this._usedTypes.add(type) if (word.length == 0) continue
this._addAffect(':' + type, name) if (this._templateParam(word)) continue
this._usedTypes.add(word)
this._addAffect(':' + word, name)
}
} }
} }
} }
@ -526,25 +529,40 @@ export default class PocomathInstance {
if (!imps) { if (!imps) {
throw new SyntaxError(`No implementations for ${name}`) throw new SyntaxError(`No implementations for ${name}`)
} }
const usableEntries = Object.entries(imps).filter( /* Collect the entries we know the types for */
([signature]) => subsetOfKeys(typesOfSignature(signature), this.Types)) const usableEntries = []
for (const entry of Object.entries(imps)) {
let keep = true
for (const type of typesOfSignature(entry[0])) {
if (type in this.Types) continue
const baseType = type.split('<')[0]
if (baseType in this._templateTypes) continue
keep = false
break
}
if (keep) usableEntries.push(entry)
}
if (usableEntries.length === 0) { if (usableEntries.length === 0) {
throw new SyntaxError( throw new SyntaxError(
`Every implementation for ${name} uses an undefined type;\n` `Every implementation for ${name} uses an undefined type;\n`
+ ` signatures: ${Object.keys(imps)}`) + ` signatures: ${Object.keys(imps)}`)
} }
// Mark this method as being in the midst of being reassembled /* Initial error checking done; mark this method as being
* in the midst of being reassembled
*/
Object.defineProperty(this, name, {configurable: true, value: 'limbo'}) Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
const tf_imps = {} const tf_imps = {}
for (const [rawSignature, behavior] of usableEntries) { for (const [rawSignature, behavior] of usableEntries) {
/* Check if it's an ordinary non-template signature */ /* Check if it's an ordinary non-template signature */
let explicit = true let explicit = true
for (const type of typesOfSignature(rawSignature)) { for (const type of typesOfSignature(rawSignature)) {
if (this._templateParam(type)) { // template types need better check for (const word of type.split(/[<>]/)) {
if (this._templateParam(word)) {
explicit = false explicit = false
break break
} }
} }
}
if (explicit) { if (explicit) {
this._addTFimplementation(tf_imps, rawSignature, behavior) this._addTFimplementation(tf_imps, rawSignature, behavior)
continue continue
@ -591,36 +609,31 @@ export default class PocomathInstance {
innerRefs[dep] = refs[outerName] innerRefs[dep] = refs[outerName]
} }
} }
const original = behavior.does(innerRefs)
return behavior.does(innerRefs) return behavior.does(innerRefs)
} }
this._addTFimplementation(tf_imps, signature, {uses, does: patch}) this._addTFimplementation(tf_imps, signature, {uses, does: patch})
} }
/* Now add the catchall signature */ /* Now add the catchall signature */
let templateCall = `<${theTemplateParam}>`
/* Relying here that the base of 'Foo<T>' is 'Foo': */
let baseSignature = substituteInSig(trimSignature, templateCall, '')
/* Any remaining template params are top-level */
const signature = substituteInSig( const signature = substituteInSig(
trimSignature, theTemplateParam, 'any') baseSignature, theTemplateParam, 'any')
/* The catchall signature has to detect the actual type of the call /* The catchall signature has to detect the actual type of the call
* and add the new instantiations. We should really be using the * and add the new instantiations.
* typed-function parser to do the manipulations below, but we don't * First, prepare the type inference data:
* have access. The firs section prepares the type inference data:
*/ */
const parTypes = trimSignature.split(',') const parTypes = trimSignature.split(',')
const inferences = [] const topTyper = entity => this.typeOf(entity)
const typer = entity => this.typeOf(entity) const inferences = parTypes.map(
let ambiguous = true type => generateTypeExtractor(
for (let parType of parTypes) { type,
parType = parType.trim() theTemplateParam,
if (parType.slice(0,3) === '...') { topTyper,
parType = parType.slice(3).trim() this.joinTypes.bind(this),
} this._templateTypes))
if (parType === theTemplateParam) { if (inferences.every(x => !x)) { // all false
inferences.push(typer)
ambiguous = false
} else {
inferences.push(false)
}
}
if (ambiguous) {
throw new SyntaxError( throw new SyntaxError(
`Cannot find template parameter in ${rawSignature}`) `Cannot find template parameter in ${rawSignature}`)
} }
@ -654,9 +667,24 @@ export default class PocomathInstance {
usedConversions = true usedConversions = true
instantiateFor = self.joinTypes(argTypes, usedConversions) instantiateFor = self.joinTypes(argTypes, usedConversions)
if (instantiateFor === 'any') { if (instantiateFor === 'any') {
// Need a more informative error message here
throw TypeError('No common type for arguments to ' + name) throw TypeError('No common type for arguments to ' + name)
} }
} }
/* Generate the list of actual wanted types */
const wantTypes = parTypes.map(type => substituteInSig(
type, theTemplateParam, instantiateFor))
/* Now we have to add any actual types that are relevant
* to this invocation. Namely, that would be every formal parameter
* type in the invocation, with the parameter template instantiated
* by instantiateFor, and for all of instantiateFor's "prior types"
*/
for (j = 0; j < parTypes.length; ++j) {
if (wantTypes[i] !== parTypes[i] && wantTypes.includes('<')) {
// actually used the param and is a template
self._ensureTemplateTypes(parTypes[i], instantiateFor)
}
}
/* Transform the arguments if we used any conversions: */ /* Transform the arguments if we used any conversions: */
if (usedConversions) { if (usedConversions) {
i = - 1 i = - 1
@ -774,4 +802,12 @@ export default class PocomathInstance {
} }
imps[signature] = does(refs) imps[signature] = does(refs)
} }
/* HERE!! This function needs to analyze the template and make sure the
* instantiations of it for type and all prior types of type are present
* in the instance
*/
_ensureTemplateTypes(template, type) {
}
} }

View File

@ -1,12 +0,0 @@
/* Call this with an empty Set object S, and it returns an entity E
* from which properties can be extracted, and at any time S will
* contain all of the property names that have been extracted from E.
*/
export default function dependencyExtractor(destinationSet) {
return new Proxy({}, {
get: (target, property) => {
destinationSet.add(property)
return {}
}
})
}

41
src/core/extractors.mjs Normal file
View File

@ -0,0 +1,41 @@
/* Call this with an empty Set object S, and it returns an entity E
* from which properties can be extracted, and at any time S will
* contain all of the property names that have been extracted from E.
*/
export function dependencyExtractor(destinationSet) {
return new Proxy({}, {
get: (target, property) => {
destinationSet.add(property)
return {}
}
})
}
/* Given a (template) type name, what the template parameter is,
* a top level typer, and a library of templates,
* produces a function that will extract the instantantion type from an
* instance. Currently relies heavily on there being only unary templates.
*
* We should really be using the typed-function parser to do the
* manipulations below, but at the moment we don't have access.
*/
export function generateTypeExtractor(
type, param, topTyper, typeJoiner, templates)
{
type = type.trim()
if (type.slice(0,3) === '...') {
type = type.slice(3).trim()
}
if (type === param) return topTyper
if (!(type.includes('<'))) return false // no template type to extract
const base = type.split('<',1)[0]
if (!(base in templates)) return false // unknown template
const arg = type.slice(base.length+1, -1)
const argExtractor = generateTypeExtractor(
arg, param, topTyper, typeJointer, templates)
if (!argExtractor) return false
return templates[base].infer({
typeOf: argExtractor,
joinTypes: typeJoiner
})
}

View File

@ -1,5 +1,5 @@
import assert from 'assert' import assert from 'assert'
import dependencyExtractor from '../../src/core/dependencyExtractor.mjs' import {dependencyExtractor} from '../../src/core/extractors.mjs'
describe('dependencyExtractor', () => { describe('dependencyExtractor', () => {
it('will record the keys of a destructuring function', () => { it('will record the keys of a destructuring function', () => {