All files ZWrapper.js

94.66% Statements 124/131
87.5% Branches 70/80
100% Functions 13/13
94.66% Lines 124/131

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312    1x 1x 1x 1x           1x   1x                                 940734x 940734x 940734x 940734x               2229056x 1288322x   940734x 940734x 2134452x 2134452x 2134452x 2134452x   471549x 471549x 240018x   471549x       940734x       12464964x 118724x   12346240x       20999x         37652x 32755x   37652x 37652x 37263x   37652x 259x   37652x 37652x 37652x 37652x 49911x 49911x 49911x   49911x 49911x 49911x 114x 114x     114x 3x   111x 111x     49797x 49787x       49787x 1907x 1907x 1907x 1907x 1907x     47890x 47880x 47880x 5470x 5470x     5470x 10x   5460x 5460x 5460x     42420x 4781x 4781x     4781x 4781x 4781x                   4781x   37639x   37639x             21003x 21003x 21003x   21003x 21003x 21003x 7x   20996x           20996x     5284x     5284x 5241x 5241x                   5241x 5241x       5241x 5241x     2x       20994x 20994x   20994x                                         16649x   16649x                                               47952x 23608x   24344x 24344x   3x   24341x 21003x   21003x 9x     24332x 24332x 24332x         24332x       5318514x 5318514x 11948080x 11948080x 5168301x   11948080x   5318514x       5340102x       543903x       247767x         1x  
'use strict';
 
const { EmptyFrame } = require( './frame.js' );
const { containsError, createSchema, isGenericType, makeWrappedResultEnvelope } = require( './utils.js' );
const { error, normalError } = require( '../function-schemata/javascript/src/error.js' );
const { isString, isUserDefined } = require( '../function-schemata/javascript/src/utils' );
const {
	validatesAsFunctionCall,
	validatesAsReference,
	validatesAsArgumentReference,
	validatesAsType
} = require( '../function-schemata/javascript/src/schema.js' );
 
const MutationType = Object.freeze( {
	REFERENCE: Symbol( 'REFERENCE' ),
	ARGUMENT_REFERENCE: Symbol( 'ARGUMENT_REFERENCE' ),
	FUNCTION_CALL: Symbol( 'FUNCTION_CALL' ),
	GENERIC_INSTANCE: Symbol( 'GENERIC_INSTANCE' )
} );
 
/**
 * Wrapper around ZObjects that should be used instead of bare ZObjects during evaluation.
 *
 * The wrapper keeps track of the scope under which the object should be evaluated, and caches
 * the results of resolving subobjects for future use.
 */
class ZWrapper {
 
	// Private. Use {@link ZWrapper#create} instead.
	constructor() {
		this.original_ = new Map();
		this.resolved_ = new Map();
		this.keys_ = new Set();
		this.scope_ = null;
	}
 
	// Creates an equivalent ZWrapper representation for the given ZObject and its subobjects.
	// The resulting ZWrapper has the same fields as the ZObject, each of which is itself a
	// ZWrapper, and so on.
	// TODO(T309635): We should probably always provide the scope when creating a ZWrapper.
	static create( zobjectJSON ) {
		if ( isString( zobjectJSON ) || zobjectJSON instanceof ZWrapper ) {
			return zobjectJSON;
		}
		const result = new ZWrapper();
		for ( const key of Object.keys( zobjectJSON ) ) {
			const value = ZWrapper.create( zobjectJSON[ key ] );
			result.original_.set( key, value );
			result.keys_.add( key );
			Object.defineProperty( result, key, {
				get: function () {
					const result = this.getName( key );
					if ( result instanceof ZWrapper && result.getScope() === null ) {
						result.setScope( this.getScope() );
					}
					return result;
				}
			} );
		}
		return result;
	}
 
	getName( key ) {
		if ( this.resolved_.has( key ) ) {
			return this.resolved_.get( key );
		}
		return this.original_.get( key );
	}
 
	setName( key, value ) {
		this.resolved_.set( key, value );
	}
 
	// private
	async resolveInternal_( invariants, scope, ignoreList, resolveInternals, doValidate ) {
		if ( ignoreList === null ) {
			ignoreList = new Set();
		}
		let innerScope = this.getScope();
		if ( innerScope === null ) {
			innerScope = new EmptyFrame();
		}
		if ( scope === null ) {
			scope = new EmptyFrame();
		}
		scope = innerScope.mergedCopy( scope );
		let nextObject = this;
		let setObject = nextObject;
		while ( true ) {
			let nextJSON = nextObject;
			Eif ( nextJSON instanceof ZWrapper ) {
				nextJSON = nextJSON.asJSON();
			}
			Eif ( !ignoreList.has( MutationType.ARGUMENT_REFERENCE ) ) {
				const argumentReferenceStatus = await validatesAsArgumentReference( nextJSON );
				if ( argumentReferenceStatus.isValid() && scope !== null ) {
					const refKey = nextObject.Z18K1.Z6K1;
					const dereferenced = await scope.retrieveArgument(
						refKey, invariants, /* lazily= */ false, doValidate,
						resolveInternals, ignoreList );
					if ( dereferenced.state === 'ERROR' ) {
						return [ makeWrappedResultEnvelope( null, dereferenced.error ), setObject ];
					}
					nextObject = dereferenced.argumentDict.argument;
					continue;
				}
			}
			if ( !ignoreList.has( MutationType.REFERENCE ) ) {
				const referenceStatus = await validatesAsReference( nextJSON );
				// TODO (T296686): isUserDefined call here is only an
				// optimization/testing expedient; it would be better to pre-populate
				// the cache with builtin types.
				if ( referenceStatus.isValid() && isUserDefined( nextObject.Z9K1 ) ) {
					const refKey = nextObject.Z9K1;
					const dereferenced = await invariants.resolver.dereference( [ refKey ] );
					nextObject = dereferenced[ refKey ].Z2K2;
					setObject = nextObject;
					continue;
				}
			}
			if ( !ignoreList.has( MutationType.FUNCTION_CALL ) ) {
				const functionCallStatus = await validatesAsFunctionCall( nextJSON );
				if ( functionCallStatus.isValid() ) {
					const { execute } = require( './execute.js' );
					const Z22 = await execute(
						nextObject, invariants, scope, doValidate,
						/* implementationSelector= */ null, resolveInternals );
					if ( containsError( Z22 ) ) {
						return [ Z22, setObject ];
					}
					nextObject = Z22.Z22K1;
					setObject = nextObject;
					continue;
				}
			}
			if ( await isGenericType( nextObject ) ) {
				const executionResult = await nextObject.resolveKey( [ 'Z1K1' ], invariants, scope, ignoreList, resolveInternals, doValidate );
				Iif ( containsError( executionResult ) ) {
					return [ executionResult, setObject ];
				}
				const Z4 = nextObject.Z1K1;
				const typeStatus = await validatesAsType( Z4.asJSON() );
				Iif ( !typeStatus.isValid() ) {
					// TODO (T2966681): Return typeStatus.getZ5() as part of this result.
					return [
						makeWrappedResultEnvelope(
							null,
							normalError(
								[ error.argument_type_mismatch ],
								[ 'Generic type function did not return a Z4: ' + JSON.stringify( Z4.asJSON() ) ] ) ),
						setObject ];
				}
				continue;
			}
			break;
		}
		return [ makeWrappedResultEnvelope( nextObject, null ), setObject ];
	}
 
	// private
	async resolveKeyInternal_(
		key, invariants, scope, ignoreList, resolveInternals, doValidate ) {
		let newValue, resultPair, setObject;
		const currentValue = this.getName( key );
		Eif ( currentValue instanceof ZWrapper ) {
			const resultTuple = await ( currentValue.resolveInternal_(
				invariants, scope, ignoreList, resolveInternals, doValidate ) );
			resultPair = resultTuple[ 0 ];
			setObject = resultTuple[ 1 ];
			if ( containsError( resultPair ) ) {
				return resultPair;
			}
			newValue = resultPair.Z22K1;
		} else {
			resolveInternals = false;
			resultPair = makeWrappedResultEnvelope( this, null );
			newValue = currentValue;
		}
		if ( resolveInternals ) {
			// Validate that the newly-mutated object validates in accordance with the
			// original object's key declaration.
			const theSchema = await createSchema( this.asJSON() );
			// We validate elsewhere that Z1K1 must be a type, so the schemata do not
			// surface separate validators for Z1K1.
			if ( key !== 'Z1K1' ) {
				const subValidator = theSchema.subValidator( key );
				Iif ( subValidator === undefined ) {
					// Should never happen?
					return makeWrappedResultEnvelope(
						null,
						normalError(
							[ error.invalid_key ],
							[ `ZObject does not have the key ${key}` ] ) );
				}
 
				let toValidate;
				Eif ( newValue instanceof ZWrapper ) {
					toValidate = newValue.asJSON();
				} else {
					toValidate = newValue;
				}
				const theStatus = await subValidator.validateStatus( toValidate );
				if ( !theStatus.isValid() ) {
					// TODO (T302015): Find a way to incorporate information about where this
					// error came from.
					return makeWrappedResultEnvelope( null, theStatus.getZ5() );
				}
			}
		}
		Eif ( setObject !== null ) {
			this.setName( key, setObject );
		}
		return resultPair;
	}
 
	/**
	 * Repeatedly evaluates the top-level object according to its evaluation rules.
	 *
	 * The returned object does not have any evaluation rule that applies to it (i.e. it is not a
	 * reference, argument reference, function call, etc.) but the same is not true for its
	 * subobjects; they should be resolved separately. Moreover, it is wrapped in a result envelope
	 * to indicate any errors.
	 *
	 * @param {Invariants} invariants
	 * @param {Frame} scope Doesn't seem needed since the scope is attached to the zobject?
	 * @param {Set(MutationType)} ignoreList
	 * @param {boolean} resolveInternals
	 * @param {boolean} doValidate
	 * @return {ZWrapper} A result envelope zobject representing the result.
	 */
	async resolve(
		invariants, scope = null, ignoreList = null, resolveInternals = true, doValidate = true
	) {
		const result = await this.resolveInternal_(
			invariants, scope, ignoreList, resolveInternals, doValidate );
		return result[ 0 ];
	}
 
	/**
	 * Recursively traverses and resolves the current object along the given keys, caching the
	 * results for future calls.
	 *
	 * The returned object does not have any evaluation rule that applies to it (i.e. it is not a
	 * reference, argument reference, function call, etc.) but the same is not true for its
	 * subobjects; they should be resolved separately. Moreover, it is wrapped in a result envelope
	 * to indicate any errors.
	 *
	 * @param {Array(string)} keys Path of subobjects to resolve
	 * @param {Invariants} invariants
	 * @param {Frame} scope Doesn't seem needed since the scope is attached to the zobject?
	 * @param {Set(MutationType)} ignoreList
	 * @param {booleanl} resolveInternals
	 * @param {boolean} doValidate
	 * @return {ZWrapper} A result envelope zobject representing the result.
	 */
	async resolveKey(
		keys, invariants, scope = null, ignoreList = null,
		resolveInternals = true, doValidate = true ) {
		let result;
		if ( keys.length <= 0 ) {
			return makeWrappedResultEnvelope( this, null );
		}
		const key = keys.shift();
		if ( !( this.keys_.has( key ) ) ) {
			// TODO (T309809): Return an error in this case.
			return makeWrappedResultEnvelope( this, null );
		}
		if ( !this.resolved_.has( key ) ) {
			result = await this.resolveKeyInternal_(
				key, invariants, scope, ignoreList, resolveInternals, doValidate );
			if ( containsError( result ) ) {
				return result;
			}
		}
		const nextValue = this.getName( key );
		Eif ( nextValue instanceof ZWrapper ) {
			result = await (
				nextValue.resolveKey(
					keys, invariants, scope, ignoreList, resolveInternals, doValidate )
			);
		}
		return result;
	}
 
	asJSON() {
		const result = {};
		for ( const key of this.keys() ) {
			let value = this.getName( key );
			if ( value instanceof ZWrapper ) {
				value = value.asJSON();
			}
			result[ key ] = value;
		}
		return result;
	}
 
	keys() {
		return this.keys_.values();
	}
 
	getScope() {
		return this.scope_;
	}
 
	setScope( scope ) {
		this.scope_ = scope;
	}
 
}
 
module.exports = { MutationType, ZWrapper };