From 907eafced190ee11b3857ca9cdec9304ffc0d3fe Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Sun, 11 Jun 2023 18:37:39 +0200 Subject: [PATCH 01/66] Extract JS snippets to embedded *.js files --- compiler/prelude/goroutines.go | 395 +---------------- compiler/prelude/goroutines.js | 389 +++++++++++++++++ compiler/prelude/jsmapping.go | 419 +----------------- compiler/prelude/jsmapping.js | 413 ++++++++++++++++++ compiler/prelude/numeric.go | 218 +-------- compiler/prelude/numeric.js | 212 +++++++++ compiler/prelude/prelude.go | 580 +----------------------- compiler/prelude/prelude.js | 571 ++++++++++++++++++++++++ compiler/prelude/types.go | 776 +-------------------------------- compiler/prelude/types.js | 770 ++++++++++++++++++++++++++++++++ 10 files changed, 2382 insertions(+), 2361 deletions(-) create mode 100644 compiler/prelude/goroutines.js create mode 100644 compiler/prelude/jsmapping.js create mode 100644 compiler/prelude/numeric.js create mode 100644 compiler/prelude/prelude.js create mode 100644 compiler/prelude/types.js diff --git a/compiler/prelude/goroutines.go b/compiler/prelude/goroutines.go index 6478aba7a..60116907e 100644 --- a/compiler/prelude/goroutines.go +++ b/compiler/prelude/goroutines.go @@ -1,393 +1,8 @@ package prelude -const goroutines = ` -var $stackDepthOffset = 0; -var $getStackDepth = function() { - var err = new Error(); - if (err.stack === undefined) { - return undefined; - } - return $stackDepthOffset + err.stack.split("\n").length; -}; +import ( + _ "embed" +) -var $panicStackDepth = null, $panicValue; -var $callDeferred = function(deferred, jsErr, fromPanic) { - if (!fromPanic && deferred !== null && $curGoroutine.deferStack.indexOf(deferred) == -1) { - throw jsErr; - } - if (jsErr !== null) { - var newErr = null; - try { - $panic(new $jsErrorPtr(jsErr)); - } catch (err) { - newErr = err; - } - $callDeferred(deferred, newErr); - return; - } - if ($curGoroutine.asleep) { - return; - } - - $stackDepthOffset--; - var outerPanicStackDepth = $panicStackDepth; - var outerPanicValue = $panicValue; - - var localPanicValue = $curGoroutine.panicStack.pop(); - if (localPanicValue !== undefined) { - $panicStackDepth = $getStackDepth(); - $panicValue = localPanicValue; - } - - try { - while (true) { - if (deferred === null) { - deferred = $curGoroutine.deferStack[$curGoroutine.deferStack.length - 1]; - if (deferred === undefined) { - /* The panic reached the top of the stack. Clear it and throw it as a JavaScript error. */ - $panicStackDepth = null; - if (localPanicValue.Object instanceof Error) { - throw localPanicValue.Object; - } - var msg; - if (localPanicValue.constructor === $String) { - msg = localPanicValue.$val; - } else if (localPanicValue.Error !== undefined) { - msg = localPanicValue.Error(); - } else if (localPanicValue.String !== undefined) { - msg = localPanicValue.String(); - } else { - msg = localPanicValue; - } - throw new Error(msg); - } - } - var call = deferred.pop(); - if (call === undefined) { - $curGoroutine.deferStack.pop(); - if (localPanicValue !== undefined) { - deferred = null; - continue; - } - return; - } - var r = call[0].apply(call[2], call[1]); - if (r && r.$blk !== undefined) { - deferred.push([r.$blk, [], r]); - if (fromPanic) { - throw null; - } - return; - } - - if (localPanicValue !== undefined && $panicStackDepth === null) { - /* error was recovered */ - if (fromPanic) { - throw null; - } - return; - } - } - } catch(e) { - // Deferred function threw a JavaScript exception or tries to unwind stack - // to the point where a panic was handled. - if (fromPanic) { - // Re-throw the exception to reach deferral execution call at the end - // of the function. - throw e; - } - // We are at the end of the function, handle the error or re-throw to - // continue unwinding if necessary, or simply stop unwinding if we got far - // enough. - $callDeferred(deferred, e, fromPanic); - } finally { - if (localPanicValue !== undefined) { - if ($panicStackDepth !== null) { - $curGoroutine.panicStack.push(localPanicValue); - } - $panicStackDepth = outerPanicStackDepth; - $panicValue = outerPanicValue; - } - $stackDepthOffset++; - } -}; - -var $panic = function(value) { - $curGoroutine.panicStack.push(value); - $callDeferred(null, null, true); -}; -var $recover = function() { - if ($panicStackDepth === null || ($panicStackDepth !== undefined && $panicStackDepth !== $getStackDepth() - 2)) { - return $ifaceNil; - } - $panicStackDepth = null; - return $panicValue; -}; -var $throw = function(err) { throw err; }; - -var $noGoroutine = { asleep: false, exit: false, deferStack: [], panicStack: [] }; -var $curGoroutine = $noGoroutine, $totalGoroutines = 0, $awakeGoroutines = 0, $checkForDeadlock = true, $exportedFunctions = 0; -var $mainFinished = false; -var $go = function(fun, args) { - $totalGoroutines++; - $awakeGoroutines++; - var $goroutine = function() { - try { - $curGoroutine = $goroutine; - var r = fun.apply(undefined, args); - if (r && r.$blk !== undefined) { - fun = function() { return r.$blk(); }; - args = []; - return; - } - $goroutine.exit = true; - } catch (err) { - if (!$goroutine.exit) { - throw err; - } - } finally { - $curGoroutine = $noGoroutine; - if ($goroutine.exit) { /* also set by runtime.Goexit() */ - $totalGoroutines--; - $goroutine.asleep = true; - } - if ($goroutine.asleep) { - $awakeGoroutines--; - if (!$mainFinished && $awakeGoroutines === 0 && $checkForDeadlock && $exportedFunctions === 0) { - console.error("fatal error: all goroutines are asleep - deadlock!"); - if ($global.process !== undefined) { - $global.process.exit(2); - } - } - } - } - }; - $goroutine.asleep = false; - $goroutine.exit = false; - $goroutine.deferStack = []; - $goroutine.panicStack = []; - $schedule($goroutine); -}; - -var $scheduled = []; -var $runScheduled = function() { - // For nested setTimeout calls browsers enforce 4ms minimum delay. We minimize - // the effect of this penalty by queueing the timer preemptively before we run - // the goroutines, and later cancelling it if it turns out unneeded. See: - // https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#nested_timeouts - var nextRun = setTimeout($runScheduled); - try { - var start = Date.now(); - var r; - while ((r = $scheduled.shift()) !== undefined) { - r(); - // We need to interrupt this loop in order to allow the event loop to - // process timers, IO, etc. However, invoking scheduling through - // setTimeout is ~1000 times more expensive, so we amortize this cost by - // looping until the 4ms minimal delay has elapsed (assuming there are - // scheduled goroutines to run), and then yield to the event loop. - var elapsed = Date.now() - start; - if (elapsed > 4 || elapsed < 0) { break; } - } - } finally { - if ($scheduled.length == 0) { - // Cancel scheduling pass if there's nothing to run. - clearTimeout(nextRun); - } - } -}; - -var $schedule = function(goroutine) { - if (goroutine.asleep) { - goroutine.asleep = false; - $awakeGoroutines++; - } - $scheduled.push(goroutine); - if ($curGoroutine === $noGoroutine) { - $runScheduled(); - } -}; - -var $setTimeout = function(f, t) { - $awakeGoroutines++; - return setTimeout(function() { - $awakeGoroutines--; - f(); - }, t); -}; - -var $block = function() { - if ($curGoroutine === $noGoroutine) { - $throwRuntimeError("cannot block in JavaScript callback, fix by wrapping code in goroutine"); - } - $curGoroutine.asleep = true; -}; - -var $restore = function(context, params) { - if (context !== undefined && context.$blk !== undefined) { - return context; - } - return params; -} - -var $send = function(chan, value) { - if (chan.$closed) { - $throwRuntimeError("send on closed channel"); - } - var queuedRecv = chan.$recvQueue.shift(); - if (queuedRecv !== undefined) { - queuedRecv([value, true]); - return; - } - if (chan.$buffer.length < chan.$capacity) { - chan.$buffer.push(value); - return; - } - - var thisGoroutine = $curGoroutine; - var closedDuringSend; - chan.$sendQueue.push(function(closed) { - closedDuringSend = closed; - $schedule(thisGoroutine); - return value; - }); - $block(); - return { - $blk: function() { - if (closedDuringSend) { - $throwRuntimeError("send on closed channel"); - } - } - }; -}; -var $recv = function(chan) { - var queuedSend = chan.$sendQueue.shift(); - if (queuedSend !== undefined) { - chan.$buffer.push(queuedSend(false)); - } - var bufferedValue = chan.$buffer.shift(); - if (bufferedValue !== undefined) { - return [bufferedValue, true]; - } - if (chan.$closed) { - return [chan.$elem.zero(), false]; - } - - var thisGoroutine = $curGoroutine; - var f = { $blk: function() { return this.value; } }; - var queueEntry = function(v) { - f.value = v; - $schedule(thisGoroutine); - }; - chan.$recvQueue.push(queueEntry); - $block(); - return f; -}; -var $close = function(chan) { - if (chan.$closed) { - $throwRuntimeError("close of closed channel"); - } - chan.$closed = true; - while (true) { - var queuedSend = chan.$sendQueue.shift(); - if (queuedSend === undefined) { - break; - } - queuedSend(true); /* will panic */ - } - while (true) { - var queuedRecv = chan.$recvQueue.shift(); - if (queuedRecv === undefined) { - break; - } - queuedRecv([chan.$elem.zero(), false]); - } -}; -var $select = function(comms) { - var ready = []; - var selection = -1; - for (var i = 0; i < comms.length; i++) { - var comm = comms[i]; - var chan = comm[0]; - switch (comm.length) { - case 0: /* default */ - selection = i; - break; - case 1: /* recv */ - if (chan.$sendQueue.length !== 0 || chan.$buffer.length !== 0 || chan.$closed) { - ready.push(i); - } - break; - case 2: /* send */ - if (chan.$closed) { - $throwRuntimeError("send on closed channel"); - } - if (chan.$recvQueue.length !== 0 || chan.$buffer.length < chan.$capacity) { - ready.push(i); - } - break; - } - } - - if (ready.length !== 0) { - selection = ready[Math.floor(Math.random() * ready.length)]; - } - if (selection !== -1) { - var comm = comms[selection]; - switch (comm.length) { - case 0: /* default */ - return [selection]; - case 1: /* recv */ - return [selection, $recv(comm[0])]; - case 2: /* send */ - $send(comm[0], comm[1]); - return [selection]; - } - } - - var entries = []; - var thisGoroutine = $curGoroutine; - var f = { $blk: function() { return this.selection; } }; - var removeFromQueues = function() { - for (var i = 0; i < entries.length; i++) { - var entry = entries[i]; - var queue = entry[0]; - var index = queue.indexOf(entry[1]); - if (index !== -1) { - queue.splice(index, 1); - } - } - }; - for (var i = 0; i < comms.length; i++) { - (function(i) { - var comm = comms[i]; - switch (comm.length) { - case 1: /* recv */ - var queueEntry = function(value) { - f.selection = [i, value]; - removeFromQueues(); - $schedule(thisGoroutine); - }; - entries.push([comm[0].$recvQueue, queueEntry]); - comm[0].$recvQueue.push(queueEntry); - break; - case 2: /* send */ - var queueEntry = function() { - if (comm[0].$closed) { - $throwRuntimeError("send on closed channel"); - } - f.selection = [i]; - removeFromQueues(); - $schedule(thisGoroutine); - return comm[1]; - }; - entries.push([comm[0].$sendQueue, queueEntry]); - comm[0].$sendQueue.push(queueEntry); - break; - } - })(i); - } - $block(); - return f; -}; -` +//go:embed goroutines.js +var goroutines string diff --git a/compiler/prelude/goroutines.js b/compiler/prelude/goroutines.js new file mode 100644 index 000000000..bff08976b --- /dev/null +++ b/compiler/prelude/goroutines.js @@ -0,0 +1,389 @@ +var $stackDepthOffset = 0; +var $getStackDepth = function () { + var err = new Error(); + if (err.stack === undefined) { + return undefined; + } + return $stackDepthOffset + err.stack.split("\n").length; +}; + +var $panicStackDepth = null, $panicValue; +var $callDeferred = function (deferred, jsErr, fromPanic) { + if (!fromPanic && deferred !== null && $curGoroutine.deferStack.indexOf(deferred) == -1) { + throw jsErr; + } + if (jsErr !== null) { + var newErr = null; + try { + $panic(new $jsErrorPtr(jsErr)); + } catch (err) { + newErr = err; + } + $callDeferred(deferred, newErr); + return; + } + if ($curGoroutine.asleep) { + return; + } + + $stackDepthOffset--; + var outerPanicStackDepth = $panicStackDepth; + var outerPanicValue = $panicValue; + + var localPanicValue = $curGoroutine.panicStack.pop(); + if (localPanicValue !== undefined) { + $panicStackDepth = $getStackDepth(); + $panicValue = localPanicValue; + } + + try { + while (true) { + if (deferred === null) { + deferred = $curGoroutine.deferStack[$curGoroutine.deferStack.length - 1]; + if (deferred === undefined) { + /* The panic reached the top of the stack. Clear it and throw it as a JavaScript error. */ + $panicStackDepth = null; + if (localPanicValue.Object instanceof Error) { + throw localPanicValue.Object; + } + var msg; + if (localPanicValue.constructor === $String) { + msg = localPanicValue.$val; + } else if (localPanicValue.Error !== undefined) { + msg = localPanicValue.Error(); + } else if (localPanicValue.String !== undefined) { + msg = localPanicValue.String(); + } else { + msg = localPanicValue; + } + throw new Error(msg); + } + } + var call = deferred.pop(); + if (call === undefined) { + $curGoroutine.deferStack.pop(); + if (localPanicValue !== undefined) { + deferred = null; + continue; + } + return; + } + var r = call[0].apply(call[2], call[1]); + if (r && r.$blk !== undefined) { + deferred.push([r.$blk, [], r]); + if (fromPanic) { + throw null; + } + return; + } + + if (localPanicValue !== undefined && $panicStackDepth === null) { + /* error was recovered */ + if (fromPanic) { + throw null; + } + return; + } + } + } catch (e) { + // Deferred function threw a JavaScript exception or tries to unwind stack + // to the point where a panic was handled. + if (fromPanic) { + // Re-throw the exception to reach deferral execution call at the end + // of the function. + throw e; + } + // We are at the end of the function, handle the error or re-throw to + // continue unwinding if necessary, or simply stop unwinding if we got far + // enough. + $callDeferred(deferred, e, fromPanic); + } finally { + if (localPanicValue !== undefined) { + if ($panicStackDepth !== null) { + $curGoroutine.panicStack.push(localPanicValue); + } + $panicStackDepth = outerPanicStackDepth; + $panicValue = outerPanicValue; + } + $stackDepthOffset++; + } +}; + +var $panic = function (value) { + $curGoroutine.panicStack.push(value); + $callDeferred(null, null, true); +}; +var $recover = function () { + if ($panicStackDepth === null || ($panicStackDepth !== undefined && $panicStackDepth !== $getStackDepth() - 2)) { + return $ifaceNil; + } + $panicStackDepth = null; + return $panicValue; +}; +var $throw = function (err) { throw err; }; + +var $noGoroutine = { asleep: false, exit: false, deferStack: [], panicStack: [] }; +var $curGoroutine = $noGoroutine, $totalGoroutines = 0, $awakeGoroutines = 0, $checkForDeadlock = true, $exportedFunctions = 0; +var $mainFinished = false; +var $go = function (fun, args) { + $totalGoroutines++; + $awakeGoroutines++; + var $goroutine = function () { + try { + $curGoroutine = $goroutine; + var r = fun.apply(undefined, args); + if (r && r.$blk !== undefined) { + fun = function () { return r.$blk(); }; + args = []; + return; + } + $goroutine.exit = true; + } catch (err) { + if (!$goroutine.exit) { + throw err; + } + } finally { + $curGoroutine = $noGoroutine; + if ($goroutine.exit) { /* also set by runtime.Goexit() */ + $totalGoroutines--; + $goroutine.asleep = true; + } + if ($goroutine.asleep) { + $awakeGoroutines--; + if (!$mainFinished && $awakeGoroutines === 0 && $checkForDeadlock && $exportedFunctions === 0) { + console.error("fatal error: all goroutines are asleep - deadlock!"); + if ($global.process !== undefined) { + $global.process.exit(2); + } + } + } + } + }; + $goroutine.asleep = false; + $goroutine.exit = false; + $goroutine.deferStack = []; + $goroutine.panicStack = []; + $schedule($goroutine); +}; + +var $scheduled = []; +var $runScheduled = function () { + // For nested setTimeout calls browsers enforce 4ms minimum delay. We minimize + // the effect of this penalty by queueing the timer preemptively before we run + // the goroutines, and later cancelling it if it turns out unneeded. See: + // https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#nested_timeouts + var nextRun = setTimeout($runScheduled); + try { + var start = Date.now(); + var r; + while ((r = $scheduled.shift()) !== undefined) { + r(); + // We need to interrupt this loop in order to allow the event loop to + // process timers, IO, etc. However, invoking scheduling through + // setTimeout is ~1000 times more expensive, so we amortize this cost by + // looping until the 4ms minimal delay has elapsed (assuming there are + // scheduled goroutines to run), and then yield to the event loop. + var elapsed = Date.now() - start; + if (elapsed > 4 || elapsed < 0) { break; } + } + } finally { + if ($scheduled.length == 0) { + // Cancel scheduling pass if there's nothing to run. + clearTimeout(nextRun); + } + } +}; + +var $schedule = function (goroutine) { + if (goroutine.asleep) { + goroutine.asleep = false; + $awakeGoroutines++; + } + $scheduled.push(goroutine); + if ($curGoroutine === $noGoroutine) { + $runScheduled(); + } +}; + +var $setTimeout = function (f, t) { + $awakeGoroutines++; + return setTimeout(function () { + $awakeGoroutines--; + f(); + }, t); +}; + +var $block = function () { + if ($curGoroutine === $noGoroutine) { + $throwRuntimeError("cannot block in JavaScript callback, fix by wrapping code in goroutine"); + } + $curGoroutine.asleep = true; +}; + +var $restore = function (context, params) { + if (context !== undefined && context.$blk !== undefined) { + return context; + } + return params; +} + +var $send = function (chan, value) { + if (chan.$closed) { + $throwRuntimeError("send on closed channel"); + } + var queuedRecv = chan.$recvQueue.shift(); + if (queuedRecv !== undefined) { + queuedRecv([value, true]); + return; + } + if (chan.$buffer.length < chan.$capacity) { + chan.$buffer.push(value); + return; + } + + var thisGoroutine = $curGoroutine; + var closedDuringSend; + chan.$sendQueue.push(function (closed) { + closedDuringSend = closed; + $schedule(thisGoroutine); + return value; + }); + $block(); + return { + $blk: function () { + if (closedDuringSend) { + $throwRuntimeError("send on closed channel"); + } + } + }; +}; +var $recv = function (chan) { + var queuedSend = chan.$sendQueue.shift(); + if (queuedSend !== undefined) { + chan.$buffer.push(queuedSend(false)); + } + var bufferedValue = chan.$buffer.shift(); + if (bufferedValue !== undefined) { + return [bufferedValue, true]; + } + if (chan.$closed) { + return [chan.$elem.zero(), false]; + } + + var thisGoroutine = $curGoroutine; + var f = { $blk: function () { return this.value; } }; + var queueEntry = function (v) { + f.value = v; + $schedule(thisGoroutine); + }; + chan.$recvQueue.push(queueEntry); + $block(); + return f; +}; +var $close = function (chan) { + if (chan.$closed) { + $throwRuntimeError("close of closed channel"); + } + chan.$closed = true; + while (true) { + var queuedSend = chan.$sendQueue.shift(); + if (queuedSend === undefined) { + break; + } + queuedSend(true); /* will panic */ + } + while (true) { + var queuedRecv = chan.$recvQueue.shift(); + if (queuedRecv === undefined) { + break; + } + queuedRecv([chan.$elem.zero(), false]); + } +}; +var $select = function (comms) { + var ready = []; + var selection = -1; + for (var i = 0; i < comms.length; i++) { + var comm = comms[i]; + var chan = comm[0]; + switch (comm.length) { + case 0: /* default */ + selection = i; + break; + case 1: /* recv */ + if (chan.$sendQueue.length !== 0 || chan.$buffer.length !== 0 || chan.$closed) { + ready.push(i); + } + break; + case 2: /* send */ + if (chan.$closed) { + $throwRuntimeError("send on closed channel"); + } + if (chan.$recvQueue.length !== 0 || chan.$buffer.length < chan.$capacity) { + ready.push(i); + } + break; + } + } + + if (ready.length !== 0) { + selection = ready[Math.floor(Math.random() * ready.length)]; + } + if (selection !== -1) { + var comm = comms[selection]; + switch (comm.length) { + case 0: /* default */ + return [selection]; + case 1: /* recv */ + return [selection, $recv(comm[0])]; + case 2: /* send */ + $send(comm[0], comm[1]); + return [selection]; + } + } + + var entries = []; + var thisGoroutine = $curGoroutine; + var f = { $blk: function () { return this.selection; } }; + var removeFromQueues = function () { + for (var i = 0; i < entries.length; i++) { + var entry = entries[i]; + var queue = entry[0]; + var index = queue.indexOf(entry[1]); + if (index !== -1) { + queue.splice(index, 1); + } + } + }; + for (var i = 0; i < comms.length; i++) { + (function (i) { + var comm = comms[i]; + switch (comm.length) { + case 1: /* recv */ + var queueEntry = function (value) { + f.selection = [i, value]; + removeFromQueues(); + $schedule(thisGoroutine); + }; + entries.push([comm[0].$recvQueue, queueEntry]); + comm[0].$recvQueue.push(queueEntry); + break; + case 2: /* send */ + var queueEntry = function () { + if (comm[0].$closed) { + $throwRuntimeError("send on closed channel"); + } + f.selection = [i]; + removeFromQueues(); + $schedule(thisGoroutine); + return comm[1]; + }; + entries.push([comm[0].$sendQueue, queueEntry]); + comm[0].$sendQueue.push(queueEntry); + break; + } + })(i); + } + $block(); + return f; +}; diff --git a/compiler/prelude/jsmapping.go b/compiler/prelude/jsmapping.go index 5cb3c14f5..98129198c 100644 --- a/compiler/prelude/jsmapping.go +++ b/compiler/prelude/jsmapping.go @@ -1,417 +1,8 @@ package prelude -const jsmapping = ` -var $jsObjectPtr, $jsErrorPtr; +import ( + _ "embed" +) -var $needsExternalization = function(t) { - switch (t.kind) { - case $kindBool: - case $kindInt: - case $kindInt8: - case $kindInt16: - case $kindInt32: - case $kindUint: - case $kindUint8: - case $kindUint16: - case $kindUint32: - case $kindUintptr: - case $kindFloat32: - case $kindFloat64: - return false; - default: - return t !== $jsObjectPtr; - } -}; - -var $externalize = function(v, t, makeWrapper) { - if (t === $jsObjectPtr) { - return v; - } - switch (t.kind) { - case $kindBool: - case $kindInt: - case $kindInt8: - case $kindInt16: - case $kindInt32: - case $kindUint: - case $kindUint8: - case $kindUint16: - case $kindUint32: - case $kindUintptr: - case $kindFloat32: - case $kindFloat64: - return v; - case $kindInt64: - case $kindUint64: - return $flatten64(v); - case $kindArray: - if ($needsExternalization(t.elem)) { - return $mapArray(v, function(e) { return $externalize(e, t.elem, makeWrapper); }); - } - return v; - case $kindFunc: - return $externalizeFunction(v, t, false, makeWrapper); - case $kindInterface: - if (v === $ifaceNil) { - return null; - } - if (v.constructor === $jsObjectPtr) { - return v.$val.object; - } - return $externalize(v.$val, v.constructor, makeWrapper); - case $kindMap: - if (v.keys === undefined) { - return null; - } - var m = {}; - var keys = Array.from(v.keys()); - for (var i = 0; i < keys.length; i++) { - var entry = v.get(keys[i]); - m[$externalize(entry.k, t.key, makeWrapper)] = $externalize(entry.v, t.elem, makeWrapper); - } - return m; - case $kindPtr: - if (v === t.nil) { - return null; - } - return $externalize(v.$get(), t.elem, makeWrapper); - case $kindSlice: - if (v === v.constructor.nil) { - return null; - } - if ($needsExternalization(t.elem)) { - return $mapArray($sliceToNativeArray(v), function(e) { return $externalize(e, t.elem, makeWrapper); }); - } - return $sliceToNativeArray(v); - case $kindString: - if ($isASCII(v)) { - return v; - } - var s = "", r; - for (var i = 0; i < v.length; i += r[1]) { - r = $decodeRune(v, i); - var c = r[0]; - if (c > 0xFFFF) { - var h = Math.floor((c - 0x10000) / 0x400) + 0xD800; - var l = (c - 0x10000) % 0x400 + 0xDC00; - s += String.fromCharCode(h, l); - continue; - } - s += String.fromCharCode(c); - } - return s; - case $kindStruct: - var timePkg = $packages["time"]; - if (timePkg !== undefined && v.constructor === timePkg.Time.ptr) { - var milli = $div64(v.UnixNano(), new $Int64(0, 1000000)); - return new Date($flatten64(milli)); - } - - var noJsObject = {}; - var searchJsObject = function(v, t) { - if (t === $jsObjectPtr) { - return v; - } - switch (t.kind) { - case $kindPtr: - if (v === t.nil) { - return noJsObject; - } - return searchJsObject(v.$get(), t.elem); - case $kindStruct: - if (t.fields.length === 0) { - return noJsObject; - } - var f = t.fields[0]; - return searchJsObject(v[f.prop], f.typ); - case $kindInterface: - return searchJsObject(v.$val, v.constructor); - default: - return noJsObject; - } - }; - var o = searchJsObject(v, t); - if (o !== noJsObject) { - return o; - } - - if (makeWrapper !== undefined) { - return makeWrapper(v); - } - - o = {}; - for (var i = 0; i < t.fields.length; i++) { - var f = t.fields[i]; - if (!f.exported) { - continue; - } - o[f.name] = $externalize(v[f.prop], f.typ, makeWrapper); - } - return o; - } - $throwRuntimeError("cannot externalize " + t.string); -}; - -var $externalizeFunction = function(v, t, passThis, makeWrapper) { - if (v === $throwNilPointerError) { - return null; - } - if (v.$externalizeWrapper === undefined) { - $checkForDeadlock = false; - v.$externalizeWrapper = function() { - var args = []; - for (var i = 0; i < t.params.length; i++) { - if (t.variadic && i === t.params.length - 1) { - var vt = t.params[i].elem, varargs = []; - for (var j = i; j < arguments.length; j++) { - varargs.push($internalize(arguments[j], vt, makeWrapper)); - } - args.push(new (t.params[i])(varargs)); - break; - } - args.push($internalize(arguments[i], t.params[i], makeWrapper)); - } - var result = v.apply(passThis ? this : undefined, args); - switch (t.results.length) { - case 0: - return; - case 1: - return $externalize($copyIfRequired(result, t.results[0]), t.results[0], makeWrapper); - default: - for (var i = 0; i < t.results.length; i++) { - result[i] = $externalize($copyIfRequired(result[i], t.results[i]), t.results[i], makeWrapper); - } - return result; - } - }; - } - return v.$externalizeWrapper; -}; - -var $internalize = function(v, t, recv, seen, makeWrapper) { - if (t === $jsObjectPtr) { - return v; - } - if (t === $jsObjectPtr.elem) { - $throwRuntimeError("cannot internalize js.Object, use *js.Object instead"); - } - if (v && v.__internal_object__ !== undefined) { - return $assertType(v.__internal_object__, t, false); - } - var timePkg = $packages["time"]; - if (timePkg !== undefined && t === timePkg.Time) { - if (!(v !== null && v !== undefined && v.constructor === Date)) { - $throwRuntimeError("cannot internalize time.Time from " + typeof v + ", must be Date"); - } - return timePkg.Unix(new $Int64(0, 0), new $Int64(0, v.getTime() * 1000000)); - } - - // Cache for values we've already internalized in order to deal with circular - // references. - if (seen === undefined) { seen = new Map(); } - if (!seen.has(t)) { seen.set(t, new Map()); } - if (seen.get(t).has(v)) { return seen.get(t).get(v); } - - switch (t.kind) { - case $kindBool: - return !!v; - case $kindInt: - return parseInt(v); - case $kindInt8: - return parseInt(v) << 24 >> 24; - case $kindInt16: - return parseInt(v) << 16 >> 16; - case $kindInt32: - return parseInt(v) >> 0; - case $kindUint: - return parseInt(v); - case $kindUint8: - return parseInt(v) << 24 >>> 24; - case $kindUint16: - return parseInt(v) << 16 >>> 16; - case $kindUint32: - case $kindUintptr: - return parseInt(v) >>> 0; - case $kindInt64: - case $kindUint64: - return new t(0, v); - case $kindFloat32: - case $kindFloat64: - return parseFloat(v); - case $kindArray: - if (v.length !== t.len) { - $throwRuntimeError("got array with wrong size from JavaScript native"); - } - return $mapArray(v, function(e) { return $internalize(e, t.elem, makeWrapper); }); - case $kindFunc: - return function() { - var args = []; - for (var i = 0; i < t.params.length; i++) { - if (t.variadic && i === t.params.length - 1) { - var vt = t.params[i].elem, varargs = arguments[i]; - for (var j = 0; j < varargs.$length; j++) { - args.push($externalize(varargs.$array[varargs.$offset + j], vt, makeWrapper)); - } - break; - } - args.push($externalize(arguments[i], t.params[i], makeWrapper)); - } - var result = v.apply(recv, args); - switch (t.results.length) { - case 0: - return; - case 1: - return $internalize(result, t.results[0], makeWrapper); - default: - for (var i = 0; i < t.results.length; i++) { - result[i] = $internalize(result[i], t.results[i], makeWrapper); - } - return result; - } - }; - case $kindInterface: - if (t.methods.length !== 0) { - $throwRuntimeError("cannot internalize " + t.string); - } - if (v === null) { - return $ifaceNil; - } - if (v === undefined) { - return new $jsObjectPtr(undefined); - } - switch (v.constructor) { - case Int8Array: - return new ($sliceType($Int8))(v); - case Int16Array: - return new ($sliceType($Int16))(v); - case Int32Array: - return new ($sliceType($Int))(v); - case Uint8Array: - return new ($sliceType($Uint8))(v); - case Uint16Array: - return new ($sliceType($Uint16))(v); - case Uint32Array: - return new ($sliceType($Uint))(v); - case Float32Array: - return new ($sliceType($Float32))(v); - case Float64Array: - return new ($sliceType($Float64))(v); - case Array: - return $internalize(v, $sliceType($emptyInterface), makeWrapper); - case Boolean: - return new $Bool(!!v); - case Date: - if (timePkg === undefined) { - /* time package is not present, internalize as &js.Object{Date} so it can be externalized into original Date. */ - return new $jsObjectPtr(v); - } - return new timePkg.Time($internalize(v, timePkg.Time, makeWrapper)); - case (function () { }).constructor: // is usually Function, but in Chrome extensions it is something else - var funcType = $funcType([$sliceType($emptyInterface)], [$jsObjectPtr], true); - return new funcType($internalize(v, funcType, makeWrapper)); - case Number: - return new $Float64(parseFloat(v)); - case String: - return new $String($internalize(v, $String, makeWrapper)); - default: - if ($global.Node && v instanceof $global.Node) { - return new $jsObjectPtr(v); - } - var mapType = $mapType($String, $emptyInterface); - return new mapType($internalize(v, mapType, recv, seen, makeWrapper)); - } - case $kindMap: - var m = new Map(); - seen.get(t).set(v, m); - var keys = $keys(v); - for (var i = 0; i < keys.length; i++) { - var k = $internalize(keys[i], t.key, recv, seen, makeWrapper); - m.set(t.key.keyFor(k), { k: k, v: $internalize(v[keys[i]], t.elem, recv, seen, makeWrapper) }); - } - return m; - case $kindPtr: - if (t.elem.kind === $kindStruct) { - return $internalize(v, t.elem, makeWrapper); - } - case $kindSlice: - return new t($mapArray(v, function(e) { return $internalize(e, t.elem, makeWrapper); })); - case $kindString: - v = String(v); - if ($isASCII(v)) { - return v; - } - var s = ""; - var i = 0; - while (i < v.length) { - var h = v.charCodeAt(i); - if (0xD800 <= h && h <= 0xDBFF) { - var l = v.charCodeAt(i + 1); - var c = (h - 0xD800) * 0x400 + l - 0xDC00 + 0x10000; - s += $encodeRune(c); - i += 2; - continue; - } - s += $encodeRune(h); - i++; - } - return s; - case $kindStruct: - var noJsObject = {}; - var searchJsObject = function(t) { - if (t === $jsObjectPtr) { - return v; - } - if (t === $jsObjectPtr.elem) { - $throwRuntimeError("cannot internalize js.Object, use *js.Object instead"); - } - switch (t.kind) { - case $kindPtr: - return searchJsObject(t.elem); - case $kindStruct: - if (t.fields.length === 0) { - return noJsObject; - } - var f = t.fields[0]; - var o = searchJsObject(f.typ); - if (o !== noJsObject) { - var n = new t.ptr(); - n[f.prop] = o; - return n; - } - return noJsObject; - default: - return noJsObject; - } - }; - var o = searchJsObject(t); - if (o !== noJsObject) { - return o; - } - } - $throwRuntimeError("cannot internalize " + t.string); -}; - -var $copyIfRequired = function(v, typ) { - // interface values - if (v && v.constructor && v.constructor.copy) { - return new v.constructor($clone(v.$val, v.constructor)) - } - // array and struct values - if (typ.copy) { - var clone = typ.zero(); - typ.copy(clone, v); - return clone; - } - return v; -} - -/* $isASCII reports whether string s contains only ASCII characters. */ -var $isASCII = function(s) { - for (var i = 0; i < s.length; i++) { - if (s.charCodeAt(i) >= 128) { - return false; - } - } - return true; -}; -` +//go:embed jsmapping.js +var jsmapping string diff --git a/compiler/prelude/jsmapping.js b/compiler/prelude/jsmapping.js new file mode 100644 index 000000000..1aa3debe2 --- /dev/null +++ b/compiler/prelude/jsmapping.js @@ -0,0 +1,413 @@ +var $jsObjectPtr, $jsErrorPtr; + +var $needsExternalization = function (t) { + switch (t.kind) { + case $kindBool: + case $kindInt: + case $kindInt8: + case $kindInt16: + case $kindInt32: + case $kindUint: + case $kindUint8: + case $kindUint16: + case $kindUint32: + case $kindUintptr: + case $kindFloat32: + case $kindFloat64: + return false; + default: + return t !== $jsObjectPtr; + } +}; + +var $externalize = function (v, t, makeWrapper) { + if (t === $jsObjectPtr) { + return v; + } + switch (t.kind) { + case $kindBool: + case $kindInt: + case $kindInt8: + case $kindInt16: + case $kindInt32: + case $kindUint: + case $kindUint8: + case $kindUint16: + case $kindUint32: + case $kindUintptr: + case $kindFloat32: + case $kindFloat64: + return v; + case $kindInt64: + case $kindUint64: + return $flatten64(v); + case $kindArray: + if ($needsExternalization(t.elem)) { + return $mapArray(v, function (e) { return $externalize(e, t.elem, makeWrapper); }); + } + return v; + case $kindFunc: + return $externalizeFunction(v, t, false, makeWrapper); + case $kindInterface: + if (v === $ifaceNil) { + return null; + } + if (v.constructor === $jsObjectPtr) { + return v.$val.object; + } + return $externalize(v.$val, v.constructor, makeWrapper); + case $kindMap: + if (v.keys === undefined) { + return null; + } + var m = {}; + var keys = Array.from(v.keys()); + for (var i = 0; i < keys.length; i++) { + var entry = v.get(keys[i]); + m[$externalize(entry.k, t.key, makeWrapper)] = $externalize(entry.v, t.elem, makeWrapper); + } + return m; + case $kindPtr: + if (v === t.nil) { + return null; + } + return $externalize(v.$get(), t.elem, makeWrapper); + case $kindSlice: + if (v === v.constructor.nil) { + return null; + } + if ($needsExternalization(t.elem)) { + return $mapArray($sliceToNativeArray(v), function (e) { return $externalize(e, t.elem, makeWrapper); }); + } + return $sliceToNativeArray(v); + case $kindString: + if ($isASCII(v)) { + return v; + } + var s = "", r; + for (var i = 0; i < v.length; i += r[1]) { + r = $decodeRune(v, i); + var c = r[0]; + if (c > 0xFFFF) { + var h = Math.floor((c - 0x10000) / 0x400) + 0xD800; + var l = (c - 0x10000) % 0x400 + 0xDC00; + s += String.fromCharCode(h, l); + continue; + } + s += String.fromCharCode(c); + } + return s; + case $kindStruct: + var timePkg = $packages["time"]; + if (timePkg !== undefined && v.constructor === timePkg.Time.ptr) { + var milli = $div64(v.UnixNano(), new $Int64(0, 1000000)); + return new Date($flatten64(milli)); + } + + var noJsObject = {}; + var searchJsObject = function (v, t) { + if (t === $jsObjectPtr) { + return v; + } + switch (t.kind) { + case $kindPtr: + if (v === t.nil) { + return noJsObject; + } + return searchJsObject(v.$get(), t.elem); + case $kindStruct: + if (t.fields.length === 0) { + return noJsObject; + } + var f = t.fields[0]; + return searchJsObject(v[f.prop], f.typ); + case $kindInterface: + return searchJsObject(v.$val, v.constructor); + default: + return noJsObject; + } + }; + var o = searchJsObject(v, t); + if (o !== noJsObject) { + return o; + } + + if (makeWrapper !== undefined) { + return makeWrapper(v); + } + + o = {}; + for (var i = 0; i < t.fields.length; i++) { + var f = t.fields[i]; + if (!f.exported) { + continue; + } + o[f.name] = $externalize(v[f.prop], f.typ, makeWrapper); + } + return o; + } + $throwRuntimeError("cannot externalize " + t.string); +}; + +var $externalizeFunction = function (v, t, passThis, makeWrapper) { + if (v === $throwNilPointerError) { + return null; + } + if (v.$externalizeWrapper === undefined) { + $checkForDeadlock = false; + v.$externalizeWrapper = function () { + var args = []; + for (var i = 0; i < t.params.length; i++) { + if (t.variadic && i === t.params.length - 1) { + var vt = t.params[i].elem, varargs = []; + for (var j = i; j < arguments.length; j++) { + varargs.push($internalize(arguments[j], vt, makeWrapper)); + } + args.push(new (t.params[i])(varargs)); + break; + } + args.push($internalize(arguments[i], t.params[i], makeWrapper)); + } + var result = v.apply(passThis ? this : undefined, args); + switch (t.results.length) { + case 0: + return; + case 1: + return $externalize($copyIfRequired(result, t.results[0]), t.results[0], makeWrapper); + default: + for (var i = 0; i < t.results.length; i++) { + result[i] = $externalize($copyIfRequired(result[i], t.results[i]), t.results[i], makeWrapper); + } + return result; + } + }; + } + return v.$externalizeWrapper; +}; + +var $internalize = function (v, t, recv, seen, makeWrapper) { + if (t === $jsObjectPtr) { + return v; + } + if (t === $jsObjectPtr.elem) { + $throwRuntimeError("cannot internalize js.Object, use *js.Object instead"); + } + if (v && v.__internal_object__ !== undefined) { + return $assertType(v.__internal_object__, t, false); + } + var timePkg = $packages["time"]; + if (timePkg !== undefined && t === timePkg.Time) { + if (!(v !== null && v !== undefined && v.constructor === Date)) { + $throwRuntimeError("cannot internalize time.Time from " + typeof v + ", must be Date"); + } + return timePkg.Unix(new $Int64(0, 0), new $Int64(0, v.getTime() * 1000000)); + } + + // Cache for values we've already internalized in order to deal with circular + // references. + if (seen === undefined) { seen = new Map(); } + if (!seen.has(t)) { seen.set(t, new Map()); } + if (seen.get(t).has(v)) { return seen.get(t).get(v); } + + switch (t.kind) { + case $kindBool: + return !!v; + case $kindInt: + return parseInt(v); + case $kindInt8: + return parseInt(v) << 24 >> 24; + case $kindInt16: + return parseInt(v) << 16 >> 16; + case $kindInt32: + return parseInt(v) >> 0; + case $kindUint: + return parseInt(v); + case $kindUint8: + return parseInt(v) << 24 >>> 24; + case $kindUint16: + return parseInt(v) << 16 >>> 16; + case $kindUint32: + case $kindUintptr: + return parseInt(v) >>> 0; + case $kindInt64: + case $kindUint64: + return new t(0, v); + case $kindFloat32: + case $kindFloat64: + return parseFloat(v); + case $kindArray: + if (v.length !== t.len) { + $throwRuntimeError("got array with wrong size from JavaScript native"); + } + return $mapArray(v, function (e) { return $internalize(e, t.elem, makeWrapper); }); + case $kindFunc: + return function () { + var args = []; + for (var i = 0; i < t.params.length; i++) { + if (t.variadic && i === t.params.length - 1) { + var vt = t.params[i].elem, varargs = arguments[i]; + for (var j = 0; j < varargs.$length; j++) { + args.push($externalize(varargs.$array[varargs.$offset + j], vt, makeWrapper)); + } + break; + } + args.push($externalize(arguments[i], t.params[i], makeWrapper)); + } + var result = v.apply(recv, args); + switch (t.results.length) { + case 0: + return; + case 1: + return $internalize(result, t.results[0], makeWrapper); + default: + for (var i = 0; i < t.results.length; i++) { + result[i] = $internalize(result[i], t.results[i], makeWrapper); + } + return result; + } + }; + case $kindInterface: + if (t.methods.length !== 0) { + $throwRuntimeError("cannot internalize " + t.string); + } + if (v === null) { + return $ifaceNil; + } + if (v === undefined) { + return new $jsObjectPtr(undefined); + } + switch (v.constructor) { + case Int8Array: + return new ($sliceType($Int8))(v); + case Int16Array: + return new ($sliceType($Int16))(v); + case Int32Array: + return new ($sliceType($Int))(v); + case Uint8Array: + return new ($sliceType($Uint8))(v); + case Uint16Array: + return new ($sliceType($Uint16))(v); + case Uint32Array: + return new ($sliceType($Uint))(v); + case Float32Array: + return new ($sliceType($Float32))(v); + case Float64Array: + return new ($sliceType($Float64))(v); + case Array: + return $internalize(v, $sliceType($emptyInterface), makeWrapper); + case Boolean: + return new $Bool(!!v); + case Date: + if (timePkg === undefined) { + /* time package is not present, internalize as &js.Object{Date} so it can be externalized into original Date. */ + return new $jsObjectPtr(v); + } + return new timePkg.Time($internalize(v, timePkg.Time, makeWrapper)); + case (function () { }).constructor: // is usually Function, but in Chrome extensions it is something else + var funcType = $funcType([$sliceType($emptyInterface)], [$jsObjectPtr], true); + return new funcType($internalize(v, funcType, makeWrapper)); + case Number: + return new $Float64(parseFloat(v)); + case String: + return new $String($internalize(v, $String, makeWrapper)); + default: + if ($global.Node && v instanceof $global.Node) { + return new $jsObjectPtr(v); + } + var mapType = $mapType($String, $emptyInterface); + return new mapType($internalize(v, mapType, recv, seen, makeWrapper)); + } + case $kindMap: + var m = new Map(); + seen.get(t).set(v, m); + var keys = $keys(v); + for (var i = 0; i < keys.length; i++) { + var k = $internalize(keys[i], t.key, recv, seen, makeWrapper); + m.set(t.key.keyFor(k), { k: k, v: $internalize(v[keys[i]], t.elem, recv, seen, makeWrapper) }); + } + return m; + case $kindPtr: + if (t.elem.kind === $kindStruct) { + return $internalize(v, t.elem, makeWrapper); + } + case $kindSlice: + return new t($mapArray(v, function (e) { return $internalize(e, t.elem, makeWrapper); })); + case $kindString: + v = String(v); + if ($isASCII(v)) { + return v; + } + var s = ""; + var i = 0; + while (i < v.length) { + var h = v.charCodeAt(i); + if (0xD800 <= h && h <= 0xDBFF) { + var l = v.charCodeAt(i + 1); + var c = (h - 0xD800) * 0x400 + l - 0xDC00 + 0x10000; + s += $encodeRune(c); + i += 2; + continue; + } + s += $encodeRune(h); + i++; + } + return s; + case $kindStruct: + var noJsObject = {}; + var searchJsObject = function (t) { + if (t === $jsObjectPtr) { + return v; + } + if (t === $jsObjectPtr.elem) { + $throwRuntimeError("cannot internalize js.Object, use *js.Object instead"); + } + switch (t.kind) { + case $kindPtr: + return searchJsObject(t.elem); + case $kindStruct: + if (t.fields.length === 0) { + return noJsObject; + } + var f = t.fields[0]; + var o = searchJsObject(f.typ); + if (o !== noJsObject) { + var n = new t.ptr(); + n[f.prop] = o; + return n; + } + return noJsObject; + default: + return noJsObject; + } + }; + var o = searchJsObject(t); + if (o !== noJsObject) { + return o; + } + } + $throwRuntimeError("cannot internalize " + t.string); +}; + +var $copyIfRequired = function (v, typ) { + // interface values + if (v && v.constructor && v.constructor.copy) { + return new v.constructor($clone(v.$val, v.constructor)) + } + // array and struct values + if (typ.copy) { + var clone = typ.zero(); + typ.copy(clone, v); + return clone; + } + return v; +} + +/* $isASCII reports whether string s contains only ASCII characters. */ +var $isASCII = function (s) { + for (var i = 0; i < s.length; i++) { + if (s.charCodeAt(i) >= 128) { + return false; + } + } + return true; +}; diff --git a/compiler/prelude/numeric.go b/compiler/prelude/numeric.go index 76bc30a2d..b80e72420 100644 --- a/compiler/prelude/numeric.go +++ b/compiler/prelude/numeric.go @@ -1,216 +1,8 @@ package prelude -const numeric = ` -var $min = Math.min; -var $mod = function(x, y) { return x % y; }; -var $parseInt = parseInt; -var $parseFloat = function(f) { - if (f !== undefined && f !== null && f.constructor === Number) { - return f; - } - return parseFloat(f); -}; +import ( + _ "embed" +) -var $froundBuf = new Float32Array(1); -var $fround = Math.fround || function(f) { - $froundBuf[0] = f; - return $froundBuf[0]; -}; - -var $imul = Math.imul || function(a, b) { - var ah = (a >>> 16) & 0xffff; - var al = a & 0xffff; - var bh = (b >>> 16) & 0xffff; - var bl = b & 0xffff; - return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) >> 0); -}; - -var $floatKey = function(f) { - if (f !== f) { - $idCounter++; - return "NaN$" + $idCounter; - } - return String(f); -}; - -var $flatten64 = function(x) { - return x.$high * 4294967296 + x.$low; -}; - -var $shiftLeft64 = function(x, y) { - if (y === 0) { - return x; - } - if (y < 32) { - return new x.constructor(x.$high << y | x.$low >>> (32 - y), (x.$low << y) >>> 0); - } - if (y < 64) { - return new x.constructor(x.$low << (y - 32), 0); - } - return new x.constructor(0, 0); -}; - -var $shiftRightInt64 = function(x, y) { - if (y === 0) { - return x; - } - if (y < 32) { - return new x.constructor(x.$high >> y, (x.$low >>> y | x.$high << (32 - y)) >>> 0); - } - if (y < 64) { - return new x.constructor(x.$high >> 31, (x.$high >> (y - 32)) >>> 0); - } - if (x.$high < 0) { - return new x.constructor(-1, 4294967295); - } - return new x.constructor(0, 0); -}; - -var $shiftRightUint64 = function(x, y) { - if (y === 0) { - return x; - } - if (y < 32) { - return new x.constructor(x.$high >>> y, (x.$low >>> y | x.$high << (32 - y)) >>> 0); - } - if (y < 64) { - return new x.constructor(0, x.$high >>> (y - 32)); - } - return new x.constructor(0, 0); -}; - -var $mul64 = function(x, y) { - var x48 = x.$high >>> 16; - var x32 = x.$high & 0xFFFF; - var x16 = x.$low >>> 16; - var x00 = x.$low & 0xFFFF; - - var y48 = y.$high >>> 16; - var y32 = y.$high & 0xFFFF; - var y16 = y.$low >>> 16; - var y00 = y.$low & 0xFFFF; - - var z48 = 0, z32 = 0, z16 = 0, z00 = 0; - z00 += x00 * y00; - z16 += z00 >>> 16; - z00 &= 0xFFFF; - z16 += x16 * y00; - z32 += z16 >>> 16; - z16 &= 0xFFFF; - z16 += x00 * y16; - z32 += z16 >>> 16; - z16 &= 0xFFFF; - z32 += x32 * y00; - z48 += z32 >>> 16; - z32 &= 0xFFFF; - z32 += x16 * y16; - z48 += z32 >>> 16; - z32 &= 0xFFFF; - z32 += x00 * y32; - z48 += z32 >>> 16; - z32 &= 0xFFFF; - z48 += x48 * y00 + x32 * y16 + x16 * y32 + x00 * y48; - z48 &= 0xFFFF; - - var hi = ((z48 << 16) | z32) >>> 0; - var lo = ((z16 << 16) | z00) >>> 0; - - var r = new x.constructor(hi, lo); - return r; -}; - -var $div64 = function(x, y, returnRemainder) { - if (y.$high === 0 && y.$low === 0) { - $throwRuntimeError("integer divide by zero"); - } - - var s = 1; - var rs = 1; - - var xHigh = x.$high; - var xLow = x.$low; - if (xHigh < 0) { - s = -1; - rs = -1; - xHigh = -xHigh; - if (xLow !== 0) { - xHigh--; - xLow = 4294967296 - xLow; - } - } - - var yHigh = y.$high; - var yLow = y.$low; - if (y.$high < 0) { - s *= -1; - yHigh = -yHigh; - if (yLow !== 0) { - yHigh--; - yLow = 4294967296 - yLow; - } - } - - var high = 0, low = 0, n = 0; - while (yHigh < 2147483648 && ((xHigh > yHigh) || (xHigh === yHigh && xLow > yLow))) { - yHigh = (yHigh << 1 | yLow >>> 31) >>> 0; - yLow = (yLow << 1) >>> 0; - n++; - } - for (var i = 0; i <= n; i++) { - high = high << 1 | low >>> 31; - low = (low << 1) >>> 0; - if ((xHigh > yHigh) || (xHigh === yHigh && xLow >= yLow)) { - xHigh = xHigh - yHigh; - xLow = xLow - yLow; - if (xLow < 0) { - xHigh--; - xLow += 4294967296; - } - low++; - if (low === 4294967296) { - high++; - low = 0; - } - } - yLow = (yLow >>> 1 | yHigh << (32 - 1)) >>> 0; - yHigh = yHigh >>> 1; - } - - if (returnRemainder) { - return new x.constructor(xHigh * rs, xLow * rs); - } - return new x.constructor(high * s, low * s); -}; - -var $divComplex = function(n, d) { - var ninf = n.$real === Infinity || n.$real === -Infinity || n.$imag === Infinity || n.$imag === -Infinity; - var dinf = d.$real === Infinity || d.$real === -Infinity || d.$imag === Infinity || d.$imag === -Infinity; - var nnan = !ninf && (n.$real !== n.$real || n.$imag !== n.$imag); - var dnan = !dinf && (d.$real !== d.$real || d.$imag !== d.$imag); - if(nnan || dnan) { - return new n.constructor(NaN, NaN); - } - if (ninf && !dinf) { - return new n.constructor(Infinity, Infinity); - } - if (!ninf && dinf) { - return new n.constructor(0, 0); - } - if (d.$real === 0 && d.$imag === 0) { - if (n.$real === 0 && n.$imag === 0) { - return new n.constructor(NaN, NaN); - } - return new n.constructor(Infinity, Infinity); - } - var a = Math.abs(d.$real); - var b = Math.abs(d.$imag); - if (a <= b) { - var ratio = d.$real / d.$imag; - var denom = d.$real * ratio + d.$imag; - return new n.constructor((n.$real * ratio + n.$imag) / denom, (n.$imag * ratio - n.$real) / denom); - } - var ratio = d.$imag / d.$real; - var denom = d.$imag * ratio + d.$real; - return new n.constructor((n.$imag * ratio + n.$real) / denom, (n.$imag - n.$real * ratio) / denom); -}; -` +//go:embed numeric.js +var numeric string diff --git a/compiler/prelude/numeric.js b/compiler/prelude/numeric.js new file mode 100644 index 000000000..38d33ef65 --- /dev/null +++ b/compiler/prelude/numeric.js @@ -0,0 +1,212 @@ +var $min = Math.min; +var $mod = function (x, y) { return x % y; }; +var $parseInt = parseInt; +var $parseFloat = function (f) { + if (f !== undefined && f !== null && f.constructor === Number) { + return f; + } + return parseFloat(f); +}; + +var $froundBuf = new Float32Array(1); +var $fround = Math.fround || function (f) { + $froundBuf[0] = f; + return $froundBuf[0]; +}; + +var $imul = Math.imul || function (a, b) { + var ah = (a >>> 16) & 0xffff; + var al = a & 0xffff; + var bh = (b >>> 16) & 0xffff; + var bl = b & 0xffff; + return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) >> 0); +}; + +var $floatKey = function (f) { + if (f !== f) { + $idCounter++; + return "NaN$" + $idCounter; + } + return String(f); +}; + +var $flatten64 = function (x) { + return x.$high * 4294967296 + x.$low; +}; + +var $shiftLeft64 = function (x, y) { + if (y === 0) { + return x; + } + if (y < 32) { + return new x.constructor(x.$high << y | x.$low >>> (32 - y), (x.$low << y) >>> 0); + } + if (y < 64) { + return new x.constructor(x.$low << (y - 32), 0); + } + return new x.constructor(0, 0); +}; + +var $shiftRightInt64 = function (x, y) { + if (y === 0) { + return x; + } + if (y < 32) { + return new x.constructor(x.$high >> y, (x.$low >>> y | x.$high << (32 - y)) >>> 0); + } + if (y < 64) { + return new x.constructor(x.$high >> 31, (x.$high >> (y - 32)) >>> 0); + } + if (x.$high < 0) { + return new x.constructor(-1, 4294967295); + } + return new x.constructor(0, 0); +}; + +var $shiftRightUint64 = function (x, y) { + if (y === 0) { + return x; + } + if (y < 32) { + return new x.constructor(x.$high >>> y, (x.$low >>> y | x.$high << (32 - y)) >>> 0); + } + if (y < 64) { + return new x.constructor(0, x.$high >>> (y - 32)); + } + return new x.constructor(0, 0); +}; + +var $mul64 = function (x, y) { + var x48 = x.$high >>> 16; + var x32 = x.$high & 0xFFFF; + var x16 = x.$low >>> 16; + var x00 = x.$low & 0xFFFF; + + var y48 = y.$high >>> 16; + var y32 = y.$high & 0xFFFF; + var y16 = y.$low >>> 16; + var y00 = y.$low & 0xFFFF; + + var z48 = 0, z32 = 0, z16 = 0, z00 = 0; + z00 += x00 * y00; + z16 += z00 >>> 16; + z00 &= 0xFFFF; + z16 += x16 * y00; + z32 += z16 >>> 16; + z16 &= 0xFFFF; + z16 += x00 * y16; + z32 += z16 >>> 16; + z16 &= 0xFFFF; + z32 += x32 * y00; + z48 += z32 >>> 16; + z32 &= 0xFFFF; + z32 += x16 * y16; + z48 += z32 >>> 16; + z32 &= 0xFFFF; + z32 += x00 * y32; + z48 += z32 >>> 16; + z32 &= 0xFFFF; + z48 += x48 * y00 + x32 * y16 + x16 * y32 + x00 * y48; + z48 &= 0xFFFF; + + var hi = ((z48 << 16) | z32) >>> 0; + var lo = ((z16 << 16) | z00) >>> 0; + + var r = new x.constructor(hi, lo); + return r; +}; + +var $div64 = function (x, y, returnRemainder) { + if (y.$high === 0 && y.$low === 0) { + $throwRuntimeError("integer divide by zero"); + } + + var s = 1; + var rs = 1; + + var xHigh = x.$high; + var xLow = x.$low; + if (xHigh < 0) { + s = -1; + rs = -1; + xHigh = -xHigh; + if (xLow !== 0) { + xHigh--; + xLow = 4294967296 - xLow; + } + } + + var yHigh = y.$high; + var yLow = y.$low; + if (y.$high < 0) { + s *= -1; + yHigh = -yHigh; + if (yLow !== 0) { + yHigh--; + yLow = 4294967296 - yLow; + } + } + + var high = 0, low = 0, n = 0; + while (yHigh < 2147483648 && ((xHigh > yHigh) || (xHigh === yHigh && xLow > yLow))) { + yHigh = (yHigh << 1 | yLow >>> 31) >>> 0; + yLow = (yLow << 1) >>> 0; + n++; + } + for (var i = 0; i <= n; i++) { + high = high << 1 | low >>> 31; + low = (low << 1) >>> 0; + if ((xHigh > yHigh) || (xHigh === yHigh && xLow >= yLow)) { + xHigh = xHigh - yHigh; + xLow = xLow - yLow; + if (xLow < 0) { + xHigh--; + xLow += 4294967296; + } + low++; + if (low === 4294967296) { + high++; + low = 0; + } + } + yLow = (yLow >>> 1 | yHigh << (32 - 1)) >>> 0; + yHigh = yHigh >>> 1; + } + + if (returnRemainder) { + return new x.constructor(xHigh * rs, xLow * rs); + } + return new x.constructor(high * s, low * s); +}; + +var $divComplex = function (n, d) { + var ninf = n.$real === Infinity || n.$real === -Infinity || n.$imag === Infinity || n.$imag === -Infinity; + var dinf = d.$real === Infinity || d.$real === -Infinity || d.$imag === Infinity || d.$imag === -Infinity; + var nnan = !ninf && (n.$real !== n.$real || n.$imag !== n.$imag); + var dnan = !dinf && (d.$real !== d.$real || d.$imag !== d.$imag); + if (nnan || dnan) { + return new n.constructor(NaN, NaN); + } + if (ninf && !dinf) { + return new n.constructor(Infinity, Infinity); + } + if (!ninf && dinf) { + return new n.constructor(0, 0); + } + if (d.$real === 0 && d.$imag === 0) { + if (n.$real === 0 && n.$imag === 0) { + return new n.constructor(NaN, NaN); + } + return new n.constructor(Infinity, Infinity); + } + var a = Math.abs(d.$real); + var b = Math.abs(d.$imag); + if (a <= b) { + var ratio = d.$real / d.$imag; + var denom = d.$real * ratio + d.$imag; + return new n.constructor((n.$real * ratio + n.$imag) / denom, (n.$imag * ratio - n.$real) / denom); + } + var ratio = d.$imag / d.$real; + var denom = d.$imag * ratio + d.$real; + return new n.constructor((n.$imag * ratio + n.$real) / denom, (n.$imag - n.$real * ratio) / denom); +}; diff --git a/compiler/prelude/prelude.go b/compiler/prelude/prelude.go index ad5ceeb28..7a2cf6513 100644 --- a/compiler/prelude/prelude.go +++ b/compiler/prelude/prelude.go @@ -1,579 +1,13 @@ package prelude +import ( + _ "embed" +) + //go:generate go run genmin.go // Prelude is the GopherJS JavaScript interop layer. -const Prelude = prelude + numeric + types + goroutines + jsmapping - -const prelude = `Error.stackTraceLimit = Infinity; - -var $NaN = NaN; -var $global, $module; -if (typeof window !== "undefined") { /* web page */ - $global = window; -} else if (typeof self !== "undefined") { /* web worker */ - $global = self; -} else if (typeof global !== "undefined") { /* Node.js */ - $global = global; - $global.require = require; -} else { /* others (e.g. Nashorn) */ - $global = this; -} - -if ($global === undefined || $global.Array === undefined) { - throw new Error("no global object found"); -} -if (typeof module !== "undefined") { - $module = module; -} - -if (!$global.fs && $global.require) { - try { - var fs = $global.require('fs'); - if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) { - $global.fs = fs; - } - } catch(e) { /* Ignore if the module couldn't be loaded. */ } -} - -if (!$global.fs) { - var outputBuf = ""; - var decoder = new TextDecoder("utf-8"); - $global.fs = { - constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused - writeSync: function writeSync(fd, buf) { - outputBuf += decoder.decode(buf); - var nl = outputBuf.lastIndexOf("\n"); - if (nl != -1) { - console.log(outputBuf.substr(0, nl)); - outputBuf = outputBuf.substr(nl + 1); - } - return buf.length; - }, - write: function write(fd, buf, offset, length, position, callback) { - if (offset !== 0 || length !== buf.length || position !== null) { - callback(enosys()); - return; - } - var n = this.writeSync(fd, buf); - callback(null, n); - } - }; -} - -var $linknames = {} // Collection of functions referenced by a go:linkname directive. -var $packages = {}, $idCounter = 0; -var $keys = function(m) { return m ? Object.keys(m) : []; }; -var $flushConsole = function() {}; -var $throwRuntimeError; /* set by package "runtime" */ -var $throwNilPointerError = function() { $throwRuntimeError("invalid memory address or nil pointer dereference"); }; -var $call = function(fn, rcvr, args) { return fn.apply(rcvr, args); }; -var $makeFunc = function(fn) { return function() { return $externalize(fn(this, new ($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments, []))), $emptyInterface); }; }; -var $unused = function(v) {}; -var $print = console.log; -// Under Node we can emulate print() more closely by avoiding a newline. -if (($global.process !== undefined) && $global.require) { - try { - var util = $global.require('util'); - $print = function() { $global.process.stderr.write(util.format.apply(this, arguments)); }; - } catch (e) { - // Failed to require util module, keep using console.log(). - } -} -var $println = console.log - -var $initAllLinknames = function() { - var names = $keys($packages); - for (var i = 0; i < names.length; i++) { - var f = $packages[names[i]]["$initLinknames"]; - if (typeof f == 'function') { - f(); - } - } -} - -var $mapArray = function(array, f) { - var newArray = new array.constructor(array.length); - for (var i = 0; i < array.length; i++) { - newArray[i] = f(array[i]); - } - return newArray; -}; - -// $mapIndex returns the value of the given key in m, or undefined if m is nil/undefined or not a map -var $mapIndex = function(m, key) { - return typeof m.get === "function" ? m.get(key) : undefined; -}; -// $mapDelete deletes the key and associated value from m. If m is nil/undefined or not a map, $mapDelete is a no-op -var $mapDelete = function(m, key) { - typeof m.delete === "function" && m.delete(key) -}; -// Returns a method bound to the receiver instance, safe to invoke as a -// standalone function. Bound function is cached for later reuse. -var $methodVal = function(recv, name) { - var vals = recv.$methodVals || {}; - recv.$methodVals = vals; /* noop for primitives */ - var f = vals[name]; - if (f !== undefined) { - return f; - } - var method = recv[name]; - f = method.bind(recv); - vals[name] = f; - return f; -}; - -var $methodExpr = function(typ, name) { - var method = typ.prototype[name]; - if (method.$expr === undefined) { - method.$expr = function() { - $stackDepthOffset--; - try { - if (typ.wrapped) { - arguments[0] = new typ(arguments[0]); - } - return Function.call.apply(method, arguments); - } finally { - $stackDepthOffset++; - } - }; - } - return method.$expr; -}; - -var $ifaceMethodExprs = {}; -var $ifaceMethodExpr = function(name) { - var expr = $ifaceMethodExprs["$" + name]; - if (expr === undefined) { - expr = $ifaceMethodExprs["$" + name] = function() { - $stackDepthOffset--; - try { - return Function.call.apply(arguments[0][name], arguments); - } finally { - $stackDepthOffset++; - } - }; - } - return expr; -}; - -var $subslice = function(slice, low, high, max) { - if (high === undefined) { - high = slice.$length; - } - if (max === undefined) { - max = slice.$capacity; - } - if (low < 0 || high < low || max < high || high > slice.$capacity || max > slice.$capacity) { - $throwRuntimeError("slice bounds out of range"); - } - if (slice === slice.constructor.nil) { - return slice; - } - var s = new slice.constructor(slice.$array); - s.$offset = slice.$offset + low; - s.$length = high - low; - s.$capacity = max - low; - return s; -}; - -var $substring = function(str, low, high) { - if (low < 0 || high < low || high > str.length) { - $throwRuntimeError("slice bounds out of range"); - } - return str.substring(low, high); -}; - -// Convert Go slice to an equivalent JS array type. -var $sliceToNativeArray = function(slice) { - if (slice.$array.constructor !== Array) { - return slice.$array.subarray(slice.$offset, slice.$offset + slice.$length); - } - return slice.$array.slice(slice.$offset, slice.$offset + slice.$length); -}; - -// Convert Go slice to a pointer to an underlying Go array. -// -// Note that an array pointer can be represented by an "unwrapped" native array -// type, and it will be wrapped back into its Go type when necessary. -var $sliceToGoArray = function(slice, arrayPtrType) { - var arrayType = arrayPtrType.elem; - if (arrayType !== undefined && slice.$length < arrayType.len) { - $throwRuntimeError("cannot convert slice with length " + slice.$length + " to pointer to array with length " + arrayType.len); - } - if (slice == slice.constructor.nil) { - return arrayPtrType.nil; // Nil slice converts to nil array pointer. - } - if (slice.$array.constructor !== Array) { - return slice.$array.subarray(slice.$offset, slice.$offset + arrayType.len); - } - if (slice.$offset == 0 && slice.$length == slice.$capacity && slice.$length == arrayType.len) { - return slice.$array; - } - if (arrayType.len == 0) { - return new arrayType([]); - } - - // Array.slice (unlike TypedArray.subarray) returns a copy of an array range, - // which is not sharing memory with the original one, which violates the spec - // for slice to array conversion. This is incompatible with the Go spec, in - // particular that the assignments to the array elements would be visible in - // the slice. Prefer to fail explicitly instead of creating subtle bugs. - $throwRuntimeError("gopherjs: non-numeric slice to underlying array conversion is not supported for subslices"); -}; - -// Convert between compatible slice types (e.g. native and names). -var $convertSliceType = function(slice, desiredType) { - if (slice == slice.constructor.nil) { - return desiredType.nil; // Preserve nil value. - } - - return $subslice(new desiredType(slice.$array), slice.$offset, slice.$offset + slice.$length); -} - -var $decodeRune = function(str, pos) { - var c0 = str.charCodeAt(pos); - - if (c0 < 0x80) { - return [c0, 1]; - } - - if (c0 !== c0 || c0 < 0xC0) { - return [0xFFFD, 1]; - } - - var c1 = str.charCodeAt(pos + 1); - if (c1 !== c1 || c1 < 0x80 || 0xC0 <= c1) { - return [0xFFFD, 1]; - } - - if (c0 < 0xE0) { - var r = (c0 & 0x1F) << 6 | (c1 & 0x3F); - if (r <= 0x7F) { - return [0xFFFD, 1]; - } - return [r, 2]; - } - - var c2 = str.charCodeAt(pos + 2); - if (c2 !== c2 || c2 < 0x80 || 0xC0 <= c2) { - return [0xFFFD, 1]; - } - - if (c0 < 0xF0) { - var r = (c0 & 0x0F) << 12 | (c1 & 0x3F) << 6 | (c2 & 0x3F); - if (r <= 0x7FF) { - return [0xFFFD, 1]; - } - if (0xD800 <= r && r <= 0xDFFF) { - return [0xFFFD, 1]; - } - return [r, 3]; - } - - var c3 = str.charCodeAt(pos + 3); - if (c3 !== c3 || c3 < 0x80 || 0xC0 <= c3) { - return [0xFFFD, 1]; - } - - if (c0 < 0xF8) { - var r = (c0 & 0x07) << 18 | (c1 & 0x3F) << 12 | (c2 & 0x3F) << 6 | (c3 & 0x3F); - if (r <= 0xFFFF || 0x10FFFF < r) { - return [0xFFFD, 1]; - } - return [r, 4]; - } - - return [0xFFFD, 1]; -}; - -var $encodeRune = function(r) { - if (r < 0 || r > 0x10FFFF || (0xD800 <= r && r <= 0xDFFF)) { - r = 0xFFFD; - } - if (r <= 0x7F) { - return String.fromCharCode(r); - } - if (r <= 0x7FF) { - return String.fromCharCode(0xC0 | r >> 6, 0x80 | (r & 0x3F)); - } - if (r <= 0xFFFF) { - return String.fromCharCode(0xE0 | r >> 12, 0x80 | (r >> 6 & 0x3F), 0x80 | (r & 0x3F)); - } - return String.fromCharCode(0xF0 | r >> 18, 0x80 | (r >> 12 & 0x3F), 0x80 | (r >> 6 & 0x3F), 0x80 | (r & 0x3F)); -}; - -var $stringToBytes = function(str) { - var array = new Uint8Array(str.length); - for (var i = 0; i < str.length; i++) { - array[i] = str.charCodeAt(i); - } - return array; -}; - -var $bytesToString = function(slice) { - if (slice.$length === 0) { - return ""; - } - var str = ""; - for (var i = 0; i < slice.$length; i += 10000) { - str += String.fromCharCode.apply(undefined, slice.$array.subarray(slice.$offset + i, slice.$offset + Math.min(slice.$length, i + 10000))); - } - return str; -}; - -var $stringToRunes = function(str) { - var array = new Int32Array(str.length); - var rune, j = 0; - for (var i = 0; i < str.length; i += rune[1], j++) { - rune = $decodeRune(str, i); - array[j] = rune[0]; - } - return array.subarray(0, j); -}; - -var $runesToString = function(slice) { - if (slice.$length === 0) { - return ""; - } - var str = ""; - for (var i = 0; i < slice.$length; i++) { - str += $encodeRune(slice.$array[slice.$offset + i]); - } - return str; -}; - -var $copyString = function(dst, src) { - var n = Math.min(src.length, dst.$length); - for (var i = 0; i < n; i++) { - dst.$array[dst.$offset + i] = src.charCodeAt(i); - } - return n; -}; - -var $copySlice = function(dst, src) { - var n = Math.min(src.$length, dst.$length); - $copyArray(dst.$array, src.$array, dst.$offset, src.$offset, n, dst.constructor.elem); - return n; -}; - -var $copyArray = function(dst, src, dstOffset, srcOffset, n, elem) { - if (n === 0 || (dst === src && dstOffset === srcOffset)) { - return; - } - - if (src.subarray) { - dst.set(src.subarray(srcOffset, srcOffset + n), dstOffset); - return; - } - - switch (elem.kind) { - case $kindArray: - case $kindStruct: - if (dst === src && dstOffset > srcOffset) { - for (var i = n - 1; i >= 0; i--) { - elem.copy(dst[dstOffset + i], src[srcOffset + i]); - } - return; - } - for (var i = 0; i < n; i++) { - elem.copy(dst[dstOffset + i], src[srcOffset + i]); - } - return; - } - - if (dst === src && dstOffset > srcOffset) { - for (var i = n - 1; i >= 0; i--) { - dst[dstOffset + i] = src[srcOffset + i]; - } - return; - } - for (var i = 0; i < n; i++) { - dst[dstOffset + i] = src[srcOffset + i]; - } -}; - -var $clone = function(src, type) { - var clone = type.zero(); - type.copy(clone, src); - return clone; -}; - -var $pointerOfStructConversion = function(obj, type) { - if(obj.$proxies === undefined) { - obj.$proxies = {}; - obj.$proxies[obj.constructor.string] = obj; - } - var proxy = obj.$proxies[type.string]; - if (proxy === undefined) { - var properties = {}; - for (var i = 0; i < type.elem.fields.length; i++) { - (function(fieldProp) { - properties[fieldProp] = { - get: function() { return obj[fieldProp]; }, - set: function(value) { obj[fieldProp] = value; } - }; - })(type.elem.fields[i].prop); - } - proxy = Object.create(type.prototype, properties); - proxy.$val = proxy; - obj.$proxies[type.string] = proxy; - proxy.$proxies = obj.$proxies; - } - return proxy; -}; - -var $append = function(slice) { - return $internalAppend(slice, arguments, 1, arguments.length - 1); -}; - -var $appendSlice = function(slice, toAppend) { - if (toAppend.constructor === String) { - var bytes = $stringToBytes(toAppend); - return $internalAppend(slice, bytes, 0, bytes.length); - } - return $internalAppend(slice, toAppend.$array, toAppend.$offset, toAppend.$length); -}; - -var $internalAppend = function(slice, array, offset, length) { - if (length === 0) { - return slice; - } - - var newArray = slice.$array; - var newOffset = slice.$offset; - var newLength = slice.$length + length; - var newCapacity = slice.$capacity; - - if (newLength > newCapacity) { - newOffset = 0; - newCapacity = Math.max(newLength, slice.$capacity < 1024 ? slice.$capacity * 2 : Math.floor(slice.$capacity * 5 / 4)); - - if (slice.$array.constructor === Array) { - newArray = slice.$array.slice(slice.$offset, slice.$offset + slice.$length); - newArray.length = newCapacity; - var zero = slice.constructor.elem.zero; - for (var i = slice.$length; i < newCapacity; i++) { - newArray[i] = zero(); - } - } else { - newArray = new slice.$array.constructor(newCapacity); - newArray.set(slice.$array.subarray(slice.$offset, slice.$offset + slice.$length)); - } - } - - $copyArray(newArray, array, newOffset + slice.$length, offset, length, slice.constructor.elem); - - var newSlice = new slice.constructor(newArray); - newSlice.$offset = newOffset; - newSlice.$length = newLength; - newSlice.$capacity = newCapacity; - return newSlice; -}; - -var $equal = function(a, b, type) { - if (type === $jsObjectPtr) { - return a === b; - } - switch (type.kind) { - case $kindComplex64: - case $kindComplex128: - return a.$real === b.$real && a.$imag === b.$imag; - case $kindInt64: - case $kindUint64: - return a.$high === b.$high && a.$low === b.$low; - case $kindArray: - if (a.length !== b.length) { - return false; - } - for (var i = 0; i < a.length; i++) { - if (!$equal(a[i], b[i], type.elem)) { - return false; - } - } - return true; - case $kindStruct: - for (var i = 0; i < type.fields.length; i++) { - var f = type.fields[i]; - if (!$equal(a[f.prop], b[f.prop], f.typ)) { - return false; - } - } - return true; - case $kindInterface: - return $interfaceIsEqual(a, b); - default: - return a === b; - } -}; - -var $interfaceIsEqual = function(a, b) { - if (a === $ifaceNil || b === $ifaceNil) { - return a === b; - } - if (a.constructor !== b.constructor) { - return false; - } - if (a.constructor === $jsObjectPtr) { - return a.object === b.object; - } - if (!a.constructor.comparable) { - $throwRuntimeError("comparing uncomparable type " + a.constructor.string); - } - return $equal(a.$val, b.$val, a.constructor); -}; - -var $unsafeMethodToFunction = function(typ, name, isPtr) { - if (isPtr) { - return function(r, ...args) { - var ptrType = $ptrType(typ); - if (r.constructor != ptrType) { - switch (typ.kind) { - case $kindStruct: - r = $pointerOfStructConversion(r, ptrType); - break; - case $kindArray: - r = new ptrType(r); - break; - default: - r = new ptrType(r.$get,r.$set,r.$target); - } - } - return r[name](...args); - } - } else { - return function(r, ...args) { - var ptrType = $ptrType(typ); - if (r.constructor != ptrType) { - switch (typ.kind) { - case $kindStruct: - r = $clone(r, typ); - break; - case $kindSlice: - r = $convertSliceType(r, typ); - break; - case $kindComplex64: - case $kindComplex128: - r = new typ(r.$real, r.$imag); - break; - default: - r = new typ(r); - } - } - return r[name](...args); - } - } -}; - -var $id = function(x) { - return x; -}; - -var $instanceOf = function(x, y) { - return x instanceof y; -}; +var Prelude = prelude + numeric + types + goroutines + jsmapping -var $typeOf = function(x) { - return typeof(x); -}; -` +//go:embed prelude.js +var prelude string diff --git a/compiler/prelude/prelude.js b/compiler/prelude/prelude.js new file mode 100644 index 000000000..130396849 --- /dev/null +++ b/compiler/prelude/prelude.js @@ -0,0 +1,571 @@ +Error.stackTraceLimit = Infinity; + +var $NaN = NaN; +var $global, $module; +if (typeof window !== "undefined") { /* web page */ + $global = window; +} else if (typeof self !== "undefined") { /* web worker */ + $global = self; +} else if (typeof global !== "undefined") { /* Node.js */ + $global = global; + $global.require = require; +} else { /* others (e.g. Nashorn) */ + $global = this; +} + +if ($global === undefined || $global.Array === undefined) { + throw new Error("no global object found"); +} +if (typeof module !== "undefined") { + $module = module; +} + +if (!$global.fs && $global.require) { + try { + var fs = $global.require('fs'); + if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) { + $global.fs = fs; + } + } catch (e) { /* Ignore if the module couldn't be loaded. */ } +} + +if (!$global.fs) { + var outputBuf = ""; + var decoder = new TextDecoder("utf-8"); + $global.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused + writeSync: function writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + var nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substr(0, nl)); + outputBuf = outputBuf.substr(nl + 1); + } + return buf.length; + }, + write: function write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()); + return; + } + var n = this.writeSync(fd, buf); + callback(null, n); + } + }; +} + +var $linknames = {} // Collection of functions referenced by a go:linkname directive. +var $packages = {}, $idCounter = 0; +var $keys = function (m) { return m ? Object.keys(m) : []; }; +var $flushConsole = function () { }; +var $throwRuntimeError; /* set by package "runtime" */ +var $throwNilPointerError = function () { $throwRuntimeError("invalid memory address or nil pointer dereference"); }; +var $call = function (fn, rcvr, args) { return fn.apply(rcvr, args); }; +var $makeFunc = function (fn) { return function () { return $externalize(fn(this, new ($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments, []))), $emptyInterface); }; }; +var $unused = function (v) { }; +var $print = console.log; +// Under Node we can emulate print() more closely by avoiding a newline. +if (($global.process !== undefined) && $global.require) { + try { + var util = $global.require('util'); + $print = function () { $global.process.stderr.write(util.format.apply(this, arguments)); }; + } catch (e) { + // Failed to require util module, keep using console.log(). + } +} +var $println = console.log + +var $initAllLinknames = function () { + var names = $keys($packages); + for (var i = 0; i < names.length; i++) { + var f = $packages[names[i]]["$initLinknames"]; + if (typeof f == 'function') { + f(); + } + } +} + +var $mapArray = function (array, f) { + var newArray = new array.constructor(array.length); + for (var i = 0; i < array.length; i++) { + newArray[i] = f(array[i]); + } + return newArray; +}; + +// $mapIndex returns the value of the given key in m, or undefined if m is nil/undefined or not a map +var $mapIndex = function (m, key) { + return typeof m.get === "function" ? m.get(key) : undefined; +}; +// $mapDelete deletes the key and associated value from m. If m is nil/undefined or not a map, $mapDelete is a no-op +var $mapDelete = function (m, key) { + typeof m.delete === "function" && m.delete(key) +}; +// Returns a method bound to the receiver instance, safe to invoke as a +// standalone function. Bound function is cached for later reuse. +var $methodVal = function (recv, name) { + var vals = recv.$methodVals || {}; + recv.$methodVals = vals; /* noop for primitives */ + var f = vals[name]; + if (f !== undefined) { + return f; + } + var method = recv[name]; + f = method.bind(recv); + vals[name] = f; + return f; +}; + +var $methodExpr = function (typ, name) { + var method = typ.prototype[name]; + if (method.$expr === undefined) { + method.$expr = function () { + $stackDepthOffset--; + try { + if (typ.wrapped) { + arguments[0] = new typ(arguments[0]); + } + return Function.call.apply(method, arguments); + } finally { + $stackDepthOffset++; + } + }; + } + return method.$expr; +}; + +var $ifaceMethodExprs = {}; +var $ifaceMethodExpr = function (name) { + var expr = $ifaceMethodExprs["$" + name]; + if (expr === undefined) { + expr = $ifaceMethodExprs["$" + name] = function () { + $stackDepthOffset--; + try { + return Function.call.apply(arguments[0][name], arguments); + } finally { + $stackDepthOffset++; + } + }; + } + return expr; +}; + +var $subslice = function (slice, low, high, max) { + if (high === undefined) { + high = slice.$length; + } + if (max === undefined) { + max = slice.$capacity; + } + if (low < 0 || high < low || max < high || high > slice.$capacity || max > slice.$capacity) { + $throwRuntimeError("slice bounds out of range"); + } + if (slice === slice.constructor.nil) { + return slice; + } + var s = new slice.constructor(slice.$array); + s.$offset = slice.$offset + low; + s.$length = high - low; + s.$capacity = max - low; + return s; +}; + +var $substring = function (str, low, high) { + if (low < 0 || high < low || high > str.length) { + $throwRuntimeError("slice bounds out of range"); + } + return str.substring(low, high); +}; + +// Convert Go slice to an equivalent JS array type. +var $sliceToNativeArray = function (slice) { + if (slice.$array.constructor !== Array) { + return slice.$array.subarray(slice.$offset, slice.$offset + slice.$length); + } + return slice.$array.slice(slice.$offset, slice.$offset + slice.$length); +}; + +// Convert Go slice to a pointer to an underlying Go array. +// +// Note that an array pointer can be represented by an "unwrapped" native array +// type, and it will be wrapped back into its Go type when necessary. +var $sliceToGoArray = function (slice, arrayPtrType) { + var arrayType = arrayPtrType.elem; + if (arrayType !== undefined && slice.$length < arrayType.len) { + $throwRuntimeError("cannot convert slice with length " + slice.$length + " to pointer to array with length " + arrayType.len); + } + if (slice == slice.constructor.nil) { + return arrayPtrType.nil; // Nil slice converts to nil array pointer. + } + if (slice.$array.constructor !== Array) { + return slice.$array.subarray(slice.$offset, slice.$offset + arrayType.len); + } + if (slice.$offset == 0 && slice.$length == slice.$capacity && slice.$length == arrayType.len) { + return slice.$array; + } + if (arrayType.len == 0) { + return new arrayType([]); + } + + // Array.slice (unlike TypedArray.subarray) returns a copy of an array range, + // which is not sharing memory with the original one, which violates the spec + // for slice to array conversion. This is incompatible with the Go spec, in + // particular that the assignments to the array elements would be visible in + // the slice. Prefer to fail explicitly instead of creating subtle bugs. + $throwRuntimeError("gopherjs: non-numeric slice to underlying array conversion is not supported for subslices"); +}; + +// Convert between compatible slice types (e.g. native and names). +var $convertSliceType = function (slice, desiredType) { + if (slice == slice.constructor.nil) { + return desiredType.nil; // Preserve nil value. + } + + return $subslice(new desiredType(slice.$array), slice.$offset, slice.$offset + slice.$length); +} + +var $decodeRune = function (str, pos) { + var c0 = str.charCodeAt(pos); + + if (c0 < 0x80) { + return [c0, 1]; + } + + if (c0 !== c0 || c0 < 0xC0) { + return [0xFFFD, 1]; + } + + var c1 = str.charCodeAt(pos + 1); + if (c1 !== c1 || c1 < 0x80 || 0xC0 <= c1) { + return [0xFFFD, 1]; + } + + if (c0 < 0xE0) { + var r = (c0 & 0x1F) << 6 | (c1 & 0x3F); + if (r <= 0x7F) { + return [0xFFFD, 1]; + } + return [r, 2]; + } + + var c2 = str.charCodeAt(pos + 2); + if (c2 !== c2 || c2 < 0x80 || 0xC0 <= c2) { + return [0xFFFD, 1]; + } + + if (c0 < 0xF0) { + var r = (c0 & 0x0F) << 12 | (c1 & 0x3F) << 6 | (c2 & 0x3F); + if (r <= 0x7FF) { + return [0xFFFD, 1]; + } + if (0xD800 <= r && r <= 0xDFFF) { + return [0xFFFD, 1]; + } + return [r, 3]; + } + + var c3 = str.charCodeAt(pos + 3); + if (c3 !== c3 || c3 < 0x80 || 0xC0 <= c3) { + return [0xFFFD, 1]; + } + + if (c0 < 0xF8) { + var r = (c0 & 0x07) << 18 | (c1 & 0x3F) << 12 | (c2 & 0x3F) << 6 | (c3 & 0x3F); + if (r <= 0xFFFF || 0x10FFFF < r) { + return [0xFFFD, 1]; + } + return [r, 4]; + } + + return [0xFFFD, 1]; +}; + +var $encodeRune = function (r) { + if (r < 0 || r > 0x10FFFF || (0xD800 <= r && r <= 0xDFFF)) { + r = 0xFFFD; + } + if (r <= 0x7F) { + return String.fromCharCode(r); + } + if (r <= 0x7FF) { + return String.fromCharCode(0xC0 | r >> 6, 0x80 | (r & 0x3F)); + } + if (r <= 0xFFFF) { + return String.fromCharCode(0xE0 | r >> 12, 0x80 | (r >> 6 & 0x3F), 0x80 | (r & 0x3F)); + } + return String.fromCharCode(0xF0 | r >> 18, 0x80 | (r >> 12 & 0x3F), 0x80 | (r >> 6 & 0x3F), 0x80 | (r & 0x3F)); +}; + +var $stringToBytes = function (str) { + var array = new Uint8Array(str.length); + for (var i = 0; i < str.length; i++) { + array[i] = str.charCodeAt(i); + } + return array; +}; + +var $bytesToString = function (slice) { + if (slice.$length === 0) { + return ""; + } + var str = ""; + for (var i = 0; i < slice.$length; i += 10000) { + str += String.fromCharCode.apply(undefined, slice.$array.subarray(slice.$offset + i, slice.$offset + Math.min(slice.$length, i + 10000))); + } + return str; +}; + +var $stringToRunes = function (str) { + var array = new Int32Array(str.length); + var rune, j = 0; + for (var i = 0; i < str.length; i += rune[1], j++) { + rune = $decodeRune(str, i); + array[j] = rune[0]; + } + return array.subarray(0, j); +}; + +var $runesToString = function (slice) { + if (slice.$length === 0) { + return ""; + } + var str = ""; + for (var i = 0; i < slice.$length; i++) { + str += $encodeRune(slice.$array[slice.$offset + i]); + } + return str; +}; + +var $copyString = function (dst, src) { + var n = Math.min(src.length, dst.$length); + for (var i = 0; i < n; i++) { + dst.$array[dst.$offset + i] = src.charCodeAt(i); + } + return n; +}; + +var $copySlice = function (dst, src) { + var n = Math.min(src.$length, dst.$length); + $copyArray(dst.$array, src.$array, dst.$offset, src.$offset, n, dst.constructor.elem); + return n; +}; + +var $copyArray = function (dst, src, dstOffset, srcOffset, n, elem) { + if (n === 0 || (dst === src && dstOffset === srcOffset)) { + return; + } + + if (src.subarray) { + dst.set(src.subarray(srcOffset, srcOffset + n), dstOffset); + return; + } + + switch (elem.kind) { + case $kindArray: + case $kindStruct: + if (dst === src && dstOffset > srcOffset) { + for (var i = n - 1; i >= 0; i--) { + elem.copy(dst[dstOffset + i], src[srcOffset + i]); + } + return; + } + for (var i = 0; i < n; i++) { + elem.copy(dst[dstOffset + i], src[srcOffset + i]); + } + return; + } + + if (dst === src && dstOffset > srcOffset) { + for (var i = n - 1; i >= 0; i--) { + dst[dstOffset + i] = src[srcOffset + i]; + } + return; + } + for (var i = 0; i < n; i++) { + dst[dstOffset + i] = src[srcOffset + i]; + } +}; + +var $clone = function (src, type) { + var clone = type.zero(); + type.copy(clone, src); + return clone; +}; + +var $pointerOfStructConversion = function (obj, type) { + if (obj.$proxies === undefined) { + obj.$proxies = {}; + obj.$proxies[obj.constructor.string] = obj; + } + var proxy = obj.$proxies[type.string]; + if (proxy === undefined) { + var properties = {}; + for (var i = 0; i < type.elem.fields.length; i++) { + (function (fieldProp) { + properties[fieldProp] = { + get: function () { return obj[fieldProp]; }, + set: function (value) { obj[fieldProp] = value; } + }; + })(type.elem.fields[i].prop); + } + proxy = Object.create(type.prototype, properties); + proxy.$val = proxy; + obj.$proxies[type.string] = proxy; + proxy.$proxies = obj.$proxies; + } + return proxy; +}; + +var $append = function (slice) { + return $internalAppend(slice, arguments, 1, arguments.length - 1); +}; + +var $appendSlice = function (slice, toAppend) { + if (toAppend.constructor === String) { + var bytes = $stringToBytes(toAppend); + return $internalAppend(slice, bytes, 0, bytes.length); + } + return $internalAppend(slice, toAppend.$array, toAppend.$offset, toAppend.$length); +}; + +var $internalAppend = function (slice, array, offset, length) { + if (length === 0) { + return slice; + } + + var newArray = slice.$array; + var newOffset = slice.$offset; + var newLength = slice.$length + length; + var newCapacity = slice.$capacity; + + if (newLength > newCapacity) { + newOffset = 0; + newCapacity = Math.max(newLength, slice.$capacity < 1024 ? slice.$capacity * 2 : Math.floor(slice.$capacity * 5 / 4)); + + if (slice.$array.constructor === Array) { + newArray = slice.$array.slice(slice.$offset, slice.$offset + slice.$length); + newArray.length = newCapacity; + var zero = slice.constructor.elem.zero; + for (var i = slice.$length; i < newCapacity; i++) { + newArray[i] = zero(); + } + } else { + newArray = new slice.$array.constructor(newCapacity); + newArray.set(slice.$array.subarray(slice.$offset, slice.$offset + slice.$length)); + } + } + + $copyArray(newArray, array, newOffset + slice.$length, offset, length, slice.constructor.elem); + + var newSlice = new slice.constructor(newArray); + newSlice.$offset = newOffset; + newSlice.$length = newLength; + newSlice.$capacity = newCapacity; + return newSlice; +}; + +var $equal = function (a, b, type) { + if (type === $jsObjectPtr) { + return a === b; + } + switch (type.kind) { + case $kindComplex64: + case $kindComplex128: + return a.$real === b.$real && a.$imag === b.$imag; + case $kindInt64: + case $kindUint64: + return a.$high === b.$high && a.$low === b.$low; + case $kindArray: + if (a.length !== b.length) { + return false; + } + for (var i = 0; i < a.length; i++) { + if (!$equal(a[i], b[i], type.elem)) { + return false; + } + } + return true; + case $kindStruct: + for (var i = 0; i < type.fields.length; i++) { + var f = type.fields[i]; + if (!$equal(a[f.prop], b[f.prop], f.typ)) { + return false; + } + } + return true; + case $kindInterface: + return $interfaceIsEqual(a, b); + default: + return a === b; + } +}; + +var $interfaceIsEqual = function (a, b) { + if (a === $ifaceNil || b === $ifaceNil) { + return a === b; + } + if (a.constructor !== b.constructor) { + return false; + } + if (a.constructor === $jsObjectPtr) { + return a.object === b.object; + } + if (!a.constructor.comparable) { + $throwRuntimeError("comparing uncomparable type " + a.constructor.string); + } + return $equal(a.$val, b.$val, a.constructor); +}; + +var $unsafeMethodToFunction = function (typ, name, isPtr) { + if (isPtr) { + return function (r, ...args) { + var ptrType = $ptrType(typ); + if (r.constructor != ptrType) { + switch (typ.kind) { + case $kindStruct: + r = $pointerOfStructConversion(r, ptrType); + break; + case $kindArray: + r = new ptrType(r); + break; + default: + r = new ptrType(r.$get, r.$set, r.$target); + } + } + return r[name](...args); + } + } else { + return function (r, ...args) { + var ptrType = $ptrType(typ); + if (r.constructor != ptrType) { + switch (typ.kind) { + case $kindStruct: + r = $clone(r, typ); + break; + case $kindSlice: + r = $convertSliceType(r, typ); + break; + case $kindComplex64: + case $kindComplex128: + r = new typ(r.$real, r.$imag); + break; + default: + r = new typ(r); + } + } + return r[name](...args); + } + } +}; + +var $id = function (x) { + return x; +}; + +var $instanceOf = function (x, y) { + return x instanceof y; +}; + +var $typeOf = function (x) { + return typeof (x); +}; diff --git a/compiler/prelude/types.go b/compiler/prelude/types.go index 69afe8af1..c96f68e41 100644 --- a/compiler/prelude/types.go +++ b/compiler/prelude/types.go @@ -1,774 +1,8 @@ package prelude -const types = ` -var $kindBool = 1; -var $kindInt = 2; -var $kindInt8 = 3; -var $kindInt16 = 4; -var $kindInt32 = 5; -var $kindInt64 = 6; -var $kindUint = 7; -var $kindUint8 = 8; -var $kindUint16 = 9; -var $kindUint32 = 10; -var $kindUint64 = 11; -var $kindUintptr = 12; -var $kindFloat32 = 13; -var $kindFloat64 = 14; -var $kindComplex64 = 15; -var $kindComplex128 = 16; -var $kindArray = 17; -var $kindChan = 18; -var $kindFunc = 19; -var $kindInterface = 20; -var $kindMap = 21; -var $kindPtr = 22; -var $kindSlice = 23; -var $kindString = 24; -var $kindStruct = 25; -var $kindUnsafePointer = 26; +import ( + _ "embed" +) -var $methodSynthesizers = []; -var $addMethodSynthesizer = function(f) { - if ($methodSynthesizers === null) { - f(); - return; - } - $methodSynthesizers.push(f); -}; -var $synthesizeMethods = function() { - $methodSynthesizers.forEach(function(f) { f(); }); - $methodSynthesizers = null; -}; - -var $ifaceKeyFor = function(x) { - if (x === $ifaceNil) { - return 'nil'; - } - var c = x.constructor; - return c.string + '$' + c.keyFor(x.$val); -}; - -var $identity = function(x) { return x; }; - -var $typeIDCounter = 0; - -var $idKey = function(x) { - if (x.$id === undefined) { - $idCounter++; - x.$id = $idCounter; - } - return String(x.$id); -}; - -// Creates constructor functions for array pointer types. Returns a new function -// instace each time to make sure each type is independent of the other. -var $arrayPtrCtor = function() { - return function(array) { - this.$get = function() { return array; }; - this.$set = function(v) { typ.copy(this, v); }; - this.$val = array; - } -} - -var $newType = function(size, kind, string, named, pkg, exported, constructor) { - var typ; - switch(kind) { - case $kindBool: - case $kindInt: - case $kindInt8: - case $kindInt16: - case $kindInt32: - case $kindUint: - case $kindUint8: - case $kindUint16: - case $kindUint32: - case $kindUintptr: - case $kindUnsafePointer: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.keyFor = $identity; - break; - - case $kindString: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.keyFor = function(x) { return "$" + x; }; - break; - - case $kindFloat32: - case $kindFloat64: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.keyFor = function(x) { return $floatKey(x); }; - break; - - case $kindInt64: - typ = function(high, low) { - this.$high = (high + Math.floor(Math.ceil(low) / 4294967296)) >> 0; - this.$low = low >>> 0; - this.$val = this; - }; - typ.keyFor = function(x) { return x.$high + "$" + x.$low; }; - break; - - case $kindUint64: - typ = function(high, low) { - this.$high = (high + Math.floor(Math.ceil(low) / 4294967296)) >>> 0; - this.$low = low >>> 0; - this.$val = this; - }; - typ.keyFor = function(x) { return x.$high + "$" + x.$low; }; - break; - - case $kindComplex64: - typ = function(real, imag) { - this.$real = $fround(real); - this.$imag = $fround(imag); - this.$val = this; - }; - typ.keyFor = function(x) { return x.$real + "$" + x.$imag; }; - break; - - case $kindComplex128: - typ = function(real, imag) { - this.$real = real; - this.$imag = imag; - this.$val = this; - }; - typ.keyFor = function(x) { return x.$real + "$" + x.$imag; }; - break; - - case $kindArray: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.ptr = $newType(4, $kindPtr, "*" + string, false, "", false, $arrayPtrCtor()); - typ.init = function(elem, len) { - typ.elem = elem; - typ.len = len; - typ.comparable = elem.comparable; - typ.keyFor = function(x) { - return Array.prototype.join.call($mapArray(x, function(e) { - return String(elem.keyFor(e)).replace(/\\/g, "\\\\").replace(/\$/g, "\\$"); - }), "$"); - }; - typ.copy = function(dst, src) { - $copyArray(dst, src, 0, 0, src.length, elem); - }; - typ.ptr.init(typ); - Object.defineProperty(typ.ptr.nil, "nilCheck", { get: $throwNilPointerError }); - }; - break; - - case $kindChan: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.keyFor = $idKey; - typ.init = function(elem, sendOnly, recvOnly) { - typ.elem = elem; - typ.sendOnly = sendOnly; - typ.recvOnly = recvOnly; - }; - break; - - case $kindFunc: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.init = function(params, results, variadic) { - typ.params = params; - typ.results = results; - typ.variadic = variadic; - typ.comparable = false; - }; - break; - - case $kindInterface: - typ = { implementedBy: {}, missingMethodFor: {} }; - typ.keyFor = $ifaceKeyFor; - typ.init = function(methods) { - typ.methods = methods; - methods.forEach(function(m) { - $ifaceNil[m.prop] = $throwNilPointerError; - }); - }; - break; - - case $kindMap: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.init = function(key, elem) { - typ.key = key; - typ.elem = elem; - typ.comparable = false; - }; - break; - - case $kindPtr: - typ = constructor || function(getter, setter, target) { - this.$get = getter; - this.$set = setter; - this.$target = target; - this.$val = this; - }; - typ.keyFor = $idKey; - typ.init = function(elem) { - typ.elem = elem; - typ.wrapped = (elem.kind === $kindArray); - typ.nil = new typ($throwNilPointerError, $throwNilPointerError); - }; - break; - - case $kindSlice: - typ = function(array) { - if (array.constructor !== typ.nativeArray) { - array = new typ.nativeArray(array); - } - this.$array = array; - this.$offset = 0; - this.$length = array.length; - this.$capacity = array.length; - this.$val = this; - }; - typ.init = function(elem) { - typ.elem = elem; - typ.comparable = false; - typ.nativeArray = $nativeArray(elem.kind); - typ.nil = new typ([]); - }; - break; - - case $kindStruct: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.ptr = $newType(4, $kindPtr, "*" + string, false, pkg, exported, constructor); - typ.ptr.elem = typ; - typ.ptr.prototype.$get = function() { return this; }; - typ.ptr.prototype.$set = function(v) { typ.copy(this, v); }; - typ.init = function(pkgPath, fields) { - typ.pkgPath = pkgPath; - typ.fields = fields; - fields.forEach(function(f) { - if (!f.typ.comparable) { - typ.comparable = false; - } - }); - typ.keyFor = function(x) { - var val = x.$val; - return $mapArray(fields, function(f) { - return String(f.typ.keyFor(val[f.prop])).replace(/\\/g, "\\\\").replace(/\$/g, "\\$"); - }).join("$"); - }; - typ.copy = function(dst, src) { - for (var i = 0; i < fields.length; i++) { - var f = fields[i]; - switch (f.typ.kind) { - case $kindArray: - case $kindStruct: - f.typ.copy(dst[f.prop], src[f.prop]); - continue; - default: - dst[f.prop] = src[f.prop]; - continue; - } - } - }; - /* nil value */ - var properties = {}; - fields.forEach(function(f) { - properties[f.prop] = { get: $throwNilPointerError, set: $throwNilPointerError }; - }); - typ.ptr.nil = Object.create(constructor.prototype, properties); - typ.ptr.nil.$val = typ.ptr.nil; - /* methods for embedded fields */ - $addMethodSynthesizer(function() { - var synthesizeMethod = function(target, m, f) { - if (target.prototype[m.prop] !== undefined) { return; } - target.prototype[m.prop] = function() { - var v = this.$val[f.prop]; - if (f.typ === $jsObjectPtr) { - v = new $jsObjectPtr(v); - } - if (v.$val === undefined) { - v = new f.typ(v); - } - return v[m.prop].apply(v, arguments); - }; - }; - fields.forEach(function(f) { - if (f.embedded) { - $methodSet(f.typ).forEach(function(m) { - synthesizeMethod(typ, m, f); - synthesizeMethod(typ.ptr, m, f); - }); - $methodSet($ptrType(f.typ)).forEach(function(m) { - synthesizeMethod(typ.ptr, m, f); - }); - } - }); - }); - }; - break; - - default: - $panic(new $String("invalid kind: " + kind)); - } - - switch (kind) { - case $kindBool: - case $kindMap: - typ.zero = function() { return false; }; - break; - - case $kindInt: - case $kindInt8: - case $kindInt16: - case $kindInt32: - case $kindUint: - case $kindUint8 : - case $kindUint16: - case $kindUint32: - case $kindUintptr: - case $kindUnsafePointer: - case $kindFloat32: - case $kindFloat64: - typ.zero = function() { return 0; }; - break; - - case $kindString: - typ.zero = function() { return ""; }; - break; - - case $kindInt64: - case $kindUint64: - case $kindComplex64: - case $kindComplex128: - var zero = new typ(0, 0); - typ.zero = function() { return zero; }; - break; - - case $kindPtr: - case $kindSlice: - typ.zero = function() { return typ.nil; }; - break; - - case $kindChan: - typ.zero = function() { return $chanNil; }; - break; - - case $kindFunc: - typ.zero = function() { return $throwNilPointerError; }; - break; - - case $kindInterface: - typ.zero = function() { return $ifaceNil; }; - break; - - case $kindArray: - typ.zero = function() { - var arrayClass = $nativeArray(typ.elem.kind); - if (arrayClass !== Array) { - return new arrayClass(typ.len); - } - var array = new Array(typ.len); - for (var i = 0; i < typ.len; i++) { - array[i] = typ.elem.zero(); - } - return array; - }; - break; - - case $kindStruct: - typ.zero = function() { return new typ.ptr(); }; - break; - - default: - $panic(new $String("invalid kind: " + kind)); - } - - typ.id = $typeIDCounter; - $typeIDCounter++; - typ.size = size; - typ.kind = kind; - typ.string = string; - typ.named = named; - typ.pkg = pkg; - typ.exported = exported; - typ.methods = []; - typ.methodSetCache = null; - typ.comparable = true; - return typ; -}; - -var $methodSet = function(typ) { - if (typ.methodSetCache !== null) { - return typ.methodSetCache; - } - var base = {}; - - var isPtr = (typ.kind === $kindPtr); - if (isPtr && typ.elem.kind === $kindInterface) { - typ.methodSetCache = []; - return []; - } - - var current = [{typ: isPtr ? typ.elem : typ, indirect: isPtr}]; - - var seen = {}; - - while (current.length > 0) { - var next = []; - var mset = []; - - current.forEach(function(e) { - if (seen[e.typ.string]) { - return; - } - seen[e.typ.string] = true; - - if (e.typ.named) { - mset = mset.concat(e.typ.methods); - if (e.indirect) { - mset = mset.concat($ptrType(e.typ).methods); - } - } - - switch (e.typ.kind) { - case $kindStruct: - e.typ.fields.forEach(function(f) { - if (f.embedded) { - var fTyp = f.typ; - var fIsPtr = (fTyp.kind === $kindPtr); - next.push({typ: fIsPtr ? fTyp.elem : fTyp, indirect: e.indirect || fIsPtr}); - } - }); - break; - - case $kindInterface: - mset = mset.concat(e.typ.methods); - break; - } - }); - - mset.forEach(function(m) { - if (base[m.name] === undefined) { - base[m.name] = m; - } - }); - - current = next; - } - - typ.methodSetCache = []; - Object.keys(base).sort().forEach(function(name) { - typ.methodSetCache.push(base[name]); - }); - return typ.methodSetCache; -}; - -var $Bool = $newType( 1, $kindBool, "bool", true, "", false, null); -var $Int = $newType( 4, $kindInt, "int", true, "", false, null); -var $Int8 = $newType( 1, $kindInt8, "int8", true, "", false, null); -var $Int16 = $newType( 2, $kindInt16, "int16", true, "", false, null); -var $Int32 = $newType( 4, $kindInt32, "int32", true, "", false, null); -var $Int64 = $newType( 8, $kindInt64, "int64", true, "", false, null); -var $Uint = $newType( 4, $kindUint, "uint", true, "", false, null); -var $Uint8 = $newType( 1, $kindUint8, "uint8", true, "", false, null); -var $Uint16 = $newType( 2, $kindUint16, "uint16", true, "", false, null); -var $Uint32 = $newType( 4, $kindUint32, "uint32", true, "", false, null); -var $Uint64 = $newType( 8, $kindUint64, "uint64", true, "", false, null); -var $Uintptr = $newType( 4, $kindUintptr, "uintptr", true, "", false, null); -var $Float32 = $newType( 4, $kindFloat32, "float32", true, "", false, null); -var $Float64 = $newType( 8, $kindFloat64, "float64", true, "", false, null); -var $Complex64 = $newType( 8, $kindComplex64, "complex64", true, "", false, null); -var $Complex128 = $newType(16, $kindComplex128, "complex128", true, "", false, null); -var $String = $newType( 8, $kindString, "string", true, "", false, null); -var $UnsafePointer = $newType( 4, $kindUnsafePointer, "unsafe.Pointer", true, "unsafe", false, null); - -var $nativeArray = function(elemKind) { - switch (elemKind) { - case $kindInt: - return Int32Array; - case $kindInt8: - return Int8Array; - case $kindInt16: - return Int16Array; - case $kindInt32: - return Int32Array; - case $kindUint: - return Uint32Array; - case $kindUint8: - return Uint8Array; - case $kindUint16: - return Uint16Array; - case $kindUint32: - return Uint32Array; - case $kindUintptr: - return Uint32Array; - case $kindFloat32: - return Float32Array; - case $kindFloat64: - return Float64Array; - default: - return Array; - } -}; -var $toNativeArray = function(elemKind, array) { - var nativeArray = $nativeArray(elemKind); - if (nativeArray === Array) { - return array; - } - return new nativeArray(array); -}; -var $arrayTypes = {}; -var $arrayType = function(elem, len) { - var typeKey = elem.id + "$" + len; - var typ = $arrayTypes[typeKey]; - if (typ === undefined) { - typ = $newType(elem.size*len, $kindArray, "[" + len + "]" + elem.string, false, "", false, null); - $arrayTypes[typeKey] = typ; - typ.init(elem, len); - } - return typ; -}; - -var $chanType = function(elem, sendOnly, recvOnly) { - var string = (recvOnly ? "<-" : "") + "chan" + (sendOnly ? "<- " : " "); - if (!sendOnly && !recvOnly && (elem.string[0] == "<")) { - string += "(" + elem.string + ")"; - } else { - string += elem.string; - } - var field = sendOnly ? "SendChan" : (recvOnly ? "RecvChan" : "Chan"); - var typ = elem[field]; - if (typ === undefined) { - typ = $newType(4, $kindChan, string, false, "", false, null); - elem[field] = typ; - typ.init(elem, sendOnly, recvOnly); - } - return typ; -}; -var $Chan = function(elem, capacity) { - if (capacity < 0 || capacity > 2147483647) { - $throwRuntimeError("makechan: size out of range"); - } - this.$elem = elem; - this.$capacity = capacity; - this.$buffer = []; - this.$sendQueue = []; - this.$recvQueue = []; - this.$closed = false; -}; -var $chanNil = new $Chan(null, 0); -$chanNil.$sendQueue = $chanNil.$recvQueue = { length: 0, push: function() {}, shift: function() { return undefined; }, indexOf: function() { return -1; } }; - -var $funcTypes = {}; -var $funcType = function(params, results, variadic) { - var typeKey = $mapArray(params, function(p) { return p.id; }).join(",") + "$" + $mapArray(results, function(r) { return r.id; }).join(",") + "$" + variadic; - var typ = $funcTypes[typeKey]; - if (typ === undefined) { - var paramTypes = $mapArray(params, function(p) { return p.string; }); - if (variadic) { - paramTypes[paramTypes.length - 1] = "..." + paramTypes[paramTypes.length - 1].substr(2); - } - var string = "func(" + paramTypes.join(", ") + ")"; - if (results.length === 1) { - string += " " + results[0].string; - } else if (results.length > 1) { - string += " (" + $mapArray(results, function(r) { return r.string; }).join(", ") + ")"; - } - typ = $newType(4, $kindFunc, string, false, "", false, null); - $funcTypes[typeKey] = typ; - typ.init(params, results, variadic); - } - return typ; -}; - -var $interfaceTypes = {}; -var $interfaceType = function(methods) { - var typeKey = $mapArray(methods, function(m) { return m.pkg + "," + m.name + "," + m.typ.id; }).join("$"); - var typ = $interfaceTypes[typeKey]; - if (typ === undefined) { - var string = "interface {}"; - if (methods.length !== 0) { - string = "interface { " + $mapArray(methods, function(m) { - return (m.pkg !== "" ? m.pkg + "." : "") + m.name + m.typ.string.substr(4); - }).join("; ") + " }"; - } - typ = $newType(8, $kindInterface, string, false, "", false, null); - $interfaceTypes[typeKey] = typ; - typ.init(methods); - } - return typ; -}; -var $emptyInterface = $interfaceType([]); -var $ifaceNil = {}; -var $error = $newType(8, $kindInterface, "error", true, "", false, null); -$error.init([{prop: "Error", name: "Error", pkg: "", typ: $funcType([], [$String], false)}]); - -var $mapTypes = {}; -var $mapType = function(key, elem) { - var typeKey = key.id + "$" + elem.id; - var typ = $mapTypes[typeKey]; - if (typ === undefined) { - typ = $newType(4, $kindMap, "map[" + key.string + "]" + elem.string, false, "", false, null); - $mapTypes[typeKey] = typ; - typ.init(key, elem); - } - return typ; -}; -var $makeMap = function(keyForFunc, entries) { - var m = new Map(); - for (var i = 0; i < entries.length; i++) { - var e = entries[i]; - m.set(keyForFunc(e.k), e); - } - return m; -}; - -var $ptrType = function(elem) { - var typ = elem.ptr; - if (typ === undefined) { - typ = $newType(4, $kindPtr, "*" + elem.string, false, "", elem.exported, null); - elem.ptr = typ; - typ.init(elem); - } - return typ; -}; - -var $newDataPointer = function(data, constructor) { - if (constructor.elem.kind === $kindStruct) { - return data; - } - return new constructor(function() { return data; }, function(v) { data = v; }); -}; - -var $indexPtr = function(array, index, constructor) { - if (array.buffer) { - // Pointers to the same underlying ArrayBuffer share cache. - var cache = array.buffer.$ptr = array.buffer.$ptr || {}; - // Pointers of different primitive types are non-comparable and stored in different caches. - var typeCache = cache[array.name] = cache[array.name] || {}; - var cacheIdx = array.BYTES_PER_ELEMENT * index + array.byteOffset; - return typeCache[cacheIdx] || (typeCache[cacheIdx] = new constructor(function() { return array[index]; }, function(v) { array[index] = v; })); - } else { - array.$ptr = array.$ptr || {}; - return array.$ptr[index] || (array.$ptr[index] = new constructor(function() { return array[index]; }, function(v) { array[index] = v; })); - } -}; - -var $sliceType = function(elem) { - var typ = elem.slice; - if (typ === undefined) { - typ = $newType(12, $kindSlice, "[]" + elem.string, false, "", false, null); - elem.slice = typ; - typ.init(elem); - } - return typ; -}; -var $makeSlice = function(typ, length, capacity) { - capacity = capacity || length; - if (length < 0 || length > 2147483647) { - $throwRuntimeError("makeslice: len out of range"); - } - if (capacity < 0 || capacity < length || capacity > 2147483647) { - $throwRuntimeError("makeslice: cap out of range"); - } - var array = new typ.nativeArray(capacity); - if (typ.nativeArray === Array) { - for (var i = 0; i < capacity; i++) { - array[i] = typ.elem.zero(); - } - } - var slice = new typ(array); - slice.$length = length; - return slice; -}; - -var $structTypes = {}; -var $structType = function(pkgPath, fields) { - var typeKey = $mapArray(fields, function(f) { return f.name + "," + f.typ.id + "," + f.tag; }).join("$"); - var typ = $structTypes[typeKey]; - if (typ === undefined) { - var string = "struct { " + $mapArray(fields, function(f) { - var str = f.typ.string + (f.tag !== "" ? (" \"" + f.tag.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"") : ""); - if (f.embedded) { - return str; - } - return f.name + " " + str; - }).join("; ") + " }"; - if (fields.length === 0) { - string = "struct {}"; - } - typ = $newType(0, $kindStruct, string, false, "", false, function() { - this.$val = this; - for (var i = 0; i < fields.length; i++) { - var f = fields[i]; - if (f.name == '_') { - continue; - } - var arg = arguments[i]; - this[f.prop] = arg !== undefined ? arg : f.typ.zero(); - } - }); - $structTypes[typeKey] = typ; - typ.init(pkgPath, fields); - } - return typ; -}; - -var $assertType = function(value, type, returnTuple) { - var isInterface = (type.kind === $kindInterface), ok, missingMethod = ""; - if (value === $ifaceNil) { - ok = false; - } else if (!isInterface) { - ok = value.constructor === type; - } else { - var valueTypeString = value.constructor.string; - ok = type.implementedBy[valueTypeString]; - if (ok === undefined) { - ok = true; - var valueMethodSet = $methodSet(value.constructor); - var interfaceMethods = type.methods; - for (var i = 0; i < interfaceMethods.length; i++) { - var tm = interfaceMethods[i]; - var found = false; - for (var j = 0; j < valueMethodSet.length; j++) { - var vm = valueMethodSet[j]; - if (vm.name === tm.name && vm.pkg === tm.pkg && vm.typ === tm.typ) { - found = true; - break; - } - } - if (!found) { - ok = false; - type.missingMethodFor[valueTypeString] = tm.name; - break; - } - } - type.implementedBy[valueTypeString] = ok; - } - if (!ok) { - missingMethod = type.missingMethodFor[valueTypeString]; - } - } - - if (!ok) { - if (returnTuple) { - return [type.zero(), false]; - } - $panic(new $packages["runtime"].TypeAssertionError.ptr( - $packages["runtime"]._type.ptr.nil, - (value === $ifaceNil ? $packages["runtime"]._type.ptr.nil : new $packages["runtime"]._type.ptr(value.constructor.string)), - new $packages["runtime"]._type.ptr(type.string), - missingMethod)); - } - - if (!isInterface) { - value = value.$val; - } - if (type === $jsObjectPtr) { - value = value.object; - } - return returnTuple ? [value, true] : value; -}; -` +//go:embed types.js +var types string diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js new file mode 100644 index 000000000..37d91827a --- /dev/null +++ b/compiler/prelude/types.js @@ -0,0 +1,770 @@ +var $kindBool = 1; +var $kindInt = 2; +var $kindInt8 = 3; +var $kindInt16 = 4; +var $kindInt32 = 5; +var $kindInt64 = 6; +var $kindUint = 7; +var $kindUint8 = 8; +var $kindUint16 = 9; +var $kindUint32 = 10; +var $kindUint64 = 11; +var $kindUintptr = 12; +var $kindFloat32 = 13; +var $kindFloat64 = 14; +var $kindComplex64 = 15; +var $kindComplex128 = 16; +var $kindArray = 17; +var $kindChan = 18; +var $kindFunc = 19; +var $kindInterface = 20; +var $kindMap = 21; +var $kindPtr = 22; +var $kindSlice = 23; +var $kindString = 24; +var $kindStruct = 25; +var $kindUnsafePointer = 26; + +var $methodSynthesizers = []; +var $addMethodSynthesizer = function (f) { + if ($methodSynthesizers === null) { + f(); + return; + } + $methodSynthesizers.push(f); +}; +var $synthesizeMethods = function () { + $methodSynthesizers.forEach(function (f) { f(); }); + $methodSynthesizers = null; +}; + +var $ifaceKeyFor = function (x) { + if (x === $ifaceNil) { + return 'nil'; + } + var c = x.constructor; + return c.string + '$' + c.keyFor(x.$val); +}; + +var $identity = function (x) { return x; }; + +var $typeIDCounter = 0; + +var $idKey = function (x) { + if (x.$id === undefined) { + $idCounter++; + x.$id = $idCounter; + } + return String(x.$id); +}; + +// Creates constructor functions for array pointer types. Returns a new function +// instace each time to make sure each type is independent of the other. +var $arrayPtrCtor = function () { + return function (array) { + this.$get = function () { return array; }; + this.$set = function (v) { typ.copy(this, v); }; + this.$val = array; + } +} + +var $newType = function (size, kind, string, named, pkg, exported, constructor) { + var typ; + switch (kind) { + case $kindBool: + case $kindInt: + case $kindInt8: + case $kindInt16: + case $kindInt32: + case $kindUint: + case $kindUint8: + case $kindUint16: + case $kindUint32: + case $kindUintptr: + case $kindUnsafePointer: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.keyFor = $identity; + break; + + case $kindString: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.keyFor = function (x) { return "$" + x; }; + break; + + case $kindFloat32: + case $kindFloat64: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.keyFor = function (x) { return $floatKey(x); }; + break; + + case $kindInt64: + typ = function (high, low) { + this.$high = (high + Math.floor(Math.ceil(low) / 4294967296)) >> 0; + this.$low = low >>> 0; + this.$val = this; + }; + typ.keyFor = function (x) { return x.$high + "$" + x.$low; }; + break; + + case $kindUint64: + typ = function (high, low) { + this.$high = (high + Math.floor(Math.ceil(low) / 4294967296)) >>> 0; + this.$low = low >>> 0; + this.$val = this; + }; + typ.keyFor = function (x) { return x.$high + "$" + x.$low; }; + break; + + case $kindComplex64: + typ = function (real, imag) { + this.$real = $fround(real); + this.$imag = $fround(imag); + this.$val = this; + }; + typ.keyFor = function (x) { return x.$real + "$" + x.$imag; }; + break; + + case $kindComplex128: + typ = function (real, imag) { + this.$real = real; + this.$imag = imag; + this.$val = this; + }; + typ.keyFor = function (x) { return x.$real + "$" + x.$imag; }; + break; + + case $kindArray: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.ptr = $newType(4, $kindPtr, "*" + string, false, "", false, $arrayPtrCtor()); + typ.init = function (elem, len) { + typ.elem = elem; + typ.len = len; + typ.comparable = elem.comparable; + typ.keyFor = function (x) { + return Array.prototype.join.call($mapArray(x, function (e) { + return String(elem.keyFor(e)).replace(/\\/g, "\\\\").replace(/\$/g, "\\$"); + }), "$"); + }; + typ.copy = function (dst, src) { + $copyArray(dst, src, 0, 0, src.length, elem); + }; + typ.ptr.init(typ); + Object.defineProperty(typ.ptr.nil, "nilCheck", { get: $throwNilPointerError }); + }; + break; + + case $kindChan: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.keyFor = $idKey; + typ.init = function (elem, sendOnly, recvOnly) { + typ.elem = elem; + typ.sendOnly = sendOnly; + typ.recvOnly = recvOnly; + }; + break; + + case $kindFunc: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.init = function (params, results, variadic) { + typ.params = params; + typ.results = results; + typ.variadic = variadic; + typ.comparable = false; + }; + break; + + case $kindInterface: + typ = { implementedBy: {}, missingMethodFor: {} }; + typ.keyFor = $ifaceKeyFor; + typ.init = function (methods) { + typ.methods = methods; + methods.forEach(function (m) { + $ifaceNil[m.prop] = $throwNilPointerError; + }); + }; + break; + + case $kindMap: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.init = function (key, elem) { + typ.key = key; + typ.elem = elem; + typ.comparable = false; + }; + break; + + case $kindPtr: + typ = constructor || function (getter, setter, target) { + this.$get = getter; + this.$set = setter; + this.$target = target; + this.$val = this; + }; + typ.keyFor = $idKey; + typ.init = function (elem) { + typ.elem = elem; + typ.wrapped = (elem.kind === $kindArray); + typ.nil = new typ($throwNilPointerError, $throwNilPointerError); + }; + break; + + case $kindSlice: + typ = function (array) { + if (array.constructor !== typ.nativeArray) { + array = new typ.nativeArray(array); + } + this.$array = array; + this.$offset = 0; + this.$length = array.length; + this.$capacity = array.length; + this.$val = this; + }; + typ.init = function (elem) { + typ.elem = elem; + typ.comparable = false; + typ.nativeArray = $nativeArray(elem.kind); + typ.nil = new typ([]); + }; + break; + + case $kindStruct: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.ptr = $newType(4, $kindPtr, "*" + string, false, pkg, exported, constructor); + typ.ptr.elem = typ; + typ.ptr.prototype.$get = function () { return this; }; + typ.ptr.prototype.$set = function (v) { typ.copy(this, v); }; + typ.init = function (pkgPath, fields) { + typ.pkgPath = pkgPath; + typ.fields = fields; + fields.forEach(function (f) { + if (!f.typ.comparable) { + typ.comparable = false; + } + }); + typ.keyFor = function (x) { + var val = x.$val; + return $mapArray(fields, function (f) { + return String(f.typ.keyFor(val[f.prop])).replace(/\\/g, "\\\\").replace(/\$/g, "\\$"); + }).join("$"); + }; + typ.copy = function (dst, src) { + for (var i = 0; i < fields.length; i++) { + var f = fields[i]; + switch (f.typ.kind) { + case $kindArray: + case $kindStruct: + f.typ.copy(dst[f.prop], src[f.prop]); + continue; + default: + dst[f.prop] = src[f.prop]; + continue; + } + } + }; + /* nil value */ + var properties = {}; + fields.forEach(function (f) { + properties[f.prop] = { get: $throwNilPointerError, set: $throwNilPointerError }; + }); + typ.ptr.nil = Object.create(constructor.prototype, properties); + typ.ptr.nil.$val = typ.ptr.nil; + /* methods for embedded fields */ + $addMethodSynthesizer(function () { + var synthesizeMethod = function (target, m, f) { + if (target.prototype[m.prop] !== undefined) { return; } + target.prototype[m.prop] = function () { + var v = this.$val[f.prop]; + if (f.typ === $jsObjectPtr) { + v = new $jsObjectPtr(v); + } + if (v.$val === undefined) { + v = new f.typ(v); + } + return v[m.prop].apply(v, arguments); + }; + }; + fields.forEach(function (f) { + if (f.embedded) { + $methodSet(f.typ).forEach(function (m) { + synthesizeMethod(typ, m, f); + synthesizeMethod(typ.ptr, m, f); + }); + $methodSet($ptrType(f.typ)).forEach(function (m) { + synthesizeMethod(typ.ptr, m, f); + }); + } + }); + }); + }; + break; + + default: + $panic(new $String("invalid kind: " + kind)); + } + + switch (kind) { + case $kindBool: + case $kindMap: + typ.zero = function () { return false; }; + break; + + case $kindInt: + case $kindInt8: + case $kindInt16: + case $kindInt32: + case $kindUint: + case $kindUint8: + case $kindUint16: + case $kindUint32: + case $kindUintptr: + case $kindUnsafePointer: + case $kindFloat32: + case $kindFloat64: + typ.zero = function () { return 0; }; + break; + + case $kindString: + typ.zero = function () { return ""; }; + break; + + case $kindInt64: + case $kindUint64: + case $kindComplex64: + case $kindComplex128: + var zero = new typ(0, 0); + typ.zero = function () { return zero; }; + break; + + case $kindPtr: + case $kindSlice: + typ.zero = function () { return typ.nil; }; + break; + + case $kindChan: + typ.zero = function () { return $chanNil; }; + break; + + case $kindFunc: + typ.zero = function () { return $throwNilPointerError; }; + break; + + case $kindInterface: + typ.zero = function () { return $ifaceNil; }; + break; + + case $kindArray: + typ.zero = function () { + var arrayClass = $nativeArray(typ.elem.kind); + if (arrayClass !== Array) { + return new arrayClass(typ.len); + } + var array = new Array(typ.len); + for (var i = 0; i < typ.len; i++) { + array[i] = typ.elem.zero(); + } + return array; + }; + break; + + case $kindStruct: + typ.zero = function () { return new typ.ptr(); }; + break; + + default: + $panic(new $String("invalid kind: " + kind)); + } + + typ.id = $typeIDCounter; + $typeIDCounter++; + typ.size = size; + typ.kind = kind; + typ.string = string; + typ.named = named; + typ.pkg = pkg; + typ.exported = exported; + typ.methods = []; + typ.methodSetCache = null; + typ.comparable = true; + return typ; +}; + +var $methodSet = function (typ) { + if (typ.methodSetCache !== null) { + return typ.methodSetCache; + } + var base = {}; + + var isPtr = (typ.kind === $kindPtr); + if (isPtr && typ.elem.kind === $kindInterface) { + typ.methodSetCache = []; + return []; + } + + var current = [{ typ: isPtr ? typ.elem : typ, indirect: isPtr }]; + + var seen = {}; + + while (current.length > 0) { + var next = []; + var mset = []; + + current.forEach(function (e) { + if (seen[e.typ.string]) { + return; + } + seen[e.typ.string] = true; + + if (e.typ.named) { + mset = mset.concat(e.typ.methods); + if (e.indirect) { + mset = mset.concat($ptrType(e.typ).methods); + } + } + + switch (e.typ.kind) { + case $kindStruct: + e.typ.fields.forEach(function (f) { + if (f.embedded) { + var fTyp = f.typ; + var fIsPtr = (fTyp.kind === $kindPtr); + next.push({ typ: fIsPtr ? fTyp.elem : fTyp, indirect: e.indirect || fIsPtr }); + } + }); + break; + + case $kindInterface: + mset = mset.concat(e.typ.methods); + break; + } + }); + + mset.forEach(function (m) { + if (base[m.name] === undefined) { + base[m.name] = m; + } + }); + + current = next; + } + + typ.methodSetCache = []; + Object.keys(base).sort().forEach(function (name) { + typ.methodSetCache.push(base[name]); + }); + return typ.methodSetCache; +}; + +var $Bool = $newType(1, $kindBool, "bool", true, "", false, null); +var $Int = $newType(4, $kindInt, "int", true, "", false, null); +var $Int8 = $newType(1, $kindInt8, "int8", true, "", false, null); +var $Int16 = $newType(2, $kindInt16, "int16", true, "", false, null); +var $Int32 = $newType(4, $kindInt32, "int32", true, "", false, null); +var $Int64 = $newType(8, $kindInt64, "int64", true, "", false, null); +var $Uint = $newType(4, $kindUint, "uint", true, "", false, null); +var $Uint8 = $newType(1, $kindUint8, "uint8", true, "", false, null); +var $Uint16 = $newType(2, $kindUint16, "uint16", true, "", false, null); +var $Uint32 = $newType(4, $kindUint32, "uint32", true, "", false, null); +var $Uint64 = $newType(8, $kindUint64, "uint64", true, "", false, null); +var $Uintptr = $newType(4, $kindUintptr, "uintptr", true, "", false, null); +var $Float32 = $newType(4, $kindFloat32, "float32", true, "", false, null); +var $Float64 = $newType(8, $kindFloat64, "float64", true, "", false, null); +var $Complex64 = $newType(8, $kindComplex64, "complex64", true, "", false, null); +var $Complex128 = $newType(16, $kindComplex128, "complex128", true, "", false, null); +var $String = $newType(8, $kindString, "string", true, "", false, null); +var $UnsafePointer = $newType(4, $kindUnsafePointer, "unsafe.Pointer", true, "unsafe", false, null); + +var $nativeArray = function (elemKind) { + switch (elemKind) { + case $kindInt: + return Int32Array; + case $kindInt8: + return Int8Array; + case $kindInt16: + return Int16Array; + case $kindInt32: + return Int32Array; + case $kindUint: + return Uint32Array; + case $kindUint8: + return Uint8Array; + case $kindUint16: + return Uint16Array; + case $kindUint32: + return Uint32Array; + case $kindUintptr: + return Uint32Array; + case $kindFloat32: + return Float32Array; + case $kindFloat64: + return Float64Array; + default: + return Array; + } +}; +var $toNativeArray = function (elemKind, array) { + var nativeArray = $nativeArray(elemKind); + if (nativeArray === Array) { + return array; + } + return new nativeArray(array); +}; +var $arrayTypes = {}; +var $arrayType = function (elem, len) { + var typeKey = elem.id + "$" + len; + var typ = $arrayTypes[typeKey]; + if (typ === undefined) { + typ = $newType(elem.size * len, $kindArray, "[" + len + "]" + elem.string, false, "", false, null); + $arrayTypes[typeKey] = typ; + typ.init(elem, len); + } + return typ; +}; + +var $chanType = function (elem, sendOnly, recvOnly) { + var string = (recvOnly ? "<-" : "") + "chan" + (sendOnly ? "<- " : " "); + if (!sendOnly && !recvOnly && (elem.string[0] == "<")) { + string += "(" + elem.string + ")"; + } else { + string += elem.string; + } + var field = sendOnly ? "SendChan" : (recvOnly ? "RecvChan" : "Chan"); + var typ = elem[field]; + if (typ === undefined) { + typ = $newType(4, $kindChan, string, false, "", false, null); + elem[field] = typ; + typ.init(elem, sendOnly, recvOnly); + } + return typ; +}; +var $Chan = function (elem, capacity) { + if (capacity < 0 || capacity > 2147483647) { + $throwRuntimeError("makechan: size out of range"); + } + this.$elem = elem; + this.$capacity = capacity; + this.$buffer = []; + this.$sendQueue = []; + this.$recvQueue = []; + this.$closed = false; +}; +var $chanNil = new $Chan(null, 0); +$chanNil.$sendQueue = $chanNil.$recvQueue = { length: 0, push: function () { }, shift: function () { return undefined; }, indexOf: function () { return -1; } }; + +var $funcTypes = {}; +var $funcType = function (params, results, variadic) { + var typeKey = $mapArray(params, function (p) { return p.id; }).join(",") + "$" + $mapArray(results, function (r) { return r.id; }).join(",") + "$" + variadic; + var typ = $funcTypes[typeKey]; + if (typ === undefined) { + var paramTypes = $mapArray(params, function (p) { return p.string; }); + if (variadic) { + paramTypes[paramTypes.length - 1] = "..." + paramTypes[paramTypes.length - 1].substr(2); + } + var string = "func(" + paramTypes.join(", ") + ")"; + if (results.length === 1) { + string += " " + results[0].string; + } else if (results.length > 1) { + string += " (" + $mapArray(results, function (r) { return r.string; }).join(", ") + ")"; + } + typ = $newType(4, $kindFunc, string, false, "", false, null); + $funcTypes[typeKey] = typ; + typ.init(params, results, variadic); + } + return typ; +}; + +var $interfaceTypes = {}; +var $interfaceType = function (methods) { + var typeKey = $mapArray(methods, function (m) { return m.pkg + "," + m.name + "," + m.typ.id; }).join("$"); + var typ = $interfaceTypes[typeKey]; + if (typ === undefined) { + var string = "interface {}"; + if (methods.length !== 0) { + string = "interface { " + $mapArray(methods, function (m) { + return (m.pkg !== "" ? m.pkg + "." : "") + m.name + m.typ.string.substr(4); + }).join("; ") + " }"; + } + typ = $newType(8, $kindInterface, string, false, "", false, null); + $interfaceTypes[typeKey] = typ; + typ.init(methods); + } + return typ; +}; +var $emptyInterface = $interfaceType([]); +var $ifaceNil = {}; +var $error = $newType(8, $kindInterface, "error", true, "", false, null); +$error.init([{ prop: "Error", name: "Error", pkg: "", typ: $funcType([], [$String], false) }]); + +var $mapTypes = {}; +var $mapType = function (key, elem) { + var typeKey = key.id + "$" + elem.id; + var typ = $mapTypes[typeKey]; + if (typ === undefined) { + typ = $newType(4, $kindMap, "map[" + key.string + "]" + elem.string, false, "", false, null); + $mapTypes[typeKey] = typ; + typ.init(key, elem); + } + return typ; +}; +var $makeMap = function (keyForFunc, entries) { + var m = new Map(); + for (var i = 0; i < entries.length; i++) { + var e = entries[i]; + m.set(keyForFunc(e.k), e); + } + return m; +}; + +var $ptrType = function (elem) { + var typ = elem.ptr; + if (typ === undefined) { + typ = $newType(4, $kindPtr, "*" + elem.string, false, "", elem.exported, null); + elem.ptr = typ; + typ.init(elem); + } + return typ; +}; + +var $newDataPointer = function (data, constructor) { + if (constructor.elem.kind === $kindStruct) { + return data; + } + return new constructor(function () { return data; }, function (v) { data = v; }); +}; + +var $indexPtr = function (array, index, constructor) { + if (array.buffer) { + // Pointers to the same underlying ArrayBuffer share cache. + var cache = array.buffer.$ptr = array.buffer.$ptr || {}; + // Pointers of different primitive types are non-comparable and stored in different caches. + var typeCache = cache[array.name] = cache[array.name] || {}; + var cacheIdx = array.BYTES_PER_ELEMENT * index + array.byteOffset; + return typeCache[cacheIdx] || (typeCache[cacheIdx] = new constructor(function () { return array[index]; }, function (v) { array[index] = v; })); + } else { + array.$ptr = array.$ptr || {}; + return array.$ptr[index] || (array.$ptr[index] = new constructor(function () { return array[index]; }, function (v) { array[index] = v; })); + } +}; + +var $sliceType = function (elem) { + var typ = elem.slice; + if (typ === undefined) { + typ = $newType(12, $kindSlice, "[]" + elem.string, false, "", false, null); + elem.slice = typ; + typ.init(elem); + } + return typ; +}; +var $makeSlice = function (typ, length, capacity) { + capacity = capacity || length; + if (length < 0 || length > 2147483647) { + $throwRuntimeError("makeslice: len out of range"); + } + if (capacity < 0 || capacity < length || capacity > 2147483647) { + $throwRuntimeError("makeslice: cap out of range"); + } + var array = new typ.nativeArray(capacity); + if (typ.nativeArray === Array) { + for (var i = 0; i < capacity; i++) { + array[i] = typ.elem.zero(); + } + } + var slice = new typ(array); + slice.$length = length; + return slice; +}; + +var $structTypes = {}; +var $structType = function (pkgPath, fields) { + var typeKey = $mapArray(fields, function (f) { return f.name + "," + f.typ.id + "," + f.tag; }).join("$"); + var typ = $structTypes[typeKey]; + if (typ === undefined) { + var string = "struct { " + $mapArray(fields, function (f) { + var str = f.typ.string + (f.tag !== "" ? (" \"" + f.tag.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"") : ""); + if (f.embedded) { + return str; + } + return f.name + " " + str; + }).join("; ") + " }"; + if (fields.length === 0) { + string = "struct {}"; + } + typ = $newType(0, $kindStruct, string, false, "", false, function () { + this.$val = this; + for (var i = 0; i < fields.length; i++) { + var f = fields[i]; + if (f.name == '_') { + continue; + } + var arg = arguments[i]; + this[f.prop] = arg !== undefined ? arg : f.typ.zero(); + } + }); + $structTypes[typeKey] = typ; + typ.init(pkgPath, fields); + } + return typ; +}; + +var $assertType = function (value, type, returnTuple) { + var isInterface = (type.kind === $kindInterface), ok, missingMethod = ""; + if (value === $ifaceNil) { + ok = false; + } else if (!isInterface) { + ok = value.constructor === type; + } else { + var valueTypeString = value.constructor.string; + ok = type.implementedBy[valueTypeString]; + if (ok === undefined) { + ok = true; + var valueMethodSet = $methodSet(value.constructor); + var interfaceMethods = type.methods; + for (var i = 0; i < interfaceMethods.length; i++) { + var tm = interfaceMethods[i]; + var found = false; + for (var j = 0; j < valueMethodSet.length; j++) { + var vm = valueMethodSet[j]; + if (vm.name === tm.name && vm.pkg === tm.pkg && vm.typ === tm.typ) { + found = true; + break; + } + } + if (!found) { + ok = false; + type.missingMethodFor[valueTypeString] = tm.name; + break; + } + } + type.implementedBy[valueTypeString] = ok; + } + if (!ok) { + missingMethod = type.missingMethodFor[valueTypeString]; + } + } + + if (!ok) { + if (returnTuple) { + return [type.zero(), false]; + } + $panic(new $packages["runtime"].TypeAssertionError.ptr( + $packages["runtime"]._type.ptr.nil, + (value === $ifaceNil ? $packages["runtime"]._type.ptr.nil : new $packages["runtime"]._type.ptr(value.constructor.string)), + new $packages["runtime"]._type.ptr(type.string), + missingMethod)); + } + + if (!isInterface) { + value = value.$val; + } + if (type === $jsObjectPtr) { + value = value.object; + } + return returnTuple ? [value, true] : value; +}; From f05b53c07a672c48340a1946ee7736f8f8421803 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Sun, 11 Jun 2023 18:39:40 +0200 Subject: [PATCH 02/66] Consolidate all prelude go:embed directives to a single file --- compiler/prelude/goroutines.go | 8 -------- compiler/prelude/jsmapping.go | 8 -------- compiler/prelude/numeric.go | 8 -------- compiler/prelude/prelude.go | 12 ++++++++++++ compiler/prelude/types.go | 8 -------- 5 files changed, 12 insertions(+), 32 deletions(-) delete mode 100644 compiler/prelude/goroutines.go delete mode 100644 compiler/prelude/jsmapping.go delete mode 100644 compiler/prelude/numeric.go delete mode 100644 compiler/prelude/types.go diff --git a/compiler/prelude/goroutines.go b/compiler/prelude/goroutines.go deleted file mode 100644 index 60116907e..000000000 --- a/compiler/prelude/goroutines.go +++ /dev/null @@ -1,8 +0,0 @@ -package prelude - -import ( - _ "embed" -) - -//go:embed goroutines.js -var goroutines string diff --git a/compiler/prelude/jsmapping.go b/compiler/prelude/jsmapping.go deleted file mode 100644 index 98129198c..000000000 --- a/compiler/prelude/jsmapping.go +++ /dev/null @@ -1,8 +0,0 @@ -package prelude - -import ( - _ "embed" -) - -//go:embed jsmapping.js -var jsmapping string diff --git a/compiler/prelude/numeric.go b/compiler/prelude/numeric.go deleted file mode 100644 index b80e72420..000000000 --- a/compiler/prelude/numeric.go +++ /dev/null @@ -1,8 +0,0 @@ -package prelude - -import ( - _ "embed" -) - -//go:embed numeric.js -var numeric string diff --git a/compiler/prelude/prelude.go b/compiler/prelude/prelude.go index 7a2cf6513..67fa391ef 100644 --- a/compiler/prelude/prelude.go +++ b/compiler/prelude/prelude.go @@ -11,3 +11,15 @@ var Prelude = prelude + numeric + types + goroutines + jsmapping //go:embed prelude.js var prelude string + +//go:embed types.js +var types string + +//go:embed numeric.js +var numeric string + +//go:embed jsmapping.js +var jsmapping string + +//go:embed goroutines.js +var goroutines string diff --git a/compiler/prelude/types.go b/compiler/prelude/types.go deleted file mode 100644 index c96f68e41..000000000 --- a/compiler/prelude/types.go +++ /dev/null @@ -1,8 +0,0 @@ -package prelude - -import ( - _ "embed" -) - -//go:embed types.js -var types string From 5938478d5fd8cab0fc23e16d24688b7c0807ab79 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Sun, 11 Jun 2023 22:12:02 +0200 Subject: [PATCH 03/66] Use esbuild to minify prelude --- circle.yml | 6 ---- compiler/compiler.go | 2 +- compiler/prelude/genmin.go | 64 --------------------------------- compiler/prelude/prelude.go | 22 ++++++++++-- compiler/prelude/prelude_min.go | 6 ---- go.mod | 3 +- go.sum | 6 ++-- 7 files changed, 27 insertions(+), 82 deletions(-) delete mode 100644 compiler/prelude/genmin.go delete mode 100644 compiler/prelude/prelude_min.go diff --git a/circle.yml b/circle.yml index 068f9d5e2..2776d1b0f 100644 --- a/circle.yml +++ b/circle.yml @@ -72,12 +72,6 @@ jobs: executor: gopherjs steps: - setup_and_install_gopherjs - - run: - name: Check minified prelude - command: | - set -e - go generate github.com/gopherjs/gopherjs/compiler/prelude - diff -u <(echo -n) <(git status --porcelain) - run: name: Check gofmt command: diff -u <(echo -n) <(gofmt -d .) diff --git a/compiler/compiler.go b/compiler/compiler.go index e660b1bee..6935444b2 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -266,7 +266,7 @@ func WriteProgramCode(pkgs []*Archive, w *SourceMapFilter, goVersion string) err preludeJS := prelude.Prelude if minify { - preludeJS = prelude.Minified + preludeJS = prelude.Minified() } if _, err := io.WriteString(w, preludeJS); err != nil { return err diff --git a/compiler/prelude/genmin.go b/compiler/prelude/genmin.go deleted file mode 100644 index 6e46e058e..000000000 --- a/compiler/prelude/genmin.go +++ /dev/null @@ -1,64 +0,0 @@ -//go:build ignore -// +build ignore - -package main - -import ( - "bytes" - "fmt" - "go/build" - "io/ioutil" - "log" - "os/exec" - "path/filepath" - "strings" - - "github.com/gopherjs/gopherjs/compiler/prelude" -) - -func main() { - if err := run(); err != nil { - log.Fatalln(err) - } -} - -func run() error { - bpkg, err := build.Import("github.com/gopherjs/gopherjs", "", build.FindOnly) - if err != nil { - return fmt.Errorf("failed to locate path for github.com/gopherjs/gopherjs: %v", err) - } - - preludeDir := filepath.Join(bpkg.Dir, "compiler", "prelude") - - args := []string{ - filepath.Join(bpkg.Dir, "node_modules", ".bin", "uglifyjs"), - "--config-file", - filepath.Join(preludeDir, "uglifyjs_options.json"), - } - - stderr := new(bytes.Buffer) - cmd := exec.Command(args[0], args[1:]...) - cmd.Stdin = strings.NewReader(prelude.Prelude) - cmd.Stderr = stderr - - out, err := cmd.Output() - if err != nil { - return fmt.Errorf("failed to run %v: %v\n%s", strings.Join(args, " "), err, stderr.String()) - } - - fn := "prelude_min.go" - - outStr := fmt.Sprintf(`// Code generated by genmin; DO NOT EDIT. - -package prelude - -// Minified is an uglifyjs-minified version of Prelude. -const Minified = %q -`, out) - - if err := ioutil.WriteFile(fn, []byte(outStr), 0644); err != nil { - return fmt.Errorf("failed to write to %v: %v", fn, err) - } - - return nil -} diff --git a/compiler/prelude/prelude.go b/compiler/prelude/prelude.go index 67fa391ef..a7f4b0743 100644 --- a/compiler/prelude/prelude.go +++ b/compiler/prelude/prelude.go @@ -2,9 +2,10 @@ package prelude import ( _ "embed" -) + "fmt" -//go:generate go run genmin.go + "github.com/evanw/esbuild/pkg/api" +) // Prelude is the GopherJS JavaScript interop layer. var Prelude = prelude + numeric + types + goroutines + jsmapping @@ -23,3 +24,20 @@ var jsmapping string //go:embed goroutines.js var goroutines string + +func Minified() string { + result := api.Transform(Prelude, api.TransformOptions{ + Target: api.ES2015, + MinifyWhitespace: true, + MinifyIdentifiers: true, + MinifySyntax: true, + Charset: api.CharsetUTF8, + LegalComments: api.LegalCommentsEndOfFile, + }) + if len(result.Errors) > 0 { + e := result.Errors[0] + panic(fmt.Sprintf("%d:%d: %s\n%s\n", e.Location.Line, e.Location.Column, e.Text, e.Location.LineText)) + } + return string(result.Code) + +} diff --git a/compiler/prelude/prelude_min.go b/compiler/prelude/prelude_min.go deleted file mode 100644 index 9cd74945c..000000000 --- a/compiler/prelude/prelude_min.go +++ /dev/null @@ -1,6 +0,0 @@ -// Code generated by genmin; DO NOT EDIT. - -package prelude - -// Minified is an uglifyjs-minified version of Prelude. -const Minified = "Error.stackTraceLimit=1/0;var $global,$module,$NaN=NaN;if(\"undefined\"!=typeof window?$global=window:\"undefined\"!=typeof self?$global=self:\"undefined\"!=typeof global?($global=global).require=require:$global=this,void 0===$global||void 0===$global.Array)throw new Error(\"no global object found\");if(\"undefined\"!=typeof module&&($module=module),!$global.fs&&$global.require)try{var fs=$global.require(\"fs\");\"object\"==typeof fs&&null!==fs&&0!==Object.keys(fs).length&&($global.fs=fs)}catch(e){}if(!$global.fs){var outputBuf=\"\",decoder=new TextDecoder(\"utf-8\");$global.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync:function(e,n){var r=(outputBuf+=decoder.decode(n)).lastIndexOf(\"\\n\");return-1!=r&&(console.log(outputBuf.substr(0,r)),outputBuf=outputBuf.substr(r+1)),n.length},write:function(e,n,r,t,i,a){0===r&&t===n.length&&null===i?a(null,this.writeSync(e,n)):a(enosys())}}}var $throwRuntimeError,$linknames={},$packages={},$idCounter=0,$keys=function(e){return e?Object.keys(e):[]},$flushConsole=function(){},$throwNilPointerError=function(){$throwRuntimeError(\"invalid memory address or nil pointer dereference\")},$call=function(e,n,r){return e.apply(n,r)},$makeFunc=function(e){return function(){return $externalize(e(this,new($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments,[]))),$emptyInterface)}},$unused=function(e){},$print=console.log;if(void 0!==$global.process&&$global.require)try{var util=$global.require(\"util\");$print=function(){$global.process.stderr.write(util.format.apply(this,arguments))}}catch(e){}var $println=console.log,$initAllLinknames=function(){for(var e=$keys($packages),n=0;ne.$capacity||t>e.$capacity)&&$throwRuntimeError(\"slice bounds out of range\"),e===e.constructor.nil)return e;var i=new e.constructor(e.$array);return i.$offset=e.$offset+n,i.$length=r-n,i.$capacity=t-n,i},$substring=function(e,n,r){return(n<0||re.length)&&$throwRuntimeError(\"slice bounds out of range\"),e.substring(n,r)},$sliceToNativeArray=function(e){return e.$array.constructor!==Array?e.$array.subarray(e.$offset,e.$offset+e.$length):e.$array.slice(e.$offset,e.$offset+e.$length)},$sliceToGoArray=function(e,n){var r=n.elem;return void 0!==r&&e.$length1114111||55296<=e&&e<=57343)&&(e=65533),e<=127?String.fromCharCode(e):e<=2047?String.fromCharCode(192|e>>6,128|63&e):e<=65535?String.fromCharCode(224|e>>12,128|e>>6&63,128|63&e):String.fromCharCode(240|e>>18,128|e>>12&63,128|e>>6&63,128|63&e)},$stringToBytes=function(e){for(var n=new Uint8Array(e.length),r=0;rt){for(var o=i-1;o>=0;o--)a.copy(e[r+o],n[t+o]);return}for(o=0;ot)for(o=i-1;o>=0;o--)e[r+o]=n[t+o];else for(o=0;oc)if(a=0,c=Math.max(o,e.$capacity<1024?2*e.$capacity:Math.floor(5*e.$capacity/4)),e.$array.constructor===Array){(i=e.$array.slice(e.$offset,e.$offset+e.$length)).length=c;for(var $=e.constructor.elem.zero,u=e.$length;u>>16&65535)*t+r*(n>>>16&65535)<<16>>>0)>>0},$floatKey=function(e){return e!=e?\"NaN$\"+ ++$idCounter:String(e)},$flatten64=function(e){return 4294967296*e.$high+e.$low},$shiftLeft64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high<>>32-n,e.$low<>>0):n<64?new e.constructor(e.$low<>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(e.$high>>31,e.$high>>n-32>>>0):e.$high<0?new e.constructor(-1,4294967295):new e.constructor(0,0)},$shiftRightUint64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high>>>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(0,e.$high>>>n-32):new e.constructor(0,0)},$mul64=function(e,n){var r=e.$high>>>16,t=65535&e.$high,i=e.$low>>>16,a=65535&e.$low,o=n.$high>>>16,c=65535&n.$high,$=n.$low>>>16,u=65535&n.$low,l=0,s=0,f=0,d=0;f+=(d+=a*u)>>>16,s+=(f+=i*u)>>>16,f&=65535,s+=(f+=a*$)>>>16,l+=(s+=t*u)>>>16,s&=65535,l+=(s+=i*$)>>>16,s&=65535,l+=(s+=a*c)>>>16,l+=r*u+t*$+i*c+a*o;var p=((l&=65535)<<16|(s&=65535))>>>0,h=((f&=65535)<<16|(d&=65535))>>>0;return new e.constructor(p,h)},$div64=function(e,n,r){0===n.$high&&0===n.$low&&$throwRuntimeError(\"integer divide by zero\");var t=1,i=1,a=e.$high,o=e.$low;a<0&&(t=-1,i=-1,a=-a,0!==o&&(a--,o=4294967296-o));var c=n.$high,$=n.$low;n.$high<0&&(t*=-1,c=-c,0!==$&&(c--,$=4294967296-$));for(var u=0,l=0,s=0;c<2147483648&&(a>c||a===c&&o>$);)c=(c<<1|$>>>31)>>>0,$=$<<1>>>0,s++;for(var f=0;f<=s;f++)u=u<<1|l>>>31,l=l<<1>>>0,(a>c||a===c&&o>=$)&&(a-=c,(o-=$)<0&&(a--,o+=4294967296),4294967296===++l&&(u++,l=0)),$=($>>>1|c<<31)>>>0,c>>>=1;return r?new e.constructor(a*i,o*i):new e.constructor(u*t,l*t)},$divComplex=function(e,n){var r=e.$real===1/0||e.$real===-1/0||e.$imag===1/0||e.$imag===-1/0,t=n.$real===1/0||n.$real===-1/0||n.$imag===1/0||n.$imag===-1/0,i=!r&&(e.$real!=e.$real||e.$imag!=e.$imag),a=!t&&(n.$real!=n.$real||n.$imag!=n.$imag);if(i||a)return new e.constructor(NaN,NaN);if(r&&!t)return new e.constructor(1/0,1/0);if(!r&&t)return new e.constructor(0,0);if(0===n.$real&&0===n.$imag)return 0===e.$real&&0===e.$imag?new e.constructor(NaN,NaN):new e.constructor(1/0,1/0);if(Math.abs(n.$real)<=Math.abs(n.$imag)){var o=n.$real/n.$imag,c=n.$real*o+n.$imag;return new e.constructor((e.$real*o+e.$imag)/c,(e.$imag*o-e.$real)/c)}o=n.$imag/n.$real,c=n.$imag*o+n.$real;return new e.constructor((e.$imag*o+e.$real)/c,(e.$imag-e.$real*o)/c)},$kindBool=1,$kindInt=2,$kindInt8=3,$kindInt16=4,$kindInt32=5,$kindInt64=6,$kindUint=7,$kindUint8=8,$kindUint16=9,$kindUint32=10,$kindUint64=11,$kindUintptr=12,$kindFloat32=13,$kindFloat64=14,$kindComplex64=15,$kindComplex128=16,$kindArray=17,$kindChan=18,$kindFunc=19,$kindInterface=20,$kindMap=21,$kindPtr=22,$kindSlice=23,$kindString=24,$kindStruct=25,$kindUnsafePointer=26,$methodSynthesizers=[],$addMethodSynthesizer=function(e){null!==$methodSynthesizers?$methodSynthesizers.push(e):e()},$synthesizeMethods=function(){$methodSynthesizers.forEach(function(e){e()}),$methodSynthesizers=null},$ifaceKeyFor=function(e){if(e===$ifaceNil)return\"nil\";var n=e.constructor;return n.string+\"$\"+n.keyFor(e.$val)},$identity=function(e){return e},$typeIDCounter=0,$idKey=function(e){return void 0===e.$id&&($idCounter++,e.$id=$idCounter),String(e.$id)},$arrayPtrCtor=function(){return function(e){this.$get=function(){return e},this.$set=function(e){typ.copy(this,e)},this.$val=e}},$newType=function(e,n,r,t,i,a,o){var c;switch(n){case $kindBool:case $kindInt:case $kindInt8:case $kindInt16:case $kindInt32:case $kindUint:case $kindUint8:case $kindUint16:case $kindUint32:case $kindUintptr:case $kindUnsafePointer:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=$identity;break;case $kindString:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=function(e){return\"$\"+e};break;case $kindFloat32:case $kindFloat64:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=function(e){return $floatKey(e)};break;case $kindInt64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>0,this.$low=n>>>0,this.$val=this}).keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindUint64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>>0,this.$low=n>>>0,this.$val=this}).keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindComplex64:(c=function(e,n){this.$real=$fround(e),this.$imag=$fround(n),this.$val=this}).keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindComplex128:(c=function(e,n){this.$real=e,this.$imag=n,this.$val=this}).keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindArray:(c=function(e){this.$val=e}).wrapped=!0,c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,\"\",!1,$arrayPtrCtor()),c.init=function(e,n){c.elem=e,c.len=n,c.comparable=e.comparable,c.keyFor=function(n){return Array.prototype.join.call($mapArray(n,function(n){return String(e.keyFor(n)).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}),\"$\")},c.copy=function(n,r){$copyArray(n,r,0,0,r.length,e)},c.ptr.init(c),Object.defineProperty(c.ptr.nil,\"nilCheck\",{get:$throwNilPointerError})};break;case $kindChan:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=$idKey,c.init=function(e,n,r){c.elem=e,c.sendOnly=n,c.recvOnly=r};break;case $kindFunc:(c=function(e){this.$val=e}).wrapped=!0,c.init=function(e,n,r){c.params=e,c.results=n,c.variadic=r,c.comparable=!1};break;case $kindInterface:(c={implementedBy:{},missingMethodFor:{}}).keyFor=$ifaceKeyFor,c.init=function(e){c.methods=e,e.forEach(function(e){$ifaceNil[e.prop]=$throwNilPointerError})};break;case $kindMap:(c=function(e){this.$val=e}).wrapped=!0,c.init=function(e,n){c.key=e,c.elem=n,c.comparable=!1};break;case $kindPtr:(c=o||function(e,n,r){this.$get=e,this.$set=n,this.$target=r,this.$val=this}).keyFor=$idKey,c.init=function(e){c.elem=e,c.wrapped=e.kind===$kindArray,c.nil=new c($throwNilPointerError,$throwNilPointerError)};break;case $kindSlice:(c=function(e){e.constructor!==c.nativeArray&&(e=new c.nativeArray(e)),this.$array=e,this.$offset=0,this.$length=e.length,this.$capacity=e.length,this.$val=this}).init=function(e){c.elem=e,c.comparable=!1,c.nativeArray=$nativeArray(e.kind),c.nil=new c([])};break;case $kindStruct:(c=function(e){this.$val=e}).wrapped=!0,c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,i,a,o),c.ptr.elem=c,c.ptr.prototype.$get=function(){return this},c.ptr.prototype.$set=function(e){c.copy(this,e)},c.init=function(e,n){c.pkgPath=e,c.fields=n,n.forEach(function(e){e.typ.comparable||(c.comparable=!1)}),c.keyFor=function(e){var r=e.$val;return $mapArray(n,function(e){return String(e.typ.keyFor(r[e.prop])).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}).join(\"$\")},c.copy=function(e,r){for(var t=0;t0;){var a=[],o=[];t.forEach(function(e){if(!i[e.typ.string])switch(i[e.typ.string]=!0,e.typ.named&&(o=o.concat(e.typ.methods),e.indirect&&(o=o.concat($ptrType(e.typ).methods))),e.typ.kind){case $kindStruct:e.typ.fields.forEach(function(n){if(n.embedded){var r=n.typ,t=r.kind===$kindPtr;a.push({typ:t?r.elem:r,indirect:e.indirect||t})}});break;case $kindInterface:o=o.concat(e.typ.methods)}}),o.forEach(function(e){void 0===n[e.name]&&(n[e.name]=e)}),t=a}return e.methodSetCache=[],Object.keys(n).sort().forEach(function(r){e.methodSetCache.push(n[r])}),e.methodSetCache},$Bool=$newType(1,$kindBool,\"bool\",!0,\"\",!1,null),$Int=$newType(4,$kindInt,\"int\",!0,\"\",!1,null),$Int8=$newType(1,$kindInt8,\"int8\",!0,\"\",!1,null),$Int16=$newType(2,$kindInt16,\"int16\",!0,\"\",!1,null),$Int32=$newType(4,$kindInt32,\"int32\",!0,\"\",!1,null),$Int64=$newType(8,$kindInt64,\"int64\",!0,\"\",!1,null),$Uint=$newType(4,$kindUint,\"uint\",!0,\"\",!1,null),$Uint8=$newType(1,$kindUint8,\"uint8\",!0,\"\",!1,null),$Uint16=$newType(2,$kindUint16,\"uint16\",!0,\"\",!1,null),$Uint32=$newType(4,$kindUint32,\"uint32\",!0,\"\",!1,null),$Uint64=$newType(8,$kindUint64,\"uint64\",!0,\"\",!1,null),$Uintptr=$newType(4,$kindUintptr,\"uintptr\",!0,\"\",!1,null),$Float32=$newType(4,$kindFloat32,\"float32\",!0,\"\",!1,null),$Float64=$newType(8,$kindFloat64,\"float64\",!0,\"\",!1,null),$Complex64=$newType(8,$kindComplex64,\"complex64\",!0,\"\",!1,null),$Complex128=$newType(16,$kindComplex128,\"complex128\",!0,\"\",!1,null),$String=$newType(8,$kindString,\"string\",!0,\"\",!1,null),$UnsafePointer=$newType(4,$kindUnsafePointer,\"unsafe.Pointer\",!0,\"unsafe\",!1,null),$nativeArray=function(e){switch(e){case $kindInt:return Int32Array;case $kindInt8:return Int8Array;case $kindInt16:return Int16Array;case $kindInt32:return Int32Array;case $kindUint:return Uint32Array;case $kindUint8:return Uint8Array;case $kindUint16:return Uint16Array;case $kindUint32:case $kindUintptr:return Uint32Array;case $kindFloat32:return Float32Array;case $kindFloat64:return Float64Array;default:return Array}},$toNativeArray=function(e,n){var r=$nativeArray(e);return r===Array?n:new r(n)},$arrayTypes={},$arrayType=function(e,n){var r=e.id+\"$\"+n,t=$arrayTypes[r];return void 0===t&&(t=$newType(e.size*n,$kindArray,\"[\"+n+\"]\"+e.string,!1,\"\",!1,null),$arrayTypes[r]=t,t.init(e,n)),t},$chanType=function(e,n,r){var t=(r?\"<-\":\"\")+\"chan\"+(n?\"<- \":\" \");n||r||\"<\"!=e.string[0]?t+=e.string:t+=\"(\"+e.string+\")\";var i=n?\"SendChan\":r?\"RecvChan\":\"Chan\",a=e[i];return void 0===a&&(a=$newType(4,$kindChan,t,!1,\"\",!1,null),e[i]=a,a.init(e,n,r)),a},$Chan=function(e,n){(n<0||n>2147483647)&&$throwRuntimeError(\"makechan: size out of range\"),this.$elem=e,this.$capacity=n,this.$buffer=[],this.$sendQueue=[],this.$recvQueue=[],this.$closed=!1},$chanNil=new $Chan(null,0);$chanNil.$sendQueue=$chanNil.$recvQueue={length:0,push:function(){},shift:function(){},indexOf:function(){return-1}};var $funcTypes={},$funcType=function(e,n,r){var t=$mapArray(e,function(e){return e.id}).join(\",\")+\"$\"+$mapArray(n,function(e){return e.id}).join(\",\")+\"$\"+r,i=$funcTypes[t];if(void 0===i){var a=$mapArray(e,function(e){return e.string});r&&(a[a.length-1]=\"...\"+a[a.length-1].substr(2));var o=\"func(\"+a.join(\", \")+\")\";1===n.length?o+=\" \"+n[0].string:n.length>1&&(o+=\" (\"+$mapArray(n,function(e){return e.string}).join(\", \")+\")\"),i=$newType(4,$kindFunc,o,!1,\"\",!1,null),$funcTypes[t]=i,i.init(e,n,r)}return i},$interfaceTypes={},$interfaceType=function(e){var n=$mapArray(e,function(e){return e.pkg+\",\"+e.name+\",\"+e.typ.id}).join(\"$\"),r=$interfaceTypes[n];if(void 0===r){var t=\"interface {}\";0!==e.length&&(t=\"interface { \"+$mapArray(e,function(e){return(\"\"!==e.pkg?e.pkg+\".\":\"\")+e.name+e.typ.string.substr(4)}).join(\"; \")+\" }\"),r=$newType(8,$kindInterface,t,!1,\"\",!1,null),$interfaceTypes[n]=r,r.init(e)}return r},$emptyInterface=$interfaceType([]),$ifaceNil={},$error=$newType(8,$kindInterface,\"error\",!0,\"\",!1,null);$error.init([{prop:\"Error\",name:\"Error\",pkg:\"\",typ:$funcType([],[$String],!1)}]);var $panicValue,$jsObjectPtr,$jsErrorPtr,$mapTypes={},$mapType=function(e,n){var r=e.id+\"$\"+n.id,t=$mapTypes[r];return void 0===t&&(t=$newType(4,$kindMap,\"map[\"+e.string+\"]\"+n.string,!1,\"\",!1,null),$mapTypes[r]=t,t.init(e,n)),t},$makeMap=function(e,n){for(var r=new Map,t=0;t2147483647)&&$throwRuntimeError(\"makeslice: len out of range\"),(r<0||r2147483647)&&$throwRuntimeError(\"makeslice: cap out of range\");var t=new e.nativeArray(r);if(e.nativeArray===Array)for(var i=0;i4||t<0)break}}finally{0==$scheduled.length&&clearTimeout(e)}},$schedule=function(e){e.asleep&&(e.asleep=!1,$awakeGoroutines++),$scheduled.push(e),$curGoroutine===$noGoroutine&&$runScheduled()},$setTimeout=function(e,n){return $awakeGoroutines++,setTimeout(function(){$awakeGoroutines--,e()},n)},$block=function(){$curGoroutine===$noGoroutine&&$throwRuntimeError(\"cannot block in JavaScript callback, fix by wrapping code in goroutine\"),$curGoroutine.asleep=!0},$restore=function(e,n){return void 0!==e&&void 0!==e.$blk?e:n},$send=function(e,n){e.$closed&&$throwRuntimeError(\"send on closed channel\");var r=e.$recvQueue.shift();if(void 0===r){if(!(e.$buffer.length65535){var l=Math.floor((u-65536)/1024)+55296,s=(u-65536)%1024+56320;$+=String.fromCharCode(l,s)}else $+=String.fromCharCode(u)}return $;case $kindStruct:var f=$packages.time;if(void 0!==f&&e.constructor===f.Time.ptr){var d=$div64(e.UnixNano(),new $Int64(0,1e6));return new Date($flatten64(d))}var p={},h=function(e,n){if(n===$jsObjectPtr)return e;switch(n.kind){case $kindPtr:return e===n.nil?p:h(e.$get(),n.elem);case $kindStruct:if(0===n.fields.length)return p;var r=n.fields[0];return h(e[r.prop],r.typ);case $kindInterface:return h(e.$val,e.constructor);default:return p}},k=h(e,n);if(k!==p)return k;if(void 0!==r)return r(e);k={};for(a=0;a>24;case $kindInt16:return parseInt(e)<<16>>16;case $kindInt32:return parseInt(e)>>0;case $kindUint:return parseInt(e);case $kindUint8:return parseInt(e)<<24>>>24;case $kindUint16:return parseInt(e)<<16>>>16;case $kindUint32:case $kindUintptr:return parseInt(e)>>>0;case $kindInt64:case $kindUint64:return new n(0,e);case $kindFloat32:case $kindFloat64:return parseFloat(e);case $kindArray:return e.length!==n.len&&$throwRuntimeError(\"got array with wrong size from JavaScript native\"),$mapArray(e,function(e){return $internalize(e,n.elem,i)});case $kindFunc:return function(){for(var t=[],a=0;a=128)return!1;return!0};\n" diff --git a/go.mod b/go.mod index f94ccc3ff..cce3b71d9 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/gopherjs/gopherjs go 1.18 require ( + github.com/evanw/esbuild v0.18.0 github.com/fsnotify/fsnotify v1.5.1 github.com/google/go-cmp v0.5.7 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 @@ -14,7 +15,7 @@ require ( github.com/visualfc/goembed v0.3.3 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 golang.org/x/tools v0.1.10 ) diff --git a/go.sum b/go.sum index 4efe9da8b..0cf27e64e 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanw/esbuild v0.18.0 h1:zJrquhC5ZiricRVQxMQTWqO8zYcV7F7OfUXstB9Ucbg= +github.com/evanw/esbuild v0.18.0/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= @@ -399,8 +401,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From a73a32f3a8fbb25b374b24ca3fb7af471ca98001 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 12 Jun 2023 21:47:43 +0200 Subject: [PATCH 04/66] Enable KeepNames to preserve stack traces --- compiler/prelude/prelude.go | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/prelude/prelude.go b/compiler/prelude/prelude.go index a7f4b0743..a30289995 100644 --- a/compiler/prelude/prelude.go +++ b/compiler/prelude/prelude.go @@ -31,6 +31,7 @@ func Minified() string { MinifyWhitespace: true, MinifyIdentifiers: true, MinifySyntax: true, + KeepNames: true, Charset: api.CharsetUTF8, LegalComments: api.LegalCommentsEndOfFile, }) From 5b7f364ae04fd0217539cdcce2dd871c2493475c Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Tue, 13 Jun 2023 22:00:38 +0200 Subject: [PATCH 05/66] Log all minifcation warnings and errors, not just one --- compiler/prelude/prelude.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/compiler/prelude/prelude.go b/compiler/prelude/prelude.go index a30289995..bea9402f8 100644 --- a/compiler/prelude/prelude.go +++ b/compiler/prelude/prelude.go @@ -2,7 +2,7 @@ package prelude import ( _ "embed" - "fmt" + "log" "github.com/evanw/esbuild/pkg/api" ) @@ -35,9 +35,14 @@ func Minified() string { Charset: api.CharsetUTF8, LegalComments: api.LegalCommentsEndOfFile, }) - if len(result.Errors) > 0 { - e := result.Errors[0] - panic(fmt.Sprintf("%d:%d: %s\n%s\n", e.Location.Line, e.Location.Column, e.Text, e.Location.LineText)) + for _, w := range result.Warnings { + log.Printf("WARNING %d:%d: %s\n%s\n", w.Location.Line, w.Location.Column, w.Text, w.Location.LineText) + } + if errCount := len(result.Errors); errCount > 0 { + for _, e := range result.Errors { + log.Printf("ERROR %d:%d: %s\n%s\n", e.Location.Line, e.Location.Column, e.Text, e.Location.LineText) + } + log.Fatalf("JS minification failed with %d errors", errCount) } return string(result.Code) From be642b60e11950415bc92a43c1b28fbec812f0fa Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Wed, 14 Jun 2023 11:07:47 +0200 Subject: [PATCH 06/66] Use logrus for minification error logging --- compiler/prelude/prelude.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/prelude/prelude.go b/compiler/prelude/prelude.go index bea9402f8..9fdbba5d5 100644 --- a/compiler/prelude/prelude.go +++ b/compiler/prelude/prelude.go @@ -2,9 +2,9 @@ package prelude import ( _ "embed" - "log" "github.com/evanw/esbuild/pkg/api" + log "github.com/sirupsen/logrus" ) // Prelude is the GopherJS JavaScript interop layer. @@ -36,13 +36,13 @@ func Minified() string { LegalComments: api.LegalCommentsEndOfFile, }) for _, w := range result.Warnings { - log.Printf("WARNING %d:%d: %s\n%s\n", w.Location.Line, w.Location.Column, w.Text, w.Location.LineText) + log.Warnf("%d:%d: %s\n%s\n", w.Location.Line, w.Location.Column, w.Text, w.Location.LineText) } if errCount := len(result.Errors); errCount > 0 { for _, e := range result.Errors { - log.Printf("ERROR %d:%d: %s\n%s\n", e.Location.Line, e.Location.Column, e.Text, e.Location.LineText) + log.Errorf("%d:%d: %s\n%s\n", e.Location.Line, e.Location.Column, e.Text, e.Location.LineText) } - log.Fatalf("JS minification failed with %d errors", errCount) + log.Fatalf("Prelude minification failed with %d errors", errCount) } return string(result.Code) From fef762dcca2ddce0075cceabf3009ce1ab8fa75d Mon Sep 17 00:00:00 2001 From: Dave Brophy Date: Sat, 24 Feb 2018 15:16:52 +0100 Subject: [PATCH 07/66] Improve ordering of EscapingObjects --- compiler/analysis/escape.go | 12 ++++++------ compiler/utils.go | 6 ------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/compiler/analysis/escape.go b/compiler/analysis/escape.go index 2807ecf64..4209fca6c 100644 --- a/compiler/analysis/escape.go +++ b/compiler/analysis/escape.go @@ -14,16 +14,13 @@ func EscapingObjects(n ast.Node, info *types.Info) []*types.Var { bottomScopes: make(map[*types.Scope]bool), } ast.Walk(&v, n) - var list []*types.Var - for obj := range v.escaping { - list = append(list, obj) - } - return list + return v.ordered } type escapeAnalysis struct { info *types.Info escaping map[*types.Var]bool + ordered []*types.Var topScope *types.Scope bottomScopes map[*types.Scope]bool } @@ -57,7 +54,10 @@ func (v *escapingObjectCollector) Visit(node ast.Node) (w ast.Visitor) { if obj, ok := v.analysis.info.Uses[id].(*types.Var); ok { for s := obj.Parent(); s != nil; s = s.Parent() { if s == v.analysis.topScope { - v.analysis.escaping[obj] = true + if !v.analysis.escaping[obj] { + v.analysis.escaping[obj] = true + v.analysis.ordered = append(v.analysis.ordered, obj) + } break } if v.analysis.bottomScopes[s] { diff --git a/compiler/utils.go b/compiler/utils.go index 5cde7b3c7..f147f84b2 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -397,12 +397,6 @@ func (fc *funcContext) handleEscapingVars(n ast.Node) { var names []string objs := analysis.EscapingObjects(n, fc.pkgCtx.Info.Info) - sort.Slice(objs, func(i, j int) bool { - if objs[i].Name() == objs[j].Name() { - return objs[i].Pos() < objs[j].Pos() - } - return objs[i].Name() < objs[j].Name() - }) for _, obj := range objs { names = append(names, fc.objectName(obj)) fc.pkgCtx.escapingVars[obj] = true From d2ed205a31956b7b02a78d08ec80cdef1696f4d9 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 19 Jun 2023 15:35:19 +0200 Subject: [PATCH 08/66] gofumpt the repo --- build/build.go | 2 +- build/cache/cache.go | 2 +- build/embed.go | 18 ++++---- compiler/compiler.go | 6 ++- compiler/natives/src/embed/embed.go | 3 +- compiler/natives/src/encoding/gob/gob_test.go | 2 +- .../src/internal/reflectlite/all_test.go | 3 +- .../src/internal/reflectlite/reflectlite.go | 12 +++--- .../natives/src/internal/reflectlite/utils.go | 4 +- compiler/natives/src/math/math.go | 10 +++-- compiler/natives/src/net/netip/export_test.go | 1 + compiler/natives/src/reflect/reflect_test.go | 2 +- compiler/natives/src/runtime/runtime.go | 8 ++-- compiler/natives/src/sync/pool_test.go | 1 - compiler/natives/src/syscall/legacy.go | 8 ++-- compiler/prelude/prelude.go | 1 - tests/arrays_test.go | 1 - tests/copy_test.go | 4 +- tests/deferblock_test.go | 2 +- tests/gorepo/run.go | 8 ++-- tests/js_test.go | 6 +-- tests/linkname_test.go | 8 ++-- tests/map_js_test.go | 5 +-- tests/misc_test.go | 42 +++++++++++-------- tests/syscall_test.go | 2 +- tests/testdata/defer_builtin.go | 6 ++- tests/testdata/linkname/three/three.go | 2 +- tests/testdata/linkname/two/two.go | 2 +- 28 files changed, 93 insertions(+), 78 deletions(-) diff --git a/build/build.go b/build/build.go index e82e8052e..f4c78e7b0 100644 --- a/build/build.go +++ b/build/build.go @@ -730,7 +730,7 @@ func (s *Session) SourceMappingCallback(m *sourcemap.Map) func(generatedLine, ge // WriteCommandPackage writes the final JavaScript output file at pkgObj path. func (s *Session) WriteCommandPackage(archive *compiler.Archive, pkgObj string) error { - if err := os.MkdirAll(filepath.Dir(pkgObj), 0777); err != nil { + if err := os.MkdirAll(filepath.Dir(pkgObj), 0o777); err != nil { return err } codeFile, err := os.Create(pkgObj) diff --git a/build/cache/cache.go b/build/cache/cache.go index 79e0471cf..14257ef9e 100644 --- a/build/cache/cache.go +++ b/build/cache/cache.go @@ -95,7 +95,7 @@ func (bc *BuildCache) StoreArchive(a *compiler.Archive) { return // Caching is disabled. } path := cachedPath(bc.archiveKey(a.ImportPath)) - if err := os.MkdirAll(filepath.Dir(path), 0750); err != nil { + if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil { log.Warningf("Failed to create build cache directory: %v", err) return } diff --git a/build/embed.go b/build/embed.go index 2b5661f6c..c749eeb50 100644 --- a/build/embed.go +++ b/build/embed.go @@ -55,17 +55,21 @@ func embedFiles(pkg *PackageData, fset *token.FileSet, files []*ast.File) (*ast. &ast.CallExpr{ Fun: em.Spec.Type, Args: []ast.Expr{ - &ast.Ident{Name: buildIdent(fs[0].Name), - NamePos: em.Spec.Names[0].NamePos}, + &ast.Ident{ + Name: buildIdent(fs[0].Name), + NamePos: em.Spec.Names[0].NamePos, + }, }, - }} + }, + } case goembed.EmbedBytes: // value = []byte(data) em.Spec.Values = []ast.Expr{ &ast.CallExpr{ Fun: em.Spec.Type, Args: []ast.Expr{ast.NewIdent(buildIdent(fs[0].Name))}, - }} + }, + } case goembed.EmbedString: // value = data em.Spec.Values = []ast.Expr{ast.NewIdent(buildIdent(fs[0].Name))} @@ -115,15 +119,15 @@ func embedFiles(pkg *PackageData, fset *token.FileSet, files []*ast.File) (*ast. Elt: &ast.StructType{ Fields: &ast.FieldList{ List: []*ast.Field{ - &ast.Field{ + { Names: []*ast.Ident{ast.NewIdent("name")}, Type: ast.NewIdent("string"), }, - &ast.Field{ + { Names: []*ast.Ident{ast.NewIdent("data")}, Type: ast.NewIdent("string"), }, - &ast.Field{ + { Names: []*ast.Ident{ast.NewIdent("hash")}, Type: &ast.ArrayType{ Len: &ast.BasicLit{Kind: token.INT, Value: "16"}, diff --git a/compiler/compiler.go b/compiler/compiler.go index 6935444b2..0588a923c 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -21,8 +21,10 @@ import ( "golang.org/x/tools/go/gcexportdata" ) -var sizes32 = &types.StdSizes{WordSize: 4, MaxAlign: 8} -var reservedKeywords = make(map[string]bool) +var ( + sizes32 = &types.StdSizes{WordSize: 4, MaxAlign: 8} + reservedKeywords = make(map[string]bool) +) func init() { for _, keyword := range []string{"abstract", "arguments", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "eval", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "undefined", "var", "void", "volatile", "while", "with", "yield"} { diff --git a/compiler/natives/src/embed/embed.go b/compiler/natives/src/embed/embed.go index 18cb7001f..bb9738546 100644 --- a/compiler/natives/src/embed/embed.go +++ b/compiler/natives/src/embed/embed.go @@ -7,7 +7,8 @@ func buildFS(list []struct { name string data string hash [16]byte -}) (f FS) { +}, +) (f FS) { n := len(list) files := make([]file, n) for i := 0; i < n; i++ { diff --git a/compiler/natives/src/encoding/gob/gob_test.go b/compiler/natives/src/encoding/gob/gob_test.go index 94a999ccd..823b572ac 100644 --- a/compiler/natives/src/encoding/gob/gob_test.go +++ b/compiler/natives/src/encoding/gob/gob_test.go @@ -68,7 +68,7 @@ func TestEndToEnd(t *testing.T) { // TODO: Fix this problem: // TypeError: dst.$set is not a function // at typedmemmove (/github.com/gopherjs/gopherjs/reflect.go:487:3) - //Marr: map[[2]string][2]*float64{arr1: floatArr1, arr2: floatArr2}, + // Marr: map[[2]string][2]*float64{arr1: floatArr1, arr2: floatArr2}, EmptyMap: make(map[string]int), N: &[3]float64{1.5, 2.5, 3.5}, Strs: &[2]string{s1, s2}, diff --git a/compiler/natives/src/internal/reflectlite/all_test.go b/compiler/natives/src/internal/reflectlite/all_test.go index 55639d276..977438e4e 100644 --- a/compiler/natives/src/internal/reflectlite/all_test.go +++ b/compiler/natives/src/internal/reflectlite/all_test.go @@ -4,8 +4,9 @@ package reflectlite_test import ( - . "internal/reflectlite" "testing" + + . "internal/reflectlite" ) func TestTypes(t *testing.T) { diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index e4c03bed5..0be1eb09b 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -32,9 +32,7 @@ func init() { uint8Type = TypeOf(uint8(0)).(*rtype) // set for real } -var ( - uint8Type *rtype -) +var uint8Type *rtype var ( idJsType = "_jsType" @@ -578,7 +576,7 @@ func maplen(m unsafe.Pointer) int { } func cvtDirect(v Value, typ Type) Value { - var srcVal = v.object() + srcVal := v.object() if srcVal == jsType(v.typ).Get("nil") { return makeValue(typ, jsType(typ).Get("nil"), v.flag) } @@ -914,7 +912,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { return true } } - var n = v1.Len() + n := v1.Len() if n != v2.Len() { return false } @@ -932,7 +930,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { case Ptr: return deepValueEqualJs(v1.Elem(), v2.Elem(), visited) case Struct: - var n = v1.NumField() + n := v1.NumField() for i := 0; i < n; i++ { if !deepValueEqualJs(v1.Field(i), v2.Field(i), visited) { return false @@ -946,7 +944,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { if v1.object() == v2.object() { return true } - var keys = v1.MapKeys() + keys := v1.MapKeys() if len(keys) != v2.Len() { return false } diff --git a/compiler/natives/src/internal/reflectlite/utils.go b/compiler/natives/src/internal/reflectlite/utils.go index f74182476..1941f0d0e 100644 --- a/compiler/natives/src/internal/reflectlite/utils.go +++ b/compiler/natives/src/internal/reflectlite/utils.go @@ -23,9 +23,7 @@ func (e *errorString) Error() string { return e.s } -var ( - ErrSyntax = &errorString{"invalid syntax"} -) +var ErrSyntax = &errorString{"invalid syntax"} func unquote(s string) (string, error) { if len(s) < 2 { diff --git a/compiler/natives/src/math/math.go b/compiler/natives/src/math/math.go index d1ed66edb..b0ed2da0d 100644 --- a/compiler/natives/src/math/math.go +++ b/compiler/natives/src/math/math.go @@ -7,10 +7,12 @@ import ( "github.com/gopherjs/gopherjs/js" ) -var math = js.Global.Get("Math") -var _zero float64 = 0 -var posInf = 1 / _zero -var negInf = -1 / _zero +var ( + math = js.Global.Get("Math") + _zero float64 = 0 + posInf = 1 / _zero + negInf = -1 / _zero +) // Usually, NaN can be obtained in JavaScript with `0 / 0` operation. However, // in V8, `0 / _zero` yields a bitwise-different value of NaN compared to the diff --git a/compiler/natives/src/net/netip/export_test.go b/compiler/natives/src/net/netip/export_test.go index b8748228f..001f348cc 100644 --- a/compiler/natives/src/net/netip/export_test.go +++ b/compiler/natives/src/net/netip/export_test.go @@ -4,6 +4,7 @@ package netip import ( "fmt" + "internal/intern" ) diff --git a/compiler/natives/src/reflect/reflect_test.go b/compiler/natives/src/reflect/reflect_test.go index 0ba5f29d5..112119a4e 100644 --- a/compiler/natives/src/reflect/reflect_test.go +++ b/compiler/natives/src/reflect/reflect_test.go @@ -157,7 +157,7 @@ func TestIssue22073(t *testing.T) { // TypeError: Cannot read property 'apply' of undefined // Shouldn't panic. - //m.Call(nil) + // m.Call(nil) } func TestCallReturnsEmpty(t *testing.T) { diff --git a/compiler/natives/src/runtime/runtime.go b/compiler/natives/src/runtime/runtime.go index bcea9ad8e..7e76e3ccc 100644 --- a/compiler/natives/src/runtime/runtime.go +++ b/compiler/natives/src/runtime/runtime.go @@ -7,9 +7,11 @@ import ( "github.com/gopherjs/gopherjs/js" ) -const GOOS = "js" -const GOARCH = "ecmascript" -const Compiler = "gopherjs" +const ( + GOOS = "js" + GOARCH = "ecmascript" + Compiler = "gopherjs" +) // The Error interface identifies a run time error. type Error interface { diff --git a/compiler/natives/src/sync/pool_test.go b/compiler/natives/src/sync/pool_test.go index 218852658..ea35fd136 100644 --- a/compiler/natives/src/sync/pool_test.go +++ b/compiler/natives/src/sync/pool_test.go @@ -24,7 +24,6 @@ func TestPool(t *testing.T) { t.Fatalf("Got: p.Get() returned: %s. Want: %s.", got, want) } } - } func TestPoolGC(t *testing.T) { diff --git a/compiler/natives/src/syscall/legacy.go b/compiler/natives/src/syscall/legacy.go index 239ba388c..beb99eb78 100644 --- a/compiler/natives/src/syscall/legacy.go +++ b/compiler/natives/src/syscall/legacy.go @@ -6,9 +6,11 @@ import ( "github.com/gopherjs/gopherjs/js" ) -var syscallModule *js.Object -var alreadyTriedToLoad = false -var minusOne = -1 +var ( + syscallModule *js.Object + alreadyTriedToLoad = false + minusOne = -1 +) var warningPrinted = false diff --git a/compiler/prelude/prelude.go b/compiler/prelude/prelude.go index 9fdbba5d5..0b605b697 100644 --- a/compiler/prelude/prelude.go +++ b/compiler/prelude/prelude.go @@ -45,5 +45,4 @@ func Minified() string { log.Fatalf("Prelude minification failed with %d errors", errCount) } return string(result.Code) - } diff --git a/tests/arrays_test.go b/tests/arrays_test.go index c202f2e9e..e79989991 100644 --- a/tests/arrays_test.go +++ b/tests/arrays_test.go @@ -74,7 +74,6 @@ func TestArrayPointer(t *testing.T) { }) t.Run("reflect.IsNil", func(t *testing.T) { - }) } diff --git a/tests/copy_test.go b/tests/copy_test.go index b0ea8e09f..866b554b4 100644 --- a/tests/copy_test.go +++ b/tests/copy_test.go @@ -125,7 +125,7 @@ func (t T) M() int { } func TestExplicitConversion(t *testing.T) { - var coolGuy = S{x: 42} + coolGuy := S{x: 42} var i I i = T(coolGuy) if i.M() != 42 { @@ -138,7 +138,7 @@ func TestCopyStructByReflect(t *testing.T) { type Info struct { name string } - a := []Info{Info{"A"}, Info{"B"}, Info{"C"}} + a := []Info{{"A"}, {"B"}, {"C"}} v := reflect.ValueOf(a) i := v.Index(0).Interface() a[0] = Info{"X"} diff --git a/tests/deferblock_test.go b/tests/deferblock_test.go index 7667f6d88..3443b936f 100644 --- a/tests/deferblock_test.go +++ b/tests/deferblock_test.go @@ -45,7 +45,7 @@ func TestBlockingInDefer(t *testing.T) { func TestIssue1083(t *testing.T) { // https://github.com/gopherjs/gopherjs/issues/1083 - var block = make(chan bool) + block := make(chan bool) recoverCompleted := false diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index dd6b392f8..656dd55a3 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -683,7 +683,7 @@ func (t *test) run() { t.makeTempDir() defer os.RemoveAll(t.tempDir) - err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644) + err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0o644) check(err) // A few tests (of things like the environment) require these to be set. @@ -855,7 +855,7 @@ func (t *test) run() { return } tfile := filepath.Join(t.tempDir, "tmp__.go") - if err := ioutil.WriteFile(tfile, out, 0666); err != nil { + if err := ioutil.WriteFile(tfile, out, 0o666); err != nil { t.err = fmt.Errorf("write tempfile:%s", err) return } @@ -876,7 +876,7 @@ func (t *test) run() { return } tfile := filepath.Join(t.tempDir, "tmp__.go") - err = ioutil.WriteFile(tfile, out, 0666) + err = ioutil.WriteFile(tfile, out, 0o666) if err != nil { t.err = fmt.Errorf("write tempfile:%s", err) return @@ -1079,7 +1079,7 @@ func (t *test) updateErrors(out string, file string) { } } // Write new file. - err = ioutil.WriteFile(file, []byte(strings.Join(lines, "\n")), 0640) + err = ioutil.WriteFile(file, []byte(strings.Join(lines, "\n")), 0o640) if err != nil { fmt.Fprintln(os.Stderr, err) return diff --git a/tests/js_test.go b/tests/js_test.go index 18dbf0c03..4dd3573f6 100644 --- a/tests/js_test.go +++ b/tests/js_test.go @@ -294,7 +294,7 @@ func TestDate(t *testing.T) { // https://github.com/gopherjs/gopherjs/issues/287 func TestInternalizeDate(t *testing.T) { - var a = time.Unix(0, (123 * time.Millisecond).Nanoseconds()) + a := time.Unix(0, (123 * time.Millisecond).Nanoseconds()) var b time.Time js.Global.Set("internalizeDate", func(t time.Time) { b = t }) js.Global.Call("eval", "(internalizeDate(new Date(123)))") @@ -478,7 +478,6 @@ func TestMakeWrapper(t *testing.T) { if js.MakeWrapper(m).Interface() != m { t.Fail() } - } func TestMakeFullWrapperType(t *testing.T) { @@ -502,7 +501,7 @@ func TestMakeFullWrapperGettersAndSetters(t *testing.T) { Name: "Gopher", Struct: F{Field: 42}, Pointer: f, - Array: [1]F{F{Field: 42}}, + Array: [1]F{{Field: 42}}, Slice: []*F{f}, } @@ -731,7 +730,6 @@ func TestExternalize(t *testing.T) { if result != tt.want { t.Errorf("Unexpected result %q != %q", result, tt.want) } - }) } } diff --git a/tests/linkname_test.go b/tests/linkname_test.go index 9fb34ad03..e6e9e90f9 100644 --- a/tests/linkname_test.go +++ b/tests/linkname_test.go @@ -38,9 +38,11 @@ func TestLinknameMethods(t *testing.T) { method.TestLinkname(t) } -type name struct{ bytes *byte } -type nameOff int32 -type rtype struct{} +type ( + name struct{ bytes *byte } + nameOff int32 + rtype struct{} +) //go:linkname rtype_nameOff reflect.(*rtype).nameOff func rtype_nameOff(r *rtype, off nameOff) name diff --git a/tests/map_js_test.go b/tests/map_js_test.go index 26d55e20c..64cc8e6f0 100644 --- a/tests/map_js_test.go +++ b/tests/map_js_test.go @@ -4,8 +4,9 @@ package tests import ( - "github.com/gopherjs/gopherjs/js" "testing" + + "github.com/gopherjs/gopherjs/js" ) func Test_MapWrapper(t *testing.T) { @@ -43,7 +44,6 @@ func Test_MapWrapper(t *testing.T) { } if got := swmWrapper.Get("DummyMap").Get("key").Get("Msg").String(); got != swm.DummyMap["key"].Msg { t.Errorf("DummyMap Got: %s, Want: %s", got, swm.DummyMap["key"].Msg) - } if got := swmWrapper.Call("MapFunc", mapFuncArg).Get("key2").String(); got != mapFuncArg["key2"] { t.Errorf("MapFunc Got: %s, Want: %s", got, mapFuncArg["key2"]) @@ -111,5 +111,4 @@ func Test_MapEmbeddedObject(t *testing.T) { if d.Props["two"] != 2 { t.Errorf("key 'two' value Got: %d, Want: %d", d.Props["two"], 2) } - } diff --git a/tests/misc_test.go b/tests/misc_test.go index 3608d126b..b22b76980 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -430,7 +430,7 @@ func TestEmptySelectCase(t *testing.T) { ch := make(chan int, 1) ch <- 42 - var v = 0 + v := 0 select { case v = <-ch: } @@ -439,17 +439,21 @@ func TestEmptySelectCase(t *testing.T) { } } -var a int -var b int -var C int -var D int +var ( + a int + b int + C int + D int +) -var a1 = &a -var a2 = &a -var b1 = &b -var C1 = &C -var C2 = &C -var D1 = &D +var ( + a1 = &a + a2 = &a + b1 = &b + C1 = &C + C2 = &C + D1 = &D +) func TestPkgVarPointers(t *testing.T) { if a1 != a2 || a1 == b1 || C1 != C2 || C1 == D1 { @@ -554,10 +558,12 @@ var tuple2called = 0 func tuple1() (interface{}, error) { return tuple2() } + func tuple2() (int, error) { tuple2called++ return 14, nil } + func TestTupleReturnImplicitCast(t *testing.T) { x, _ := tuple1() if x != 14 || tuple2called != 1 { @@ -664,7 +670,7 @@ func TestSlicingNilSlice(t *testing.T) { s = s[5:10] }) t.Run("DoesNotBecomeNil", func(t *testing.T) { - var s = []int{} + s := []int{} s = s[:] if s == nil { t.Errorf("non-nil slice became nil after slicing: %#v, want []int{}", s) @@ -815,12 +821,12 @@ func TestUntypedNil(t *testing.T) { _ = x == nil ) } - var _ = (*int)(nil) - var _ = (func())(nil) - var _ = ([]byte)(nil) - var _ = (map[int]int)(nil) - var _ = (chan int)(nil) - var _ = (interface{})(nil) + _ = (*int)(nil) + _ = (func())(nil) + _ = ([]byte)(nil) + _ = (map[int]int)(nil) + _ = (chan int)(nil) + _ = (interface{})(nil) { f := func(*int) {} f(nil) diff --git a/tests/syscall_test.go b/tests/syscall_test.go index 13631ad24..5e18776a3 100644 --- a/tests/syscall_test.go +++ b/tests/syscall_test.go @@ -26,7 +26,7 @@ func TestOpen(t *testing.T) { } f.Close() defer os.Remove(f.Name()) - fd, err := syscall.Open(f.Name(), syscall.O_RDONLY, 0600) + fd, err := syscall.Open(f.Name(), syscall.O_RDONLY, 0o600) if err != nil { t.Fatalf("syscall.Open() returned error: %s", err) } diff --git a/tests/testdata/defer_builtin.go b/tests/testdata/defer_builtin.go index 95d22f4a6..264b78b61 100644 --- a/tests/testdata/defer_builtin.go +++ b/tests/testdata/defer_builtin.go @@ -1,7 +1,9 @@ package main -type set map[interface{}]struct{} -type key struct{ a int } +type ( + set map[interface{}]struct{} + key struct{ a int } +) var m = set{} diff --git a/tests/testdata/linkname/three/three.go b/tests/testdata/linkname/three/three.go index 4943d3c1d..e705dc79b 100644 --- a/tests/testdata/linkname/three/three.go +++ b/tests/testdata/linkname/three/three.go @@ -7,7 +7,7 @@ func DoThree() string { func init() { // Avoid dead-code elimination. // TODO(nevkontakte): This should not be necessary. - var _ = doInternalThree + _ = doInternalThree } var threeSecret = "three secret" diff --git a/tests/testdata/linkname/two/two.go b/tests/testdata/linkname/two/two.go index 10f8f9a37..42f8362b2 100644 --- a/tests/testdata/linkname/two/two.go +++ b/tests/testdata/linkname/two/two.go @@ -5,7 +5,7 @@ import _ "unsafe" // for go:linkname func init() { // Avoid dead-code elimination. // TODO(nevkontakte): This should not be necessary. - var _ = doInternalOne + _ = doInternalOne } func DoTwo() string { From 0e53da987f653bd8ab3f62dd7ac9a580f875dce5 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Fri, 23 Jun 2023 21:53:44 +0200 Subject: [PATCH 09/66] Add minimal linter config to enforce gofumpt --- .github/workflows/lint.yaml | 25 +++++++++++++++++++++++++ .golangci.toml | 14 ++++++++++++++ circle.yml | 3 --- 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/lint.yaml create mode 100644 .golangci.toml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 000000000..5f6c971e7 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,25 @@ +name: golangci-lint +on: + pull_request: +permissions: + contents: read +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: "1.18.10" + + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.53.3 + only-new-issues: true + + - name: Check go.mod + run: | + go mod tidy && git diff --exit-code diff --git a/.golangci.toml b/.golangci.toml new file mode 100644 index 000000000..003b56f8b --- /dev/null +++ b/.golangci.toml @@ -0,0 +1,14 @@ +[run] +timeout = "1m" + +[output] +format = "colored-line-number" + +[linters] +disable-all = true +enable = [ + "gofumpt", +] + +[issues] +exclude-use-default = false diff --git a/circle.yml b/circle.yml index 2776d1b0f..0e4f01dcb 100644 --- a/circle.yml +++ b/circle.yml @@ -72,9 +72,6 @@ jobs: executor: gopherjs steps: - setup_and_install_gopherjs - - run: - name: Check gofmt - command: diff -u <(echo -n) <(gofmt -d .) - run: name: Check go vet command: | From 6e3de82cedb370ad84524afeb29b70d520acc888 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 23 Jun 2023 21:11:40 +0100 Subject: [PATCH 10/66] Fix compiler panic when handling composite literals representing named pointer types. Prior to this change, the following code would panic the compiler: ```go type ( S struct{ f int } PS *[]S ) var _ = []*S{{}} // This compiles, but potentially incorrectly. var _ = []PS{{}} // Compiler panics here. ``` Prior to this change, GopherJS compiler didn't expect that a pointer type could be named in this context, and would panic. This is addressed by checking the underlying type, rather than the type itself. However, there was a bigger correctness problem here too. According to the Go spec: > Within a composite literal of array, slice, or map type T, elements or map keys that are themselves composite literals may elide the respective literal type if it is identical to the element or key type of T. Similarly, elements or keys that are addresses of composite literals may elide the &T when the element or key type is *T. So in the example above, `[]PS{{}}` expression is equivalent to `[]PS{PS(*S{})}`. However, even with the first part of the fix, the code emitted by the compiler would have been equivalent to `[]PS{S{}}`. This mostly works because at runtime GopherJS represents a pointer to the struct and the struct type as the same JS object, but it would break down when it comes to methods and non-struct types. So the second part of the fix is to generate the explicit AST for taking an address of the value type and type conversion, and compiling that. --- compiler/expressions.go | 32 +++++++++++++++++++++++++++++--- compiler/utils.go | 6 ++++++ tests/misc_test.go | 15 +++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 578c3f09d..db08cc31f 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -100,8 +100,34 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { switch e := expr.(type) { case *ast.CompositeLit: - if ptrType, isPointer := exprType.(*types.Pointer); isPointer { - exprType = ptrType.Elem() + if ptrType, isPointer := exprType.Underlying().(*types.Pointer); isPointer { + // Go automatically treats `[]*T{{}}` as `[]*T{&T{}}`, in which case the + // inner composite literal `{}` would has a pointer type. To make sure the + // type conversion is handled correctly, we generate the explicit AST for + // this. + var rewritten ast.Expr = fc.setType(&ast.UnaryExpr{ + OpPos: e.Pos(), + Op: token.AND, + X: fc.setType(&ast.CompositeLit{ + Elts: e.Elts, + }, ptrType.Elem()), + }, ptrType) + + if exprType, ok := exprType.(*types.Named); ok { + // Handle a special case when the pointer type is named, e.g.: + // type PS *S + // _ = []PS{{}} + // In that case the value corresponding to the inner literal `{}` is + // initialized as `&S{}` and then converted to `PS`: `[]PS{PS(&S{})}`. + typeCast := fc.setType(&ast.CallExpr{ + Fun: fc.newTypeIdent(exprType.String(), exprType.Obj()), + Lparen: e.Lbrace, + Args: []ast.Expr{rewritten}, + Rparen: e.Rbrace, + }, exprType) + rewritten = typeCast + } + return fc.translateExpr(rewritten) } collectIndexedElements := func(elementType types.Type) []string { @@ -173,7 +199,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { } return fc.formatExpr("new %s.ptr(%s)", fc.typeName(exprType), strings.Join(elements, ", ")) default: - panic(fmt.Sprintf("Unhandled CompositeLit type: %T\n", t)) + panic(fmt.Sprintf("Unhandled CompositeLit type: %[1]T %[1]v\n", t)) } case *ast.FuncLit: diff --git a/compiler/utils.go b/compiler/utils.go index f147f84b2..c70efb989 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -282,6 +282,12 @@ func (fc *funcContext) newIdent(name string, t types.Type) *ast.Ident { return ident } +func (fc *funcContext) newTypeIdent(name string, obj types.Object) *ast.Ident { + ident := ast.NewIdent(name) + fc.pkgCtx.Info.Uses[ident] = obj + return ident +} + func (fc *funcContext) setType(e ast.Expr, t types.Type) ast.Expr { fc.pkgCtx.Types[e] = types.TypeAndValue{Type: t} return e diff --git a/tests/misc_test.go b/tests/misc_test.go index 3608d126b..aeb0985f9 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -920,3 +920,18 @@ func TestAssignImplicitConversion(t *testing.T) { } }) } + +func TestCompositeLiterals(t *testing.T) { + type S struct{} + type SP *S + + s1 := []*S{{}} + if got := reflect.TypeOf(s1[0]); got.String() != "*tests.S" { + t.Errorf("Got: reflect.TypeOf(s1[0]) = %v. Want: *tests.S", got) + } + + s2 := []SP{{}} + if got := reflect.TypeOf(s2[0]); got.String() != "tests.SP" { + t.Errorf("Got: reflect.TypeOf(s2[0]) = %v. Want: tests.SP", got) + } +} From 255771f6f5148dea7016495f9c4f22252d835173 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Jun 2023 18:59:35 +0000 Subject: [PATCH 11/66] Bump semver from 7.3.5 to 7.5.3 in /node-syscall Bumps [semver](https://github.com/npm/node-semver) from 7.3.5 to 7.5.3. - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v7.3.5...v7.5.3) --- updated-dependencies: - dependency-name: semver dependency-type: indirect ... Signed-off-by: dependabot[bot] --- node-syscall/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node-syscall/package-lock.json b/node-syscall/package-lock.json index 41781ff3b..a92b06df6 100644 --- a/node-syscall/package-lock.json +++ b/node-syscall/package-lock.json @@ -555,9 +555,9 @@ "optional": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "requires": { "lru-cache": "^6.0.0" } From f1137601ee13aa415f3d89891a702c31e05a7b9d Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Sat, 24 Jun 2023 21:48:09 +0200 Subject: [PATCH 12/66] Rerun gofumpt with a modern version, to adapt the 1.19 GoDoc changes --- build/build.go | 6 ++-- compiler/linkname.go | 20 ++++++------ compiler/natives/src/math/big/big.go | 3 +- compiler/utils.go | 48 +++++++++++++++------------- js/js.go | 38 +++++++++++----------- nosync/once.go | 8 +++-- nosync/pool.go | 1 - tests/gorepo/run.go | 3 +- tests/runtime_test.go | 8 ++--- 9 files changed, 70 insertions(+), 65 deletions(-) diff --git a/build/build.go b/build/build.go index f4c78e7b0..63c89c50f 100644 --- a/build/build.go +++ b/build/build.go @@ -72,9 +72,9 @@ func NewBuildContext(installSuffix string, buildTags []string) XContext { // In the directory containing the package, .go and .inc.js files are // considered part of the package except for: // -// - .go files in package documentation -// - files starting with _ or . (likely editor temporary files) -// - files with build constraints not satisfied by the context +// - .go files in package documentation +// - files starting with _ or . (likely editor temporary files) +// - files with build constraints not satisfied by the context // // If an error occurs, Import returns a non-nil error and a nil // *PackageData. diff --git a/compiler/linkname.go b/compiler/linkname.go index 5e1782462..ae1e3ea2b 100644 --- a/compiler/linkname.go +++ b/compiler/linkname.go @@ -26,10 +26,10 @@ type GoLinkname struct { // This is a logical equivalent of a symbol name used by traditional linkers. // The following properties should hold true: // -// - Each named symbol within a program has a unique SymName. -// - Similarly named methods of different types will have different symbol names. -// - The string representation is opaque and should not be attempted to reversed -// to a struct form. +// - Each named symbol within a program has a unique SymName. +// - Similarly named methods of different types will have different symbol names. +// - The string representation is opaque and should not be attempted to reversed +// to a struct form. type SymName struct { PkgPath string // Full package import path. Name string // Symbol name. @@ -85,12 +85,12 @@ func (n SymName) IsMethod() (recv string, method string, ok bool) { // // GopherJS directive support has the following limitations: // -// - External linkname must be specified. -// - The directive must be applied to a package-level function or method (variables -// are not supported). -// - The local function referenced by the directive must have no body (in other -// words, it can only "import" an external function implementation into the -// local scope). +// - External linkname must be specified. +// - The directive must be applied to a package-level function or method (variables +// are not supported). +// - The local function referenced by the directive must have no body (in other +// words, it can only "import" an external function implementation into the +// local scope). func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]GoLinkname, error) { var errs ErrorList = nil var directives []GoLinkname diff --git a/compiler/natives/src/math/big/big.go b/compiler/natives/src/math/big/big.go index 9e068d5cd..25512db31 100644 --- a/compiler/natives/src/math/big/big.go +++ b/compiler/natives/src/math/big/big.go @@ -4,5 +4,6 @@ package big // TODO: This is a workaround for https://github.com/gopherjs/gopherjs/issues/652. -// Remove after that issue is resolved. +// +// Remove after that issue is resolved. type Word uintptr diff --git a/compiler/utils.go b/compiler/utils.go index c70efb989..8b18c29c1 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -84,13 +84,18 @@ func (fc *funcContext) Delayed(f func()) { // tuple elements. // // For example, for functions defined as: -// func a() (int, string) {return 42, "foo"} -// func b(a1 int, a2 string) {} +// +// func a() (int, string) {return 42, "foo"} +// func b(a1 int, a2 string) {} +// // ...the following statement: -// b(a()) +// +// b(a()) +// // ...will be transformed into: -// _tuple := a() -// b(_tuple[0], _tuple[1]) +// +// _tuple := a() +// b(_tuple[0], _tuple[1]) func (fc *funcContext) expandTupleArgs(argExprs []ast.Expr) []ast.Expr { if len(argExprs) != 1 { return argExprs @@ -513,24 +518,24 @@ func isBlank(expr ast.Expr) bool { // // For example, consider a Go type: // -// type SecretInt int -// func (_ SecretInt) String() string { return "" } +// type SecretInt int +// func (_ SecretInt) String() string { return "" } // -// func main() { -// var i SecretInt = 1 -// println(i.String()) -// } +// func main() { +// var i SecretInt = 1 +// println(i.String()) +// } // // For this example the compiler will generate code similar to the snippet below: // -// SecretInt = $pkg.SecretInt = $newType(4, $kindInt, "main.SecretInt", true, "main", true, null); -// SecretInt.prototype.String = function() { -// return ""; -// }; -// main = function() { -// var i = 1; -// console.log(new SecretInt(i).String()); -// }; +// SecretInt = $pkg.SecretInt = $newType(4, $kindInt, "main.SecretInt", true, "main", true, null); +// SecretInt.prototype.String = function() { +// return ""; +// }; +// main = function() { +// var i = 1; +// console.log(new SecretInt(i).String()); +// }; // // Note that the generated code assigns a primitive "number" value into i, and // only boxes it into an object when it's necessary to access its methods. @@ -699,13 +704,12 @@ func encodeIdent(name string) string { // // For example: // -// "my_name" -> ".my_name" -// "my name" -> `["my name"]` +// "my_name" -> ".my_name" +// "my name" -> `["my name"]` // // For more information about JavaScript property accessors and identifiers, see // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors and // https://developer.mozilla.org/en-US/docs/Glossary/Identifier. -// func formatJSStructTagVal(jsTag string) string { for i, r := range jsTag { ok := unicode.IsLetter(r) || (i != 0 && unicode.IsNumber(r)) || r == '$' || r == '_' diff --git a/js/js.go b/js/js.go index a1ded9eeb..e4b523275 100644 --- a/js/js.go +++ b/js/js.go @@ -2,25 +2,25 @@ // // Use MakeWrapper to expose methods to JavaScript. Use MakeFullWrapper to expose methods AND fields to JavaScript. When passing values directly, the following type conversions are performed: // -// | Go type | JavaScript type | Conversions back to interface{} | -// | --------------------- | --------------------- | ------------------------------- | -// | bool | Boolean | bool | -// | integers and floats | Number | float64 | -// | string | String | string | -// | []int8 | Int8Array | []int8 | -// | []int16 | Int16Array | []int16 | -// | []int32, []int | Int32Array | []int | -// | []uint8 | Uint8Array | []uint8 | -// | []uint16 | Uint16Array | []uint16 | -// | []uint32, []uint | Uint32Array | []uint | -// | []float32 | Float32Array | []float32 | -// | []float64 | Float64Array | []float64 | -// | all other slices | Array | []interface{} | -// | arrays | see slice type | see slice type | -// | functions | Function | func(...interface{}) *js.Object | -// | time.Time | Date | time.Time | -// | - | instanceof Node | *js.Object | -// | maps, structs | instanceof Object | map[string]interface{} | +// | Go type | JavaScript type | Conversions back to interface{} | +// | --------------------- | --------------------- | ------------------------------- | +// | bool | Boolean | bool | +// | integers and floats | Number | float64 | +// | string | String | string | +// | []int8 | Int8Array | []int8 | +// | []int16 | Int16Array | []int16 | +// | []int32, []int | Int32Array | []int | +// | []uint8 | Uint8Array | []uint8 | +// | []uint16 | Uint16Array | []uint16 | +// | []uint32, []uint | Uint32Array | []uint | +// | []float32 | Float32Array | []float32 | +// | []float64 | Float64Array | []float64 | +// | all other slices | Array | []interface{} | +// | arrays | see slice type | see slice type | +// | functions | Function | func(...interface{}) *js.Object | +// | time.Time | Date | time.Time | +// | - | instanceof Node | *js.Object | +// | maps, structs | instanceof Object | map[string]interface{} | // // Additionally, for a struct containing a *js.Object field, only the content of the field will be passed to JavaScript and vice versa. package js diff --git a/nosync/once.go b/nosync/once.go index f4cb69540..2af58f873 100644 --- a/nosync/once.go +++ b/nosync/once.go @@ -8,7 +8,9 @@ type Once struct { // Do calls the function f if and only if Do is being called for the // first time for this instance of Once. In other words, given -// var once Once +// +// var once Once +// // if once.Do(f) is called multiple times, only the first call will invoke f, // even if f has a different value in each invocation. A new instance of // Once is required for each function to execute. @@ -16,13 +18,13 @@ type Once struct { // Do is intended for initialization that must be run exactly once. Since f // is niladic, it may be necessary to use a function literal to capture the // arguments to a function to be invoked by Do: -// config.once.Do(func() { config.init(filename) }) +// +// config.once.Do(func() { config.init(filename) }) // // If f causes Do to be called, it will panic. // // If f panics, Do considers it to have returned; future calls of Do return // without calling f. -// func (o *Once) Do(f func()) { if o.done { return diff --git a/nosync/pool.go b/nosync/pool.go index 3d448e0fc..ae792b642 100644 --- a/nosync/pool.go +++ b/nosync/pool.go @@ -28,7 +28,6 @@ package nosync // not a suitable use for a Pool, since the overhead does not amortize well in // that scenario. It is more efficient to have such objects implement their own // free list. -// type Pool struct { store []interface{} New func() interface{} diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 656dd55a3..0ed56a7fb 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -11,7 +11,7 @@ // // To run manually with summary, verbose output, and full stack traces of of known failures: // -// go run run.go -summary -v -show_known_fails +// go run run.go -summary -v -show_known_fails // // TODO(bradfitz): docs of some sort, once we figure out how we're changing // headers of files @@ -45,7 +45,6 @@ import ( // GOPHERJS: Known test fails for GopherJS compiler. // // TODO: Reduce these to zero or as close as possible. -// var knownFails = map[string]failReason{ "fixedbugs/bug114.go": {desc: "fixedbugs/bug114.go:15:27: B32 (untyped int constant 4294967295) overflows int"}, "fixedbugs/bug242.go": {desc: "bad map check 13 false false Error: fail"}, diff --git a/tests/runtime_test.go b/tests/runtime_test.go index 1d5b97d02..12f0b34c3 100644 --- a/tests/runtime_test.go +++ b/tests/runtime_test.go @@ -23,14 +23,14 @@ func Test_parseCallFrame(t *testing.T) { want: "foo https://gopherjs.github.io/playground/playground.js 102 11836", }, { - name: "Chrome 96, anonymous eval", + name: "Chrome 96, anonymous eval", input: " at eval ()", - want: "eval 0 0", + want: "eval 0 0", }, { - name: "Chrome 96, anonymous Array.forEach", + name: "Chrome 96, anonymous Array.forEach", input: " at Array.forEach ()", - want: "Array.forEach 0 0", + want: "Array.forEach 0 0", }, { name: "Chrome 96, file location only", From c542edf32d92a2797473be5edd57341b00c5c4f3 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 09:20:14 +0200 Subject: [PATCH 13/66] Use golangci-lint to run govet --- .golangci.toml | 1 + circle.yml | 8 -------- tests/goroutine_test.go | 2 +- tests/misc_test.go | 2 +- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.golangci.toml b/.golangci.toml index 003b56f8b..535ea6b50 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -8,6 +8,7 @@ format = "colored-line-number" disable-all = true enable = [ "gofumpt", + "govet", ] [issues] diff --git a/circle.yml b/circle.yml index 0e4f01dcb..95a1877a2 100644 --- a/circle.yml +++ b/circle.yml @@ -72,14 +72,6 @@ jobs: executor: gopherjs steps: - setup_and_install_gopherjs - - run: - name: Check go vet - command: | - set -e - # Go package in root directory. - go vet . - # All subdirectories except "doc", "tests", "node*". - for d in */; do echo ./$d...; done | grep -v ./doc | grep -v ./tests | grep -v ./node | xargs go vet - run: name: Check natives build tags command: diff -u <(echo -n) <(go list ./compiler/natives/src/...) # All those packages should have // +build js. diff --git a/tests/goroutine_test.go b/tests/goroutine_test.go index 011e3e8af..a1d387263 100644 --- a/tests/goroutine_test.go +++ b/tests/goroutine_test.go @@ -73,7 +73,7 @@ func testPanic2(t *testing.T) { time.Sleep(0) checkI(t, 4) panic(7) - checkI(t, -3) + checkI(t, -3) //nolint:govet // Unreachable code is intentional for panic test } func TestPanicAdvanced(t *testing.T) { diff --git a/tests/misc_test.go b/tests/misc_test.go index 0c5f4ec8f..49cf82120 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -536,7 +536,7 @@ func TestTrivialSwitch(t *testing.T) { } return } - t.Fail() + t.Fail() //nolint:govet // unreachable code intentional for test } func TestTupleFnReturnImplicitCast(t *testing.T) { From c74f4efb41224aaa8969856412ed7a63c75534e3 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 12:03:10 +0200 Subject: [PATCH 14/66] Use rest arguments where possible > lebab --replace . --transform arg-rest --- compiler/prelude/prelude.js | 14 +++++++------- compiler/prelude/types.js | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/prelude/prelude.js b/compiler/prelude/prelude.js index 130396849..2dc22b6e5 100644 --- a/compiler/prelude/prelude.js +++ b/compiler/prelude/prelude.js @@ -61,14 +61,14 @@ var $flushConsole = function () { }; var $throwRuntimeError; /* set by package "runtime" */ var $throwNilPointerError = function () { $throwRuntimeError("invalid memory address or nil pointer dereference"); }; var $call = function (fn, rcvr, args) { return fn.apply(rcvr, args); }; -var $makeFunc = function (fn) { return function () { return $externalize(fn(this, new ($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments, []))), $emptyInterface); }; }; +var $makeFunc = function (fn) { return function(...args) { return $externalize(fn(this, new ($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(args, []))), $emptyInterface); }; }; var $unused = function (v) { }; var $print = console.log; // Under Node we can emulate print() more closely by avoiding a newline. if (($global.process !== undefined) && $global.require) { try { var util = $global.require('util'); - $print = function () { $global.process.stderr.write(util.format.apply(this, arguments)); }; + $print = function(...args) { $global.process.stderr.write(util.format.apply(this, args)); }; } catch (e) { // Failed to require util module, keep using console.log(). } @@ -119,13 +119,13 @@ var $methodVal = function (recv, name) { var $methodExpr = function (typ, name) { var method = typ.prototype[name]; if (method.$expr === undefined) { - method.$expr = function () { + method.$expr = function(...args) { $stackDepthOffset--; try { if (typ.wrapped) { - arguments[0] = new typ(arguments[0]); + args[0] = new typ(args[0]); } - return Function.call.apply(method, arguments); + return Function.call.apply(method, args); } finally { $stackDepthOffset++; } @@ -138,10 +138,10 @@ var $ifaceMethodExprs = {}; var $ifaceMethodExpr = function (name) { var expr = $ifaceMethodExprs["$" + name]; if (expr === undefined) { - expr = $ifaceMethodExprs["$" + name] = function () { + expr = $ifaceMethodExprs["$" + name] = function(...args) { $stackDepthOffset--; try { - return Function.call.apply(arguments[0][name], arguments); + return Function.call.apply(args[0][name], args); } finally { $stackDepthOffset++; } diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 37d91827a..1f1e66ec8 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -280,7 +280,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) $addMethodSynthesizer(function () { var synthesizeMethod = function (target, m, f) { if (target.prototype[m.prop] !== undefined) { return; } - target.prototype[m.prop] = function () { + target.prototype[m.prop] = function(...args) { var v = this.$val[f.prop]; if (f.typ === $jsObjectPtr) { v = new $jsObjectPtr(v); @@ -288,7 +288,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) if (v.$val === undefined) { v = new f.typ(v); } - return v[m.prop].apply(v, arguments); + return v[m.prop].apply(v, args); }; }; fields.forEach(function (f) { @@ -696,14 +696,14 @@ var $structType = function (pkgPath, fields) { if (fields.length === 0) { string = "struct {}"; } - typ = $newType(0, $kindStruct, string, false, "", false, function () { + typ = $newType(0, $kindStruct, string, false, "", false, function(...args) { this.$val = this; for (var i = 0; i < fields.length; i++) { var f = fields[i]; if (f.name == '_') { continue; } - var arg = arguments[i]; + var arg = args[i]; this[f.prop] = arg !== undefined ? arg : f.typ.zero(); } }); From 0d005b75e8ce7b767ce87623bc9e1fe817b4b592 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 12:04:39 +0200 Subject: [PATCH 15/66] Use spread operator instead of apply() where applicable > lebab --replace . --transform arg-spread --- compiler/prelude/goroutines.js | 2 +- compiler/prelude/types.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/prelude/goroutines.js b/compiler/prelude/goroutines.js index bff08976b..7dbc844d3 100644 --- a/compiler/prelude/goroutines.js +++ b/compiler/prelude/goroutines.js @@ -131,7 +131,7 @@ var $go = function (fun, args) { var $goroutine = function () { try { $curGoroutine = $goroutine; - var r = fun.apply(undefined, args); + var r = fun(...args); if (r && r.$blk !== undefined) { fun = function () { return r.$blk(); }; args = []; diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 1f1e66ec8..9785d65a5 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -288,7 +288,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) if (v.$val === undefined) { v = new f.typ(v); } - return v[m.prop].apply(v, args); + return v[m.prop](...args); }; }; fields.forEach(function (f) { From f2f85f522de23a5f3633006628fc39d942da28c3 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 12:05:36 +0200 Subject: [PATCH 16/66] Use arrow functions where possible in prelude > lebab --replace . --transform arrow --- compiler/prelude/goroutines.js | 48 +++++----- compiler/prelude/jsmapping.js | 26 +++--- compiler/prelude/numeric.js | 28 +++--- compiler/prelude/prelude.js | 88 +++++++++---------- compiler/prelude/types.js | 156 ++++++++++++++++----------------- 5 files changed, 173 insertions(+), 173 deletions(-) diff --git a/compiler/prelude/goroutines.js b/compiler/prelude/goroutines.js index 7dbc844d3..0be950e82 100644 --- a/compiler/prelude/goroutines.js +++ b/compiler/prelude/goroutines.js @@ -1,5 +1,5 @@ var $stackDepthOffset = 0; -var $getStackDepth = function () { +var $getStackDepth = () => { var err = new Error(); if (err.stack === undefined) { return undefined; @@ -8,7 +8,7 @@ var $getStackDepth = function () { }; var $panicStackDepth = null, $panicValue; -var $callDeferred = function (deferred, jsErr, fromPanic) { +var $callDeferred = (deferred, jsErr, fromPanic) => { if (!fromPanic && deferred !== null && $curGoroutine.deferStack.indexOf(deferred) == -1) { throw jsErr; } @@ -109,31 +109,31 @@ var $callDeferred = function (deferred, jsErr, fromPanic) { } }; -var $panic = function (value) { +var $panic = value => { $curGoroutine.panicStack.push(value); $callDeferred(null, null, true); }; -var $recover = function () { +var $recover = () => { if ($panicStackDepth === null || ($panicStackDepth !== undefined && $panicStackDepth !== $getStackDepth() - 2)) { return $ifaceNil; } $panicStackDepth = null; return $panicValue; }; -var $throw = function (err) { throw err; }; +var $throw = err => { throw err; }; var $noGoroutine = { asleep: false, exit: false, deferStack: [], panicStack: [] }; var $curGoroutine = $noGoroutine, $totalGoroutines = 0, $awakeGoroutines = 0, $checkForDeadlock = true, $exportedFunctions = 0; var $mainFinished = false; -var $go = function (fun, args) { +var $go = (fun, args) => { $totalGoroutines++; $awakeGoroutines++; - var $goroutine = function () { + var $goroutine = () => { try { $curGoroutine = $goroutine; var r = fun(...args); if (r && r.$blk !== undefined) { - fun = function () { return r.$blk(); }; + fun = () => { return r.$blk(); }; args = []; return; } @@ -167,7 +167,7 @@ var $go = function (fun, args) { }; var $scheduled = []; -var $runScheduled = function () { +var $runScheduled = () => { // For nested setTimeout calls browsers enforce 4ms minimum delay. We minimize // the effect of this penalty by queueing the timer preemptively before we run // the goroutines, and later cancelling it if it turns out unneeded. See: @@ -194,7 +194,7 @@ var $runScheduled = function () { } }; -var $schedule = function (goroutine) { +var $schedule = goroutine => { if (goroutine.asleep) { goroutine.asleep = false; $awakeGoroutines++; @@ -205,29 +205,29 @@ var $schedule = function (goroutine) { } }; -var $setTimeout = function (f, t) { +var $setTimeout = (f, t) => { $awakeGoroutines++; - return setTimeout(function () { + return setTimeout(() => { $awakeGoroutines--; f(); }, t); }; -var $block = function () { +var $block = () => { if ($curGoroutine === $noGoroutine) { $throwRuntimeError("cannot block in JavaScript callback, fix by wrapping code in goroutine"); } $curGoroutine.asleep = true; }; -var $restore = function (context, params) { +var $restore = (context, params) => { if (context !== undefined && context.$blk !== undefined) { return context; } return params; } -var $send = function (chan, value) { +var $send = (chan, value) => { if (chan.$closed) { $throwRuntimeError("send on closed channel"); } @@ -243,7 +243,7 @@ var $send = function (chan, value) { var thisGoroutine = $curGoroutine; var closedDuringSend; - chan.$sendQueue.push(function (closed) { + chan.$sendQueue.push(closed => { closedDuringSend = closed; $schedule(thisGoroutine); return value; @@ -257,7 +257,7 @@ var $send = function (chan, value) { } }; }; -var $recv = function (chan) { +var $recv = chan => { var queuedSend = chan.$sendQueue.shift(); if (queuedSend !== undefined) { chan.$buffer.push(queuedSend(false)); @@ -272,7 +272,7 @@ var $recv = function (chan) { var thisGoroutine = $curGoroutine; var f = { $blk: function () { return this.value; } }; - var queueEntry = function (v) { + var queueEntry = v => { f.value = v; $schedule(thisGoroutine); }; @@ -280,7 +280,7 @@ var $recv = function (chan) { $block(); return f; }; -var $close = function (chan) { +var $close = chan => { if (chan.$closed) { $throwRuntimeError("close of closed channel"); } @@ -300,7 +300,7 @@ var $close = function (chan) { queuedRecv([chan.$elem.zero(), false]); } }; -var $select = function (comms) { +var $select = comms => { var ready = []; var selection = -1; for (var i = 0; i < comms.length; i++) { @@ -345,7 +345,7 @@ var $select = function (comms) { var entries = []; var thisGoroutine = $curGoroutine; var f = { $blk: function () { return this.selection; } }; - var removeFromQueues = function () { + var removeFromQueues = () => { for (var i = 0; i < entries.length; i++) { var entry = entries[i]; var queue = entry[0]; @@ -356,11 +356,11 @@ var $select = function (comms) { } }; for (var i = 0; i < comms.length; i++) { - (function (i) { + (i => { var comm = comms[i]; switch (comm.length) { case 1: /* recv */ - var queueEntry = function (value) { + var queueEntry = value => { f.selection = [i, value]; removeFromQueues(); $schedule(thisGoroutine); @@ -369,7 +369,7 @@ var $select = function (comms) { comm[0].$recvQueue.push(queueEntry); break; case 2: /* send */ - var queueEntry = function () { + var queueEntry = () => { if (comm[0].$closed) { $throwRuntimeError("send on closed channel"); } diff --git a/compiler/prelude/jsmapping.js b/compiler/prelude/jsmapping.js index 1aa3debe2..395d86236 100644 --- a/compiler/prelude/jsmapping.js +++ b/compiler/prelude/jsmapping.js @@ -1,6 +1,6 @@ var $jsObjectPtr, $jsErrorPtr; -var $needsExternalization = function (t) { +var $needsExternalization = t => { switch (t.kind) { case $kindBool: case $kindInt: @@ -20,7 +20,7 @@ var $needsExternalization = function (t) { } }; -var $externalize = function (v, t, makeWrapper) { +var $externalize = (v, t, makeWrapper) => { if (t === $jsObjectPtr) { return v; } @@ -43,7 +43,7 @@ var $externalize = function (v, t, makeWrapper) { return $flatten64(v); case $kindArray: if ($needsExternalization(t.elem)) { - return $mapArray(v, function (e) { return $externalize(e, t.elem, makeWrapper); }); + return $mapArray(v, e => { return $externalize(e, t.elem, makeWrapper); }); } return v; case $kindFunc: @@ -77,7 +77,7 @@ var $externalize = function (v, t, makeWrapper) { return null; } if ($needsExternalization(t.elem)) { - return $mapArray($sliceToNativeArray(v), function (e) { return $externalize(e, t.elem, makeWrapper); }); + return $mapArray($sliceToNativeArray(v), e => { return $externalize(e, t.elem, makeWrapper); }); } return $sliceToNativeArray(v); case $kindString: @@ -105,7 +105,7 @@ var $externalize = function (v, t, makeWrapper) { } var noJsObject = {}; - var searchJsObject = function (v, t) { + var searchJsObject = (v, t) => { if (t === $jsObjectPtr) { return v; } @@ -149,7 +149,7 @@ var $externalize = function (v, t, makeWrapper) { $throwRuntimeError("cannot externalize " + t.string); }; -var $externalizeFunction = function (v, t, passThis, makeWrapper) { +var $externalizeFunction = (v, t, passThis, makeWrapper) => { if (v === $throwNilPointerError) { return null; } @@ -185,7 +185,7 @@ var $externalizeFunction = function (v, t, passThis, makeWrapper) { return v.$externalizeWrapper; }; -var $internalize = function (v, t, recv, seen, makeWrapper) { +var $internalize = (v, t, recv, seen, makeWrapper) => { if (t === $jsObjectPtr) { return v; } @@ -239,7 +239,7 @@ var $internalize = function (v, t, recv, seen, makeWrapper) { if (v.length !== t.len) { $throwRuntimeError("got array with wrong size from JavaScript native"); } - return $mapArray(v, function (e) { return $internalize(e, t.elem, makeWrapper); }); + return $mapArray(v, e => { return $internalize(e, t.elem, makeWrapper); }); case $kindFunc: return function () { var args = []; @@ -303,7 +303,7 @@ var $internalize = function (v, t, recv, seen, makeWrapper) { return new $jsObjectPtr(v); } return new timePkg.Time($internalize(v, timePkg.Time, makeWrapper)); - case (function () { }).constructor: // is usually Function, but in Chrome extensions it is something else + case ((() => { })).constructor: // is usually Function, but in Chrome extensions it is something else var funcType = $funcType([$sliceType($emptyInterface)], [$jsObjectPtr], true); return new funcType($internalize(v, funcType, makeWrapper)); case Number: @@ -331,7 +331,7 @@ var $internalize = function (v, t, recv, seen, makeWrapper) { return $internalize(v, t.elem, makeWrapper); } case $kindSlice: - return new t($mapArray(v, function (e) { return $internalize(e, t.elem, makeWrapper); })); + return new t($mapArray(v, e => { return $internalize(e, t.elem, makeWrapper); })); case $kindString: v = String(v); if ($isASCII(v)) { @@ -354,7 +354,7 @@ var $internalize = function (v, t, recv, seen, makeWrapper) { return s; case $kindStruct: var noJsObject = {}; - var searchJsObject = function (t) { + var searchJsObject = t => { if (t === $jsObjectPtr) { return v; } @@ -388,7 +388,7 @@ var $internalize = function (v, t, recv, seen, makeWrapper) { $throwRuntimeError("cannot internalize " + t.string); }; -var $copyIfRequired = function (v, typ) { +var $copyIfRequired = (v, typ) => { // interface values if (v && v.constructor && v.constructor.copy) { return new v.constructor($clone(v.$val, v.constructor)) @@ -403,7 +403,7 @@ var $copyIfRequired = function (v, typ) { } /* $isASCII reports whether string s contains only ASCII characters. */ -var $isASCII = function (s) { +var $isASCII = s => { for (var i = 0; i < s.length; i++) { if (s.charCodeAt(i) >= 128) { return false; diff --git a/compiler/prelude/numeric.js b/compiler/prelude/numeric.js index 38d33ef65..5cfd7644d 100644 --- a/compiler/prelude/numeric.js +++ b/compiler/prelude/numeric.js @@ -1,7 +1,7 @@ var $min = Math.min; -var $mod = function (x, y) { return x % y; }; +var $mod = (x, y) => { return x % y; }; var $parseInt = parseInt; -var $parseFloat = function (f) { +var $parseFloat = f => { if (f !== undefined && f !== null && f.constructor === Number) { return f; } @@ -9,20 +9,20 @@ var $parseFloat = function (f) { }; var $froundBuf = new Float32Array(1); -var $fround = Math.fround || function (f) { +var $fround = Math.fround || (f => { $froundBuf[0] = f; return $froundBuf[0]; -}; +}); -var $imul = Math.imul || function (a, b) { +var $imul = Math.imul || ((a, b) => { var ah = (a >>> 16) & 0xffff; var al = a & 0xffff; var bh = (b >>> 16) & 0xffff; var bl = b & 0xffff; return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) >> 0); -}; +}); -var $floatKey = function (f) { +var $floatKey = f => { if (f !== f) { $idCounter++; return "NaN$" + $idCounter; @@ -30,11 +30,11 @@ var $floatKey = function (f) { return String(f); }; -var $flatten64 = function (x) { +var $flatten64 = x => { return x.$high * 4294967296 + x.$low; }; -var $shiftLeft64 = function (x, y) { +var $shiftLeft64 = (x, y) => { if (y === 0) { return x; } @@ -47,7 +47,7 @@ var $shiftLeft64 = function (x, y) { return new x.constructor(0, 0); }; -var $shiftRightInt64 = function (x, y) { +var $shiftRightInt64 = (x, y) => { if (y === 0) { return x; } @@ -63,7 +63,7 @@ var $shiftRightInt64 = function (x, y) { return new x.constructor(0, 0); }; -var $shiftRightUint64 = function (x, y) { +var $shiftRightUint64 = (x, y) => { if (y === 0) { return x; } @@ -76,7 +76,7 @@ var $shiftRightUint64 = function (x, y) { return new x.constructor(0, 0); }; -var $mul64 = function (x, y) { +var $mul64 = (x, y) => { var x48 = x.$high >>> 16; var x32 = x.$high & 0xFFFF; var x16 = x.$low >>> 16; @@ -116,7 +116,7 @@ var $mul64 = function (x, y) { return r; }; -var $div64 = function (x, y, returnRemainder) { +var $div64 = (x, y, returnRemainder) => { if (y.$high === 0 && y.$low === 0) { $throwRuntimeError("integer divide by zero"); } @@ -179,7 +179,7 @@ var $div64 = function (x, y, returnRemainder) { return new x.constructor(high * s, low * s); }; -var $divComplex = function (n, d) { +var $divComplex = (n, d) => { var ninf = n.$real === Infinity || n.$real === -Infinity || n.$imag === Infinity || n.$imag === -Infinity; var dinf = d.$real === Infinity || d.$real === -Infinity || d.$imag === Infinity || d.$imag === -Infinity; var nnan = !ninf && (n.$real !== n.$real || n.$imag !== n.$imag); diff --git a/compiler/prelude/prelude.js b/compiler/prelude/prelude.js index 2dc22b6e5..3daa5ce98 100644 --- a/compiler/prelude/prelude.js +++ b/compiler/prelude/prelude.js @@ -56,13 +56,13 @@ if (!$global.fs) { var $linknames = {} // Collection of functions referenced by a go:linkname directive. var $packages = {}, $idCounter = 0; -var $keys = function (m) { return m ? Object.keys(m) : []; }; -var $flushConsole = function () { }; +var $keys = m => { return m ? Object.keys(m) : []; }; +var $flushConsole = () => { }; var $throwRuntimeError; /* set by package "runtime" */ -var $throwNilPointerError = function () { $throwRuntimeError("invalid memory address or nil pointer dereference"); }; -var $call = function (fn, rcvr, args) { return fn.apply(rcvr, args); }; -var $makeFunc = function (fn) { return function(...args) { return $externalize(fn(this, new ($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(args, []))), $emptyInterface); }; }; -var $unused = function (v) { }; +var $throwNilPointerError = () => { $throwRuntimeError("invalid memory address or nil pointer dereference"); }; +var $call = (fn, rcvr, args) => { return fn.apply(rcvr, args); }; +var $makeFunc = fn => { return function(...args) { return $externalize(fn(this, new ($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(args, []))), $emptyInterface); }; }; +var $unused = v => { }; var $print = console.log; // Under Node we can emulate print() more closely by avoiding a newline. if (($global.process !== undefined) && $global.require) { @@ -75,7 +75,7 @@ if (($global.process !== undefined) && $global.require) { } var $println = console.log -var $initAllLinknames = function () { +var $initAllLinknames = () => { var names = $keys($packages); for (var i = 0; i < names.length; i++) { var f = $packages[names[i]]["$initLinknames"]; @@ -85,7 +85,7 @@ var $initAllLinknames = function () { } } -var $mapArray = function (array, f) { +var $mapArray = (array, f) => { var newArray = new array.constructor(array.length); for (var i = 0; i < array.length; i++) { newArray[i] = f(array[i]); @@ -94,16 +94,16 @@ var $mapArray = function (array, f) { }; // $mapIndex returns the value of the given key in m, or undefined if m is nil/undefined or not a map -var $mapIndex = function (m, key) { +var $mapIndex = (m, key) => { return typeof m.get === "function" ? m.get(key) : undefined; }; // $mapDelete deletes the key and associated value from m. If m is nil/undefined or not a map, $mapDelete is a no-op -var $mapDelete = function (m, key) { +var $mapDelete = (m, key) => { typeof m.delete === "function" && m.delete(key) }; // Returns a method bound to the receiver instance, safe to invoke as a // standalone function. Bound function is cached for later reuse. -var $methodVal = function (recv, name) { +var $methodVal = (recv, name) => { var vals = recv.$methodVals || {}; recv.$methodVals = vals; /* noop for primitives */ var f = vals[name]; @@ -116,10 +116,10 @@ var $methodVal = function (recv, name) { return f; }; -var $methodExpr = function (typ, name) { +var $methodExpr = (typ, name) => { var method = typ.prototype[name]; if (method.$expr === undefined) { - method.$expr = function(...args) { + method.$expr = (...args) => { $stackDepthOffset--; try { if (typ.wrapped) { @@ -135,10 +135,10 @@ var $methodExpr = function (typ, name) { }; var $ifaceMethodExprs = {}; -var $ifaceMethodExpr = function (name) { +var $ifaceMethodExpr = name => { var expr = $ifaceMethodExprs["$" + name]; if (expr === undefined) { - expr = $ifaceMethodExprs["$" + name] = function(...args) { + expr = $ifaceMethodExprs["$" + name] = (...args) => { $stackDepthOffset--; try { return Function.call.apply(args[0][name], args); @@ -150,7 +150,7 @@ var $ifaceMethodExpr = function (name) { return expr; }; -var $subslice = function (slice, low, high, max) { +var $subslice = (slice, low, high, max) => { if (high === undefined) { high = slice.$length; } @@ -170,7 +170,7 @@ var $subslice = function (slice, low, high, max) { return s; }; -var $substring = function (str, low, high) { +var $substring = (str, low, high) => { if (low < 0 || high < low || high > str.length) { $throwRuntimeError("slice bounds out of range"); } @@ -178,7 +178,7 @@ var $substring = function (str, low, high) { }; // Convert Go slice to an equivalent JS array type. -var $sliceToNativeArray = function (slice) { +var $sliceToNativeArray = slice => { if (slice.$array.constructor !== Array) { return slice.$array.subarray(slice.$offset, slice.$offset + slice.$length); } @@ -189,7 +189,7 @@ var $sliceToNativeArray = function (slice) { // // Note that an array pointer can be represented by an "unwrapped" native array // type, and it will be wrapped back into its Go type when necessary. -var $sliceToGoArray = function (slice, arrayPtrType) { +var $sliceToGoArray = (slice, arrayPtrType) => { var arrayType = arrayPtrType.elem; if (arrayType !== undefined && slice.$length < arrayType.len) { $throwRuntimeError("cannot convert slice with length " + slice.$length + " to pointer to array with length " + arrayType.len); @@ -216,7 +216,7 @@ var $sliceToGoArray = function (slice, arrayPtrType) { }; // Convert between compatible slice types (e.g. native and names). -var $convertSliceType = function (slice, desiredType) { +var $convertSliceType = (slice, desiredType) => { if (slice == slice.constructor.nil) { return desiredType.nil; // Preserve nil value. } @@ -224,7 +224,7 @@ var $convertSliceType = function (slice, desiredType) { return $subslice(new desiredType(slice.$array), slice.$offset, slice.$offset + slice.$length); } -var $decodeRune = function (str, pos) { +var $decodeRune = (str, pos) => { var c0 = str.charCodeAt(pos); if (c0 < 0x80) { @@ -280,7 +280,7 @@ var $decodeRune = function (str, pos) { return [0xFFFD, 1]; }; -var $encodeRune = function (r) { +var $encodeRune = r => { if (r < 0 || r > 0x10FFFF || (0xD800 <= r && r <= 0xDFFF)) { r = 0xFFFD; } @@ -296,7 +296,7 @@ var $encodeRune = function (r) { return String.fromCharCode(0xF0 | r >> 18, 0x80 | (r >> 12 & 0x3F), 0x80 | (r >> 6 & 0x3F), 0x80 | (r & 0x3F)); }; -var $stringToBytes = function (str) { +var $stringToBytes = str => { var array = new Uint8Array(str.length); for (var i = 0; i < str.length; i++) { array[i] = str.charCodeAt(i); @@ -304,7 +304,7 @@ var $stringToBytes = function (str) { return array; }; -var $bytesToString = function (slice) { +var $bytesToString = slice => { if (slice.$length === 0) { return ""; } @@ -315,7 +315,7 @@ var $bytesToString = function (slice) { return str; }; -var $stringToRunes = function (str) { +var $stringToRunes = str => { var array = new Int32Array(str.length); var rune, j = 0; for (var i = 0; i < str.length; i += rune[1], j++) { @@ -325,7 +325,7 @@ var $stringToRunes = function (str) { return array.subarray(0, j); }; -var $runesToString = function (slice) { +var $runesToString = slice => { if (slice.$length === 0) { return ""; } @@ -336,7 +336,7 @@ var $runesToString = function (slice) { return str; }; -var $copyString = function (dst, src) { +var $copyString = (dst, src) => { var n = Math.min(src.length, dst.$length); for (var i = 0; i < n; i++) { dst.$array[dst.$offset + i] = src.charCodeAt(i); @@ -344,13 +344,13 @@ var $copyString = function (dst, src) { return n; }; -var $copySlice = function (dst, src) { +var $copySlice = (dst, src) => { var n = Math.min(src.$length, dst.$length); $copyArray(dst.$array, src.$array, dst.$offset, src.$offset, n, dst.constructor.elem); return n; }; -var $copyArray = function (dst, src, dstOffset, srcOffset, n, elem) { +var $copyArray = (dst, src, dstOffset, srcOffset, n, elem) => { if (n === 0 || (dst === src && dstOffset === srcOffset)) { return; } @@ -386,13 +386,13 @@ var $copyArray = function (dst, src, dstOffset, srcOffset, n, elem) { } }; -var $clone = function (src, type) { +var $clone = (src, type) => { var clone = type.zero(); type.copy(clone, src); return clone; }; -var $pointerOfStructConversion = function (obj, type) { +var $pointerOfStructConversion = (obj, type) => { if (obj.$proxies === undefined) { obj.$proxies = {}; obj.$proxies[obj.constructor.string] = obj; @@ -401,7 +401,7 @@ var $pointerOfStructConversion = function (obj, type) { if (proxy === undefined) { var properties = {}; for (var i = 0; i < type.elem.fields.length; i++) { - (function (fieldProp) { + (fieldProp => { properties[fieldProp] = { get: function () { return obj[fieldProp]; }, set: function (value) { obj[fieldProp] = value; } @@ -420,7 +420,7 @@ var $append = function (slice) { return $internalAppend(slice, arguments, 1, arguments.length - 1); }; -var $appendSlice = function (slice, toAppend) { +var $appendSlice = (slice, toAppend) => { if (toAppend.constructor === String) { var bytes = $stringToBytes(toAppend); return $internalAppend(slice, bytes, 0, bytes.length); @@ -428,7 +428,7 @@ var $appendSlice = function (slice, toAppend) { return $internalAppend(slice, toAppend.$array, toAppend.$offset, toAppend.$length); }; -var $internalAppend = function (slice, array, offset, length) { +var $internalAppend = (slice, array, offset, length) => { if (length === 0) { return slice; } @@ -464,7 +464,7 @@ var $internalAppend = function (slice, array, offset, length) { return newSlice; }; -var $equal = function (a, b, type) { +var $equal = (a, b, type) => { if (type === $jsObjectPtr) { return a === b; } @@ -500,7 +500,7 @@ var $equal = function (a, b, type) { } }; -var $interfaceIsEqual = function (a, b) { +var $interfaceIsEqual = (a, b) => { if (a === $ifaceNil || b === $ifaceNil) { return a === b; } @@ -516,9 +516,9 @@ var $interfaceIsEqual = function (a, b) { return $equal(a.$val, b.$val, a.constructor); }; -var $unsafeMethodToFunction = function (typ, name, isPtr) { +var $unsafeMethodToFunction = (typ, name, isPtr) => { if (isPtr) { - return function (r, ...args) { + return (r, ...args) => { var ptrType = $ptrType(typ); if (r.constructor != ptrType) { switch (typ.kind) { @@ -533,9 +533,9 @@ var $unsafeMethodToFunction = function (typ, name, isPtr) { } } return r[name](...args); - } + }; } else { - return function (r, ...args) { + return (r, ...args) => { var ptrType = $ptrType(typ); if (r.constructor != ptrType) { switch (typ.kind) { @@ -554,18 +554,18 @@ var $unsafeMethodToFunction = function (typ, name, isPtr) { } } return r[name](...args); - } + }; } }; -var $id = function (x) { +var $id = x => { return x; }; -var $instanceOf = function (x, y) { +var $instanceOf = (x, y) => { return x instanceof y; }; -var $typeOf = function (x) { +var $typeOf = x => { return typeof (x); }; diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 9785d65a5..c7c738f2f 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -26,19 +26,19 @@ var $kindStruct = 25; var $kindUnsafePointer = 26; var $methodSynthesizers = []; -var $addMethodSynthesizer = function (f) { +var $addMethodSynthesizer = f => { if ($methodSynthesizers === null) { f(); return; } $methodSynthesizers.push(f); }; -var $synthesizeMethods = function () { - $methodSynthesizers.forEach(function (f) { f(); }); +var $synthesizeMethods = () => { + $methodSynthesizers.forEach(f => { f(); }); $methodSynthesizers = null; }; -var $ifaceKeyFor = function (x) { +var $ifaceKeyFor = x => { if (x === $ifaceNil) { return 'nil'; } @@ -46,11 +46,11 @@ var $ifaceKeyFor = function (x) { return c.string + '$' + c.keyFor(x.$val); }; -var $identity = function (x) { return x; }; +var $identity = x => { return x; }; var $typeIDCounter = 0; -var $idKey = function (x) { +var $idKey = x => { if (x.$id === undefined) { $idCounter++; x.$id = $idCounter; @@ -60,15 +60,15 @@ var $idKey = function (x) { // Creates constructor functions for array pointer types. Returns a new function // instace each time to make sure each type is independent of the other. -var $arrayPtrCtor = function () { +var $arrayPtrCtor = () => { return function (array) { - this.$get = function () { return array; }; + this.$get = () => { return array; }; this.$set = function (v) { typ.copy(this, v); }; this.$val = array; - } + }; } -var $newType = function (size, kind, string, named, pkg, exported, constructor) { +var $newType = (size, kind, string, named, pkg, exported, constructor) => { var typ; switch (kind) { case $kindBool: @@ -90,14 +90,14 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) case $kindString: typ = function (v) { this.$val = v; }; typ.wrapped = true; - typ.keyFor = function (x) { return "$" + x; }; + typ.keyFor = x => { return "$" + x; }; break; case $kindFloat32: case $kindFloat64: typ = function (v) { this.$val = v; }; typ.wrapped = true; - typ.keyFor = function (x) { return $floatKey(x); }; + typ.keyFor = x => { return $floatKey(x); }; break; case $kindInt64: @@ -106,7 +106,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) this.$low = low >>> 0; this.$val = this; }; - typ.keyFor = function (x) { return x.$high + "$" + x.$low; }; + typ.keyFor = x => { return x.$high + "$" + x.$low; }; break; case $kindUint64: @@ -115,7 +115,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) this.$low = low >>> 0; this.$val = this; }; - typ.keyFor = function (x) { return x.$high + "$" + x.$low; }; + typ.keyFor = x => { return x.$high + "$" + x.$low; }; break; case $kindComplex64: @@ -124,7 +124,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) this.$imag = $fround(imag); this.$val = this; }; - typ.keyFor = function (x) { return x.$real + "$" + x.$imag; }; + typ.keyFor = x => { return x.$real + "$" + x.$imag; }; break; case $kindComplex128: @@ -133,23 +133,23 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) this.$imag = imag; this.$val = this; }; - typ.keyFor = function (x) { return x.$real + "$" + x.$imag; }; + typ.keyFor = x => { return x.$real + "$" + x.$imag; }; break; case $kindArray: typ = function (v) { this.$val = v; }; typ.wrapped = true; typ.ptr = $newType(4, $kindPtr, "*" + string, false, "", false, $arrayPtrCtor()); - typ.init = function (elem, len) { + typ.init = (elem, len) => { typ.elem = elem; typ.len = len; typ.comparable = elem.comparable; - typ.keyFor = function (x) { - return Array.prototype.join.call($mapArray(x, function (e) { + typ.keyFor = x => { + return Array.prototype.join.call($mapArray(x, e => { return String(elem.keyFor(e)).replace(/\\/g, "\\\\").replace(/\$/g, "\\$"); }), "$"); }; - typ.copy = function (dst, src) { + typ.copy = (dst, src) => { $copyArray(dst, src, 0, 0, src.length, elem); }; typ.ptr.init(typ); @@ -161,7 +161,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) typ = function (v) { this.$val = v; }; typ.wrapped = true; typ.keyFor = $idKey; - typ.init = function (elem, sendOnly, recvOnly) { + typ.init = (elem, sendOnly, recvOnly) => { typ.elem = elem; typ.sendOnly = sendOnly; typ.recvOnly = recvOnly; @@ -171,7 +171,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) case $kindFunc: typ = function (v) { this.$val = v; }; typ.wrapped = true; - typ.init = function (params, results, variadic) { + typ.init = (params, results, variadic) => { typ.params = params; typ.results = results; typ.variadic = variadic; @@ -182,9 +182,9 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) case $kindInterface: typ = { implementedBy: {}, missingMethodFor: {} }; typ.keyFor = $ifaceKeyFor; - typ.init = function (methods) { + typ.init = methods => { typ.methods = methods; - methods.forEach(function (m) { + methods.forEach(m => { $ifaceNil[m.prop] = $throwNilPointerError; }); }; @@ -193,7 +193,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) case $kindMap: typ = function (v) { this.$val = v; }; typ.wrapped = true; - typ.init = function (key, elem) { + typ.init = (key, elem) => { typ.key = key; typ.elem = elem; typ.comparable = false; @@ -208,7 +208,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) this.$val = this; }; typ.keyFor = $idKey; - typ.init = function (elem) { + typ.init = elem => { typ.elem = elem; typ.wrapped = (elem.kind === $kindArray); typ.nil = new typ($throwNilPointerError, $throwNilPointerError); @@ -226,7 +226,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) this.$capacity = array.length; this.$val = this; }; - typ.init = function (elem) { + typ.init = elem => { typ.elem = elem; typ.comparable = false; typ.nativeArray = $nativeArray(elem.kind); @@ -241,21 +241,21 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) typ.ptr.elem = typ; typ.ptr.prototype.$get = function () { return this; }; typ.ptr.prototype.$set = function (v) { typ.copy(this, v); }; - typ.init = function (pkgPath, fields) { + typ.init = (pkgPath, fields) => { typ.pkgPath = pkgPath; typ.fields = fields; - fields.forEach(function (f) { + fields.forEach(f => { if (!f.typ.comparable) { typ.comparable = false; } }); - typ.keyFor = function (x) { + typ.keyFor = x => { var val = x.$val; - return $mapArray(fields, function (f) { + return $mapArray(fields, f => { return String(f.typ.keyFor(val[f.prop])).replace(/\\/g, "\\\\").replace(/\$/g, "\\$"); }).join("$"); }; - typ.copy = function (dst, src) { + typ.copy = (dst, src) => { for (var i = 0; i < fields.length; i++) { var f = fields[i]; switch (f.typ.kind) { @@ -271,14 +271,14 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) }; /* nil value */ var properties = {}; - fields.forEach(function (f) { + fields.forEach(f => { properties[f.prop] = { get: $throwNilPointerError, set: $throwNilPointerError }; }); typ.ptr.nil = Object.create(constructor.prototype, properties); typ.ptr.nil.$val = typ.ptr.nil; /* methods for embedded fields */ - $addMethodSynthesizer(function () { - var synthesizeMethod = function (target, m, f) { + $addMethodSynthesizer(() => { + var synthesizeMethod = (target, m, f) => { if (target.prototype[m.prop] !== undefined) { return; } target.prototype[m.prop] = function(...args) { var v = this.$val[f.prop]; @@ -291,13 +291,13 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) return v[m.prop](...args); }; }; - fields.forEach(function (f) { + fields.forEach(f => { if (f.embedded) { - $methodSet(f.typ).forEach(function (m) { + $methodSet(f.typ).forEach(m => { synthesizeMethod(typ, m, f); synthesizeMethod(typ.ptr, m, f); }); - $methodSet($ptrType(f.typ)).forEach(function (m) { + $methodSet($ptrType(f.typ)).forEach(m => { synthesizeMethod(typ.ptr, m, f); }); } @@ -313,7 +313,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) switch (kind) { case $kindBool: case $kindMap: - typ.zero = function () { return false; }; + typ.zero = () => { return false; }; break; case $kindInt: @@ -328,11 +328,11 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) case $kindUnsafePointer: case $kindFloat32: case $kindFloat64: - typ.zero = function () { return 0; }; + typ.zero = () => { return 0; }; break; case $kindString: - typ.zero = function () { return ""; }; + typ.zero = () => { return ""; }; break; case $kindInt64: @@ -340,28 +340,28 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) case $kindComplex64: case $kindComplex128: var zero = new typ(0, 0); - typ.zero = function () { return zero; }; + typ.zero = () => { return zero; }; break; case $kindPtr: case $kindSlice: - typ.zero = function () { return typ.nil; }; + typ.zero = () => { return typ.nil; }; break; case $kindChan: - typ.zero = function () { return $chanNil; }; + typ.zero = () => { return $chanNil; }; break; case $kindFunc: - typ.zero = function () { return $throwNilPointerError; }; + typ.zero = () => { return $throwNilPointerError; }; break; case $kindInterface: - typ.zero = function () { return $ifaceNil; }; + typ.zero = () => { return $ifaceNil; }; break; case $kindArray: - typ.zero = function () { + typ.zero = () => { var arrayClass = $nativeArray(typ.elem.kind); if (arrayClass !== Array) { return new arrayClass(typ.len); @@ -375,7 +375,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) break; case $kindStruct: - typ.zero = function () { return new typ.ptr(); }; + typ.zero = () => { return new typ.ptr(); }; break; default: @@ -396,7 +396,7 @@ var $newType = function (size, kind, string, named, pkg, exported, constructor) return typ; }; -var $methodSet = function (typ) { +var $methodSet = typ => { if (typ.methodSetCache !== null) { return typ.methodSetCache; } @@ -416,7 +416,7 @@ var $methodSet = function (typ) { var next = []; var mset = []; - current.forEach(function (e) { + current.forEach(e => { if (seen[e.typ.string]) { return; } @@ -431,7 +431,7 @@ var $methodSet = function (typ) { switch (e.typ.kind) { case $kindStruct: - e.typ.fields.forEach(function (f) { + e.typ.fields.forEach(f => { if (f.embedded) { var fTyp = f.typ; var fIsPtr = (fTyp.kind === $kindPtr); @@ -446,7 +446,7 @@ var $methodSet = function (typ) { } }); - mset.forEach(function (m) { + mset.forEach(m => { if (base[m.name] === undefined) { base[m.name] = m; } @@ -456,7 +456,7 @@ var $methodSet = function (typ) { } typ.methodSetCache = []; - Object.keys(base).sort().forEach(function (name) { + Object.keys(base).sort().forEach(name => { typ.methodSetCache.push(base[name]); }); return typ.methodSetCache; @@ -481,7 +481,7 @@ var $Complex128 = $newType(16, $kindComplex128, "complex128", true, "", false, n var $String = $newType(8, $kindString, "string", true, "", false, null); var $UnsafePointer = $newType(4, $kindUnsafePointer, "unsafe.Pointer", true, "unsafe", false, null); -var $nativeArray = function (elemKind) { +var $nativeArray = elemKind => { switch (elemKind) { case $kindInt: return Int32Array; @@ -509,7 +509,7 @@ var $nativeArray = function (elemKind) { return Array; } }; -var $toNativeArray = function (elemKind, array) { +var $toNativeArray = (elemKind, array) => { var nativeArray = $nativeArray(elemKind); if (nativeArray === Array) { return array; @@ -517,7 +517,7 @@ var $toNativeArray = function (elemKind, array) { return new nativeArray(array); }; var $arrayTypes = {}; -var $arrayType = function (elem, len) { +var $arrayType = (elem, len) => { var typeKey = elem.id + "$" + len; var typ = $arrayTypes[typeKey]; if (typ === undefined) { @@ -528,7 +528,7 @@ var $arrayType = function (elem, len) { return typ; }; -var $chanType = function (elem, sendOnly, recvOnly) { +var $chanType = (elem, sendOnly, recvOnly) => { var string = (recvOnly ? "<-" : "") + "chan" + (sendOnly ? "<- " : " "); if (!sendOnly && !recvOnly && (elem.string[0] == "<")) { string += "(" + elem.string + ")"; @@ -559,11 +559,11 @@ var $chanNil = new $Chan(null, 0); $chanNil.$sendQueue = $chanNil.$recvQueue = { length: 0, push: function () { }, shift: function () { return undefined; }, indexOf: function () { return -1; } }; var $funcTypes = {}; -var $funcType = function (params, results, variadic) { - var typeKey = $mapArray(params, function (p) { return p.id; }).join(",") + "$" + $mapArray(results, function (r) { return r.id; }).join(",") + "$" + variadic; +var $funcType = (params, results, variadic) => { + var typeKey = $mapArray(params, p => { return p.id; }).join(",") + "$" + $mapArray(results, r => { return r.id; }).join(",") + "$" + variadic; var typ = $funcTypes[typeKey]; if (typ === undefined) { - var paramTypes = $mapArray(params, function (p) { return p.string; }); + var paramTypes = $mapArray(params, p => { return p.string; }); if (variadic) { paramTypes[paramTypes.length - 1] = "..." + paramTypes[paramTypes.length - 1].substr(2); } @@ -571,7 +571,7 @@ var $funcType = function (params, results, variadic) { if (results.length === 1) { string += " " + results[0].string; } else if (results.length > 1) { - string += " (" + $mapArray(results, function (r) { return r.string; }).join(", ") + ")"; + string += " (" + $mapArray(results, r => { return r.string; }).join(", ") + ")"; } typ = $newType(4, $kindFunc, string, false, "", false, null); $funcTypes[typeKey] = typ; @@ -581,13 +581,13 @@ var $funcType = function (params, results, variadic) { }; var $interfaceTypes = {}; -var $interfaceType = function (methods) { - var typeKey = $mapArray(methods, function (m) { return m.pkg + "," + m.name + "," + m.typ.id; }).join("$"); +var $interfaceType = methods => { + var typeKey = $mapArray(methods, m => { return m.pkg + "," + m.name + "," + m.typ.id; }).join("$"); var typ = $interfaceTypes[typeKey]; if (typ === undefined) { var string = "interface {}"; if (methods.length !== 0) { - string = "interface { " + $mapArray(methods, function (m) { + string = "interface { " + $mapArray(methods, m => { return (m.pkg !== "" ? m.pkg + "." : "") + m.name + m.typ.string.substr(4); }).join("; ") + " }"; } @@ -603,7 +603,7 @@ var $error = $newType(8, $kindInterface, "error", true, "", false, null); $error.init([{ prop: "Error", name: "Error", pkg: "", typ: $funcType([], [$String], false) }]); var $mapTypes = {}; -var $mapType = function (key, elem) { +var $mapType = (key, elem) => { var typeKey = key.id + "$" + elem.id; var typ = $mapTypes[typeKey]; if (typ === undefined) { @@ -613,7 +613,7 @@ var $mapType = function (key, elem) { } return typ; }; -var $makeMap = function (keyForFunc, entries) { +var $makeMap = (keyForFunc, entries) => { var m = new Map(); for (var i = 0; i < entries.length; i++) { var e = entries[i]; @@ -622,7 +622,7 @@ var $makeMap = function (keyForFunc, entries) { return m; }; -var $ptrType = function (elem) { +var $ptrType = elem => { var typ = elem.ptr; if (typ === undefined) { typ = $newType(4, $kindPtr, "*" + elem.string, false, "", elem.exported, null); @@ -632,28 +632,28 @@ var $ptrType = function (elem) { return typ; }; -var $newDataPointer = function (data, constructor) { +var $newDataPointer = (data, constructor) => { if (constructor.elem.kind === $kindStruct) { return data; } - return new constructor(function () { return data; }, function (v) { data = v; }); + return new constructor(() => { return data; }, v => { data = v; }); }; -var $indexPtr = function (array, index, constructor) { +var $indexPtr = (array, index, constructor) => { if (array.buffer) { // Pointers to the same underlying ArrayBuffer share cache. var cache = array.buffer.$ptr = array.buffer.$ptr || {}; // Pointers of different primitive types are non-comparable and stored in different caches. var typeCache = cache[array.name] = cache[array.name] || {}; var cacheIdx = array.BYTES_PER_ELEMENT * index + array.byteOffset; - return typeCache[cacheIdx] || (typeCache[cacheIdx] = new constructor(function () { return array[index]; }, function (v) { array[index] = v; })); + return typeCache[cacheIdx] || (typeCache[cacheIdx] = new constructor(() => { return array[index]; }, v => { array[index] = v; })); } else { array.$ptr = array.$ptr || {}; - return array.$ptr[index] || (array.$ptr[index] = new constructor(function () { return array[index]; }, function (v) { array[index] = v; })); + return array.$ptr[index] || (array.$ptr[index] = new constructor(() => { return array[index]; }, v => { array[index] = v; })); } }; -var $sliceType = function (elem) { +var $sliceType = elem => { var typ = elem.slice; if (typ === undefined) { typ = $newType(12, $kindSlice, "[]" + elem.string, false, "", false, null); @@ -662,7 +662,7 @@ var $sliceType = function (elem) { } return typ; }; -var $makeSlice = function (typ, length, capacity) { +var $makeSlice = (typ, length, capacity) => { capacity = capacity || length; if (length < 0 || length > 2147483647) { $throwRuntimeError("makeslice: len out of range"); @@ -682,11 +682,11 @@ var $makeSlice = function (typ, length, capacity) { }; var $structTypes = {}; -var $structType = function (pkgPath, fields) { - var typeKey = $mapArray(fields, function (f) { return f.name + "," + f.typ.id + "," + f.tag; }).join("$"); +var $structType = (pkgPath, fields) => { + var typeKey = $mapArray(fields, f => { return f.name + "," + f.typ.id + "," + f.tag; }).join("$"); var typ = $structTypes[typeKey]; if (typ === undefined) { - var string = "struct { " + $mapArray(fields, function (f) { + var string = "struct { " + $mapArray(fields, f => { var str = f.typ.string + (f.tag !== "" ? (" \"" + f.tag.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"") : ""); if (f.embedded) { return str; @@ -713,7 +713,7 @@ var $structType = function (pkgPath, fields) { return typ; }; -var $assertType = function (value, type, returnTuple) { +var $assertType = (value, type, returnTuple) => { var isInterface = (type.kind === $kindInterface), ok, missingMethod = ""; if (value === $ifaceNil) { ok = false; From 579d64d86c68c2d072044cd626df4b7dd234e5c4 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 12:09:21 +0200 Subject: [PATCH 17/66] Shorter method declaration > lebab --replace . --transform obj-method --- compiler/prelude/goroutines.js | 6 +++--- compiler/prelude/prelude.js | 4 ++-- compiler/prelude/types.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/prelude/goroutines.js b/compiler/prelude/goroutines.js index 0be950e82..65dabe36e 100644 --- a/compiler/prelude/goroutines.js +++ b/compiler/prelude/goroutines.js @@ -250,7 +250,7 @@ var $send = (chan, value) => { }); $block(); return { - $blk: function () { + $blk() { if (closedDuringSend) { $throwRuntimeError("send on closed channel"); } @@ -271,7 +271,7 @@ var $recv = chan => { } var thisGoroutine = $curGoroutine; - var f = { $blk: function () { return this.value; } }; + var f = { $blk() { return this.value; } }; var queueEntry = v => { f.value = v; $schedule(thisGoroutine); @@ -344,7 +344,7 @@ var $select = comms => { var entries = []; var thisGoroutine = $curGoroutine; - var f = { $blk: function () { return this.selection; } }; + var f = { $blk() { return this.selection; } }; var removeFromQueues = () => { for (var i = 0; i < entries.length; i++) { var entry = entries[i]; diff --git a/compiler/prelude/prelude.js b/compiler/prelude/prelude.js index 3daa5ce98..d35de6b01 100644 --- a/compiler/prelude/prelude.js +++ b/compiler/prelude/prelude.js @@ -403,8 +403,8 @@ var $pointerOfStructConversion = (obj, type) => { for (var i = 0; i < type.elem.fields.length; i++) { (fieldProp => { properties[fieldProp] = { - get: function () { return obj[fieldProp]; }, - set: function (value) { obj[fieldProp] = value; } + get() { return obj[fieldProp]; }, + set(value) { obj[fieldProp] = value; } }; })(type.elem.fields[i].prop); } diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index c7c738f2f..7f58168b5 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -556,7 +556,7 @@ var $Chan = function (elem, capacity) { this.$closed = false; }; var $chanNil = new $Chan(null, 0); -$chanNil.$sendQueue = $chanNil.$recvQueue = { length: 0, push: function () { }, shift: function () { return undefined; }, indexOf: function () { return -1; } }; +$chanNil.$sendQueue = $chanNil.$recvQueue = { length: 0, push() { }, shift() { return undefined; }, indexOf() { return -1; } }; var $funcTypes = {}; var $funcType = (params, results, variadic) => { From bd84b0d0bb0488f1d148302d1a579ebb6baa5ced Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 12:10:02 +0200 Subject: [PATCH 18/66] Use object shorthand where possible > lebab --replace . --transform obj-shorthand --- compiler/prelude/jsmapping.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/prelude/jsmapping.js b/compiler/prelude/jsmapping.js index 395d86236..e93e91176 100644 --- a/compiler/prelude/jsmapping.js +++ b/compiler/prelude/jsmapping.js @@ -323,7 +323,7 @@ var $internalize = (v, t, recv, seen, makeWrapper) => { var keys = $keys(v); for (var i = 0; i < keys.length; i++) { var k = $internalize(keys[i], t.key, recv, seen, makeWrapper); - m.set(t.key.keyFor(k), { k: k, v: $internalize(v[keys[i]], t.elem, recv, seen, makeWrapper) }); + m.set(t.key.keyFor(k), { k, v: $internalize(v[keys[i]], t.elem, recv, seen, makeWrapper) }); } return m; case $kindPtr: From 4bc5c0f787b695507e60458c15f6c8fb9f48a308 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 12:30:07 +0200 Subject: [PATCH 19/66] Use default parameter value where possible Informed by: > lebab --replace . --transform default-param But I only selected the one case that seemed relevant. --- compiler/prelude/types.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 7f58168b5..61475454e 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -662,8 +662,7 @@ var $sliceType = elem => { } return typ; }; -var $makeSlice = (typ, length, capacity) => { - capacity = capacity || length; +var $makeSlice = (typ, length, capacity = length) => { if (length < 0 || length > 2147483647) { $throwRuntimeError("makeslice: len out of range"); } From c47ddde90f810ea9024c6c4ed25887acb77d6438 Mon Sep 17 00:00:00 2001 From: Renaud Lehoux Date: Wed, 21 Dec 2016 16:26:06 +0100 Subject: [PATCH 20/66] build: Allow overriden functions calls Before this commit, every overriden standard library function was renamed '_'. Now, if there is the following directive in the function comment: "//gopherjs:keep_overridden", they are prefixed with "_gopherjs_overridden_" so that they can be called from the overriding function. This is typically useful to use the standard library implementation as a fallback. --- build/build.go | 52 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/build/build.go b/build/build.go index 63c89c50f..e0f19b197 100644 --- a/build/build.go +++ b/build/build.go @@ -117,6 +117,23 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag return pkg, nil } +// Test if we find the '//gopherjs:keep_overridden' comment +func findKeepOverridenComment(doc *ast.CommentGroup) bool { + if doc == nil { + return false + } + for _, comment := range doc.List { + text := comment.Text + if i := strings.Index(text, " "); i >= 0 { + text = text[:i] + } + if text == "//gopherjs:keep_overridden" { + return true + } + } + return false +} + // parseAndAugment parses and returns all .go files of given pkg. // Standard Go library packages are augmented with files in compiler/natives folder. // If isTest is true and pkg.ImportPath has no _test suffix, package is built for running internal tests. @@ -125,12 +142,19 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag // The native packages are augmented by the contents of natives.FS in the following way. // The file names do not matter except the usual `_test` suffix. The files for // native overrides get added to the package (even if they have the same name -// as an existing file from the standard library). For all identifiers that exist -// in the original AND the overrides, the original identifier in the AST gets -// replaced by `_`. New identifiers that don't exist in original package get added. +// as an existing file from the standard library). For function identifiers that exist +// in the original AND the overrides AND that include the following directive in their comment: +// //gopherjs:keep_overridden, the original identifier in the AST gets prefixed by +// `_gopherjs_overridden_`. For other identifiers that exist in the original AND the overrides, +// the original identifier gets replaced by `_`. New identifiers that don't exist in original +// package get added. func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) { var files []*ast.File - replacedDeclNames := make(map[string]bool) + + type overrideInfo struct { + keepOverriden bool + } + replacedDeclNames := make(map[string]overrideInfo) pruneOriginalFuncs := make(map[string]bool) isXTest := strings.HasSuffix(pkg.ImportPath, "_test") @@ -170,18 +194,18 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) - replacedDeclNames[k] = true + replacedDeclNames[k] = overrideInfo{keepOverriden: findKeepOverridenComment(d.Doc)} pruneOriginalFuncs[k] = astutil.PruneOriginal(d) case *ast.GenDecl: switch d.Tok { case token.TYPE: for _, spec := range d.Specs { - replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = true + replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = overrideInfo{} } case token.VAR, token.CONST: for _, spec := range d.Specs { for _, name := range spec.(*ast.ValueSpec).Names { - replacedDeclNames[name.Name] = true + replacedDeclNames[name.Name] = overrideInfo{} } } } @@ -234,20 +258,26 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) - if replacedDeclNames[k] { - d.Name = ast.NewIdent("_") + if info, ok := replacedDeclNames[k]; ok { if pruneOriginalFuncs[k] { // Prune function bodies, since it may contain code invalid for // GopherJS and pin unwanted imports. d.Body = nil } + if info.keepOverriden { + // Allow overridden function calls + // The standard library implementation of foo() becomes _gopherjs_overridden_foo() + d.Name.Name = "_gopherjs_overridden_" + d.Name.Name + } else { + d.Name = ast.NewIdent("_") + } } case *ast.GenDecl: switch d.Tok { case token.TYPE: for _, spec := range d.Specs { s := spec.(*ast.TypeSpec) - if replacedDeclNames[s.Name.Name] { + if _, ok := replacedDeclNames[s.Name.Name]; ok { s.Name = ast.NewIdent("_") s.Type = &ast.StructType{Struct: s.Pos(), Fields: &ast.FieldList{}} s.TypeParams = nil @@ -257,7 +287,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke for _, spec := range d.Specs { s := spec.(*ast.ValueSpec) for i, name := range s.Names { - if replacedDeclNames[name.Name] { + if _, ok := replacedDeclNames[name.Name]; ok { s.Names[i] = ast.NewIdent("_") } } From 9d91789ac258df65b55463d138839083a5f106d0 Mon Sep 17 00:00:00 2001 From: Renaud Lehoux Date: Sat, 21 Apr 2018 01:20:37 +0200 Subject: [PATCH 21/66] regexp test: added an example use for gopherjs:keep_overridden Before this commit, TestOnePassCutoff was skipped because a 'Maximum call stack size exceeded' message may occur. Now we use gopherjs:keep_overridden to try the original test, and skip only if the original test failed. --- compiler/natives/src/regexp/regexp_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/compiler/natives/src/regexp/regexp_test.go b/compiler/natives/src/regexp/regexp_test.go index d5fa9211d..f7fc25031 100644 --- a/compiler/natives/src/regexp/regexp_test.go +++ b/compiler/natives/src/regexp/regexp_test.go @@ -7,6 +7,14 @@ import ( "testing" ) +//gopherjs:keep_overridden func TestOnePassCutoff(t *testing.T) { - t.Skip() // "Maximum call stack size exceeded" on V8 + defer func() { + if r := recover(); r != nil { + t.Log(r) + t.Skip("'Maximum call stack size exceeded' may happen on V8, skipping") + } + }() + + _gopherjs_overridden_TestOnePassCutoff(t) } From 06d6901afb8ad12971eec6c73808a2baf0d7bce3 Mon Sep 17 00:00:00 2001 From: Renaud Lehoux Date: Sat, 21 Apr 2018 20:06:26 +0200 Subject: [PATCH 22/66] fixed typo --- build/build.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build/build.go b/build/build.go index e0f19b197..2ad426856 100644 --- a/build/build.go +++ b/build/build.go @@ -118,7 +118,7 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag } // Test if we find the '//gopherjs:keep_overridden' comment -func findKeepOverridenComment(doc *ast.CommentGroup) bool { +func findKeepOverriddenComment(doc *ast.CommentGroup) bool { if doc == nil { return false } @@ -152,7 +152,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke var files []*ast.File type overrideInfo struct { - keepOverriden bool + keepOverridden bool } replacedDeclNames := make(map[string]overrideInfo) pruneOriginalFuncs := make(map[string]bool) @@ -194,7 +194,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) - replacedDeclNames[k] = overrideInfo{keepOverriden: findKeepOverridenComment(d.Doc)} + replacedDeclNames[k] = overrideInfo{keepOverridden: findKeepOverriddenComment(d.Doc)} pruneOriginalFuncs[k] = astutil.PruneOriginal(d) case *ast.GenDecl: switch d.Tok { @@ -264,7 +264,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke // GopherJS and pin unwanted imports. d.Body = nil } - if info.keepOverriden { + if info.keepOverridden { // Allow overridden function calls // The standard library implementation of foo() becomes _gopherjs_overridden_foo() d.Name.Name = "_gopherjs_overridden_" + d.Name.Name From fe0510dc1eaf4979c9190c02622622973a7cd44e Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 14:24:25 +0200 Subject: [PATCH 23/66] Rename `gopherjs:keep_overridden` to `gopherjs:keep-original` ... as per https://github.com/gopherjs/gopherjs/pull/798#issuecomment-955238429 --- build/build.go | 6 +++--- compiler/natives/src/regexp/regexp_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/build.go b/build/build.go index 2ad426856..cbefb4bc6 100644 --- a/build/build.go +++ b/build/build.go @@ -117,7 +117,7 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag return pkg, nil } -// Test if we find the '//gopherjs:keep_overridden' comment +// Test if we find the '//gopherjs:keep-original' comment func findKeepOverriddenComment(doc *ast.CommentGroup) bool { if doc == nil { return false @@ -127,7 +127,7 @@ func findKeepOverriddenComment(doc *ast.CommentGroup) bool { if i := strings.Index(text, " "); i >= 0 { text = text[:i] } - if text == "//gopherjs:keep_overridden" { + if text == "//gopherjs:keep-original" { return true } } @@ -144,7 +144,7 @@ func findKeepOverriddenComment(doc *ast.CommentGroup) bool { // native overrides get added to the package (even if they have the same name // as an existing file from the standard library). For function identifiers that exist // in the original AND the overrides AND that include the following directive in their comment: -// //gopherjs:keep_overridden, the original identifier in the AST gets prefixed by +// //gopherjs:keep-original, the original identifier in the AST gets prefixed by // `_gopherjs_overridden_`. For other identifiers that exist in the original AND the overrides, // the original identifier gets replaced by `_`. New identifiers that don't exist in original // package get added. diff --git a/compiler/natives/src/regexp/regexp_test.go b/compiler/natives/src/regexp/regexp_test.go index f7fc25031..b60f72564 100644 --- a/compiler/natives/src/regexp/regexp_test.go +++ b/compiler/natives/src/regexp/regexp_test.go @@ -7,7 +7,7 @@ import ( "testing" ) -//gopherjs:keep_overridden +//gopherjs:keep-original func TestOnePassCutoff(t *testing.T) { defer func() { if r := recover(); r != nil { From a8add9b0975474f3faf61fec274caec094930a27 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 14:37:01 +0200 Subject: [PATCH 24/66] Move findKeepOverriddenComments into astutil package for consistency with other such helpers --- build/build.go | 19 +------------------ compiler/astutil/astutil.go | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/build/build.go b/build/build.go index cbefb4bc6..d0295cabf 100644 --- a/build/build.go +++ b/build/build.go @@ -117,23 +117,6 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag return pkg, nil } -// Test if we find the '//gopherjs:keep-original' comment -func findKeepOverriddenComment(doc *ast.CommentGroup) bool { - if doc == nil { - return false - } - for _, comment := range doc.List { - text := comment.Text - if i := strings.Index(text, " "); i >= 0 { - text = text[:i] - } - if text == "//gopherjs:keep-original" { - return true - } - } - return false -} - // parseAndAugment parses and returns all .go files of given pkg. // Standard Go library packages are augmented with files in compiler/natives folder. // If isTest is true and pkg.ImportPath has no _test suffix, package is built for running internal tests. @@ -194,7 +177,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) - replacedDeclNames[k] = overrideInfo{keepOverridden: findKeepOverriddenComment(d.Doc)} + replacedDeclNames[k] = overrideInfo{keepOverridden: astutil.KeepOriginal(d)} pruneOriginalFuncs[k] = astutil.PruneOriginal(d) case *ast.GenDecl: switch d.Tok { diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index b9c4b54c8..2f68f4c9c 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -93,6 +93,26 @@ func PruneOriginal(d *ast.FuncDecl) bool { return false } +// KeepOriginal returns true if gopherjs:keep-original directive is present +// before a function decl. +// +// `//gopherjs:keep-original` is a GopherJS-specific directive, which can be +// applied to functions in native overlays and will instruct the augmentation +// logic to expose the original function such that it can be called. For a +// function in the original called `foo`, it will be accessible by the name +// `_gopherjs_overridden_foo`. +func KeepOriginal(d *ast.FuncDecl) bool { + if d.Doc == nil { + return false + } + for _, c := range d.Doc.List { + if strings.HasPrefix(c.Text, "//gopherjs:keep-original") { + return true + } + } + return false +} + // FindLoopStmt tries to find the loop statement among the AST nodes in the // |stack| that corresponds to the break/continue statement represented by // branch. From f9b66ec251851cc54763dd4f440c6d9e5a18b4ba Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 14:40:11 +0200 Subject: [PATCH 25/66] Merge keepOriginal and pruneOriginal into a single struct --- build/build.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/build/build.go b/build/build.go index d0295cabf..86fc15014 100644 --- a/build/build.go +++ b/build/build.go @@ -135,10 +135,10 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke var files []*ast.File type overrideInfo struct { - keepOverridden bool + keepOriginal bool + pruneOriginal bool } replacedDeclNames := make(map[string]overrideInfo) - pruneOriginalFuncs := make(map[string]bool) isXTest := strings.HasSuffix(pkg.ImportPath, "_test") importPath := pkg.ImportPath @@ -177,8 +177,10 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) - replacedDeclNames[k] = overrideInfo{keepOverridden: astutil.KeepOriginal(d)} - pruneOriginalFuncs[k] = astutil.PruneOriginal(d) + replacedDeclNames[k] = overrideInfo{ + keepOriginal: astutil.KeepOriginal(d), + pruneOriginal: astutil.PruneOriginal(d), + } case *ast.GenDecl: switch d.Tok { case token.TYPE: @@ -242,12 +244,12 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke case *ast.FuncDecl: k := astutil.FuncKey(d) if info, ok := replacedDeclNames[k]; ok { - if pruneOriginalFuncs[k] { + if info.pruneOriginal { // Prune function bodies, since it may contain code invalid for // GopherJS and pin unwanted imports. d.Body = nil } - if info.keepOverridden { + if info.keepOriginal { // Allow overridden function calls // The standard library implementation of foo() becomes _gopherjs_overridden_foo() d.Name.Name = "_gopherjs_overridden_" + d.Name.Name From 634857c4a2b4819720b18cf3f48cc05ee34295a9 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Tue, 27 Jun 2023 09:37:43 +0200 Subject: [PATCH 26/66] Rename _gopherjs_overridden_ to _gopherjs_original_ for greater consistency --- build/build.go | 6 +++--- compiler/astutil/astutil.go | 2 +- compiler/natives/src/regexp/regexp_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/build.go b/build/build.go index 86fc15014..070d05df1 100644 --- a/build/build.go +++ b/build/build.go @@ -128,7 +128,7 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag // as an existing file from the standard library). For function identifiers that exist // in the original AND the overrides AND that include the following directive in their comment: // //gopherjs:keep-original, the original identifier in the AST gets prefixed by -// `_gopherjs_overridden_`. For other identifiers that exist in the original AND the overrides, +// `_gopherjs_original_`. For other identifiers that exist in the original AND the overrides, // the original identifier gets replaced by `_`. New identifiers that don't exist in original // package get added. func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) { @@ -251,8 +251,8 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke } if info.keepOriginal { // Allow overridden function calls - // The standard library implementation of foo() becomes _gopherjs_overridden_foo() - d.Name.Name = "_gopherjs_overridden_" + d.Name.Name + // The standard library implementation of foo() becomes _gopherjs_original_foo() + d.Name.Name = "_gopherjs_original_" + d.Name.Name } else { d.Name = ast.NewIdent("_") } diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index 2f68f4c9c..30febe1cb 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -100,7 +100,7 @@ func PruneOriginal(d *ast.FuncDecl) bool { // applied to functions in native overlays and will instruct the augmentation // logic to expose the original function such that it can be called. For a // function in the original called `foo`, it will be accessible by the name -// `_gopherjs_overridden_foo`. +// `_gopherjs_original_foo`. func KeepOriginal(d *ast.FuncDecl) bool { if d.Doc == nil { return false diff --git a/compiler/natives/src/regexp/regexp_test.go b/compiler/natives/src/regexp/regexp_test.go index b60f72564..3a2d58d32 100644 --- a/compiler/natives/src/regexp/regexp_test.go +++ b/compiler/natives/src/regexp/regexp_test.go @@ -16,5 +16,5 @@ func TestOnePassCutoff(t *testing.T) { } }() - _gopherjs_overridden_TestOnePassCutoff(t) + _gopherjs_original_TestOnePassCutoff(t) } From 82dcedca759cf8d2984145bb664722abc4e15c71 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Fri, 14 Jul 2023 16:07:38 +0200 Subject: [PATCH 27/66] Update golang.org/x/tools/go/gcexportdata --- go.mod | 6 +++--- go.sum | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index cce3b71d9..d1e88d3d5 100644 --- a/go.mod +++ b/go.mod @@ -14,9 +14,9 @@ require ( github.com/spf13/pflag v1.0.5 github.com/visualfc/goembed v0.3.3 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 - golang.org/x/tools v0.1.10 + golang.org/x/sync v0.3.0 + golang.org/x/sys v0.10.0 + golang.org/x/tools v0.11.0 ) require ( diff --git a/go.sum b/go.sum index 0cf27e64e..80bdd5b30 100644 --- a/go.sum +++ b/go.sum @@ -297,7 +297,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -356,8 +356,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -401,8 +402,9 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -467,8 +469,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From da4ca5e2cf6631d46e27371788216c4bdc4f5fde Mon Sep 17 00:00:00 2001 From: visualfc Date: Thu, 3 Aug 2023 19:48:09 +0800 Subject: [PATCH 28/66] compiler/natives/src/reflect: add func name.setPkgPath --- compiler/natives/src/reflect/reflect.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 7ed8b5f06..3261d70ca 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -266,6 +266,9 @@ func (n name) name() (s string) { return nameMap[n.bytes].name } func (n name) tag() (s string) { return nameMap[n.bytes].tag } func (n name) pkgPath() string { return nameMap[n.bytes].pkgPath } func (n name) isExported() bool { return nameMap[n.bytes].exported } +func (n name) setPkgPath(pkgpath string) { + nameMap[n.bytes].pkgPath = pkgpath +} func newName(n, tag string, exported bool) name { b := new(byte) From f716f9988413de7c56fa0ea687b5461c0f92b17e Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Mon, 28 Aug 2023 16:18:54 +0200 Subject: [PATCH 29/66] natives: duplicate aliasing.go in x/crypto revisted Same as fcd349a8b55efd1bb45c715624591e75dd237138. x/crypto internally moved the AnyOverlap from x/crypto/internal/subtle/ to /x/crypto/internal/alias/. --- .../src/crypto/internal/subtle/aliasing.go | 5 +++-- .../x/crypto/internal/alias/alias.go | 21 +++++++++++++++++++ .../x/crypto/internal/subtle/aliasing.go | 5 +++-- 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 compiler/natives/src/golang.org/x/crypto/internal/alias/alias.go diff --git a/compiler/natives/src/crypto/internal/subtle/aliasing.go b/compiler/natives/src/crypto/internal/subtle/aliasing.go index 104ac82bb..145687d59 100644 --- a/compiler/natives/src/crypto/internal/subtle/aliasing.go +++ b/compiler/natives/src/crypto/internal/subtle/aliasing.go @@ -4,8 +4,9 @@ package subtle // This file duplicated is these two locations: -// - src/crypto/internal/subtle/ -// - src/golang.org/x/crypto/internal/subtle/ +// - src/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/alias/alias.go import "github.com/gopherjs/gopherjs/js" diff --git a/compiler/natives/src/golang.org/x/crypto/internal/alias/alias.go b/compiler/natives/src/golang.org/x/crypto/internal/alias/alias.go new file mode 100644 index 000000000..a3e1e7f79 --- /dev/null +++ b/compiler/natives/src/golang.org/x/crypto/internal/alias/alias.go @@ -0,0 +1,21 @@ +//go:build js +// +build js + +package alias + +// This file duplicated is these two locations: +// - src/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/alias/alias.go + +import "github.com/gopherjs/gopherjs/js" + +// AnyOverlap reports whether x and y share memory at any (not necessarily +// corresponding) index. The memory beyond the slice length is ignored. +func AnyOverlap(x, y []byte) bool { + // GopherJS: We can't rely on pointer arithmetic, so use GopherJS slice internals. + return len(x) > 0 && len(y) > 0 && + js.InternalObject(x).Get("$array") == js.InternalObject(y).Get("$array") && + js.InternalObject(x).Get("$offset").Int() <= js.InternalObject(y).Get("$offset").Int()+len(y)-1 && + js.InternalObject(y).Get("$offset").Int() <= js.InternalObject(x).Get("$offset").Int()+len(x)-1 +} diff --git a/compiler/natives/src/golang.org/x/crypto/internal/subtle/aliasing.go b/compiler/natives/src/golang.org/x/crypto/internal/subtle/aliasing.go index 104ac82bb..145687d59 100644 --- a/compiler/natives/src/golang.org/x/crypto/internal/subtle/aliasing.go +++ b/compiler/natives/src/golang.org/x/crypto/internal/subtle/aliasing.go @@ -4,8 +4,9 @@ package subtle // This file duplicated is these two locations: -// - src/crypto/internal/subtle/ -// - src/golang.org/x/crypto/internal/subtle/ +// - src/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/alias/alias.go import "github.com/gopherjs/gopherjs/js" From 203add55fc33ed967f944f27aaa888b5d39e0f94 Mon Sep 17 00:00:00 2001 From: Naor Zruk Date: Tue, 29 Aug 2023 23:13:09 +0300 Subject: [PATCH 30/66] support internalising structs --- compiler/prelude/jsmapping.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/compiler/prelude/jsmapping.js b/compiler/prelude/jsmapping.js index e93e91176..b22454bc3 100644 --- a/compiler/prelude/jsmapping.js +++ b/compiler/prelude/jsmapping.js @@ -384,6 +384,19 @@ var $internalize = (v, t, recv, seen, makeWrapper) => { if (o !== noJsObject) { return o; } + var n = new t.ptr(); + for (var i = 0; i < t.fields.length; i++) { + var f = t.fields[i]; + + if (!f.exported) { + continue; + } + var jsProp = v[f.name]; + + n[f.prop] = $internalize(jsProp, f.typ, recv, seen, makeWrapper); + } + + return n; } $throwRuntimeError("cannot internalize " + t.string); }; From 90fcda66d48ce4af2598624755c148bc01776787 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Wed, 30 Aug 2023 10:13:32 +0200 Subject: [PATCH 31/66] Bump xcode version used in CI --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 95a1877a2..95b0826cb 100644 --- a/circle.yml +++ b/circle.yml @@ -198,7 +198,7 @@ jobs: darwin_smoke: macos: - xcode: 13.3.0 # Mac OS 12.2.1 + xcode: 13.4.1 # Mac OS 12.6.1, see https://circleci.com/docs/using-macos/ steps: - checkout - setup_environment From dfd0433ed852a9840fe2310887c70c5270b5efc5 Mon Sep 17 00:00:00 2001 From: Naor Zruk Date: Wed, 30 Aug 2023 19:18:13 +0300 Subject: [PATCH 32/66] add tests --- tests/js_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/tests/js_test.go b/tests/js_test.go index 4dd3573f6..b8d9d46d4 100644 --- a/tests/js_test.go +++ b/tests/js_test.go @@ -303,6 +303,95 @@ func TestInternalizeDate(t *testing.T) { } } +func TestInternalizeStruct(t *testing.T) { + type Person struct { + Name string + Age int + } + var a Person + + js.Global.Set("return_person", func(p *Person) *Person { + if p == nil { + t.Fail() // Or t.Error("Received nil pointer") + return nil + } + a = *p + return p + }) + + js.Global.Call("eval", "return_person({Name: 'foo', Age: 952})") + + if a.Name != "foo" || a.Age != 952 { + t.Fail() + } +} + +func TestInternalizeStructUnexportedFields(t *testing.T) { + type Person struct { + Name string + age int + } + var a Person + + js.Global.Set("return_person", func(p *Person) *Person { + a = *p + return p + }) + + js.Global.Call("eval", "return_person({Name: 'foo', age: 952})") + + if a.Name != "foo" || a.age != 0 { + t.Fail() + } +} + +func TestInternalizeStructNested(t *testing.T) { + type FullName struct { + FirstName string + LastName string + } + type Person struct { + Name string + Age int + F FullName + } + var a Person + + js.Global.Set("return_person", func(p *Person) *Person { + a = *p + return p + }) + + js.Global.Call("eval", "return_person({Name: 'foo', Age: 952, F: {FirstName: 'John', LastName: 'Doe'}})") + + if a.Name != "foo" || a.Age != 952 || a.F.FirstName != "John" || a.F.LastName != "Doe" { + t.Fail() + } +} + +func TestInternalizeArrayOfStructs(t *testing.T) { + type Person struct { + Name string + Age int + } + type ArrayOfStructs struct { + People []Person + } + + var a ArrayOfStructs + + js.Global.Set("return_people_array", func(p ArrayOfStructs) ArrayOfStructs { + a = p + return p + }) + + js.Global.Call("eval", `return_people_array({People: [{Name: "Alice", Age: 30}, {Name: "Bob", Age: 40}]})`) + + if len(a.People) != 2 || a.People[0].Name != "Alice" || a.People[1].Age != 40 { + t.Fail() + } +} + func TestEquality(t *testing.T) { if js.Global.Get("Array") != js.Global.Get("Array") || js.Global.Get("Array") == js.Global.Get("String") { t.Fail() From f65fb109acbce7f8ae3ba89f31b4b979635f0936 Mon Sep 17 00:00:00 2001 From: Naor Zruk Date: Wed, 30 Aug 2023 23:54:10 +0300 Subject: [PATCH 33/66] change tests to use google compare package --- tests/js_test.go | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/tests/js_test.go b/tests/js_test.go index b8d9d46d4..7680749dc 100644 --- a/tests/js_test.go +++ b/tests/js_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/gopherjs/gopherjs/js" ) @@ -308,11 +309,12 @@ func TestInternalizeStruct(t *testing.T) { Name string Age int } - var a Person + var a, expected Person + expected = Person{Name: "foo", Age: 952} js.Global.Set("return_person", func(p *Person) *Person { if p == nil { - t.Fail() // Or t.Error("Received nil pointer") + t.Fail() return nil } a = *p @@ -320,19 +322,17 @@ func TestInternalizeStruct(t *testing.T) { }) js.Global.Call("eval", "return_person({Name: 'foo', Age: 952})") - - if a.Name != "foo" || a.Age != 952 { - t.Fail() + if diff := cmp.Diff(a, expected); diff != "" { + t.Errorf("Mismatch (-want +got):\n%s", diff) } } - func TestInternalizeStructUnexportedFields(t *testing.T) { type Person struct { Name string age int } - var a Person - + var a, expected Person + expected = Person{Name: "foo", age: 0} js.Global.Set("return_person", func(p *Person) *Person { a = *p return p @@ -340,8 +340,14 @@ func TestInternalizeStructUnexportedFields(t *testing.T) { js.Global.Call("eval", "return_person({Name: 'foo', age: 952})") - if a.Name != "foo" || a.age != 0 { - t.Fail() + // Manually check unexported fields + if a.age != expected.age { + t.Errorf("Mismatch in age: got %v, want %v", a.age, expected.age) + } + + // Check exported fields using cmp.Diff + if diff := cmp.Diff(a.Name, expected.Name); diff != "" { + t.Errorf("Mismatch in Name (-want +got):\n%s", diff) } } @@ -355,7 +361,8 @@ func TestInternalizeStructNested(t *testing.T) { Age int F FullName } - var a Person + var a, expected Person + expected = Person{Name: "foo", Age: 952, F: FullName{FirstName: "John", LastName: "Doe"}} js.Global.Set("return_person", func(p *Person) *Person { a = *p @@ -363,9 +370,8 @@ func TestInternalizeStructNested(t *testing.T) { }) js.Global.Call("eval", "return_person({Name: 'foo', Age: 952, F: {FirstName: 'John', LastName: 'Doe'}})") - - if a.Name != "foo" || a.Age != 952 || a.F.FirstName != "John" || a.F.LastName != "Doe" { - t.Fail() + if diff := cmp.Diff(a, expected); diff != "" { + t.Errorf("Mismatch (-want +got):\n%s", diff) } } @@ -377,8 +383,8 @@ func TestInternalizeArrayOfStructs(t *testing.T) { type ArrayOfStructs struct { People []Person } - - var a ArrayOfStructs + var a, expected ArrayOfStructs + expected = ArrayOfStructs{People: []Person{{Name: "Alice", Age: 30}, {Name: "Bob", Age: 40}}} js.Global.Set("return_people_array", func(p ArrayOfStructs) ArrayOfStructs { a = p @@ -386,9 +392,8 @@ func TestInternalizeArrayOfStructs(t *testing.T) { }) js.Global.Call("eval", `return_people_array({People: [{Name: "Alice", Age: 30}, {Name: "Bob", Age: 40}]})`) - - if len(a.People) != 2 || a.People[0].Name != "Alice" || a.People[1].Age != 40 { - t.Fail() + if diff := cmp.Diff(a, expected); diff != "" { + t.Errorf("Mismatch (-want +got):\n%s", diff) } } From 67798d860cae5276afdb68499ea453f4d4a80f76 Mon Sep 17 00:00:00 2001 From: makki_d Date: Fri, 1 Sep 2023 20:53:50 +0900 Subject: [PATCH 34/66] Use /bin/go --- build/context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/context.go b/build/context.go index 111f313ed..316bfb2bb 100644 --- a/build/context.go +++ b/build/context.go @@ -179,7 +179,7 @@ func (sc simpleCtx) gotool(subcommand string, args ...string) (string, error) { panic(fmt.Errorf("can't use go tool with a virtual build context")) } args = append([]string{subcommand}, args...) - cmd := exec.Command("go", args...) + cmd := exec.Command(filepath.Join(sc.bctx.GOROOT, "bin", "go"), args...) if sc.bctx.Dir != "" { cmd.Dir = sc.bctx.Dir From c9dc8a9aab0d0c984339d3c37be8a9a562b96075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E5=8F=B6?= Date: Tue, 12 Sep 2023 06:52:07 +0800 Subject: [PATCH 35/66] nosync: fix build for golang.org/x/tools/internal/tokeninternal (#1233) nosync: fix build for golang.org/x/tools/internal/tokeninternal Also fix CircleCI workflow for TodoMVC in GOPATH mode to make sure it uses gopherjs packages from the PR to build it. --- circle.yml | 3 ++- nosync/mutex.go | 12 +++++++++++- tests/misc_test.go | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 95b0826cb..12b0aa45f 100644 --- a/circle.yml +++ b/circle.yml @@ -99,7 +99,8 @@ jobs: set -e export GO111MODULE=off export GOPATH=/tmp/gopath - mkdir -p $GOPATH/src + mkdir -p $GOPATH/src/github.com/gopherjs/gopherjs + cp -r -p . $GOPATH/src/github.com/gopherjs/gopherjs/ go get -v github.com/gopherjs/todomvc gopherjs build -v -o /tmp/todomvc_gopath.js github.com/gopherjs/todomvc gopherjs test -v github.com/gopherjs/todomvc/... diff --git a/nosync/mutex.go b/nosync/mutex.go index 03f20dc40..d988e8ffa 100644 --- a/nosync/mutex.go +++ b/nosync/mutex.go @@ -3,6 +3,10 @@ package nosync // Mutex is a dummy which is non-blocking. type Mutex struct { locked bool + _ bool + _ bool + _ bool + _ uint32 } // Lock locks m. It is a run-time error if m is already locked. @@ -23,8 +27,14 @@ func (m *Mutex) Unlock() { // RWMutex is a dummy which is non-blocking. type RWMutex struct { + _ Mutex writeLocked bool - readLockCounter int + _ bool + _ bool + _ bool + readLockCounter int32 + _ int32 + _ int32 } // Lock locks m for writing. It is a run-time error if rw is already locked for reading or writing. diff --git a/tests/misc_test.go b/tests/misc_test.go index 49cf82120..a38d91c81 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -1,12 +1,15 @@ package tests import ( + "go/token" "math" "reflect" "runtime" "strings" + "sync" "testing" "time" + "unsafe" "github.com/gopherjs/gopherjs/tests/otherpkg" ) @@ -941,3 +944,18 @@ func TestCompositeLiterals(t *testing.T) { t.Errorf("Got: reflect.TypeOf(s2[0]) = %v. Want: tests.SP", got) } } + +func TestFileSetSize(t *testing.T) { + type tokenFileSet struct { + // This type remained essentially consistent from go1.16 to go1.21. + mutex sync.RWMutex + base int + files []*token.File + _ *token.File // changed to atomic.Pointer[token.File] in go1.19 + } + n1 := unsafe.Sizeof(tokenFileSet{}) + n2 := unsafe.Sizeof(token.FileSet{}) + if n1 != n2 { + t.Errorf("Got: unsafe.Sizeof(token.FileSet{}) %v, Want: %v", n2, n1) + } +} From c7cfb39076039398e1f3c0e3b1acec4a2b315308 Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Mon, 23 Oct 2023 04:19:01 -0400 Subject: [PATCH 36/66] compiler: discard metadata in VERSION file As of Go 1.21, the VERSION file has additional metadata after the first line. Use only the text on the first line when reporting the Go version. For #1018. GitHub-Pull-Request: https://github.com/gopherjs/gopherjs/pull/1236 --- compiler/version_check.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/version_check.go b/compiler/version_check.go index 10f10e537..536bfddca 100644 --- a/compiler/version_check.go +++ b/compiler/version_check.go @@ -35,13 +35,13 @@ func CheckGoVersion(goroot string) error { return nil } -// goRootVersion defermines Go release for the given GOROOT installation. +// goRootVersion determines the Go release for the given GOROOT installation. func goRootVersion(goroot string) (string, error) { - v, err := os.ReadFile(filepath.Join(goroot, "VERSION")) - if err == nil { - // Standard Go distribution has VERSION file inside its GOROOT, checking it - // is the most efficient option. - return string(v), nil + if b, err := os.ReadFile(filepath.Join(goroot, "VERSION")); err == nil { + // Standard Go distribution has a VERSION file inside its GOROOT, + // checking its first line is the most efficient option. + v, _, _ := strings.Cut(string(b), "\n") + return v, nil } // Fall back to the "go version" command. @@ -58,8 +58,8 @@ func goRootVersion(goroot string) (string, error) { return parts[2], nil } -// GoRelease does a best-effort to identify Go release we are building with. -// If unable to determin the precise version for the given GOROOT, falls back +// GoRelease does a best-effort to identify the Go release we are building with. +// If unable to determine the precise version for the given GOROOT, falls back // to the best guess available. func GoRelease(goroot string) string { v, err := goRootVersion(goroot) From a0b71356c9063970cb0ff72a54b9fd658e85b15e Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 12 Nov 2023 16:30:00 +0000 Subject: [PATCH 37/66] Update Go version to 1.19.13 to begin work on Go 1.19 support. --- .github/workflows/lint.yaml | 2 +- .github/workflows/measure-size.yml | 2 +- README.md | 12 ++++++------ circle.yml | 2 +- compiler/version_check.go | 7 +++---- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 5f6c971e7..03fa75d9c 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -12,7 +12,7 @@ jobs: - uses: actions/setup-go@v3 with: - go-version: "1.18.10" + go-version: "1.19.13" - name: golangci-lint uses: golangci/golangci-lint-action@v3 diff --git a/.github/workflows/measure-size.yml b/.github/workflows/measure-size.yml index d193b0df6..ee4024e6a 100644 --- a/.github/workflows/measure-size.yml +++ b/.github/workflows/measure-size.yml @@ -11,7 +11,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-go@v2 with: - go-version: '~1.18.10' + go-version: '~1.19.13' - uses: gopherjs/output-size-action/measure@main with: name: jQuery TodoMVC diff --git a/README.md b/README.md index 63c5aabee..f29bb9084 100644 --- a/README.md +++ b/README.md @@ -31,21 +31,21 @@ Nearly everything, including Goroutines ([compatibility documentation](https://g ### Installation and Usage -GopherJS [requires Go 1.18 or newer](https://github.com/gopherjs/gopherjs/blob/master/doc/compatibility.md#go-version-compatibility). If you need an older Go +GopherJS [requires Go 1.19 or newer](https://github.com/gopherjs/gopherjs/blob/master/doc/compatibility.md#go-version-compatibility). If you need an older Go version, you can use an [older GopherJS release](https://github.com/gopherjs/gopherjs/releases). Install GopherJS with `go install`: ``` -go install github.com/gopherjs/gopherjs@v1.18.0-beta3 # Or replace 'v1.18.0-beta3' with another version. +go install github.com/gopherjs/gopherjs@v1.19.0-alpha1 # Or replace 'v1.19.0-alpha1' with another version. ``` -If your local Go distribution as reported by `go version` is newer than Go 1.18, then you need to set the `GOPHERJS_GOROOT` environment variable to a directory that contains a Go 1.18 distribution. For example: +If your local Go distribution as reported by `go version` is newer than Go 1.19, then you need to set the `GOPHERJS_GOROOT` environment variable to a directory that contains a Go 1.19 distribution. For example: ``` -go install golang.org/dl/go1.18.10@latest -go1.18.10 download -export GOPHERJS_GOROOT="$(go1.18.10 env GOROOT)" # Also add this line to your .profile or equivalent. +go install golang.org/dl/go1.19.13@latest +go1.19.13 download +export GOPHERJS_GOROOT="$(go1.19.13 env GOROOT)" # Also add this line to your .profile or equivalent. ``` Now you can use `gopherjs build [package]`, `gopherjs build [files]` or `gopherjs install [package]` which behave similar to the `go` tool. For `main` packages, these commands create a `.js` file and `.js.map` source map in the current directory or in `$GOPATH/bin`. The generated JavaScript file can be used as usual in a website. Use `gopherjs help [command]` to get a list of possible command line flags, e.g. for minification and automatically watching for changes. diff --git a/circle.yml b/circle.yml index 12b0aa45f..62031ce4b 100644 --- a/circle.yml +++ b/circle.yml @@ -54,7 +54,7 @@ workflows: parameters: go_version: type: string - default: "1.18.10" + default: "1.19.13" nvm_version: type: string default: "0.38.0" diff --git a/compiler/version_check.go b/compiler/version_check.go index 536bfddca..36bd4acd3 100644 --- a/compiler/version_check.go +++ b/compiler/version_check.go @@ -1,5 +1,4 @@ -//go:build go1.18 -// +build go1.18 +//go:build go1.19 package compiler @@ -13,10 +12,10 @@ import ( ) // Version is the GopherJS compiler version string. -const Version = "1.18.0-beta3+go1.18.10" +const Version = "1.19.0-alpha1+go1.19.13" // GoVersion is the current Go 1.x version that GopherJS is compatible with. -const GoVersion = 18 +const GoVersion = 19 // CheckGoVersion checks the version of the Go distribution // at goroot, and reports an error if it's not compatible From c146af03512be23903ae1e5cb8355242e14f76c8 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 14 Nov 2023 12:20:54 -0700 Subject: [PATCH 38/66] updated reflect and reflectlite --- .../src/internal/reflectlite/reflectlite.go | 29 +++++++++---------- compiler/natives/src/reflect/reflect.go | 23 +++++++-------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index 0be1eb09b..d48f15987 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -50,7 +50,7 @@ func reflectType(typ *js.Object) *rtype { rt := &rtype{ size: uintptr(typ.Get("size").Int()), kind: uint8(typ.Get("kind").Int()), - str: newNameOff(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool())), + str: newNameOff(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), } js.InternalObject(rt).Set(idJsType, typ) typ.Set(idReflectType, js.InternalObject(rt)) @@ -69,7 +69,7 @@ func reflectType(typ *js.Object) *rtype { continue } reflectMethods = append(reflectMethods, method{ - name: newNameOff(newName(internalStr(m.Get("name")), "", exported)), + name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), mtyp: newTypeOff(reflectType(m.Get("typ"))), }) } @@ -81,12 +81,12 @@ func reflectType(typ *js.Object) *rtype { continue } reflectMethods = append(reflectMethods, method{ - name: newNameOff(newName(internalStr(m.Get("name")), "", exported)), + name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), mtyp: newTypeOff(reflectType(m.Get("typ"))), }) } ut := &uncommonType{ - pkgPath: newNameOff(newName(internalStr(typ.Get("pkg")), "", false)), + pkgPath: newNameOff(newName(internalStr(typ.Get("pkg")), "", false, false)), mcount: uint16(methodSet.Length()), xcount: xcount, _methods: reflectMethods, @@ -141,13 +141,13 @@ func reflectType(typ *js.Object) *rtype { for i := range imethods { m := methods.Index(i) imethods[i] = imethod{ - name: newNameOff(newName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "")), + name: newNameOff(newName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), typ: newTypeOff(reflectType(m.Get("typ"))), } } setKindType(rt, &interfaceType{ rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkg")), "", false), + pkgPath: newName(internalStr(typ.Get("pkg")), "", false, false), methods: imethods, }) case Map: @@ -168,19 +168,15 @@ func reflectType(typ *js.Object) *rtype { reflectFields := make([]structField, fields.Length()) for i := range reflectFields { f := fields.Index(i) - offsetEmbed := uintptr(i) << 1 - if f.Get("embedded").Bool() { - offsetEmbed |= 1 - } reflectFields[i] = structField{ - name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool()), - typ: reflectType(f.Get("typ")), - offsetEmbed: offsetEmbed, + name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), + typ: reflectType(f.Get("typ")), + offset: uintptr(i), } } setKindType(rt, &structType{ rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false), + pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false, false), fields: reflectFields, }) } @@ -242,6 +238,7 @@ type nameData struct { name string tag string exported bool + embedded bool } var nameMap = make(map[*byte]*nameData) @@ -250,13 +247,15 @@ func (n name) name() (s string) { return nameMap[n.bytes].name } func (n name) tag() (s string) { return nameMap[n.bytes].tag } func (n name) pkgPath() string { return "" } func (n name) isExported() bool { return nameMap[n.bytes].exported } +func (n name) embedded() bool { return nameMap[n.bytes].embedded } -func newName(n, tag string, exported bool) name { +func newName(n, tag string, exported, embedded bool) name { b := new(byte) nameMap[b] = &nameData{ name: n, tag: tag, exported: exported, + embedded: embedded, } return name{ bytes: b, diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 3261d70ca..ed5a90835 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -63,7 +63,7 @@ func reflectType(typ *js.Object) *rtype { rt := &rtype{ size: uintptr(typ.Get("size").Int()), kind: uint8(typ.Get("kind").Int()), - str: resolveReflectName(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool())), + str: resolveReflectName(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), } js.InternalObject(rt).Set("jsType", typ) typ.Set("reflectType", js.InternalObject(rt)) @@ -99,7 +99,7 @@ func reflectType(typ *js.Object) *rtype { }) } ut := &uncommonType{ - pkgPath: resolveReflectName(newName(internalStr(typ.Get("pkg")), "", false)), + pkgPath: resolveReflectName(newName(internalStr(typ.Get("pkg")), "", false, false)), mcount: uint16(methodSet.Length()), xcount: xcount, _methods: reflectMethods, @@ -160,7 +160,7 @@ func reflectType(typ *js.Object) *rtype { } setKindType(rt, &interfaceType{ rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkg")), "", false), + pkgPath: newName(internalStr(typ.Get("pkg")), "", false, false), methods: imethods, }) case Map: @@ -181,19 +181,15 @@ func reflectType(typ *js.Object) *rtype { reflectFields := make([]structField, fields.Length()) for i := range reflectFields { f := fields.Index(i) - offsetEmbed := uintptr(i) << 1 - if f.Get("embedded").Bool() { - offsetEmbed |= 1 - } reflectFields[i] = structField{ - name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool()), - typ: reflectType(f.Get("typ")), - offsetEmbed: offsetEmbed, + name: newName(internalStr(f.Index.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), + typ: reflectType(f.Get("typ")), + offset: uintptr(i), } } setKindType(rt, &structType{ rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false), + pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false, false), fields: reflectFields, }) } @@ -257,6 +253,7 @@ type nameData struct { name string tag string exported bool + embedded bool pkgPath string } @@ -266,16 +263,18 @@ func (n name) name() (s string) { return nameMap[n.bytes].name } func (n name) tag() (s string) { return nameMap[n.bytes].tag } func (n name) pkgPath() string { return nameMap[n.bytes].pkgPath } func (n name) isExported() bool { return nameMap[n.bytes].exported } +func (n name) embedded() bool { return nameMap[n.bytes].embedded } func (n name) setPkgPath(pkgpath string) { nameMap[n.bytes].pkgPath = pkgpath } -func newName(n, tag string, exported bool) name { +func newName(n, tag string, exported, embedded bool) name { b := new(byte) nameMap[b] = &nameData{ name: n, tag: tag, exported: exported, + embedded: embedded, } return name{ bytes: b, From 3e3c85eb1059108d895be36ef1b24046b0d3834a Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Mon, 20 Nov 2023 10:46:10 -0700 Subject: [PATCH 39/66] Updating deprecated method calls Updating deprecated method calls --- compiler/natives/src/net/http/http.go | 6 +++--- go.mod | 3 +-- go.sum | 2 -- tests/gorepo/run.go | 25 ++++++++++++------------- tests/js_test.go | 1 + tests/lowlevel_test.go | 4 ++-- tests/syscall_test.go | 3 +-- tool.go | 9 ++++----- 8 files changed, 24 insertions(+), 29 deletions(-) diff --git a/compiler/natives/src/net/http/http.go b/compiler/natives/src/net/http/http.go index 7843235b2..8fd607c4d 100644 --- a/compiler/natives/src/net/http/http.go +++ b/compiler/natives/src/net/http/http.go @@ -7,7 +7,7 @@ import ( "bufio" "bytes" "errors" - "io/ioutil" + "io" "net/textproto" "strconv" @@ -68,7 +68,7 @@ func (t *XHRTransport) RoundTrip(req *Request) (*Response, error) { StatusCode: xhr.Get("status").Int(), Header: Header(header), ContentLength: contentLength, - Body: ioutil.NopCloser(bytes.NewReader(body)), + Body: io.NopCloser(bytes.NewReader(body)), Request: req, } }) @@ -91,7 +91,7 @@ func (t *XHRTransport) RoundTrip(req *Request) (*Response, error) { if req.Body == nil { xhr.Call("send") } else { - body, err := ioutil.ReadAll(req.Body) + body, err := io.ReadAll(req.Body) if err != nil { req.Body.Close() // RoundTrip must always close the body, including on errors. return nil, err diff --git a/go.mod b/go.mod index d1e88d3d5..8edafd89b 100644 --- a/go.mod +++ b/go.mod @@ -13,14 +13,13 @@ require ( github.com/spf13/cobra v1.2.1 github.com/spf13/pflag v1.0.5 github.com/visualfc/goembed v0.3.3 - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 golang.org/x/sync v0.3.0 golang.org/x/sys v0.10.0 + golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 golang.org/x/tools v0.11.0 ) require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect - golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect ) diff --git a/go.sum b/go.sum index 80bdd5b30..349d599ba 100644 --- a/go.sum +++ b/go.sum @@ -260,8 +260,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 0ed56a7fb..0ceb99ff5 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -24,7 +24,6 @@ import ( "fmt" "hash/fnv" "io" - "io/ioutil" "log" "os" "os/exec" @@ -457,8 +456,8 @@ func (t *test) goDirName() string { return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1)) } -func goDirFiles(longdir string) (filter []os.FileInfo, err error) { - files, dirErr := ioutil.ReadDir(longdir) +func goDirFiles(longdir string) (filter []os.DirEntry, err error) { + files, dirErr := os.ReadDir(longdir) if dirErr != nil { return nil, dirErr } @@ -481,7 +480,7 @@ func goDirPackages(longdir string) ([][]string, error) { m := make(map[string]int) for _, file := range files { name := file.Name() - data, err := ioutil.ReadFile(filepath.Join(longdir, name)) + data, err := os.ReadFile(filepath.Join(longdir, name)) if err != nil { return nil, err } @@ -593,7 +592,7 @@ func (t *test) run() { return } - srcBytes, err := ioutil.ReadFile(t.goFileName()) + srcBytes, err := os.ReadFile(t.goFileName()) if err != nil { t.err = err return @@ -682,7 +681,7 @@ func (t *test) run() { t.makeTempDir() defer os.RemoveAll(t.tempDir) - err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0o644) + err = os.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0o644) check(err) // A few tests (of things like the environment) require these to be set. @@ -854,7 +853,7 @@ func (t *test) run() { return } tfile := filepath.Join(t.tempDir, "tmp__.go") - if err := ioutil.WriteFile(tfile, out, 0o666); err != nil { + if err := os.WriteFile(tfile, out, 0o666); err != nil { t.err = fmt.Errorf("write tempfile:%s", err) return } @@ -875,7 +874,7 @@ func (t *test) run() { return } tfile := filepath.Join(t.tempDir, "tmp__.go") - err = ioutil.WriteFile(tfile, out, 0o666) + err = os.WriteFile(tfile, out, 0o666) if err != nil { t.err = fmt.Errorf("write tempfile:%s", err) return @@ -923,7 +922,7 @@ func (t *test) String() string { func (t *test) makeTempDir() { var err error - t.tempDir, err = ioutil.TempDir("", "") + t.tempDir, err = os.MkdirTemp("", "") check(err) } @@ -931,7 +930,7 @@ func (t *test) expectedOutput() string { filename := filepath.Join(t.dir, t.gofile) filename = filename[:len(filename)-len(".go")] filename += ".out" - b, _ := ioutil.ReadFile(filename) + b, _ := os.ReadFile(filename) return string(b) } @@ -1023,7 +1022,7 @@ func (t *test) errorCheck(outStr string, fullshort ...string) (err error) { func (t *test) updateErrors(out string, file string) { // Read in source file. - src, err := ioutil.ReadFile(file) + src, err := os.ReadFile(file) if err != nil { fmt.Fprintln(os.Stderr, err) return @@ -1078,7 +1077,7 @@ func (t *test) updateErrors(out string, file string) { } } // Write new file. - err = ioutil.WriteFile(file, []byte(strings.Join(lines, "\n")), 0o640) + err = os.WriteFile(file, []byte(strings.Join(lines, "\n")), 0o640) if err != nil { fmt.Fprintln(os.Stderr, err) return @@ -1135,7 +1134,7 @@ var ( func (t *test) wantedErrors(file, short string) (errs []wantedError) { cache := make(map[string]*regexp.Regexp) - src, _ := ioutil.ReadFile(file) + src, _ := os.ReadFile(file) for i, line := range strings.Split(string(src), "\n") { lineNum := i + 1 if strings.Contains(line, "////") { diff --git a/tests/js_test.go b/tests/js_test.go index 7680749dc..2ce43865f 100644 --- a/tests/js_test.go +++ b/tests/js_test.go @@ -326,6 +326,7 @@ func TestInternalizeStruct(t *testing.T) { t.Errorf("Mismatch (-want +got):\n%s", diff) } } + func TestInternalizeStructUnexportedFields(t *testing.T) { type Person struct { Name string diff --git a/tests/lowlevel_test.go b/tests/lowlevel_test.go index e966eba7a..d25c63709 100644 --- a/tests/lowlevel_test.go +++ b/tests/lowlevel_test.go @@ -1,7 +1,7 @@ package tests_test import ( - "io/ioutil" + "os" "os/exec" "path/filepath" "runtime" @@ -26,7 +26,7 @@ func TestTimeInternalizationExternalization(t *testing.T) { t.Fatalf("%v:\n%s", err, got) } - wantb, err := ioutil.ReadFile(filepath.Join("testdata", "time_inexternalization.out")) + wantb, err := os.ReadFile(filepath.Join("testdata", "time_inexternalization.out")) want := string(wantb) if err != nil { t.Fatalf("error reading .out file: %v", err) diff --git a/tests/syscall_test.go b/tests/syscall_test.go index 5e18776a3..104800df7 100644 --- a/tests/syscall_test.go +++ b/tests/syscall_test.go @@ -4,7 +4,6 @@ package tests import ( - "io/ioutil" "os" "syscall" "testing" @@ -20,7 +19,7 @@ func TestGetpid(t *testing.T) { } func TestOpen(t *testing.T) { - f, err := ioutil.TempFile("", "") + f, err := os.CreateTemp("", "") if err != nil { t.Fatalf("Failed to create a temp file: %s", err) } diff --git a/tool.go b/tool.go index b9a47c75b..06483e96b 100644 --- a/tool.go +++ b/tool.go @@ -10,7 +10,6 @@ import ( "go/token" "go/types" "io" - "io/ioutil" "net" "net/http" "os" @@ -35,8 +34,8 @@ import ( log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" - "golang.org/x/crypto/ssh/terminal" "golang.org/x/sync/errgroup" + "golang.org/x/term" ) var currentDirectory string @@ -79,7 +78,7 @@ func main() { compilerFlags := pflag.NewFlagSet("", 0) compilerFlags.BoolVarP(&options.Minify, "minify", "m", false, "minify generated code") - compilerFlags.BoolVar(&options.Color, "color", terminal.IsTerminal(int(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb", "colored output") + compilerFlags.BoolVar(&options.Color, "color", term.IsTerminal(int(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb", "colored output") compilerFlags.StringVar(&tags, "tags", "", "a list of build tags to consider satisfied during the build") compilerFlags.BoolVar(&options.MapToLocalDisk, "localmap", false, "use local paths for sourcemap") compilerFlags.BoolVarP(&options.NoCache, "no_cache", "a", false, "rebuild all packages from scratch") @@ -280,9 +279,9 @@ func main() { return fmt.Errorf("gopherjs run: no go files listed") } - tempfile, err := ioutil.TempFile(currentDirectory, filepath.Base(args[0])+".") + tempfile, err := os.CreateTemp(currentDirectory, filepath.Base(args[0])+".") if err != nil && strings.HasPrefix(currentDirectory, runtime.GOROOT()) { - tempfile, err = ioutil.TempFile("", filepath.Base(args[0])+".") + tempfile, err = os.CreateTemp("", filepath.Base(args[0])+".") } if err != nil { return err From a76a603ce28fd9204ae424987be20333173d7622 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Mon, 4 Dec 2023 13:51:36 -0700 Subject: [PATCH 40/66] Fixing a mistake found in reflect --- compiler/natives/src/reflect/reflect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index ed5a90835..5bcfaf66d 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -182,7 +182,7 @@ func reflectType(typ *js.Object) *rtype { for i := range reflectFields { f := fields.Index(i) reflectFields[i] = structField{ - name: newName(internalStr(f.Index.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), + name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), typ: reflectType(f.Get("typ")), offset: uintptr(i), } From 5edefc65d084d463ade48366593b198eca38becd Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Mon, 18 Dec 2023 10:27:58 -0700 Subject: [PATCH 41/66] Update FuncKey and fix Http native --- build/build.go | 2 +- compiler/astutil/astutil.go | 33 +++++++++++++++++++---- compiler/astutil/astutil_test.go | 39 +++++++++++++++++++-------- compiler/natives/src/net/http/http.go | 6 ++--- 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/build/build.go b/build/build.go index 070d05df1..14d2022b7 100644 --- a/build/build.go +++ b/build/build.go @@ -678,7 +678,7 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) { archive := s.buildCache.LoadArchive(pkg.ImportPath) if archive != nil && !pkg.SrcModTime.After(archive.BuildTime) { if err := archive.RegisterTypes(s.Types); err != nil { - panic(fmt.Errorf("Failed to load type information from %v: %w", archive, err)) + panic(fmt.Errorf("failed to load type information from %v: %w", archive, err)) } s.UpToDateArchives[pkg.ImportPath] = archive // Existing archive is up to date, no need to build it from scratch. diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index 30febe1cb..b7373f96a 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -62,14 +62,37 @@ func ImportsUnsafe(file *ast.File) bool { // FuncKey returns a string, which uniquely identifies a top-level function or // method in a package. func FuncKey(d *ast.FuncDecl) string { - if d.Recv == nil || len(d.Recv.List) == 0 { - return d.Name.Name + if recvKey := FuncReceiverKey(d); len(recvKey) > 0 { + return recvKey + "." + d.Name.Name + } + return d.Name.Name +} + +// FuncReceiverKey returns a string that uniquely identifies the receiver +// struct of the function or an empty string if there is no receiver. +// This name will match the name of the struct in the struct's type spec. +func FuncReceiverKey(d *ast.FuncDecl) string { + if d == nil || d.Recv == nil || len(d.Recv.List) == 0 { + return `` } recv := d.Recv.List[0].Type - if star, ok := recv.(*ast.StarExpr); ok { - recv = star.X + for { + switch r := recv.(type) { + case *ast.IndexListExpr: + recv = r.X + continue + case *ast.IndexExpr: + recv = r.X + continue + case *ast.StarExpr: + recv = r.X + continue + case *ast.Ident: + return r.Name + default: + panic(fmt.Errorf(`unexpected type %T in receiver of function: %v`, recv, d)) + } } - return recv.(*ast.Ident).Name + "." + d.Name.Name } // PruneOriginal returns true if gopherjs:prune-original directive is present diff --git a/compiler/astutil/astutil_test.go b/compiler/astutil/astutil_test.go index a996ae73f..2362f6439 100644 --- a/compiler/astutil/astutil_test.go +++ b/compiler/astutil/astutil_test.go @@ -59,24 +59,41 @@ func TestFuncKey(t *testing.T) { want string }{ { - desc: "top-level function", - src: `package testpackage; func foo() {}`, - want: "foo", + desc: `top-level function`, + src: `func foo() {}`, + want: `foo`, + }, { + desc: `top-level exported function`, + src: `func Foo() {}`, + want: `Foo`, + }, { + desc: `method on reference`, + src: `func (_ myType) bar() {}`, + want: `myType.bar`, }, { - desc: "top-level exported function", - src: `package testpackage; func Foo() {}`, - want: "Foo", + desc: `method on pointer`, + src: ` func (_ *myType) bar() {}`, + want: `myType.bar`, }, { - desc: "method", - src: `package testpackage; func (_ myType) bar() {}`, - want: "myType.bar", + desc: `method on generic reference`, + src: ` func (_ myType[T]) bar() {}`, + want: `myType.bar`, + }, { + desc: `method on generic pointer`, + src: ` func (_ *myType[T]) bar() {}`, + want: `myType.bar`, + }, { + desc: `method on struct with multiple generics`, + src: ` func (_ *myType[T1, T2, T3, T4]) bar() {}`, + want: `myType.bar`, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - fdecl := srctesting.ParseFuncDecl(t, test.src) + src := `package testpackage; ` + test.src + fdecl := srctesting.ParseFuncDecl(t, src) if got := FuncKey(fdecl); got != test.want { - t.Errorf("Got %q, want %q", got, test.want) + t.Errorf(`Got %q, want %q`, got, test.want) } }) } diff --git a/compiler/natives/src/net/http/http.go b/compiler/natives/src/net/http/http.go index 7843235b2..8fd607c4d 100644 --- a/compiler/natives/src/net/http/http.go +++ b/compiler/natives/src/net/http/http.go @@ -7,7 +7,7 @@ import ( "bufio" "bytes" "errors" - "io/ioutil" + "io" "net/textproto" "strconv" @@ -68,7 +68,7 @@ func (t *XHRTransport) RoundTrip(req *Request) (*Response, error) { StatusCode: xhr.Get("status").Int(), Header: Header(header), ContentLength: contentLength, - Body: ioutil.NopCloser(bytes.NewReader(body)), + Body: io.NopCloser(bytes.NewReader(body)), Request: req, } }) @@ -91,7 +91,7 @@ func (t *XHRTransport) RoundTrip(req *Request) (*Response, error) { if req.Body == nil { xhr.Call("send") } else { - body, err := ioutil.ReadAll(req.Body) + body, err := io.ReadAll(req.Body) if err != nil { req.Body.Close() // RoundTrip must always close the body, including on errors. return nil, err From 17263fa5e2ab11d17bdc089852f2f29a0f246fd1 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Mon, 18 Dec 2023 12:16:33 -0700 Subject: [PATCH 42/66] Broke up parseAndAugment --- build/build.go | 257 +++++++++++++++++++++++---------------- build/build_test.go | 290 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 443 insertions(+), 104 deletions(-) diff --git a/build/build.go b/build/build.go index 070d05df1..08c96f4d9 100644 --- a/build/build.go +++ b/build/build.go @@ -117,6 +117,19 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag return pkg, nil } +// overrideInfo is used by parseAndAugment methods to manage +// directives and how the overlay and original are merged. +type overrideInfo struct { + // KeepOriginal indicates that the original code should be kept + // but the identifier will be prefixed by `_gopherjs_original_foo`. + // If false the original code is removed. + keepOriginal bool + + // pruneMethodBody indicates that the body of the methods should be + // removed because they contain something that is invalid to GopherJS. + pruneMethodBody bool +} + // parseAndAugment parses and returns all .go files of given pkg. // Standard Go library packages are augmented with files in compiler/natives folder. // If isTest is true and pkg.ImportPath has no _test suffix, package is built for running internal tests. @@ -132,84 +145,86 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag // the original identifier gets replaced by `_`. New identifiers that don't exist in original // package get added. func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) { - var files []*ast.File + jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, isTest, fileSet) + + originalFiles, err := parserOriginalFiles(pkg, fileSet) + if err != nil { + return nil, nil, err + } + + overrides := make(map[string]overrideInfo) + for _, file := range overlayFiles { + augmentOverlayFile(file, overrides) + } + delete(overrides, "init") - type overrideInfo struct { - keepOriginal bool - pruneOriginal bool + for _, file := range originalFiles { + augmentOriginalImports(pkg.ImportPath, file) + augmentOriginalFile(file, overrides) } - replacedDeclNames := make(map[string]overrideInfo) + return append(overlayFiles, originalFiles...), jsFiles, nil +} + +// parseOverlayFiles loads and parses overlay files +// to augment the original files with. +func parseOverlayFiles(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]JSFile, []*ast.File) { isXTest := strings.HasSuffix(pkg.ImportPath, "_test") importPath := pkg.ImportPath if isXTest { importPath = importPath[:len(importPath)-5] } - jsFiles := []JSFile{} - nativesContext := overlayCtx(xctx.Env()) + nativesPkg, err := nativesContext.Import(importPath, "", 0) + if err != nil { + return nil, nil + } - if nativesPkg, err := nativesContext.Import(importPath, "", 0); err == nil { - jsFiles = nativesPkg.JSFiles - names := nativesPkg.GoFiles - if isTest { - names = append(names, nativesPkg.TestGoFiles...) - } - if isXTest { - names = nativesPkg.XTestGoFiles + jsFiles := nativesPkg.JSFiles + var files []*ast.File + names := nativesPkg.GoFiles + if isTest { + names = append(names, nativesPkg.TestGoFiles...) + } + if isXTest { + names = nativesPkg.XTestGoFiles + } + + for _, name := range names { + fullPath := path.Join(nativesPkg.Dir, name) + r, err := nativesContext.bctx.OpenFile(fullPath) + if err != nil { + panic(err) } - for _, name := range names { - fullPath := path.Join(nativesPkg.Dir, name) - r, err := nativesContext.bctx.OpenFile(fullPath) - if err != nil { - panic(err) - } - // Files should be uniquely named and in the original package directory in order to be - // ordered correctly - newPath := path.Join(pkg.Dir, "gopherjs__"+name) - file, err := parser.ParseFile(fileSet, newPath, r, parser.ParseComments) - if err != nil { - panic(err) - } - r.Close() - for _, decl := range file.Decls { - switch d := decl.(type) { - case *ast.FuncDecl: - k := astutil.FuncKey(d) - replacedDeclNames[k] = overrideInfo{ - keepOriginal: astutil.KeepOriginal(d), - pruneOriginal: astutil.PruneOriginal(d), - } - case *ast.GenDecl: - switch d.Tok { - case token.TYPE: - for _, spec := range d.Specs { - replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = overrideInfo{} - } - case token.VAR, token.CONST: - for _, spec := range d.Specs { - for _, name := range spec.(*ast.ValueSpec).Names { - replacedDeclNames[name.Name] = overrideInfo{} - } - } - } - } - } - files = append(files, file) + // Files should be uniquely named and in the original package directory in order to be + // ordered correctly + newPath := path.Join(pkg.Dir, "gopherjs__"+name) + file, err := parser.ParseFile(fileSet, newPath, r, parser.ParseComments) + if err != nil { + panic(err) } + r.Close() + + files = append(files, file) } - delete(replacedDeclNames, "init") + return jsFiles, files +} +// parserOriginalFiles loads and parses the original files to augment. +func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File, error) { + var files []*ast.File var errList compiler.ErrorList for _, name := range pkg.GoFiles { if !filepath.IsAbs(name) { // name might be absolute if specified directly. E.g., `gopherjs build /abs/file.go`. name = filepath.Join(pkg.Dir, name) } + r, err := buildutil.OpenFile(pkg.bctx, name) if err != nil { - return nil, nil, err + return nil, err } + file, err := parser.ParseFile(fileSet, name, r, parser.ParseComments) r.Close() if err != nil { @@ -226,68 +241,102 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke continue } - switch pkg.ImportPath { - case "crypto/rand", "encoding/gob", "encoding/json", "expvar", "go/token", "log", "math/big", "math/rand", "regexp", "time": - for _, spec := range file.Imports { - path, _ := strconv.Unquote(spec.Path.Value) - if path == "sync" { - if spec.Name == nil { - spec.Name = ast.NewIdent("sync") + files = append(files, file) + } + + if errList != nil { + return nil, errList + } + return files, nil +} + +// augmentOverlayFile is the part of parseAndAugment that processes +// an overlay file AST to collect information such as compiler directives +// and perform any initial augmentation needed to the overlay. +func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) { + for _, decl := range file.Decls { + switch d := decl.(type) { + case *ast.FuncDecl: + k := astutil.FuncKey(d) + overrides[k] = overrideInfo{ + keepOriginal: astutil.KeepOriginal(d), + pruneMethodBody: astutil.PruneOriginal(d), + } + case *ast.GenDecl: + for _, spec := range d.Specs { + switch s := spec.(type) { + case *ast.TypeSpec: + overrides[s.Name.Name] = overrideInfo{} + case *ast.ValueSpec: + for _, name := range s.Names { + overrides[name.Name] = overrideInfo{} } - spec.Path.Value = `"github.com/gopherjs/gopherjs/nosync"` } } } + } +} - for _, decl := range file.Decls { - switch d := decl.(type) { - case *ast.FuncDecl: - k := astutil.FuncKey(d) - if info, ok := replacedDeclNames[k]; ok { - if info.pruneOriginal { - // Prune function bodies, since it may contain code invalid for - // GopherJS and pin unwanted imports. - d.Body = nil - } - if info.keepOriginal { - // Allow overridden function calls - // The standard library implementation of foo() becomes _gopherjs_original_foo() - d.Name.Name = "_gopherjs_original_" + d.Name.Name - } else { - d.Name = ast.NewIdent("_") - } +// augmentOriginalImports is the part of parseAndAugment that processes +// an original file AST to modify the imports for that file. +func augmentOriginalImports(importPath string, file *ast.File) { + switch importPath { + case "crypto/rand", "encoding/gob", "encoding/json", "expvar", "go/token", "log", "math/big", "math/rand", "regexp", "time": + for _, spec := range file.Imports { + path, _ := strconv.Unquote(spec.Path.Value) + if path == "sync" { + if spec.Name == nil { + spec.Name = ast.NewIdent("sync") } - case *ast.GenDecl: - switch d.Tok { - case token.TYPE: - for _, spec := range d.Specs { - s := spec.(*ast.TypeSpec) - if _, ok := replacedDeclNames[s.Name.Name]; ok { - s.Name = ast.NewIdent("_") - s.Type = &ast.StructType{Struct: s.Pos(), Fields: &ast.FieldList{}} - s.TypeParams = nil - } + spec.Path.Value = `"github.com/gopherjs/gopherjs/nosync"` + } + } + } +} + +// augmentOriginalFile is the part of parseAndAugment that processes an +// original file AST to augment the source code using the overrides from +// the overlay files. +func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { + for _, decl := range file.Decls { + switch d := decl.(type) { + case *ast.FuncDecl: + if info, ok := overrides[astutil.FuncKey(d)]; ok { + if info.pruneMethodBody { + // Prune function bodies, since it may contain code invalid for + // GopherJS and pin unwanted imports. + d.Body = nil + } + if info.keepOriginal { + // Allow overridden function calls + // The standard library implementation of foo() becomes _gopherjs_original_foo() + d.Name.Name = "_gopherjs_original_" + d.Name.Name + } else { + // By setting the name to an underscore, the method will + // not be outputted. Doing this will keep the dependencies the same. + d.Name = ast.NewIdent("_") + } + } + case *ast.GenDecl: + for _, spec := range d.Specs { + switch s := spec.(type) { + case *ast.TypeSpec: + if _, ok := overrides[s.Name.Name]; ok { + s.Name = ast.NewIdent("_") + // Change to struct type with no type body and not type parameters. + s.Type = &ast.StructType{Struct: s.Pos(), Fields: &ast.FieldList{}} + s.TypeParams = nil } - case token.VAR, token.CONST: - for _, spec := range d.Specs { - s := spec.(*ast.ValueSpec) - for i, name := range s.Names { - if _, ok := replacedDeclNames[name.Name]; ok { - s.Names[i] = ast.NewIdent("_") - } + case *ast.ValueSpec: + for i, name := range s.Names { + if _, ok := overrides[name.Name]; ok { + s.Names[i] = ast.NewIdent("_") } } } } } - - files = append(files, file) - } - - if errList != nil { - return nil, nil, errList } - return files, jsFiles, nil } // Options controls build process behavior. diff --git a/build/build_test.go b/build/build_test.go index 2fa17e2c5..8364052d7 100644 --- a/build/build_test.go +++ b/build/build_test.go @@ -1,12 +1,15 @@ package build import ( + "bytes" "fmt" gobuild "go/build" + "go/printer" "go/token" "strconv" "testing" + "github.com/gopherjs/gopherjs/internal/srctesting" "github.com/shurcooL/go/importgraphutil" ) @@ -127,3 +130,290 @@ func (m stringSet) String() string { } return fmt.Sprintf("%q", s) } + +func TestOverlayAugmentation(t *testing.T) { + tests := []struct { + desc string + src string + expInfo map[string]overrideInfo + }{ + { + desc: `remove function`, + src: `func Foo(a, b int) int { + return a + b + }`, + expInfo: map[string]overrideInfo{ + `Foo`: {}, + }, + }, { + desc: `keep function`, + src: `//gopherjs:keep-original + func Foo(a, b int) int { + return a + b + }`, + expInfo: map[string]overrideInfo{ + `Foo`: {keepOriginal: true}, + }, + }, { + desc: `prune function body`, + src: `//gopherjs:prune-original + func Foo(a, b int) int { + return a + b + }`, + expInfo: map[string]overrideInfo{ + `Foo`: {pruneMethodBody: true}, + }, + }, { + desc: `remove constants and values`, + src: `import "time" + + const ( + foo = 42 + bar = "gopherjs" + ) + + var now = time.Now`, + expInfo: map[string]overrideInfo{ + `foo`: {}, + `bar`: {}, + `now`: {}, + }, + }, { + desc: `remove types`, + src: `import "time" + + type ( + foo struct {} + bar int + ) + + type bob interface {}`, + expInfo: map[string]overrideInfo{ + `foo`: {}, + `bar`: {}, + `bob`: {}, + }, + }, { + desc: `remove methods`, + src: `import "cmp" + + type Foo struct { + bar int + } + + func (x *Foo) GetBar() int { return x.bar } + func (x *Foo) SetBar(bar int) { x.bar = bar }`, + expInfo: map[string]overrideInfo{ + `Foo`: {}, + `Foo.GetBar`: {}, + `Foo.SetBar`: {}, + }, + }, { + desc: `remove generics`, + src: `import "cmp" + + type Pointer[T any] struct {} + + func Sort[S ~[]E, E cmp.Ordered](x S) {} + + // this is a stub for "func Equal[S ~[]E, E any](s1, s2 S) bool {}" + func Equal[S ~[]E, E any](s1, s2 S) bool {}`, + expInfo: map[string]overrideInfo{ + `Pointer`: {}, + `Sort`: {}, + `Equal`: {}, + }, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + pkgName := "package testpackage\n\n" + fsetSrc := token.NewFileSet() + fileSrc := srctesting.Parse(t, fsetSrc, pkgName+test.src) + + overrides := map[string]overrideInfo{} + augmentOverlayFile(fileSrc, overrides) + + for key, expInfo := range test.expInfo { + if gotInfo, ok := overrides[key]; !ok { + t.Errorf(`%q was expected but not gotten`, key) + } else if expInfo != gotInfo { + t.Errorf(`%q had wrong info, got %+v`, key, gotInfo) + } + } + for key, gotInfo := range overrides { + if _, ok := test.expInfo[key]; !ok { + t.Errorf(`%q with %+v was not expected`, key, gotInfo) + } + } + }) + } +} + +func TestOriginalAugmentation(t *testing.T) { + tests := []struct { + desc string + info map[string]overrideInfo + src string + want string + }{ + { + desc: `do not affect function`, + info: map[string]overrideInfo{}, + src: `func Foo(a, b int) int { + return a + b + }`, + want: `func Foo(a, b int) int { + return a + b + }`, + }, { + desc: `change unnamed sync import`, + info: map[string]overrideInfo{}, + src: `import "sync" + + var _ = &sync.Mutex{}`, + want: `import sync "github.com/gopherjs/gopherjs/nosync" + + var _ = &sync.Mutex{}`, + }, { + desc: `change named sync import`, + info: map[string]overrideInfo{}, + src: `import foo "sync" + + var _ = &foo.Mutex{}`, + want: `import foo "github.com/gopherjs/gopherjs/nosync" + + var _ = &foo.Mutex{}`, + }, { + desc: `remove function`, + info: map[string]overrideInfo{ + `Foo`: {}, + }, + src: `func Foo(a, b int) int { + return a + b + }`, + want: `func _(a, b int) int { + return a + b + }`, + }, { + desc: `keep original function`, + info: map[string]overrideInfo{ + `Foo`: {keepOriginal: true}, + }, + src: `func Foo(a, b int) int { + return a + b + }`, + want: `func _gopherjs_original_Foo(a, b int) int { + return a + b + }`, + }, { + desc: `remove types and values`, + info: map[string]overrideInfo{ + `Foo`: {}, + `now`: {}, + `bar1`: {}, + }, + src: `import "time" + + type Foo interface{ + bob(a, b string) string + } + + var now = time.Now + const bar1, bar2 = 21, 42`, + want: `import "time" + + type _ struct { + } + + var _ = time.Now + const _, bar2 = 21, 42`, + }, { + desc: `remove in multi-value context`, + info: map[string]overrideInfo{ + `bar`: {}, + }, + src: `const foo, bar = func() (int, int) { + return 24, 12 + }()`, + want: `const foo, _ = func() (int, int) { + return 24, 12 + }()`, + }, { + desc: `remove methods`, + info: map[string]overrideInfo{ + `Foo`: {}, + `Foo.GetBar`: {}, + `Foo.SetBar`: {}, + }, + src: `import "cmp" + + type Foo struct { + bar int + } + + func (x *Foo) GetBar() int { return x.bar } + func (x *Foo) SetBar(bar int) { x.bar = bar }`, + want: `import "cmp" + + type _ struct { + } + + func (x *Foo) _() int { return x.bar } + func (x *Foo) _(bar int) { x.bar = bar }`, + }, { + desc: `remove generics`, + info: map[string]overrideInfo{ + `Pointer`: {}, + `Sort`: {}, + `Equal`: {}, + }, + src: `import "cmp" + + type Pointer[T any] struct {} + + func Sort[S ~[]E, E cmp.Ordered](x S) {} + + // overlay had stub "func Equal() {}" + func Equal[S ~[]E, E any](s1, s2 S) bool {}`, + want: `import "cmp" + + type _ struct { + } + + func _[S ~[]E, E cmp.Ordered](x S) {} + + // overlay had stub "func Equal() {}" + func _[S ~[]E, E any](s1, s2 S) bool {}`, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + pkgName := "package testpackage\n\n" + importPath := `math/rand` + fsetSrc := token.NewFileSet() + fileSrc := srctesting.Parse(t, fsetSrc, pkgName+test.src) + + augmentOriginalImports(importPath, fileSrc) + augmentOriginalFile(fileSrc, test.info) + + buf := &bytes.Buffer{} + _ = printer.Fprint(buf, fsetSrc, fileSrc) + got := buf.String() + + fsetWant := token.NewFileSet() + fileWant := srctesting.Parse(t, fsetWant, pkgName+test.want) + + buf.Reset() + _ = printer.Fprint(buf, fsetWant, fileWant) + want := buf.String() + + if got != want { + t.Errorf("augmentOriginalImports, augmentOriginalFile, and pruneImports got unexpected code:\n"+ + "returned:\n\t%q\nwant:\n\t%q", got, want) + } + }) + } +} From 6e1ec66e5e205d1b581754800b7a1b2251280af4 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Mon, 18 Dec 2023 12:35:48 -0700 Subject: [PATCH 43/66] Extending Directives --- compiler/astutil/astutil.go | 72 ++++-- compiler/astutil/astutil_test.go | 363 ++++++++++++++++++++++++++++++ internal/srctesting/srctesting.go | 36 ++- 3 files changed, 450 insertions(+), 21 deletions(-) diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index 30febe1cb..c7481fd27 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -5,7 +5,7 @@ import ( "go/ast" "go/token" "go/types" - "strings" + "regexp" ) func RemoveParens(e ast.Expr) ast.Expr { @@ -82,15 +82,7 @@ func FuncKey(d *ast.FuncDecl) string { // such as code expecting ints to be 64-bit. It should be used with caution // since it may create unused imports in the original source file. func PruneOriginal(d *ast.FuncDecl) bool { - if d.Doc == nil { - return false - } - for _, c := range d.Doc.List { - if strings.HasPrefix(c.Text, "//gopherjs:prune-original") { - return true - } - } - return false + return hasDirective(d, `prune-original`) } // KeepOriginal returns true if gopherjs:keep-original directive is present @@ -102,15 +94,61 @@ func PruneOriginal(d *ast.FuncDecl) bool { // function in the original called `foo`, it will be accessible by the name // `_gopherjs_original_foo`. func KeepOriginal(d *ast.FuncDecl) bool { - if d.Doc == nil { - return false - } - for _, c := range d.Doc.List { - if strings.HasPrefix(c.Text, "//gopherjs:keep-original") { - return true + return hasDirective(d, `keep-original`) +} + +// anyDocLine calls the given predicate on all associated documentation +// lines and line-comment lines from the given node. +// If the predicate returns true for any line then true is returned. +func anyDocLine(node any, predicate func(line string) bool) bool { + switch a := node.(type) { + case *ast.Comment: + return a != nil && predicate(a.Text) + case *ast.CommentGroup: + if a != nil { + for _, c := range a.List { + if anyDocLine(c, predicate) { + return true + } + } } + return false + case *ast.Field: + return a != nil && (anyDocLine(a.Doc, predicate) || anyDocLine(a.Comment, predicate)) + case *ast.File: + return a != nil && anyDocLine(a.Doc, predicate) + case *ast.FuncDecl: + return a != nil && anyDocLine(a.Doc, predicate) + case *ast.GenDecl: + return a != nil && anyDocLine(a.Doc, predicate) + case *ast.ImportSpec: + return a != nil && (anyDocLine(a.Doc, predicate) || anyDocLine(a.Comment, predicate)) + case *ast.TypeSpec: + return a != nil && (anyDocLine(a.Doc, predicate) || anyDocLine(a.Comment, predicate)) + case *ast.ValueSpec: + return a != nil && (anyDocLine(a.Doc, predicate) || anyDocLine(a.Comment, predicate)) + default: + panic(fmt.Errorf(`unexpected node type to get doc from: %T`, node)) } - return false +} + +// directiveMatcher is a regex which matches a GopherJS directive +// and finds the directive action. +var directiveMatcher = regexp.MustCompile(`^\/(?:\/|\*)gopherjs:([\w-]+)`) + +// hasDirective returns true if the associated documentation +// or line comments for the given node have the given directive action. +// +// All GopherJS-specific directives must start with `//gopherjs:` or +// `/*gopherjs:` and followed by an action without any whitespace. The action +// must be one or more letter, decimal, underscore, or hyphen. +// +// see https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives +func hasDirective(node any, directiveAction string) bool { + return anyDocLine(node, func(line string) bool { + m := directiveMatcher.FindStringSubmatch(line) + return len(m) == 2 && m[1] == directiveAction + }) } // FindLoopStmt tries to find the loop statement among the AST nodes in the diff --git a/compiler/astutil/astutil_test.go b/compiler/astutil/astutil_test.go index a996ae73f..61fc2b18c 100644 --- a/compiler/astutil/astutil_test.go +++ b/compiler/astutil/astutil_test.go @@ -1,6 +1,8 @@ package astutil import ( + "fmt" + "go/ast" "go/token" "testing" @@ -130,6 +132,367 @@ func TestPruneOriginal(t *testing.T) { } } +func TestHasDirectiveOnDecl(t *testing.T) { + tests := []struct { + desc string + src string + want bool + }{ + { + desc: `no comment on function`, + src: `package testpackage; + func foo() {}`, + want: false, + }, { + desc: `no directive on function with comment`, + src: `package testpackage; + // foo has no directive + func foo() {}`, + want: false, + }, { + desc: `wrong directive on function`, + src: `package testpackage; + //gopherjs:wrong-directive + func foo() {}`, + want: false, + }, { + desc: `correct directive on function`, + src: `package testpackage; + //gopherjs:do-stuff + // foo has a directive to do stuff + func foo() {}`, + want: true, + }, { + desc: `correct directive in multiline comment on function`, + src: `package testpackage; + /*gopherjs:do-stuff + foo has a directive to do stuff + */ + func foo() {}`, + want: true, + }, { + desc: `invalid directive in multiline comment on function`, + src: `package testpackage; + /* + gopherjs:do-stuff + */ + func foo() {}`, + want: false, + }, { + desc: `prefix directive on function`, + src: `package testpackage; + //gopherjs:do-stuffs + func foo() {}`, + want: false, + }, { + desc: `multiple directives on function`, + src: `package testpackage; + //gopherjs:wrong-directive + //gopherjs:do-stuff + //gopherjs:another-directive + func foo() {}`, + want: true, + }, { + desc: `directive with explanation on function`, + src: `package testpackage; + //gopherjs:do-stuff 'cause we can + func foo() {}`, + want: true, + }, { + desc: `no directive on type declaration`, + src: `package testpackage; + // Foo has a comment + type Foo int`, + want: false, + }, { + desc: `directive on type declaration`, + src: `package testpackage; + //gopherjs:do-stuff + type Foo int`, + want: true, + }, { + desc: `no directive on const declaration`, + src: `package testpackage; + const foo = 42`, + want: false, + }, { + desc: `directive on const documentation`, + src: `package testpackage; + //gopherjs:do-stuff + const foo = 42`, + want: true, + }, { + desc: `no directive on var declaration`, + src: `package testpackage; + var foo = 42`, + want: false, + }, { + desc: `directive on var documentation`, + src: `package testpackage; + //gopherjs:do-stuff + var foo = 42`, + want: true, + }, { + desc: `no directive on var declaration`, + src: `package testpackage; + import _ "embed"`, + want: false, + }, { + desc: `directive on var documentation`, + src: `package testpackage; + //gopherjs:do-stuff + import _ "embed"`, + want: true, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + const action = `do-stuff` + decl := srctesting.ParseDecl(t, test.src) + if got := hasDirective(decl, action); got != test.want { + t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, decl, action, got, test.want) + } + }) + } +} + +func TestHasDirectiveOnSpec(t *testing.T) { + tests := []struct { + desc string + src string + want bool + }{ + { + desc: `no directive on type specification`, + src: `package testpackage; + type Foo int`, + want: false, + }, { + desc: `directive in doc on type specification`, + src: `package testpackage; + type ( + //gopherjs:do-stuff + Foo int + )`, + want: true, + }, { + desc: `directive in line on type specification`, + src: `package testpackage; + type Foo int //gopherjs:do-stuff`, + want: true, + }, { + desc: `no directive on const specification`, + src: `package testpackage; + const foo = 42`, + want: false, + }, { + desc: `directive in doc on const specification`, + src: `package testpackage; + const ( + //gopherjs:do-stuff + foo = 42 + )`, + want: true, + }, { + desc: `directive in line on const specification`, + src: `package testpackage; + const foo = 42 //gopherjs:do-stuff`, + want: true, + }, { + desc: `no directive on var specification`, + src: `package testpackage; + var foo = 42`, + want: false, + }, { + desc: `directive in doc on var specification`, + src: `package testpackage; + var ( + //gopherjs:do-stuff + foo = 42 + )`, + want: true, + }, { + desc: `directive in line on var specification`, + src: `package testpackage; + var foo = 42 //gopherjs:do-stuff`, + want: true, + }, { + desc: `no directive on import specification`, + src: `package testpackage; + import _ "embed"`, + want: false, + }, { + desc: `directive in doc on import specification`, + src: `package testpackage; + import ( + //gopherjs:do-stuff + _ "embed" + )`, + want: true, + }, { + desc: `directive in line on import specification`, + src: `package testpackage; + import _ "embed" //gopherjs:do-stuff`, + want: true, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + const action = `do-stuff` + spec := srctesting.ParseSpec(t, test.src) + if got := hasDirective(spec, action); got != test.want { + t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, spec, action, got, test.want) + } + }) + } +} + +func TestHasDirectiveOnFile(t *testing.T) { + tests := []struct { + desc string + src string + want bool + }{ + { + desc: `no directive on file`, + src: `package testpackage; + //gopherjs:do-stuff + type Foo int`, + want: false, + }, { + desc: `directive on file`, + src: `//gopherjs:do-stuff + package testpackage; + type Foo int`, + want: true, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + const action = `do-stuff` + fset := token.NewFileSet() + file := srctesting.Parse(t, fset, test.src) + if got := hasDirective(file, action); got != test.want { + t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, file, action, got, test.want) + } + }) + } +} + +func TestHasDirectiveOnField(t *testing.T) { + tests := []struct { + desc string + src string + want bool + }{ + { + desc: `no directive on struct field`, + src: `package testpackage; + type Foo struct { + bar int + }`, + want: false, + }, { + desc: `directive in doc on struct field`, + src: `package testpackage; + type Foo struct { + //gopherjs:do-stuff + bar int + }`, + want: true, + }, { + desc: `directive in line on struct field`, + src: `package testpackage; + type Foo struct { + bar int //gopherjs:do-stuff + }`, + want: true, + }, { + desc: `no directive on interface method`, + src: `package testpackage; + type Foo interface { + Bar(a int) int + }`, + want: false, + }, { + desc: `directive in doc on interface method`, + src: `package testpackage; + type Foo interface { + //gopherjs:do-stuff + Bar(a int) int + }`, + want: true, + }, { + desc: `directive in line on interface method`, + src: `package testpackage; + type Foo interface { + Bar(a int) int //gopherjs:do-stuff + }`, + want: true, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + const action = `do-stuff` + spec := srctesting.ParseSpec(t, test.src) + tspec := spec.(*ast.TypeSpec) + var field *ast.Field + switch typeNode := tspec.Type.(type) { + case *ast.StructType: + field = typeNode.Fields.List[0] + case *ast.InterfaceType: + field = typeNode.Methods.List[0] + default: + t.Errorf(`unexpected node type, %T, when finding field`, typeNode) + return + } + if got := hasDirective(field, action); got != test.want { + t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, field, action, got, test.want) + } + }) + } +} + +func TestHasDirectiveBadCase(t *testing.T) { + tests := []struct { + desc string + node any + want string + }{ + { + desc: `untyped nil node`, + node: nil, + want: `unexpected node type to get doc from: `, + }, { + desc: `unexpected node type`, + node: &ast.ArrayType{}, + want: `unexpected node type to get doc from: *ast.ArrayType`, + }, { + desc: `nil expected node type`, + node: (*ast.FuncDecl)(nil), + want: ``, // no panic + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + const action = `do-stuff` + var got string + func() { + defer func() { got = fmt.Sprint(recover()) }() + hasDirective(test.node, action) + }() + if got != test.want { + t.Errorf(`hasDirective(%T, %q) returned %s, want %s`, test.node, action, got, test.want) + } + }) + } +} + func TestEndsWithReturn(t *testing.T) { tests := []struct { desc string diff --git a/internal/srctesting/srctesting.go b/internal/srctesting/srctesting.go index 1d9cecd20..4e374845e 100644 --- a/internal/srctesting/srctesting.go +++ b/internal/srctesting/srctesting.go @@ -53,17 +53,45 @@ func Check(t *testing.T, fset *token.FileSet, files ...*ast.File) (*types.Info, // // Fails the test if there isn't exactly one function declared in the source. func ParseFuncDecl(t *testing.T, src string) *ast.FuncDecl { + t.Helper() + decl := ParseDecl(t, src) + fdecl, ok := decl.(*ast.FuncDecl) + if !ok { + t.Fatalf("Got %T decl, expected *ast.FuncDecl", decl) + } + return fdecl +} + +// ParseDecl parses source with a single declaration and +// returns that declaration AST. +// +// Fails the test if there isn't exactly one declaration in the source. +func ParseDecl(t *testing.T, src string) ast.Decl { t.Helper() fset := token.NewFileSet() file := Parse(t, fset, src) if l := len(file.Decls); l != 1 { - t.Fatalf("Got %d decls in the sources, expected exactly 1", l) + t.Fatalf(`Got %d decls in the sources, expected exactly 1`, l) } - fdecl, ok := file.Decls[0].(*ast.FuncDecl) + return file.Decls[0] +} + +// ParseSpec parses source with a single declaration containing +// a single specification and returns that specification AST. +// +// Fails the test if there isn't exactly one declaration and +// one specification in the source. +func ParseSpec(t *testing.T, src string) ast.Spec { + t.Helper() + decl := ParseDecl(t, src) + gdecl, ok := decl.(*ast.GenDecl) if !ok { - t.Fatalf("Got %T decl, expected *ast.FuncDecl", file.Decls[0]) + t.Fatalf("Got %T decl, expected *ast.GenDecl", decl) } - return fdecl + if l := len(gdecl.Specs); l != 1 { + t.Fatalf(`Got %d spec in the sources, expected exactly 1`, l) + } + return gdecl.Specs[0] } // Format AST node into a string. From 27e12978b636af57adbdab76ba3e739f72a60b44 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Mon, 18 Dec 2023 16:25:37 -0700 Subject: [PATCH 44/66] Adding purge directive --- build/build.go | 214 ++++++++++++++++++---- build/build_test.go | 292 +++++++++++++++++++++++++------ compiler/astutil/astutil.go | 88 ++++++++-- compiler/astutil/astutil_test.go | 148 +++++++++++----- 4 files changed, 600 insertions(+), 142 deletions(-) diff --git a/build/build.go b/build/build.go index 4cbaa5f32..b32ed9f37 100644 --- a/build/build.go +++ b/build/build.go @@ -125,9 +125,11 @@ type overrideInfo struct { // If false the original code is removed. keepOriginal bool - // pruneMethodBody indicates that the body of the methods should be - // removed because they contain something that is invalid to GopherJS. - pruneMethodBody bool + // purgeMethods indicates that this info is for a type and + // if a method has this type as a receiver should also be removed. + // If the method is defined in the overlays and therefore has its + // own overrides, this will be ignored. + purgeMethods bool } // parseAndAugment parses and returns all .go files of given pkg. @@ -138,12 +140,19 @@ type overrideInfo struct { // The native packages are augmented by the contents of natives.FS in the following way. // The file names do not matter except the usual `_test` suffix. The files for // native overrides get added to the package (even if they have the same name -// as an existing file from the standard library). For function identifiers that exist -// in the original AND the overrides AND that include the following directive in their comment: -// //gopherjs:keep-original, the original identifier in the AST gets prefixed by -// `_gopherjs_original_`. For other identifiers that exist in the original AND the overrides, -// the original identifier gets replaced by `_`. New identifiers that don't exist in original -// package get added. +// as an existing file from the standard library). +// +// - For function identifiers that exist in the original and the overrides +// and have the directive `gopherjs:keep-original`, the original identifier +// in the AST gets prefixed by `_gopherjs_original_`. +// - For identifiers that exist in the original and the overrides and have +// the directive `gopherjs:purge`, both the original and override are +// removed. This is for completely removing something which is currently +// invalid for GopherJS. For any purged types any methods with that type as +// the receiver are also removed. +// - Otherwise for identifiers that exist in the original and the overrides, +// the original is removed. +// - New identifiers that don't exist in original package get added. func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) { jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, isTest, fileSet) @@ -155,12 +164,14 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke overrides := make(map[string]overrideInfo) for _, file := range overlayFiles { augmentOverlayFile(file, overrides) + pruneImports(file) } delete(overrides, "init") for _, file := range originalFiles { augmentOriginalImports(pkg.ImportPath, file) augmentOriginalFile(file, overrides) + pruneImports(file) } return append(overlayFiles, originalFiles...), jsFiles, nil @@ -254,27 +265,37 @@ func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File, // an overlay file AST to collect information such as compiler directives // and perform any initial augmentation needed to the overlay. func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) { - for _, decl := range file.Decls { + for i, decl := range file.Decls { + purgeDecl := astutil.Purge(decl) switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) overrides[k] = overrideInfo{ - keepOriginal: astutil.KeepOriginal(d), - pruneMethodBody: astutil.PruneOriginal(d), + keepOriginal: astutil.KeepOriginal(d), } case *ast.GenDecl: - for _, spec := range d.Specs { + for j, spec := range d.Specs { + purgeSpec := purgeDecl || astutil.Purge(spec) switch s := spec.(type) { case *ast.TypeSpec: - overrides[s.Name.Name] = overrideInfo{} + overrides[s.Name.Name] = overrideInfo{ + purgeMethods: purgeSpec, + } case *ast.ValueSpec: for _, name := range s.Names { overrides[name.Name] = overrideInfo{} } } + if purgeSpec { + d.Specs[j] = nil + } } } + if purgeDecl { + file.Decls[i] = nil + } } + finalizeRemovals(file) } // augmentOriginalImports is the part of parseAndAugment that processes @@ -298,45 +319,176 @@ func augmentOriginalImports(importPath string, file *ast.File) { // original file AST to augment the source code using the overrides from // the overlay files. func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { - for _, decl := range file.Decls { + for i, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: if info, ok := overrides[astutil.FuncKey(d)]; ok { - if info.pruneMethodBody { - // Prune function bodies, since it may contain code invalid for - // GopherJS and pin unwanted imports. - d.Body = nil - } if info.keepOriginal { // Allow overridden function calls // The standard library implementation of foo() becomes _gopherjs_original_foo() d.Name.Name = "_gopherjs_original_" + d.Name.Name } else { - // By setting the name to an underscore, the method will - // not be outputted. Doing this will keep the dependencies the same. - d.Name = ast.NewIdent("_") + file.Decls[i] = nil + } + } else if recvKey := astutil.FuncReceiverKey(d); len(recvKey) > 0 { + // check if the receiver has been purged, if so, remove the method too. + if info, ok := overrides[recvKey]; ok && info.purgeMethods { + file.Decls[i] = nil } } case *ast.GenDecl: - for _, spec := range d.Specs { + for j, spec := range d.Specs { switch s := spec.(type) { case *ast.TypeSpec: if _, ok := overrides[s.Name.Name]; ok { - s.Name = ast.NewIdent("_") - // Change to struct type with no type body and not type parameters. - s.Type = &ast.StructType{Struct: s.Pos(), Fields: &ast.FieldList{}} - s.TypeParams = nil + d.Specs[j] = nil } case *ast.ValueSpec: - for i, name := range s.Names { - if _, ok := overrides[name.Name]; ok { - s.Names[i] = ast.NewIdent("_") + if len(s.Names) == len(s.Values) { + // multi-value context + // e.g. var a, b = 2, foo[int]() + // A removal will also remove the value which may be from a + // function call. This allows us to remove unwanted statements. + // However, if that call has a side effect which still needs + // to be run, add the call into the overlay. + for k, name := range s.Names { + if _, ok := overrides[name.Name]; ok { + s.Names[k] = nil + s.Values[k] = nil + } + } + } else { + // single-value context + // e.g. var a, b = foo[int]() + // If a removal from the overlays makes all returned values unused, + // then remove the function call as well. This allows us to stop + // unwanted calls if needed. If that call has a side effect which + // still needs to be run, add the call into the overlay. + nameRemoved := false + for _, name := range s.Names { + if _, ok := overrides[name.Name]; ok { + nameRemoved = true + name.Name = `_` + } + } + if nameRemoved { + removeSpec := true + for _, name := range s.Names { + if name.Name != `_` { + removeSpec = false + break + } + } + if removeSpec { + d.Specs[j] = nil + } } } } } } } + finalizeRemovals(file) +} + +// pruneImports will remove any unused imports from the file. +// +// This will not remove any dot (`.`) or blank (`_`) imports. +// If the removal of code causes an import to be removed, the init's from that +// import may not be run anymore. If we still need to run an init for an import +// which is no longer used, add it to the overlay as a blank (`_`) import. +func pruneImports(file *ast.File) { + unused := make(map[string]int, len(file.Imports)) + for i, in := range file.Imports { + if name := astutil.ImportName(in); len(name) > 0 { + unused[name] = i + } + } + + // Remove "unused import" for any import which is used. + ast.Walk(astutil.NewCallbackVisitor(func(n ast.Node) bool { + if sel, ok := n.(*ast.SelectorExpr); ok { + if id, ok := sel.X.(*ast.Ident); ok && id.Obj == nil { + delete(unused, id.Name) + } + } + return len(unused) > 0 + }), file) + if len(unused) == 0 { + return + } + + // Remove all unused import specifications + isUnusedSpec := map[*ast.ImportSpec]bool{} + for _, index := range unused { + isUnusedSpec[file.Imports[index]] = true + } + for _, decl := range file.Decls { + if d, ok := decl.(*ast.GenDecl); ok { + for i, spec := range d.Specs { + if other, ok := spec.(*ast.ImportSpec); ok && isUnusedSpec[other] { + d.Specs[i] = nil + } + } + } + } + + // Remove the unused import copies in the file + for _, index := range unused { + file.Imports[index] = nil + } + + finalizeRemovals(file) +} + +// finalizeRemovals fully removes any declaration, specification, imports +// that have been set to nil. This will also remove the file's top-level +// comment group to remove any unassociated comments, including the comments +// from removed code. +func finalizeRemovals(file *ast.File) { + fileChanged := false + for i, decl := range file.Decls { + switch d := decl.(type) { + case nil: + fileChanged = true + case *ast.GenDecl: + declChanged := false + for j, spec := range d.Specs { + switch s := spec.(type) { + case nil: + declChanged = true + case *ast.ValueSpec: + specChanged := false + for _, name := range s.Names { + if name == nil { + specChanged = true + break + } + } + if specChanged { + s.Names = astutil.Squeeze(s.Names) + s.Values = astutil.Squeeze(s.Values) + if len(s.Names) == 0 { + declChanged = true + d.Specs[j] = nil + } + } + } + } + if declChanged { + d.Specs = astutil.Squeeze(d.Specs) + if len(d.Specs) == 0 { + fileChanged = true + file.Decls[i] = nil + } + } + } + } + if fileChanged { + file.Decls = astutil.Squeeze(file.Decls) + } + file.Imports = astutil.Squeeze(file.Imports) + file.Comments = nil } // Options controls build process behavior. diff --git a/build/build_test.go b/build/build_test.go index 8364052d7..81a12b36d 100644 --- a/build/build_test.go +++ b/build/build_test.go @@ -133,15 +133,18 @@ func (m stringSet) String() string { func TestOverlayAugmentation(t *testing.T) { tests := []struct { - desc string - src string - expInfo map[string]overrideInfo + desc string + src string + noCodeChange bool + want string + expInfo map[string]overrideInfo }{ { desc: `remove function`, src: `func Foo(a, b int) int { - return a + b - }`, + return a + b + }`, + noCodeChange: true, expInfo: map[string]overrideInfo{ `Foo`: {}, }, @@ -151,18 +154,10 @@ func TestOverlayAugmentation(t *testing.T) { func Foo(a, b int) int { return a + b }`, + noCodeChange: true, expInfo: map[string]overrideInfo{ `Foo`: {keepOriginal: true}, }, - }, { - desc: `prune function body`, - src: `//gopherjs:prune-original - func Foo(a, b int) int { - return a + b - }`, - expInfo: map[string]overrideInfo{ - `Foo`: {pruneMethodBody: true}, - }, }, { desc: `remove constants and values`, src: `import "time" @@ -173,6 +168,7 @@ func TestOverlayAugmentation(t *testing.T) { ) var now = time.Now`, + noCodeChange: true, expInfo: map[string]overrideInfo{ `foo`: {}, `bar`: {}, @@ -180,14 +176,13 @@ func TestOverlayAugmentation(t *testing.T) { }, }, { desc: `remove types`, - src: `import "time" - - type ( + src: `type ( foo struct {} bar int ) type bob interface {}`, + noCodeChange: true, expInfo: map[string]overrideInfo{ `foo`: {}, `bar`: {}, @@ -195,14 +190,13 @@ func TestOverlayAugmentation(t *testing.T) { }, }, { desc: `remove methods`, - src: `import "cmp" - - type Foo struct { + src: `type Foo struct { bar int } - + func (x *Foo) GetBar() int { return x.bar } func (x *Foo) SetBar(bar int) { x.bar = bar }`, + noCodeChange: true, expInfo: map[string]overrideInfo{ `Foo`: {}, `Foo.GetBar`: {}, @@ -218,22 +212,199 @@ func TestOverlayAugmentation(t *testing.T) { // this is a stub for "func Equal[S ~[]E, E any](s1, s2 S) bool {}" func Equal[S ~[]E, E any](s1, s2 S) bool {}`, + noCodeChange: true, expInfo: map[string]overrideInfo{ `Pointer`: {}, `Sort`: {}, `Equal`: {}, }, + }, { + desc: `prune an unused import`, + src: `import foo "some/other/bar"`, + want: ``, + expInfo: map[string]overrideInfo{}, + }, { + desc: `purge function`, + src: `//gopherjs:purge + func Foo(a, b int) int { + return a + b + }`, + want: ``, + expInfo: map[string]overrideInfo{ + `Foo`: {}, + }, + }, { + desc: `purge struct removes an import`, + src: `import "bytes" + import "math" + + //gopherjs:purge + type Foo struct { + bar *bytes.Buffer + } + + const Tau = math.Pi * 2.0`, + want: `import "math" + + const Tau = math.Pi * 2.0`, + expInfo: map[string]overrideInfo{ + `Foo`: {purgeMethods: true}, + `Tau`: {}, + }, + }, { + desc: `purge whole type decl`, + src: `//gopherjs:purge + type ( + Foo struct {} + bar interface{} + bob int + )`, + want: ``, + expInfo: map[string]overrideInfo{ + `Foo`: {purgeMethods: true}, + `bar`: {purgeMethods: true}, + `bob`: {purgeMethods: true}, + }, + }, { + desc: `purge part of type decl`, + src: `type ( + Foo struct {} + + //gopherjs:purge + bar interface{} + + //gopherjs:purge + bob int + )`, + want: `type ( + Foo struct {} + )`, + expInfo: map[string]overrideInfo{ + `Foo`: {}, + `bar`: {purgeMethods: true}, + `bob`: {purgeMethods: true}, + }, + }, { + desc: `purge all of a type decl`, + src: `type ( + //gopherjs:purge + Foo struct {} + )`, + want: ``, + expInfo: map[string]overrideInfo{ + `Foo`: {purgeMethods: true}, + }, + }, { + desc: `remove and purge values`, + src: `import "time" + + const ( + foo = 42 + //gopherjs:purge + bar = "gopherjs" + ) + + //gopherjs:purge + var now = time.Now`, + want: `const ( + foo = 42 + )`, + expInfo: map[string]overrideInfo{ + `foo`: {}, + `bar`: {}, + `now`: {}, + }, + }, { + desc: `purge all value names`, + src: `//gopherjs:purge + var foo, bar int + + //gopherjs:purge + const bob, sal = 12, 42`, + want: ``, + expInfo: map[string]overrideInfo{ + `foo`: {}, + `bar`: {}, + `bob`: {}, + `sal`: {}, + }, + }, { + desc: `imports not confused by local variables`, + src: `import ( + "cmp" + "time" + ) + + //gopherjs:purge + func Sort[S ~[]E, E cmp.Ordered](x S) {} + + func SecondsSince(start time.Time) int { + cmp := time.Now().Sub(start) + return int(cmp.Second()) + }`, + want: `import ( + "time" + ) + + func SecondsSince(start time.Time) int { + cmp := time.Now().Sub(start) + return int(cmp.Second()) + }`, + expInfo: map[string]overrideInfo{ + `Sort`: {}, + `SecondsSince`: {}, + }, + }, { + desc: `purge generics`, + src: `import "cmp" + + //gopherjs:purge + type Pointer[T any] struct {} + + //gopherjs:purge + func Sort[S ~[]E, E cmp.Ordered](x S) {} + + // stub for "func Equal[S ~[]E, E any](s1, s2 S) bool" + func Equal() {}`, + want: `// stub for "func Equal[S ~[]E, E any](s1, s2 S) bool" + func Equal() {}`, + expInfo: map[string]overrideInfo{ + `Pointer`: {purgeMethods: true}, + `Sort`: {}, + `Equal`: {}, + }, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - pkgName := "package testpackage\n\n" + const pkgName = "package testpackage\n\n" + if test.noCodeChange { + test.want = test.src + } + fsetSrc := token.NewFileSet() fileSrc := srctesting.Parse(t, fsetSrc, pkgName+test.src) overrides := map[string]overrideInfo{} augmentOverlayFile(fileSrc, overrides) + pruneImports(fileSrc) + + buf := &bytes.Buffer{} + _ = printer.Fprint(buf, fsetSrc, fileSrc) + got := buf.String() + + fsetWant := token.NewFileSet() + fileWant := srctesting.Parse(t, fsetWant, pkgName+test.want) + + buf.Reset() + _ = printer.Fprint(buf, fsetWant, fileWant) + want := buf.String() + + if got != want { + t.Errorf("augmentOverlayFile and pruneImports got unexpected code:\n"+ + "returned:\n\t%q\nwant:\n\t%q", got, want) + } for key, expInfo := range test.expInfo { if gotInfo, ok := overrides[key]; !ok { @@ -293,9 +464,7 @@ func TestOriginalAugmentation(t *testing.T) { src: `func Foo(a, b int) int { return a + b }`, - want: `func _(a, b int) int { - return a + b - }`, + want: ``, }, { desc: `keep original function`, info: map[string]overrideInfo{ @@ -322,13 +491,7 @@ func TestOriginalAugmentation(t *testing.T) { var now = time.Now const bar1, bar2 = 21, 42`, - want: `import "time" - - type _ struct { - } - - var _ = time.Now - const _, bar2 = 21, 42`, + want: `const bar2 = 42`, }, { desc: `remove in multi-value context`, info: map[string]overrideInfo{ @@ -340,28 +503,41 @@ func TestOriginalAugmentation(t *testing.T) { want: `const foo, _ = func() (int, int) { return 24, 12 }()`, + }, { + desc: `full remove in multi-value context`, + info: map[string]overrideInfo{ + `bar`: {}, + }, + src: `const _, bar = func() (int, int) { + return 24, 12 + }()`, + want: ``, }, { desc: `remove methods`, info: map[string]overrideInfo{ - `Foo`: {}, `Foo.GetBar`: {}, `Foo.SetBar`: {}, }, - src: `import "cmp" - - type Foo struct { + src: ` + func (x Foo) GetBar() int { return x.bar } + func (x *Foo) SetBar(bar int) { x.bar = bar }`, + want: ``, + }, { + desc: `purge struct and methods`, + info: map[string]overrideInfo{ + `Foo`: {purgeMethods: true}, + }, + src: `type Foo struct{ bar int } - - func (x *Foo) GetBar() int { return x.bar } - func (x *Foo) SetBar(bar int) { x.bar = bar }`, - want: `import "cmp" - type _ struct { - } - - func (x *Foo) _() int { return x.bar } - func (x *Foo) _(bar int) { x.bar = bar }`, + func (f Foo) GetBar() int { return f.bar } + func (f *Foo) SetBar(bar int) { f.bar = bar } + + func NewFoo(bar int) *Foo { return &Foo{bar: bar} }`, + // NewFoo is not removed automatically since + // only functions with Foo as a receiver is removed. + want: `func NewFoo(bar int) *Foo { return &Foo{bar: bar} }`, }, { desc: `remove generics`, info: map[string]overrideInfo{ @@ -377,15 +553,30 @@ func TestOriginalAugmentation(t *testing.T) { // overlay had stub "func Equal() {}" func Equal[S ~[]E, E any](s1, s2 S) bool {}`, - want: `import "cmp" + want: ``, + }, { + desc: `purge generics`, + info: map[string]overrideInfo{ + `Pointer`: {purgeMethods: true}, + `Sort`: {}, + `Equal`: {}, + }, + src: `import "cmp" - type _ struct { - } + type Pointer[T any] struct {} + func (x *Pointer[T]) Load() *T {} + func (x *Pointer[T]) Store(val *T) {} - func _[S ~[]E, E cmp.Ordered](x S) {} + func Sort[S ~[]E, E cmp.Ordered](x S) {} // overlay had stub "func Equal() {}" - func _[S ~[]E, E any](s1, s2 S) bool {}`, + func Equal[S ~[]E, E any](s1, s2 S) bool {}`, + want: ``, + }, { + desc: `prune an unused import`, + info: map[string]overrideInfo{}, + src: `import foo "some/other/bar"`, + want: ``, }, } @@ -398,6 +589,7 @@ func TestOriginalAugmentation(t *testing.T) { augmentOriginalImports(importPath, fileSrc) augmentOriginalFile(fileSrc, test.info) + pruneImports(fileSrc) buf := &bytes.Buffer{} _ = printer.Fprint(buf, fsetSrc, fileSrc) diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index 79292003d..24c1803c0 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -5,7 +5,10 @@ import ( "go/ast" "go/token" "go/types" + "path" + "reflect" "regexp" + "strconv" ) func RemoveParens(e ast.Expr) ast.Expr { @@ -59,6 +62,29 @@ func ImportsUnsafe(file *ast.File) bool { return false } +// ImportName tries to determine the package name for an import. +// +// If the package name isn't specified then this will make a best +// make a best guess using the import path. +// If the import name is dot (`.`), blank (`_`), or there +// was an issue determining the package name then empty is returned. +func ImportName(spec *ast.ImportSpec) string { + var name string + if spec.Name != nil { + name = spec.Name.Name + } else { + importPath, _ := strconv.Unquote(spec.Path.Value) + name = path.Base(importPath) + } + + switch name { + case `_`, `.`, `/`: + return `` + default: + return name + } +} + // FuncKey returns a string, which uniquely identifies a top-level function or // method in a package. func FuncKey(d *ast.FuncDecl) string { @@ -95,19 +121,6 @@ func FuncReceiverKey(d *ast.FuncDecl) string { } } -// PruneOriginal returns true if gopherjs:prune-original directive is present -// before a function decl. -// -// `//gopherjs:prune-original` is a GopherJS-specific directive, which can be -// applied to functions in native overlays and will instruct the augmentation -// logic to delete the body of a standard library function that was replaced. -// This directive can be used to remove code that would be invalid in GopherJS, -// such as code expecting ints to be 64-bit. It should be used with caution -// since it may create unused imports in the original source file. -func PruneOriginal(d *ast.FuncDecl) bool { - return hasDirective(d, `prune-original`) -} - // KeepOriginal returns true if gopherjs:keep-original directive is present // before a function decl. // @@ -120,6 +133,21 @@ func KeepOriginal(d *ast.FuncDecl) bool { return hasDirective(d, `keep-original`) } +// Purge returns true if gopherjs:purge directive is present +// on a struct, interface, type, variable, constant, or function. +// +// `//gopherjs:purge` is a GopherJS-specific directive, which can be +// applied in native overlays and will instruct the augmentation logic to +// delete part of the standard library without a replacement. This directive +// can be used to remove code that would be invalid in GopherJS, such as code +// using unsupported features (e.g. generic interfaces before generics were +// fully supported). It should be used with caution since it may remove needed +// dependencies. If a type is purged, all methods using that type as +// a receiver will also be purged. +func Purge(d any) bool { + return hasDirective(d, `purge`) +} + // anyDocLine calls the given predicate on all associated documentation // lines and line-comment lines from the given node. // If the predicate returns true for any line then true is returned. @@ -228,3 +256,37 @@ func EndsWithReturn(stmts []ast.Stmt) bool { return false } } + +// Squeeze removes all nil nodes from the slice. +// +// The given slice will be modified. This is designed for squeezing +// declaration, specification, imports, and identifier lists. +func Squeeze[E ast.Node, S ~[]E](s S) S { + var zero E + count, dest := len(s), 0 + for src := 0; src < count; src++ { + if !reflect.DeepEqual(s[src], zero) { + // Swap the values, this will put the nil values to the end + // of the slice so that the tail isn't holding onto pointers. + s[dest], s[src] = s[src], s[dest] + dest++ + } + } + return s[:dest] +} + +type CallbackVisitor struct { + predicate func(node ast.Node) bool +} + +func NewCallbackVisitor(predicate func(node ast.Node) bool) *CallbackVisitor { + return &CallbackVisitor{predicate: predicate} +} + +func (v *CallbackVisitor) Visit(node ast.Node) ast.Visitor { + if v.predicate != nil && v.predicate(node) { + return v + } + v.predicate = nil + return nil +} diff --git a/compiler/astutil/astutil_test.go b/compiler/astutil/astutil_test.go index 4330fdb5a..c6ee71977 100644 --- a/compiler/astutil/astutil_test.go +++ b/compiler/astutil/astutil_test.go @@ -4,6 +4,7 @@ import ( "fmt" "go/ast" "go/token" + "strconv" "testing" "github.com/gopherjs/gopherjs/internal/srctesting" @@ -54,6 +55,47 @@ func TestImportsUnsafe(t *testing.T) { } } +func TestImportName(t *testing.T) { + tests := []struct { + desc string + src string + want string + }{ + { + desc: `named import`, + src: `import foo "some/other/bar"`, + want: `foo`, + }, { + desc: `unnamed import`, + src: `import "some/other/bar"`, + want: `bar`, + }, { + desc: `dot import`, + src: `import . "some/other/bar"`, + want: ``, + }, { + desc: `blank import`, + src: `import _ "some/other/bar"`, + want: ``, + }, + } + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + src := "package testpackage\n\n" + test.src + fset := token.NewFileSet() + file := srctesting.Parse(t, fset, src) + if len(file.Imports) != 1 { + t.Fatal(`expected one and only one import`) + } + importSpec := file.Imports[0] + got := ImportName(importSpec) + if got != test.want { + t.Fatalf(`ImportName() returned %q, want %q`, got, test.want) + } + }) + } +} + func TestFuncKey(t *testing.T) { tests := []struct { desc string @@ -101,54 +143,6 @@ func TestFuncKey(t *testing.T) { } } -func TestPruneOriginal(t *testing.T) { - tests := []struct { - desc string - src string - want bool - }{ - { - desc: "no comment", - src: `package testpackage; - func foo() {}`, - want: false, - }, { - desc: "regular godoc", - src: `package testpackage; - // foo does something - func foo() {}`, - want: false, - }, { - desc: "only directive", - src: `package testpackage; - //gopherjs:prune-original - func foo() {}`, - want: true, - }, { - desc: "directive with explanation", - src: `package testpackage; - //gopherjs:prune-original because reasons - func foo() {}`, - want: true, - }, { - desc: "directive in godoc", - src: `package testpackage; - // foo does something - //gopherjs:prune-original - func foo() {}`, - want: true, - }, - } - for _, test := range tests { - t.Run(test.desc, func(t *testing.T) { - fdecl := srctesting.ParseFuncDecl(t, test.src) - if got := PruneOriginal(fdecl); got != test.want { - t.Errorf("PruneOriginal() returned %t, want %t", got, test.want) - } - }) - } -} - func TestHasDirectiveOnDecl(t *testing.T) { tests := []struct { desc string @@ -561,3 +555,61 @@ func TestEndsWithReturn(t *testing.T) { }) } } + +func TestSqueezeIdents(t *testing.T) { + tests := []struct { + desc string + count int + assign []int + }{ + { + desc: `no squeezing`, + count: 5, + assign: []int{0, 1, 2, 3, 4}, + }, { + desc: `missing front`, + count: 5, + assign: []int{3, 4}, + }, { + desc: `missing back`, + count: 5, + assign: []int{0, 1, 2}, + }, { + desc: `missing several`, + count: 10, + assign: []int{1, 2, 3, 6, 8}, + }, { + desc: `empty`, + count: 0, + assign: []int{}, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + input := make([]*ast.Ident, test.count) + for _, i := range test.assign { + input[i] = ast.NewIdent(strconv.Itoa(i)) + } + + result := Squeeze(input) + if len(result) != len(test.assign) { + t.Errorf("Squeeze() returned a slice %d long, want %d", len(result), len(test.assign)) + } + for i, id := range input { + if i < len(result) { + if id == nil { + t.Errorf(`Squeeze() returned a nil in result at %d`, i) + } else { + value, err := strconv.Atoi(id.Name) + if err != nil || value != test.assign[i] { + t.Errorf(`Squeeze() returned %s at %d instead of %d`, id.Name, i, test.assign[i]) + } + } + } else if id != nil { + t.Errorf(`Squeeze() didn't clear out tail of slice, want %d nil`, i) + } + } + }) + } +} From 84774ae19c6ae40efd143c915d4947a5166ca99a Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 19 Dec 2023 16:24:02 -0700 Subject: [PATCH 45/66] Changed to use Inspect --- compiler/astutil/astutil.go | 55 ++++++++++---------------------- compiler/astutil/astutil_test.go | 53 ++++++++++-------------------- 2 files changed, 32 insertions(+), 76 deletions(-) diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index c7481fd27..abe17d94a 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -97,41 +97,6 @@ func KeepOriginal(d *ast.FuncDecl) bool { return hasDirective(d, `keep-original`) } -// anyDocLine calls the given predicate on all associated documentation -// lines and line-comment lines from the given node. -// If the predicate returns true for any line then true is returned. -func anyDocLine(node any, predicate func(line string) bool) bool { - switch a := node.(type) { - case *ast.Comment: - return a != nil && predicate(a.Text) - case *ast.CommentGroup: - if a != nil { - for _, c := range a.List { - if anyDocLine(c, predicate) { - return true - } - } - } - return false - case *ast.Field: - return a != nil && (anyDocLine(a.Doc, predicate) || anyDocLine(a.Comment, predicate)) - case *ast.File: - return a != nil && anyDocLine(a.Doc, predicate) - case *ast.FuncDecl: - return a != nil && anyDocLine(a.Doc, predicate) - case *ast.GenDecl: - return a != nil && anyDocLine(a.Doc, predicate) - case *ast.ImportSpec: - return a != nil && (anyDocLine(a.Doc, predicate) || anyDocLine(a.Comment, predicate)) - case *ast.TypeSpec: - return a != nil && (anyDocLine(a.Doc, predicate) || anyDocLine(a.Comment, predicate)) - case *ast.ValueSpec: - return a != nil && (anyDocLine(a.Doc, predicate) || anyDocLine(a.Comment, predicate)) - default: - panic(fmt.Errorf(`unexpected node type to get doc from: %T`, node)) - } -} - // directiveMatcher is a regex which matches a GopherJS directive // and finds the directive action. var directiveMatcher = regexp.MustCompile(`^\/(?:\/|\*)gopherjs:([\w-]+)`) @@ -144,11 +109,23 @@ var directiveMatcher = regexp.MustCompile(`^\/(?:\/|\*)gopherjs:([\w-]+)`) // must be one or more letter, decimal, underscore, or hyphen. // // see https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives -func hasDirective(node any, directiveAction string) bool { - return anyDocLine(node, func(line string) bool { - m := directiveMatcher.FindStringSubmatch(line) - return len(m) == 2 && m[1] == directiveAction +func hasDirective(node ast.Node, directiveAction string) bool { + foundDirective := false + ast.Inspect(node, func(n ast.Node) bool { + switch a := n.(type) { + case *ast.Comment: + m := directiveMatcher.FindStringSubmatch(a.Text) + if len(m) == 2 && m[1] == directiveAction { + foundDirective = true + } + return false + case *ast.CommentGroup: + return !foundDirective + default: + return n == node + } }) + return foundDirective } // FindLoopStmt tries to find the loop statement among the AST nodes in the diff --git a/compiler/astutil/astutil_test.go b/compiler/astutil/astutil_test.go index 61fc2b18c..80b689b5f 100644 --- a/compiler/astutil/astutil_test.go +++ b/compiler/astutil/astutil_test.go @@ -1,7 +1,6 @@ package astutil import ( - "fmt" "go/ast" "go/token" "testing" @@ -210,6 +209,16 @@ func TestHasDirectiveOnDecl(t *testing.T) { //gopherjs:do-stuff type Foo int`, want: true, + }, { + desc: `directive on specification, not on declaration`, + src: `package testpackage; + type ( + Foo int + + //gopherjs:do-stuff + Bar struct{} + )`, + want: false, }, { desc: `no directive on const declaration`, src: `package testpackage; @@ -268,6 +277,12 @@ func TestHasDirectiveOnSpec(t *testing.T) { src: `package testpackage; type Foo int`, want: false, + }, { + desc: `directive on declaration, not on specification`, + src: `package testpackage; + //gopherjs:do-stuff + type Foo int`, + want: false, }, { desc: `directive in doc on type specification`, src: `package testpackage; @@ -457,42 +472,6 @@ func TestHasDirectiveOnField(t *testing.T) { } } -func TestHasDirectiveBadCase(t *testing.T) { - tests := []struct { - desc string - node any - want string - }{ - { - desc: `untyped nil node`, - node: nil, - want: `unexpected node type to get doc from: `, - }, { - desc: `unexpected node type`, - node: &ast.ArrayType{}, - want: `unexpected node type to get doc from: *ast.ArrayType`, - }, { - desc: `nil expected node type`, - node: (*ast.FuncDecl)(nil), - want: ``, // no panic - }, - } - - for _, test := range tests { - t.Run(test.desc, func(t *testing.T) { - const action = `do-stuff` - var got string - func() { - defer func() { got = fmt.Sprint(recover()) }() - hasDirective(test.node, action) - }() - if got != test.want { - t.Errorf(`hasDirective(%T, %q) returned %s, want %s`, test.node, action, got, test.want) - } - }) - } -} - func TestEndsWithReturn(t *testing.T) { tests := []struct { desc string From 7758c0d142d880a546e128b59f8e4b1bb79d5079 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 19 Dec 2023 16:46:10 -0700 Subject: [PATCH 46/66] Updated to use Inspect and srctesting --- build/build.go | 4 ++-- build/build_test.go | 22 +++++----------------- compiler/astutil/astutil.go | 16 ---------------- 3 files changed, 7 insertions(+), 35 deletions(-) diff --git a/build/build.go b/build/build.go index b32ed9f37..378fcc79f 100644 --- a/build/build.go +++ b/build/build.go @@ -406,14 +406,14 @@ func pruneImports(file *ast.File) { } // Remove "unused import" for any import which is used. - ast.Walk(astutil.NewCallbackVisitor(func(n ast.Node) bool { + ast.Inspect(file, func(n ast.Node) bool { if sel, ok := n.(*ast.SelectorExpr); ok { if id, ok := sel.X.(*ast.Ident); ok && id.Obj == nil { delete(unused, id.Name) } } return len(unused) > 0 - }), file) + }) if len(unused) == 0 { return } diff --git a/build/build_test.go b/build/build_test.go index 81a12b36d..8cb721554 100644 --- a/build/build_test.go +++ b/build/build_test.go @@ -1,10 +1,8 @@ package build import ( - "bytes" "fmt" gobuild "go/build" - "go/printer" "go/token" "strconv" "testing" @@ -390,16 +388,11 @@ func TestOverlayAugmentation(t *testing.T) { augmentOverlayFile(fileSrc, overrides) pruneImports(fileSrc) - buf := &bytes.Buffer{} - _ = printer.Fprint(buf, fsetSrc, fileSrc) - got := buf.String() + got := srctesting.Format(t, fsetSrc, fileSrc) fsetWant := token.NewFileSet() fileWant := srctesting.Parse(t, fsetWant, pkgName+test.want) - - buf.Reset() - _ = printer.Fprint(buf, fsetWant, fileWant) - want := buf.String() + want := srctesting.Format(t, fsetWant, fileWant) if got != want { t.Errorf("augmentOverlayFile and pruneImports got unexpected code:\n"+ @@ -536,7 +529,7 @@ func TestOriginalAugmentation(t *testing.T) { func NewFoo(bar int) *Foo { return &Foo{bar: bar} }`, // NewFoo is not removed automatically since - // only functions with Foo as a receiver is removed. + // only functions with Foo as a receiver are removed. want: `func NewFoo(bar int) *Foo { return &Foo{bar: bar} }`, }, { desc: `remove generics`, @@ -591,16 +584,11 @@ func TestOriginalAugmentation(t *testing.T) { augmentOriginalFile(fileSrc, test.info) pruneImports(fileSrc) - buf := &bytes.Buffer{} - _ = printer.Fprint(buf, fsetSrc, fileSrc) - got := buf.String() + got := srctesting.Format(t, fsetSrc, fileSrc) fsetWant := token.NewFileSet() fileWant := srctesting.Parse(t, fsetWant, pkgName+test.want) - - buf.Reset() - _ = printer.Fprint(buf, fsetWant, fileWant) - want := buf.String() + want := srctesting.Format(t, fsetWant, fileWant) if got != want { t.Errorf("augmentOriginalImports, augmentOriginalFile, and pruneImports got unexpected code:\n"+ diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index 18d693f0a..1f7196766 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -251,19 +251,3 @@ func Squeeze[E ast.Node, S ~[]E](s S) S { } return s[:dest] } - -type CallbackVisitor struct { - predicate func(node ast.Node) bool -} - -func NewCallbackVisitor(predicate func(node ast.Node) bool) *CallbackVisitor { - return &CallbackVisitor{predicate: predicate} -} - -func (v *CallbackVisitor) Visit(node ast.Node) ast.Visitor { - if v.predicate != nil && v.predicate(node) { - return v - } - v.predicate = nil - return nil -} From e03bfea71a099297df61b2371ccf0b5b5779313e Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 16 Jan 2024 14:18:42 -0700 Subject: [PATCH 47/66] Update compiler for go1.19 --- compiler/analysis/info.go | 5 ++++- compiler/expressions.go | 11 +++++++---- compiler/package.go | 30 +++++++++++++++++------------- compiler/statements.go | 8 ++++---- compiler/utils.go | 14 +++++++------- 5 files changed, 39 insertions(+), 29 deletions(-) diff --git a/compiler/analysis/info.go b/compiler/analysis/info.go index c984b726f..304c8808a 100644 --- a/compiler/analysis/info.go +++ b/compiler/analysis/info.go @@ -93,7 +93,10 @@ func (info *Info) newFuncInfo(n ast.Node) *FuncInfo { } func (info *Info) IsBlocking(fun *types.Func) bool { - return len(info.FuncDeclInfos[fun].Blocking) > 0 + if funInfo := info.FuncDeclInfos[fun]; funInfo != nil { + return len(funInfo.Blocking) > 0 + } + panic(fmt.Errorf(`info did not have function declaration for %s`, fun.FullName())) } func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool) *Info { diff --git a/compiler/expressions.go b/compiler/expressions.go index db08cc31f..21971ab5f 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -267,7 +267,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { case token.ARROW: call := &ast.CallExpr{ - Fun: fc.newIdent("$recv", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", t)), types.NewTuple(types.NewVar(0, nil, "", exprType), types.NewVar(0, nil, "", types.Typ[types.Bool])), false)), + Fun: fc.newIdent("$recv", types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewVar(0, nil, "", t)), types.NewTuple(types.NewVar(0, nil, "", exprType), types.NewVar(0, nil, "", types.Typ[types.Bool])), false)), Args: []ast.Expr{e.X}, } fc.Blocking[call] = true @@ -520,8 +520,11 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { ) case *types.Basic: return fc.formatExpr("%e.charCodeAt(%f)", e.X, e.Index) + case *types.Signature: + err := bailout(fmt.Errorf(`unsupported type parameters used at %s`, fc.pkgCtx.fileSet.Position(e.Pos()))) + panic(err) default: - panic(fmt.Sprintf("Unhandled IndexExpr: %T\n", t)) + panic(fmt.Errorf(`unhandled IndexExpr: %T`, t)) } case *ast.SliceExpr: @@ -703,7 +706,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { case "Float": return fc.internalize(recv, types.Typ[types.Float64]) case "Interface": - return fc.internalize(recv, types.NewInterface(nil, nil)) + return fc.internalize(recv, types.NewInterfaceType(nil, nil)) case "Unsafe": return recv default: @@ -998,7 +1001,7 @@ func (fc *funcContext) translateBuiltin(name string, sig *types.Signature, args panic(fmt.Sprintf("Unhandled cap type: %T\n", argType)) } case "panic": - return fc.formatExpr("$panic(%s)", fc.translateImplicitConversion(args[0], types.NewInterface(nil, nil))) + return fc.formatExpr("$panic(%s)", fc.translateImplicitConversion(args[0], types.NewInterfaceType(nil, nil))) case "append": if ellipsis || len(args) == 1 { argStr := fc.translateArgs(sig, args, ellipsis) diff --git a/compiler/package.go b/compiler/package.go index 93c22f1c5..ad918ba3e 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -131,6 +131,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor } if fe, ok := bailingOut(e); ok { // Orderly bailout, return whatever clues we already have. + fmt.Fprintf(fe, `building package %q`, importPath) err = fe return } @@ -269,7 +270,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor } sort.Strings(importedPaths) for _, impPath := range importedPaths { - id := funcCtx.newIdent(fmt.Sprintf(`%s.$init`, funcCtx.pkgCtx.pkgVars[impPath]), types.NewSignature(nil, nil, nil, false)) + id := funcCtx.newIdent(fmt.Sprintf(`%s.$init`, funcCtx.pkgCtx.pkgVars[impPath]), types.NewSignatureType(nil, nil, nil, nil, nil, false)) call := &ast.CallExpr{Fun: id} funcCtx.Blocking[call] = true funcCtx.Flattened[call] = true @@ -287,13 +288,6 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor switch d := decl.(type) { case *ast.FuncDecl: sig := funcCtx.pkgCtx.Defs[d.Name].(*types.Func).Type().(*types.Signature) - var recvType types.Type - if sig.Recv() != nil { - recvType = sig.Recv().Type() - if ptr, isPtr := recvType.(*types.Pointer); isPtr { - recvType = ptr.Elem() - } - } if sig.Recv() == nil { funcCtx.objectName(funcCtx.pkgCtx.Defs[d.Name].(*types.Func)) // register toplevel name } @@ -421,7 +415,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor d.DceObjectFilter = "" case "init": d.InitCode = funcCtx.CatchOutput(1, func() { - id := funcCtx.newIdent("", types.NewSignature(nil, nil, nil, false)) + id := funcCtx.newIdent("", types.NewSignatureType(nil, nil, nil, nil, nil, false)) funcCtx.pkgCtx.Uses[id] = o call := &ast.CallExpr{Fun: id} if len(funcCtx.pkgCtx.FuncDeclInfos[o].Blocking) != 0 { @@ -438,7 +432,14 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor if isPointer { namedRecvType = ptr.Elem().(*types.Named) } - d.NamedRecvType = funcCtx.objectName(namedRecvType.Obj()) + if namedRecvType.TypeParams() != nil { + return nil, scanner.Error{ + Pos: fileSet.Position(o.Pos()), + Msg: fmt.Sprintf("type %s: type parameters are not supported by GopherJS: https://github.com/gopherjs/gopherjs/issues/1013", o.FullName()), + } + } + name := funcCtx.objectName(namedRecvType.Obj()) + d.NamedRecvType = name d.DceObjectFilter = namedRecvType.Obj().Name() if !fun.Name.IsExported() { d.DceMethodFilter = o.Name() + "~" @@ -454,7 +455,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor if mainFunc == nil { return nil, fmt.Errorf("missing main function") } - id := funcCtx.newIdent("", types.NewSignature(nil, nil, nil, false)) + id := funcCtx.newIdent("", types.NewSignatureType(nil, nil, nil, nil, nil, false)) funcCtx.pkgCtx.Uses[id] = mainFunc call := &ast.CallExpr{Fun: id} ifStmt := &ast.IfStmt{ @@ -636,9 +637,9 @@ func (fc *funcContext) initArgs(ty types.Type) string { case *types.Map: return fmt.Sprintf("%s, %s", fc.typeName(t.Key()), fc.typeName(t.Elem())) case *types.Pointer: - return fmt.Sprintf("%s", fc.typeName(t.Elem())) + return fc.typeName(t.Elem()) case *types.Slice: - return fmt.Sprintf("%s", fc.typeName(t.Elem())) + return fc.typeName(t.Elem()) case *types.Signature: params := make([]string, t.Params().Len()) for i := range params { @@ -660,6 +661,9 @@ func (fc *funcContext) initArgs(ty types.Type) string { fields[i] = fmt.Sprintf(`{prop: "%s", name: %s, embedded: %t, exported: %t, typ: %s, tag: %s}`, fieldName(t, i), encodeString(field.Name()), field.Anonymous(), field.Exported(), fc.typeName(field.Type()), encodeString(t.Tag(i))) } return fmt.Sprintf(`"%s", [%s]`, pkgPath, strings.Join(fields, ", ")) + case *types.TypeParam: + err := bailout(fmt.Errorf(`%v has unexpected generic type parameter %T`, ty, ty)) + panic(err) default: err := bailout(fmt.Errorf("%v has unexpected type %T", ty, ty)) panic(err) diff --git a/compiler/statements.go b/compiler/statements.go index f8d791948..8518f9b71 100644 --- a/compiler/statements.go +++ b/compiler/statements.go @@ -32,7 +32,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { panic(err) // Continue orderly bailout. } - // Oh noes, we've tried to compile something so bad that compiler paniced + // Oh noes, we've tried to compile something so bad that compiler panicked // and ran away. Let's gather some debugging clues. bail := bailout(err) pos := stmt.Pos() @@ -471,7 +471,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { case *ast.SendStmt: chanType := fc.pkgCtx.TypeOf(s.Chan).Underlying().(*types.Chan) call := &ast.CallExpr{ - Fun: fc.newIdent("$send", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", chanType), types.NewVar(0, nil, "", chanType.Elem())), nil, false)), + Fun: fc.newIdent("$send", types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewVar(0, nil, "", chanType), types.NewVar(0, nil, "", chanType.Elem())), nil, false)), Args: []ast.Expr{s.Chan, fc.newIdent(fc.translateImplicitConversionWithCloning(s.Value, chanType.Elem()).String(), chanType.Elem())}, } fc.Blocking[call] = true @@ -522,8 +522,8 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { } selectCall := fc.setType(&ast.CallExpr{ - Fun: fc.newIdent("$select", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", types.NewInterface(nil, nil))), types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])), false)), - Args: []ast.Expr{fc.newIdent(fmt.Sprintf("[%s]", strings.Join(channels, ", ")), types.NewInterface(nil, nil))}, + Fun: fc.newIdent("$select", types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewVar(0, nil, "", types.NewInterfaceType(nil, nil))), types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])), false)), + Args: []ast.Expr{fc.newIdent(fmt.Sprintf("[%s]", strings.Join(channels, ", ")), types.NewInterfaceType(nil, nil))}, }, types.Typ[types.Int]) if !hasDefault { fc.Blocking[selectCall] = true diff --git a/compiler/utils.go b/compiler/utils.go index 8b18c29c1..058437f67 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -518,13 +518,13 @@ func isBlank(expr ast.Expr) bool { // // For example, consider a Go type: // -// type SecretInt int -// func (_ SecretInt) String() string { return "" } +// type SecretInt int +// func (_ SecretInt) String() string { return "" } // -// func main() { -// var i SecretInt = 1 -// println(i.String()) -// } +// func main() { +// var i SecretInt = 1 +// println(i.String()) +// } // // For this example the compiler will generate code similar to the snippet below: // @@ -765,7 +765,7 @@ func (st signatureTypes) Param(i int, ellipsis bool) types.Type { } if !st.Sig.Variadic() { // This should never happen if the code was type-checked successfully. - panic(fmt.Errorf("Tried to access parameter %d of a non-variadic signature %s", i, st.Sig)) + panic(fmt.Errorf("tried to access parameter %d of a non-variadic signature %s", i, st.Sig)) } if ellipsis { return st.VariadicType() From 5f9eeb8ca88cd5a1215840df285cc3fc77825c3b Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 16 Jan 2024 13:40:39 -0700 Subject: [PATCH 48/66] Fix Build Issue --- build/build.go | 82 ++++++++++++++++++++++++--- build/build_test.go | 109 +++++++++++++++++++++++++++++++++++- compiler/astutil/astutil.go | 32 +++++++++++ 3 files changed, 212 insertions(+), 11 deletions(-) diff --git a/build/build.go b/build/build.go index 378fcc79f..30e2b15aa 100644 --- a/build/build.go +++ b/build/build.go @@ -130,6 +130,11 @@ type overrideInfo struct { // If the method is defined in the overlays and therefore has its // own overrides, this will be ignored. purgeMethods bool + + // overrideSignature is the function definition given in the overlays + // that should be used to replace the signature in the originals. + // Only receivers, type parameters, parameters, and results will be used. + overrideSignature *ast.FuncDecl } // parseAndAugment parses and returns all .go files of given pkg. @@ -270,9 +275,14 @@ func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) { switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) - overrides[k] = overrideInfo{ + oi := overrideInfo{ keepOriginal: astutil.KeepOriginal(d), } + if astutil.OverrideSignature(d) { + oi.overrideSignature = d + purgeDecl = true + } + overrides[k] = oi case *ast.GenDecl: for j, spec := range d.Specs { purgeSpec := purgeDecl || astutil.Purge(spec) @@ -323,11 +333,21 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { switch d := decl.(type) { case *ast.FuncDecl: if info, ok := overrides[astutil.FuncKey(d)]; ok { + removeFunc := true if info.keepOriginal { // Allow overridden function calls // The standard library implementation of foo() becomes _gopherjs_original_foo() d.Name.Name = "_gopherjs_original_" + d.Name.Name - } else { + removeFunc = false + } + if overSig := info.overrideSignature; overSig != nil { + d.Recv = overSig.Recv + d.Type.TypeParams = overSig.Type.TypeParams + d.Type.Params = overSig.Type.Params + d.Type.Results = overSig.Type.Results + removeFunc = false + } + if removeFunc { file.Decls[i] = nil } } else if recvKey := astutil.FuncReceiverKey(d); len(recvKey) > 0 { @@ -391,13 +411,32 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { finalizeRemovals(file) } +// isOnlyImports determines if this file is empty except for imports. +func isOnlyImports(file *ast.File) bool { + for _, decl := range file.Decls { + if gen, ok := decl.(*ast.GenDecl); !ok || gen.Tok != token.IMPORT { + return false + } + } + return true +} + // pruneImports will remove any unused imports from the file. // -// This will not remove any dot (`.`) or blank (`_`) imports. +// This will not remove any dot (`.`) or blank (`_`) imports, unless +// there are no declarations or directives meaning that all the imports +// should be cleared. // If the removal of code causes an import to be removed, the init's from that // import may not be run anymore. If we still need to run an init for an import // which is no longer used, add it to the overlay as a blank (`_`) import. func pruneImports(file *ast.File) { + if isOnlyImports(file) && !astutil.HasDirectivePrefix(file, `//go:linkname `) { + // The file is empty, remove all imports including any `.` or `_` imports. + file.Imports = nil + file.Decls = nil + return + } + unused := make(map[string]int, len(file.Imports)) for i, in := range file.Imports { if name := astutil.ImportName(in); len(name) > 0 { @@ -405,7 +444,7 @@ func pruneImports(file *ast.File) { } } - // Remove "unused import" for any import which is used. + // Remove "unused imports" for any import which is used. ast.Inspect(file, func(n ast.Node) bool { if sel, ok := n.(*ast.SelectorExpr); ok { if id, ok := sel.X.(*ast.Ident); ok && id.Obj == nil { @@ -418,6 +457,24 @@ func pruneImports(file *ast.File) { return } + // Remove "unused imports" for any import used for a directive. + directiveImports := map[string]string{ + `unsafe`: `//go:linkname `, + `embed`: `//go:embed `, + } + for name, index := range unused { + in := file.Imports[index] + path, _ := strconv.Unquote(in.Path.Value) + directivePrefix, hasPath := directiveImports[path] + if hasPath && astutil.HasDirectivePrefix(file, directivePrefix) { + delete(unused, name) + if len(unused) == 0 { + return + } + break + } + } + // Remove all unused import specifications isUnusedSpec := map[*ast.ImportSpec]bool{} for _, index := range unused { @@ -442,9 +499,8 @@ func pruneImports(file *ast.File) { } // finalizeRemovals fully removes any declaration, specification, imports -// that have been set to nil. This will also remove the file's top-level -// comment group to remove any unassociated comments, including the comments -// from removed code. +// that have been set to nil. This will also remove any unassociated comment +// groups, including the comments from removed code. func finalizeRemovals(file *ast.File) { fileChanged := false for i, decl := range file.Decls { @@ -487,8 +543,18 @@ func finalizeRemovals(file *ast.File) { if fileChanged { file.Decls = astutil.Squeeze(file.Decls) } + file.Imports = astutil.Squeeze(file.Imports) - file.Comments = nil + + file.Comments = nil // clear this first so ast.Inspect doesn't walk it. + remComments := []*ast.CommentGroup{} + ast.Inspect(file, func(n ast.Node) bool { + if cg, ok := n.(*ast.CommentGroup); ok { + remComments = append(remComments, cg) + } + return true + }) + file.Comments = remComments } // Options controls build process behavior. diff --git a/build/build_test.go b/build/build_test.go index 8cb721554..f54281cb8 100644 --- a/build/build_test.go +++ b/build/build_test.go @@ -371,6 +371,36 @@ func TestOverlayAugmentation(t *testing.T) { `Sort`: {}, `Equal`: {}, }, + }, { + desc: `remove unsafe and embed if not needed`, + src: `import "unsafe" + import "embed" + + //gopherjs:purge + var eFile embed.FS + + //gopherjs:purge + func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)`, + want: ``, + expInfo: map[string]overrideInfo{ + `SwapPointer`: {}, + `eFile`: {}, + }, + }, { + desc: `keep unsafe and embed for directives`, + src: `import "unsafe" + import "embed" + + //go:embed hello.txt + var eFile embed.FS + + //go:linkname runtimeNano runtime.nanotime + func runtimeNano() int64`, + noCodeChange: true, + expInfo: map[string]overrideInfo{ + `eFile`: {}, + `runtimeNano`: {}, + }, }, } @@ -539,6 +569,9 @@ func TestOriginalAugmentation(t *testing.T) { `Equal`: {}, }, src: `import "cmp" + + // keeps the isOnlyImports from skipping what is being tested. + func foo() {} type Pointer[T any] struct {} @@ -546,7 +579,8 @@ func TestOriginalAugmentation(t *testing.T) { // overlay had stub "func Equal() {}" func Equal[S ~[]E, E any](s1, s2 S) bool {}`, - want: ``, + want: `// keeps the isOnlyImports from skipping what is being tested. + func foo() {}`, }, { desc: `purge generics`, info: map[string]overrideInfo{ @@ -556,6 +590,9 @@ func TestOriginalAugmentation(t *testing.T) { }, src: `import "cmp" + // keeps the isOnlyImports from skipping what is being tested. + func foo() {} + type Pointer[T any] struct {} func (x *Pointer[T]) Load() *T {} func (x *Pointer[T]) Store(val *T) {} @@ -564,12 +601,78 @@ func TestOriginalAugmentation(t *testing.T) { // overlay had stub "func Equal() {}" func Equal[S ~[]E, E any](s1, s2 S) bool {}`, - want: ``, + want: `// keeps the isOnlyImports from skipping what is being tested. + func foo() {}`, }, { desc: `prune an unused import`, info: map[string]overrideInfo{}, - src: `import foo "some/other/bar"`, + src: `import foo "some/other/bar" + + // keeps the isOnlyImports from skipping what is being tested. + func foo() {}`, + want: `// keeps the isOnlyImports from skipping what is being tested. + func foo() {}`, + }, { + desc: `override signature of function`, + info: map[string]overrideInfo{ + `Foo`: { + overrideSignature: srctesting.ParseFuncDecl(t, + `package whatever + func Foo(a, b any) (any, bool) {}`), + }, + }, + src: `func Foo[T comparable](a, b T) (T, bool) { + if a == b { + return a, true + } + return b, false + }`, + want: `func Foo(a, b any) (any, bool) { + if a == b { + return a, true + } + return b, false + }`, + }, { + desc: `override signature of method`, + info: map[string]overrideInfo{ + `Foo.Bar`: { + overrideSignature: srctesting.ParseFuncDecl(t, + `package whatever + func (r *Foo) Bar(a, b any) (any, bool) {}`), + }, + }, + src: `func (r *Foo[T]) Bar(a, b T) (T, bool) { + if r.isSame(a, b) { + return a, true + } + return b, false + }`, + want: `func (r *Foo) Bar(a, b any) (any, bool) { + if r.isSame(a, b) { + return a, true + } + return b, false + }`, + }, { + desc: `empty file removes all imports`, + info: map[string]overrideInfo{ + `foo`: {}, + }, + src: `import . "math/rand" + func foo() int { + return Int() + }`, want: ``, + }, { + desc: `empty file with directive`, + info: map[string]overrideInfo{ + `foo`: {}, + }, + src: `//go:linkname foo bar + import _ "unsafe"`, + want: `//go:linkname foo bar + import _ "unsafe"`, }, } diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index 1f7196766..5cfe2dbd3 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -9,6 +9,7 @@ import ( "reflect" "regexp" "strconv" + "strings" ) func RemoveParens(e ast.Expr) ast.Expr { @@ -148,6 +149,24 @@ func Purge(d ast.Node) bool { return hasDirective(d, `purge`) } +// OverrideSignature returns true if gopherjs:override-signature directive is +// present on a function. +// +// `//gopherjs:override-signature` is a GopherJS-specific directive, which can +// be applied in native overlays and will instruct the augmentation logic to +// replace the original function signature which has the same FuncKey with the +// signature defined in the native overlays. +// This directive can be used to remove generics from a function signature or +// to replace a receiver of a function with another one. The given native +// overlay function will be removed, so no method body is needed in the overlay. +// +// The new signature may not contain types which require a new import since +// the imports will not be automatically added when needed, only removed. +// Use a type alias in the overlay to deal manage imports. +func OverrideSignature(d *ast.FuncDecl) bool { + return hasDirective(d, `override-signature`) +} + // directiveMatcher is a regex which matches a GopherJS directive // and finds the directive action. var directiveMatcher = regexp.MustCompile(`^\/(?:\/|\*)gopherjs:([\w-]+)`) @@ -179,6 +198,19 @@ func hasDirective(node ast.Node, directiveAction string) bool { return foundDirective } +// HasDirectivePrefix determines if any line in the given file +// has the given directive prefix in it. +func HasDirectivePrefix(file *ast.File, prefix string) bool { + for _, cg := range file.Comments { + for _, c := range cg.List { + if strings.HasPrefix(c.Text, prefix) { + return true + } + } + } + return false +} + // FindLoopStmt tries to find the loop statement among the AST nodes in the // |stack| that corresponds to the break/continue statement represented by // branch. From a83c5fa72a01c93c19077fedec6fb5c2fa965915 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 16 Jan 2024 15:06:25 -0700 Subject: [PATCH 49/66] Updating natives for go1.19 --- .../natives/src/crypto/elliptic/nistec.go | 81 ++++++++ .../src/crypto/internal/boring/bbig/big.go | 42 ++++ .../src/crypto/internal/nistec/nistec_test.go | 88 +++++++++ .../src/crypto/internal/nistec/wrapper.go | 185 ++++++++++++++++++ compiler/natives/src/go/token/position.go | 26 +++ compiler/natives/src/hash/maphash/maphash.go | 2 +- compiler/natives/src/runtime/runtime.go | 3 +- compiler/natives/src/sync/atomic/atomic.go | 3 + .../natives/src/sync/atomic/atomic_test.go | 53 ++++- compiler/natives/src/sync/sync.go | 13 +- compiler/natives/src/testing/helper_test.go | 4 + .../natives/src/testing/helperfuncs_test.go | 13 ++ 12 files changed, 504 insertions(+), 9 deletions(-) create mode 100644 compiler/natives/src/crypto/elliptic/nistec.go create mode 100644 compiler/natives/src/crypto/internal/boring/bbig/big.go create mode 100644 compiler/natives/src/crypto/internal/nistec/nistec_test.go create mode 100644 compiler/natives/src/crypto/internal/nistec/wrapper.go create mode 100644 compiler/natives/src/go/token/position.go create mode 100644 compiler/natives/src/testing/helperfuncs_test.go diff --git a/compiler/natives/src/crypto/elliptic/nistec.go b/compiler/natives/src/crypto/elliptic/nistec.go new file mode 100644 index 000000000..326c602d5 --- /dev/null +++ b/compiler/natives/src/crypto/elliptic/nistec.go @@ -0,0 +1,81 @@ +//go:build js +// +build js + +package elliptic + +import ( + "crypto/internal/nistec" + "math/big" +) + +// nistPoint uses generics so must be removed for generic-less GopherJS. +// All the following code changes in this file are to make p224, p256, +// p521, and p384 still function correctly without this generic struct. +// +//gopherjs:purge for go1.19 without generics +type nistPoint[T any] interface{} + +// nistCurve replaces the generics with a version using the wrappedPoint +// interface, then update all the method signatures to also use wrappedPoint. +type nistCurve struct { + newPoint func() nistec.WrappedPoint + params *CurveParams +} + +//gopherjs:override-signature +func (curve *nistCurve) Params() *CurveParams + +//gopherjs:override-signature +func (curve *nistCurve) IsOnCurve(x, y *big.Int) bool + +//gopherjs:override-signature +func (curve *nistCurve) pointFromAffine(x, y *big.Int) (p nistec.WrappedPoint, err error) + +//gopherjs:override-signature +func (curve *nistCurve) pointToAffine(p nistec.WrappedPoint) (x, y *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) normalizeScalar(scalar []byte) []byte + +//gopherjs:override-signature +func (curve *nistCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) ScalarBaseMult(scalar []byte) (*big.Int, *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) CombinedMult(Px, Py *big.Int, s1, s2 []byte) (x, y *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) Unmarshal(data []byte) (x, y *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) UnmarshalCompressed(data []byte) (x, y *big.Int) + +var p224 = &nistCurve{ + newPoint: nistec.NewP224WrappedPoint, +} + +type p256Curve struct { + nistCurve +} + +var p256 = &p256Curve{ + nistCurve: nistCurve{ + newPoint: nistec.NewP256WrappedPoint, + }, +} + +var p521 = &nistCurve{ + newPoint: nistec.NewP521WrappedPoint, +} + +var p384 = &nistCurve{ + newPoint: nistec.NewP384WrappedPoint, +} diff --git a/compiler/natives/src/crypto/internal/boring/bbig/big.go b/compiler/natives/src/crypto/internal/boring/bbig/big.go new file mode 100644 index 000000000..30ffe1fcd --- /dev/null +++ b/compiler/natives/src/crypto/internal/boring/bbig/big.go @@ -0,0 +1,42 @@ +//go:build js +// +build js + +package bbig + +import ( + "crypto/internal/boring" + "math/big" +) + +func Enc(b *big.Int) boring.BigInt { + if b == nil { + return nil + } + x := b.Bits() + if len(x) == 0 { + return boring.BigInt{} + } + // Replacing original which uses unsafe: + // return unsafe.Slice((*uint)(&x[0]), len(x)) + b2 := make(boring.BigInt, len(x)) + for i, w := range x { + b2[i] = uint(w) + } + return b2 +} + +func Dec(b boring.BigInt) *big.Int { + if b == nil { + return nil + } + if len(b) == 0 { + return new(big.Int) + } + // Replacing original which uses unsafe: + //x := unsafe.Slice((*big.Word)(&b[0]), len(b)) + x := make([]big.Word, len(b)) + for i, w := range b { + x[i] = big.Word(w) + } + return new(big.Int).SetBits(x) +} diff --git a/compiler/natives/src/crypto/internal/nistec/nistec_test.go b/compiler/natives/src/crypto/internal/nistec/nistec_test.go new file mode 100644 index 000000000..f89aaeae0 --- /dev/null +++ b/compiler/natives/src/crypto/internal/nistec/nistec_test.go @@ -0,0 +1,88 @@ +//go:build js +// +build js + +package nistec_test + +import ( + "crypto/elliptic" + "testing" +) + +func TestAllocations(t *testing.T) { + t.Skip("testing.AllocsPerRun not supported in GopherJS") +} + +//gopherjs:purge +type nistPoint[T any] interface{} + +func TestEquivalents(t *testing.T) { + t.Run("P224", func(t *testing.T) { + testEquivalents(t, nistec.NewP224WrappedPoint, nistec.NewP224WrappedGenerator, elliptic.P224()) + }) + t.Run("P256", func(t *testing.T) { + testEquivalents(t, nistec.NewP256WrappedPoint, nistec.NewP256WrappedGenerator, elliptic.P256()) + }) + t.Run("P384", func(t *testing.T) { + testEquivalents(t, nistec.NewP384WrappedPoint, nistec.NewP384WrappedGenerator, elliptic.P384()) + }) + t.Run("P521", func(t *testing.T) { + testEquivalents(t, nistec.NewP521WrappedPoint, nistec.NewP521WrappedGenerator, elliptic.P521()) + }) +} + +//gopherjs:override-signature +func testEquivalents(t *testing.T, newPoint, newGenerator func() WrappedPoint, c elliptic.Curve) {} + +func TestScalarMult(t *testing.T) { + t.Run("P224", func(t *testing.T) { + testScalarMult(t, nistec.NewP224WrappedPoint, nistec.NewP224WrappedGenerator, elliptic.P224()) + }) + t.Run("P256", func(t *testing.T) { + testScalarMult(t, nistec.NewP256WrappedPoint, nistec.NewP256WrappedGenerator, elliptic.P256()) + }) + t.Run("P384", func(t *testing.T) { + testScalarMult(t, nistec.NewP384WrappedPoint, nistec.NewP384WrappedGenerator, elliptic.P384()) + }) + t.Run("P521", func(t *testing.T) { + testScalarMult(t, nistec.NewP521WrappedPoint, nistec.NewP521WrappedGenerator, elliptic.P521()) + }) +} + +//gopherjs:override-signature +func testScalarMult(t *testing.T, newPoint, newGenerator func() WrappedPoint, c elliptic.Curve) + +func BenchmarkScalarMult(b *testing.B) { + b.Run("P224", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP224WrappedGenerator(), 28) + }) + b.Run("P256", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP256GWrappedenerator(), 32) + }) + b.Run("P384", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP384WrappedGenerator(), 48) + }) + b.Run("P521", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP521WrappedGenerator(), 66) + }) +} + +//gopherjs:override-signature +func benchmarkScalarMult(b *testing.B, p WrappedPoint, scalarSize int) + +func BenchmarkScalarBaseMult(b *testing.B) { + b.Run("P224", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP22Wrapped4Generator(), 28) + }) + b.Run("P256", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP256WrappedGenerator(), 32) + }) + b.Run("P384", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP384WrappedGenerator(), 48) + }) + b.Run("P521", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP521GWrappedenerator(), 66) + }) +} + +//gopherjs:override-signature +func benchmarkScalarBaseMult(b *testing.B, p WrappedPoint, scalarSize int) diff --git a/compiler/natives/src/crypto/internal/nistec/wrapper.go b/compiler/natives/src/crypto/internal/nistec/wrapper.go new file mode 100644 index 000000000..0d6706b52 --- /dev/null +++ b/compiler/natives/src/crypto/internal/nistec/wrapper.go @@ -0,0 +1,185 @@ +//go:build js +// +build js + +package nistec + +type WrappedPoint interface { + Bytes() []byte + SetBytes(b []byte) (WrappedPoint, error) + Add(w1, w2 WrappedPoint) WrappedPoint + Double(w1 WrappedPoint) WrappedPoint + ScalarMult(w1 WrappedPoint, scalar []byte) (WrappedPoint, error) + ScalarBaseMult(scalar []byte) (WrappedPoint, error) +} + +type p224Wrapper struct { + point *P224Point +} + +func wrapP224(point *P224Point) WrappedPoint { + return p224Wrapper{point: point} +} + +func NewP224WrappedPoint() WrappedPoint { + return wrapP224(NewP224Point()) +} + +func NewP224WrappedGenerator() WrappedPoint { + return wrapP224(NewP224Generator()) +} + +func (w p224Wrapper) Bytes() []byte { + return w.point.Bytes() +} + +func (w p224Wrapper) SetBytes(b []byte) (WrappedPoint, error) { + p, err := w.point.SetBytes(b) + return wrapP224(p), err +} + +func (w p224Wrapper) Add(w1, w2 WrappedPoint) WrappedPoint { + return wrapP224(w.point.Add(w1.(p224Wrapper).point, w2.(p224Wrapper).point)) +} + +func (w p224Wrapper) Double(w1 WrappedPoint) WrappedPoint { + return wrapP224(w.point.Double(w1.(p224Wrapper).point)) +} + +func (w p224Wrapper) ScalarMult(w1 WrappedPoint, scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarMult(w1.(p224Wrapper).point, scalar) + return wrapP224(p), err +} + +func (w p224Wrapper) ScalarBaseMult(scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarBaseMult(scalar) + return wrapP224(p), err +} + +type p256Wrapper struct { + point *P256Point +} + +func wrapP256(point *P256Point) WrappedPoint { + return p256Wrapper{point: point} +} + +func NewP256WrappedPoint() WrappedPoint { + return wrapP256(NewP256Point()) +} + +func NewP256WrappedGenerator() WrappedPoint { + return wrapP256(NewP256Generator()) +} + +func (w p256Wrapper) Bytes() []byte { + return w.point.Bytes() +} + +func (w p256Wrapper) SetBytes(b []byte) (WrappedPoint, error) { + p, err := w.point.SetBytes(b) + return wrapP256(p), err +} + +func (w p256Wrapper) Add(w1, w2 WrappedPoint) WrappedPoint { + return wrapP256(w.point.Add(w1.(p256Wrapper).point, w2.(p256Wrapper).point)) +} + +func (w p256Wrapper) Double(w1 WrappedPoint) WrappedPoint { + return wrapP256(w.point.Double(w1.(p256Wrapper).point)) +} + +func (w p256Wrapper) ScalarMult(w1 WrappedPoint, scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarMult(w1.(p256Wrapper).point, scalar) + return wrapP256(p), err +} + +func (w p256Wrapper) ScalarBaseMult(scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarBaseMult(scalar) + return wrapP256(p), err +} + +type p521Wrapper struct { + point *P521Point +} + +func wrapP521(point *P521Point) WrappedPoint { + return p521Wrapper{point: point} +} + +func NewP521WrappedPoint() WrappedPoint { + return wrapP521(NewP521Point()) +} + +func NewP521WrappedGenerator() WrappedPoint { + return wrapP521(NewP521Generator()) +} + +func (w p521Wrapper) Bytes() []byte { + return w.point.Bytes() +} + +func (w p521Wrapper) SetBytes(b []byte) (WrappedPoint, error) { + p, err := w.point.SetBytes(b) + return wrapP521(p), err +} + +func (w p521Wrapper) Add(w1, w2 WrappedPoint) WrappedPoint { + return wrapP521(w.point.Add(w1.(p521Wrapper).point, w2.(p521Wrapper).point)) +} + +func (w p521Wrapper) Double(w1 WrappedPoint) WrappedPoint { + return wrapP521(w.point.Double(w1.(p521Wrapper).point)) +} + +func (w p521Wrapper) ScalarMult(w1 WrappedPoint, scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarMult(w1.(p521Wrapper).point, scalar) + return wrapP521(p), err +} + +func (w p521Wrapper) ScalarBaseMult(scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarBaseMult(scalar) + return wrapP521(p), err +} + +type p384Wrapper struct { + point *P384Point +} + +func wrapP384(point *P384Point) WrappedPoint { + return p384Wrapper{point: point} +} + +func NewP384WrappedPoint() WrappedPoint { + return wrapP384(NewP384Point()) +} + +func NewP384WrappedGenerator() WrappedPoint { + return wrapP384(NewP384Generator()) +} + +func (w p384Wrapper) Bytes() []byte { + return w.point.Bytes() +} + +func (w p384Wrapper) SetBytes(b []byte) (WrappedPoint, error) { + p, err := w.point.SetBytes(b) + return wrapP384(p), err +} + +func (w p384Wrapper) Add(w1, w2 WrappedPoint) WrappedPoint { + return wrapP384(w.point.Add(w1.(p384Wrapper).point, w2.(p384Wrapper).point)) +} + +func (w p384Wrapper) Double(w1 WrappedPoint) WrappedPoint { + return wrapP384(w.point.Double(w1.(p384Wrapper).point)) +} + +func (w p384Wrapper) ScalarMult(w1 WrappedPoint, scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarMult(w1.(p384Wrapper).point, scalar) + return wrapP384(p), err +} + +func (w p384Wrapper) ScalarBaseMult(scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarBaseMult(scalar) + return wrapP384(p), err +} diff --git a/compiler/natives/src/go/token/position.go b/compiler/natives/src/go/token/position.go new file mode 100644 index 000000000..c7fabb810 --- /dev/null +++ b/compiler/natives/src/go/token/position.go @@ -0,0 +1,26 @@ +//go:build js +// +build js + +package token + +import ( + "sync" + "sync/atomic" + "unsafe" +) + +type FileSet struct { + mutex sync.RWMutex + base int + files []*File + + // replaced atomic.Pointer[File] for go1.19 without generics + last atomicFilePointer +} + +type atomicFilePointer struct { + v unsafe.Pointer +} + +func (x *atomicFilePointer) Load() *File { return (*File)(atomic.LoadPointer(&x.v)) } +func (x *atomicFilePointer) Store(val *File) { atomic.StorePointer(&x.v, unsafe.Pointer(val)) } diff --git a/compiler/natives/src/hash/maphash/maphash.go b/compiler/natives/src/hash/maphash/maphash.go index 877366f04..dceff2c62 100644 --- a/compiler/natives/src/hash/maphash/maphash.go +++ b/compiler/natives/src/hash/maphash/maphash.go @@ -8,7 +8,7 @@ var hashkey [4]uint32 func init() { for i := range hashkey { - hashkey[i] = runtime_fastrand() + hashkey[i] = uint32(runtime_fastrand64()) } hashkey[0] |= 1 // make sure these numbers are odd hashkey[1] |= 1 diff --git a/compiler/natives/src/runtime/runtime.go b/compiler/natives/src/runtime/runtime.go index 7e76e3ccc..41c60876c 100644 --- a/compiler/natives/src/runtime/runtime.go +++ b/compiler/natives/src/runtime/runtime.go @@ -489,5 +489,6 @@ func throw(s string) { } func nanotime() int64 { - return js.Global.Get("Date").New().Call("getTime").Int64() * int64(1000_000) + const millisecond = 1_000_000 + return js.Global.Get("Date").New().Call("getTime").Int64() * millisecond } diff --git a/compiler/natives/src/sync/atomic/atomic.go b/compiler/natives/src/sync/atomic/atomic.go index ebc98e910..1cbfe65f9 100644 --- a/compiler/natives/src/sync/atomic/atomic.go +++ b/compiler/natives/src/sync/atomic/atomic.go @@ -220,3 +220,6 @@ func sameType(x, y interface{}) bool { // existing and differing for different types. return js.InternalObject(x).Get("constructor") == js.InternalObject(y).Get("constructor") } + +//gopherjs:purge for go1.19 without generics +type Pointer[T any] struct{} diff --git a/compiler/natives/src/sync/atomic/atomic_test.go b/compiler/natives/src/sync/atomic/atomic_test.go index f4450cc67..27ce36df9 100644 --- a/compiler/natives/src/sync/atomic/atomic_test.go +++ b/compiler/natives/src/sync/atomic/atomic_test.go @@ -3,7 +3,51 @@ package atomic_test -import "testing" +import ( + "testing" + "unsafe" +) + +//gopherjs:purge for go1.19 without generics +func testPointers() []unsafe.Pointer {} + +func TestSwapPointer(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestSwapPointerMethod(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestCompareAndSwapPointer(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestCompareAndSwapPointerMethod(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestLoadPointer(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestLoadPointerMethod(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestStorePointer(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestStorePointerMethod(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +//gopherjs:purge for go1.19 without generics +func hammerStoreLoadPointer(t *testing.T, paddr unsafe.Pointer) {} + +//gopherjs:purge for go1.19 without generics +func hammerStoreLoadPointerMethod(t *testing.T, paddr unsafe.Pointer) {} func TestHammerStoreLoad(t *testing.T) { t.Skip("use of unsafe") @@ -12,3 +56,10 @@ func TestHammerStoreLoad(t *testing.T) { func TestUnaligned64(t *testing.T) { t.Skip("GopherJS emulates atomics, which makes alignment irrelevant.") } + +func TestNilDeref(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +//gopherjs:purge for go1.19 without generics +type List struct{} diff --git a/compiler/natives/src/sync/sync.go b/compiler/natives/src/sync/sync.go index 588537751..294b0b109 100644 --- a/compiler/natives/src/sync/sync.go +++ b/compiler/natives/src/sync/sync.go @@ -3,7 +3,11 @@ package sync -import "github.com/gopherjs/gopherjs/js" +import ( + _ "unsafe" // For go:linkname + + "github.com/gopherjs/gopherjs/js" +) var semWaiters = make(map[*uint32][]chan bool) @@ -69,11 +73,8 @@ func runtime_canSpin(i int) bool { return false } -// Copy of time.runtimeNano. -func runtime_nanotime() int64 { - const millisecond = 1000000 - return js.Global.Get("Date").New().Call("getTime").Int64() * millisecond -} +//go:linkname runtime_nanotime runtime.nanotime +func runtime_nanotime() int64 // Implemented in runtime. func throw(s string) { diff --git a/compiler/natives/src/testing/helper_test.go b/compiler/natives/src/testing/helper_test.go index b277fa31f..6815fd651 100644 --- a/compiler/natives/src/testing/helper_test.go +++ b/compiler/natives/src/testing/helper_test.go @@ -2,3 +2,7 @@ // +build js package testing + +func TestTBHelper(t *T) { + t.Skip("GopherJS does not support generics yet.") +} diff --git a/compiler/natives/src/testing/helperfuncs_test.go b/compiler/natives/src/testing/helperfuncs_test.go new file mode 100644 index 000000000..54a1ee737 --- /dev/null +++ b/compiler/natives/src/testing/helperfuncs_test.go @@ -0,0 +1,13 @@ +//go:build js +// +build js + +package testing + +//gopherjs:purge for go1.19 without generics +func genericHelper[G any](t *T, msg string) + +//gopherjs:purge for go1.19 without generics +var genericIntHelper = genericHelper[int] + +//gopherjs:purge for go1.19 without generics (uses genericHelper) +func testHelper(t *T) From 5a980432222d942e19b414eff53e665560b94ad7 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Fri, 19 Jan 2024 11:39:12 -0700 Subject: [PATCH 50/66] Updating documentation for build directives --- build/build.go | 14 +++- doc/pargma.md | 174 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 176 insertions(+), 12 deletions(-) diff --git a/build/build.go b/build/build.go index 30e2b15aa..a6832a607 100644 --- a/build/build.go +++ b/build/build.go @@ -150,11 +150,16 @@ type overrideInfo struct { // - For function identifiers that exist in the original and the overrides // and have the directive `gopherjs:keep-original`, the original identifier // in the AST gets prefixed by `_gopherjs_original_`. -// - For identifiers that exist in the original and the overrides and have +// - For identifiers that exist in the original and the overrides, and have // the directive `gopherjs:purge`, both the original and override are // removed. This is for completely removing something which is currently // invalid for GopherJS. For any purged types any methods with that type as // the receiver are also removed. +// - For function identifiers that exist in the original and the overrides, +// and have the directive `gopherjs:override-signature`, the overridden +// function is removed and the original function's signature is changed +// to match the overridden function signature. This allows the receiver, +// type parameters, parameter, and return values to be modified as needed. // - Otherwise for identifiers that exist in the original and the overrides, // the original is removed. // - New identifiers that don't exist in original package get added. @@ -414,9 +419,12 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { // isOnlyImports determines if this file is empty except for imports. func isOnlyImports(file *ast.File) bool { for _, decl := range file.Decls { - if gen, ok := decl.(*ast.GenDecl); !ok || gen.Tok != token.IMPORT { - return false + if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.IMPORT { + continue } + + // The decl was either a FuncDecl or a non-import GenDecl. + return false } return true } diff --git a/doc/pargma.md b/doc/pargma.md index a13c64ce6..2bcf71f93 100644 --- a/doc/pargma.md +++ b/doc/pargma.md @@ -7,6 +7,12 @@ issues, so it is recommended to avoid using them if possible. GopherJS compiler supports the following directives: +- [go:linkname](#golinkname) +- [go:embed](#goembed) +- [gopherjs:keep-original](#gopherjskeep-original) +- [gopherjs:purge](#gopherjspurge) +- [gopherjs:override-signature](#gopherjsoverride-signature) + ## `go:linkname` This is a limited version of the `go:linkname` directive the upstream Go @@ -25,16 +31,166 @@ Signatures of `remotename` and `localname` must be identical. Since this directive can subvert package incapsulation, the source file that uses the directive must also import `unsafe`. -The following directive format is supported: -//go:linkname . -//go:linkname .. -//go:linkname .<(*type)>. +The following directive formats are supported: + +- `//go:linkname .` +- `//go:linkname ..` +- `//go:linkname .<(*type)>.` Compared to the upstream Go, the following limitations exist in GopherJS: - - The directive only works on package-level functions or methods (variables - are not supported). - - The directive can only be used to "import" implementation from another - package, and not to "provide" local implementation to another package. +- The directive only works on package-level functions or methods (variables + are not supported). +- The directive can only be used to "import" implementation from another + package, and not to "provide" local implementation to another package. + +See [gopherjs/issues/1000](https://github.com/gopherjs/gopherjs/issues/1000) +for details. + +## `go:embed` + +This is a very similar version of the `go:embed` directive the upstream Go +compiler implements. +GopherJS leverages [goembed](https://github.com/visualfc/goembed) +to parse this directive and provide support reading embedded content. Usage: + +```go +import _ "embed" // for go:embed + +//go:embed externalText +var embeddedText string + +//go:embed externalContent +var embeddedContent []byte + +//go:embed file1 +//go:embed file2 +// ... +//go:embed image/* blobs/* +var embeddedFiles embed.FS +``` + +This directive affects the variable specification (e.g. `embeddedText`) +that the comment containing the directive is associated with. +There may be one embed directives associated with `string` or `[]byte` +variables. There may be one or more embed directives associated with +`embed.FS` variables and each directive may contain one or more +file matching patterns. The effect is that the variable will be assigned to +the content (e.g. `externalText`) given in the directive. In the case +of `embed.FS`, several embedded files will be accessible. + +See [pkg.go.dev/embed](https://pkg.go.dev/embed#hdr-Directives) +for more information. + +## `gopherjs:keep-original` + +This directive is custom to GopherJS. This directive can be added to a +function declaration in the native file overrides as part of the build step. + +This will keep the original function by the same name as the function +in the overrides, however it will prepend `_gopherjs_original_` to the original +function's name. This allows the original function to be called by functions +in the overrides and the overridden function to be called instead of the +original. This is useful when wanting to augment the original behavior without +having to rewrite the entire original function. Usage: + +```go +//gopherjs:keep-original +func foo(a, b int) int { + return _gopherjs_original_foo(a+1, b+1) - 1 +} +``` + +## `gopherjs:purge` + +This directive is custom to GopherJS. This directive can be added +to most declarations and specification in the native file overrides as +part of the build step. +This can be added to structures, interfaces, methods, functions, +variables, or constants, but are not supported for imports, structure fields, +nor interface function signatures. -See https://github.com/gopherjs/gopherjs/issues/1000 for details. +This will remove the original structure, interface, etc from both the override +files and the original files. +If this is added to a structure, then all functions in the original files +that use that structure as a receiver will also be removed. +This is useful for removing all the code that is invalid in GopherJS, +such as code using unsupported features (e.g. generic interfaces before +generics were fully supported). In many cases the overrides to replace +the original code may not have use of all the original functions and +variables or the original code is not intended to be replaced yet. +Usage: + +```go +//gopherjs:purge +var data string + +//gopherjs:purge +// This will also purge any function starting with `dataType` as the receiver. +type dataType struct {} + +//gopherjs:purge +type interfaceType interface{} + +//gopherjs:purge +func doThing[T ~string](value T) +``` + +## `gopherjs:override-signature` + +This directive is custom to GopherJS. This directive can be added to a +function declaration in the native file overrides as part of the build step. + +This will remove the function from the overrides but record the signature +used in the overrides, then update the original function with that signature +provided in the overrides. +The affect is to change the receiver, type parameters, +parameters, or return types of the original function. The original function +and override function must have the same function key name so that they can +be associated, meaning the identifier of the receiver, if there is one, must +match and the identifier of the function must match. + +This allows the signature to be modified without modifying the body of a +function thus allowing the types to be adjusted to work in GopherJS. +The signature may need to be replaced because it uses a parameter type +that is invalid in GopherJS or the signature uses unsupported features +(e.g. generic interfaces before generics were fully supported). +Usage: + +```go +// -- in original file -- +func Foo[T comparable](a, b T) (T, bool) { + if a == b { + return a, true + } + return b, false +} + +// -- in override file -- +//gopherjs:override-signature +func Foo(a, b any) (any, bool) + +// -- result in augmented original -- +func Foo(a, b any) (any, bool) { + if a == b { + return a, true + } + return b, false +} +``` + +```go +// -- in original file -- +func (f *Foo[A, B, C]) Bar(a int, b *A) (*A, error) { + //... +} + +// -- in override file -- +//gopherjs:override-signature +func (f *Foo) Bar(a int, b jsTypeA) (jsTypeA, error) + +// -- result in augmented original -- +func (f *Foo) Bar(a int, b jsTypeA) (jsTypeA, error) { + //... +} +``` From 12a247d9a8bfdd5cdc8bf10feb88d70b7b3e4f14 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 16:26:14 -0700 Subject: [PATCH 51/66] Fix problem where import is named but source is augmented to only need import for a directive --- build/build.go | 2 ++ build/build_test.go | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/build/build.go b/build/build.go index a6832a607..d3cd32952 100644 --- a/build/build.go +++ b/build/build.go @@ -475,6 +475,8 @@ func pruneImports(file *ast.File) { path, _ := strconv.Unquote(in.Path.Value) directivePrefix, hasPath := directiveImports[path] if hasPath && astutil.HasDirectivePrefix(file, directivePrefix) { + // since the import is otherwise unused set the name to blank. + in.Name = ast.NewIdent(`_`) delete(unused, name) if len(unused) == 0 { return diff --git a/build/build_test.go b/build/build_test.go index f54281cb8..5f025540b 100644 --- a/build/build_test.go +++ b/build/build_test.go @@ -396,7 +396,14 @@ func TestOverlayAugmentation(t *testing.T) { //go:linkname runtimeNano runtime.nanotime func runtimeNano() int64`, - noCodeChange: true, + want: `import _ "unsafe" + import "embed" + + //go:embed hello.txt + var eFile embed.FS + + //go:linkname runtimeNano runtime.nanotime + func runtimeNano() int64`, expInfo: map[string]overrideInfo{ `eFile`: {}, `runtimeNano`: {}, From 4b5b0771d02d2db3484258727e59c151284034f5 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 16:27:48 -0700 Subject: [PATCH 52/66] Fix chocolatey go version --- circle.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 62031ce4b..5c059a6e4 100644 --- a/circle.yml +++ b/circle.yml @@ -55,6 +55,10 @@ parameters: go_version: type: string default: "1.19.13" + chocolatey_go_version: + type: string + # Chocolatey doesn't have 1.19.13, closest is 1.19.9 + default: "1.19.9" nvm_version: type: string default: "0.38.0" @@ -171,7 +175,7 @@ jobs: - run: name: Install Go command: | - choco install golang --version="<< pipeline.parameters.go_version >>" -my + choco install golang --version="<< pipeline.parameters.chocolatey_go_version >>" -my go version (Get-Command go).Path [Environment]::SetEnvironmentVariable( From d7abc77ddbbc45ec6aa20a8f10c8840de5f1017b Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 16:30:19 -0700 Subject: [PATCH 53/66] Update known fails list of fixed bugs --- tests/gorepo/run.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 0ceb99ff5..d58968ada 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -151,10 +151,16 @@ var knownFails = map[string]failReason{ // These are new tests in Go 1.18 "fixedbugs/issue46938.go": {category: notApplicable, desc: "tests -d=checkptr compiler mode, which GopherJS doesn't support"}, "fixedbugs/issue47928.go": {category: notApplicable, desc: "//go:nointerface is a part of GOEXPERIMENT=fieldtrack and is not supported by GopherJS"}, - "fixedbugs/issue49665.go": {category: other, desc: "attempts to pass -gcflags=-G=3 to enable generics, GopherJS doesn't expect the flag; re-enable in Go 1.19 where the flag is removed"}, "fixedbugs/issue48898.go": {category: other, desc: "https://github.com/gopherjs/gopherjs/issues/1128"}, "fixedbugs/issue48536.go": {category: usesUnsupportedPackage, desc: "https://github.com/gopherjs/gopherjs/issues/1130"}, "fixedbugs/issue53600.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format is different from Go's"}, + + // These are new tests in Go 1.19 + "fixedbugs/issue50672.go": {category: usesUnsupportedGenerics, desc: "Checking function nesting with one function having a type parameter."}, + "fixedbugs/issue53137.go": {category: usesUnsupportedGenerics, desc: "Checking setting type parameter of struct in parameter of a generic function."}, + "fixedbugs/issue53309.go": {category: usesUnsupportedGenerics, desc: "Checking unused type parameter in method call to interface"}, + "fixedbugs/issue53635.go": {category: usesUnsupportedGenerics, desc: "Checking switch type against nil type with unsupported type parameters"}, + "fixedbugs/issue53653.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format of int64 is different from Go's"}, } type failCategory uint8 @@ -164,6 +170,7 @@ const ( neverTerminates // Test never terminates (so avoid starting it). usesUnsupportedPackage // Test fails because it imports an unsupported package, e.g., "unsafe". requiresSourceMapSupport // Test fails without source map support (as configured in CI), because it tries to check filename/line number via runtime.Caller. + usesUnsupportedGenerics // Test uses generics (type parameters) that are not currently supported. compilerPanic unsureIfGopherJSSupportsThisFeature lowLevelRuntimeDifference // JavaScript runtime behaves differently from Go in ways that are difficult to work around. From 91f1d44246b326ba7bf3d7022ed09a34ea3e7da3 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 17:10:29 -0700 Subject: [PATCH 54/66] Updated crypto natives --- .../crypto/internal/boring/bcache/cache.go | 30 +++++++++++++++++++ .../internal/boring/bcache/cache_test.go | 10 +++++++ .../src/crypto/internal/boring/sig/sig.go | 13 ++++++++ .../src/crypto/internal/nistec/nistec_test.go | 18 ++++++----- 4 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 compiler/natives/src/crypto/internal/boring/bcache/cache.go create mode 100644 compiler/natives/src/crypto/internal/boring/bcache/cache_test.go create mode 100644 compiler/natives/src/crypto/internal/boring/sig/sig.go diff --git a/compiler/natives/src/crypto/internal/boring/bcache/cache.go b/compiler/natives/src/crypto/internal/boring/bcache/cache.go new file mode 100644 index 000000000..afff404ce --- /dev/null +++ b/compiler/natives/src/crypto/internal/boring/bcache/cache.go @@ -0,0 +1,30 @@ +//go:build js +// +build js + +package bcache + +import "unsafe" + +// Cache relies on GC to periodically clear the cache. +// Since GopherJS doesn't have the same GC hooks, it currently can not +// register this cache with the GC. +// Without this cache Boring crypto, in particular public and private +// RSA and ECDSA keys, will be slower because the cache will always miss. +type Cache struct{} + +func (c *Cache) Register() {} +func (c *Cache) Clear() {} +func (c *Cache) Get(k unsafe.Pointer) unsafe.Pointer { return nil } +func (c *Cache) Put(k, v unsafe.Pointer) {} + +//gopherjs:purge +func (c *Cache) table() *[cacheSize]unsafe.Pointer + +//gopherjs:purge +type cacheEntry struct{} + +//gopherjs:purge +func registerCache(unsafe.Pointer) + +//gopherjs:purge +const cacheSize = 1021 diff --git a/compiler/natives/src/crypto/internal/boring/bcache/cache_test.go b/compiler/natives/src/crypto/internal/boring/bcache/cache_test.go new file mode 100644 index 000000000..12f2c4da4 --- /dev/null +++ b/compiler/natives/src/crypto/internal/boring/bcache/cache_test.go @@ -0,0 +1,10 @@ +//go:build js +// +build js + +package bcache + +import "testing" + +func TestCache(t *testing.T) { + t.Skip(`This test uses runtime.GC(), which GopherJS doesn't support`) +} diff --git a/compiler/natives/src/crypto/internal/boring/sig/sig.go b/compiler/natives/src/crypto/internal/boring/sig/sig.go new file mode 100644 index 000000000..3eb2454aa --- /dev/null +++ b/compiler/natives/src/crypto/internal/boring/sig/sig.go @@ -0,0 +1,13 @@ +//go:build js +// +build js + +package sig + +// Setting to no-op +func BoringCrypto() {} + +// Setting to no-op +func FIPSOnly() {} + +// Setting to no-op +func StandardCrypto() {} diff --git a/compiler/natives/src/crypto/internal/nistec/nistec_test.go b/compiler/natives/src/crypto/internal/nistec/nistec_test.go index f89aaeae0..f5f79398e 100644 --- a/compiler/natives/src/crypto/internal/nistec/nistec_test.go +++ b/compiler/natives/src/crypto/internal/nistec/nistec_test.go @@ -4,8 +4,10 @@ package nistec_test import ( - "crypto/elliptic" "testing" + + "crypto/elliptic" + "crypto/internal/nistec" ) func TestAllocations(t *testing.T) { @@ -31,7 +33,7 @@ func TestEquivalents(t *testing.T) { } //gopherjs:override-signature -func testEquivalents(t *testing.T, newPoint, newGenerator func() WrappedPoint, c elliptic.Curve) {} +func testEquivalents(t *testing.T, newPoint, newGenerator func() nistec.WrappedPoint, c elliptic.Curve) func TestScalarMult(t *testing.T) { t.Run("P224", func(t *testing.T) { @@ -49,14 +51,14 @@ func TestScalarMult(t *testing.T) { } //gopherjs:override-signature -func testScalarMult(t *testing.T, newPoint, newGenerator func() WrappedPoint, c elliptic.Curve) +func testScalarMult(t *testing.T, newPoint, newGenerator func() nistec.WrappedPoint, c elliptic.Curve) func BenchmarkScalarMult(b *testing.B) { b.Run("P224", func(b *testing.B) { benchmarkScalarMult(b, nistec.NewP224WrappedGenerator(), 28) }) b.Run("P256", func(b *testing.B) { - benchmarkScalarMult(b, nistec.NewP256GWrappedenerator(), 32) + benchmarkScalarMult(b, nistec.NewP256WrappedGenerator(), 32) }) b.Run("P384", func(b *testing.B) { benchmarkScalarMult(b, nistec.NewP384WrappedGenerator(), 48) @@ -67,11 +69,11 @@ func BenchmarkScalarMult(b *testing.B) { } //gopherjs:override-signature -func benchmarkScalarMult(b *testing.B, p WrappedPoint, scalarSize int) +func benchmarkScalarMult(b *testing.B, p nistec.WrappedPoint, scalarSize int) func BenchmarkScalarBaseMult(b *testing.B) { b.Run("P224", func(b *testing.B) { - benchmarkScalarBaseMult(b, nistec.NewP22Wrapped4Generator(), 28) + benchmarkScalarBaseMult(b, nistec.NewP224WrappedGenerator(), 28) }) b.Run("P256", func(b *testing.B) { benchmarkScalarBaseMult(b, nistec.NewP256WrappedGenerator(), 32) @@ -80,9 +82,9 @@ func BenchmarkScalarBaseMult(b *testing.B) { benchmarkScalarBaseMult(b, nistec.NewP384WrappedGenerator(), 48) }) b.Run("P521", func(b *testing.B) { - benchmarkScalarBaseMult(b, nistec.NewP521GWrappedenerator(), 66) + benchmarkScalarBaseMult(b, nistec.NewP521WrappedGenerator(), 66) }) } //gopherjs:override-signature -func benchmarkScalarBaseMult(b *testing.B, p WrappedPoint, scalarSize int) +func benchmarkScalarBaseMult(b *testing.B, p nistec.WrappedPoint, scalarSize int) From f5c911a46f9dd13f8cadb00e789a87a852dca703 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 17:10:59 -0700 Subject: [PATCH 55/66] Fix natives for debug/pe --- compiler/natives/src/debug/pe/symbol.go | 119 ++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 compiler/natives/src/debug/pe/symbol.go diff --git a/compiler/natives/src/debug/pe/symbol.go b/compiler/natives/src/debug/pe/symbol.go new file mode 100644 index 000000000..798502ce3 --- /dev/null +++ b/compiler/natives/src/debug/pe/symbol.go @@ -0,0 +1,119 @@ +//go:build js +// +build js + +package pe + +import ( + "encoding/binary" + "fmt" + "io" +) + +// bytesBufferLite is a simplified bytes.Buffer to avoid +// including `bytes` as a new import into the pe package. +type bytesBufferLite struct { + data []byte + off int +} + +func (buf *bytesBufferLite) Write(p []byte) (int, error) { + buf.data = append(buf.data, p...) + return len(p), nil +} + +func (buf *bytesBufferLite) Read(p []byte) (int, error) { + n := copy(p, buf.data[buf.off:]) + buf.off += n + return n, nil +} + +func copyToAuxFormat5(sym *COFFSymbol) (*COFFSymbolAuxFormat5, error) { + buf := &bytesBufferLite{data: make([]byte, 0, 20)} + if err := binary.Write(buf, binary.LittleEndian, sym); err != nil { + return nil, err + } + aux := &COFFSymbolAuxFormat5{} + if err := binary.Read(buf, binary.LittleEndian, aux); err != nil { + return nil, err + } + return aux, nil +} + +func copyFromAuxFormat5(aux *COFFSymbolAuxFormat5) (*COFFSymbol, error) { + buf := &bytesBufferLite{data: make([]byte, 0, 20)} + if err := binary.Write(buf, binary.LittleEndian, aux); err != nil { + return nil, err + } + sym := &COFFSymbol{} + if err := binary.Read(buf, binary.LittleEndian, sym); err != nil { + return nil, err + } + return sym, nil +} + +func readCOFFSymbols(fh *FileHeader, r io.ReadSeeker) ([]COFFSymbol, error) { + if fh.PointerToSymbolTable == 0 { + return nil, nil + } + if fh.NumberOfSymbols <= 0 { + return nil, nil + } + _, err := r.Seek(int64(fh.PointerToSymbolTable), seekStart) + if err != nil { + return nil, fmt.Errorf("fail to seek to symbol table: %v", err) + } + syms := make([]COFFSymbol, fh.NumberOfSymbols) + naux := 0 + for k := range syms { + if naux == 0 { + err = binary.Read(r, binary.LittleEndian, &syms[k]) + if err != nil { + return nil, fmt.Errorf("fail to read symbol table: %v", err) + } + naux = int(syms[k].NumberOfAuxSymbols) + } else { + naux-- + // The following was reading into one struct with the same memory + // footprint as another struck. This doesn't work in JS so the + // `syms` value is left with a bunch of defaults. So replace + // aux := (*COFFSymbolAuxFormat5)(unsafe.Pointer(&syms[k])) + // (an in memory remap) with the following read and then copy. + aux := &COFFSymbolAuxFormat5{} + err = binary.Read(r, binary.LittleEndian, aux) + if err != nil { + return nil, fmt.Errorf("fail to read symbol table: %v", err) + } + pesymn, err := copyFromAuxFormat5(aux) + if err != nil { + return nil, err + } + syms[k] = *pesymn + } + } + if naux != 0 { + return nil, fmt.Errorf("fail to read symbol table: %d aux symbols unread", naux) + } + return syms, nil +} + +func (f *File) COFFSymbolReadSectionDefAux(idx int) (*COFFSymbolAuxFormat5, error) { + var rv *COFFSymbolAuxFormat5 + if idx < 0 || idx >= len(f.COFFSymbols) { + return rv, fmt.Errorf("invalid symbol index") + } + pesym := &f.COFFSymbols[idx] + const IMAGE_SYM_CLASS_STATIC = 3 + if pesym.StorageClass != uint8(IMAGE_SYM_CLASS_STATIC) { + return rv, fmt.Errorf("incorrect symbol storage class") + } + if pesym.NumberOfAuxSymbols == 0 || idx+1 >= len(f.COFFSymbols) { + return rv, fmt.Errorf("aux symbol unavailable") + } + pesymn := &f.COFFSymbols[idx+1] + // The following was reading one struct as another struct with + // the same memory footprint. This doesn't work in JS so the + // `rv` value is left with a bunch of `undefined`s. So replace + // rv = (*COFFSymbolAuxFormat5)(unsafe.Pointer(pesymn)) + // (an in memory remap) with the following copy. + return copyToAuxFormat5(pesymn) +} From ff493e49e8e15eda498acb4f83216e77b3c00c5d Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 17:16:30 -0700 Subject: [PATCH 56/66] Update to go/token/position --- compiler/natives/src/go/token/position.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/compiler/natives/src/go/token/position.go b/compiler/natives/src/go/token/position.go index c7fabb810..6a1ee0c15 100644 --- a/compiler/natives/src/go/token/position.go +++ b/compiler/natives/src/go/token/position.go @@ -3,24 +3,20 @@ package token -import ( - "sync" - "sync/atomic" - "unsafe" -) +import "sync" type FileSet struct { mutex sync.RWMutex base int files []*File - // replaced atomic.Pointer[File] for go1.19 without generics + // replaced atomic.Pointer[File] for go1.19 without generics. last atomicFilePointer } type atomicFilePointer struct { - v unsafe.Pointer + v *File } -func (x *atomicFilePointer) Load() *File { return (*File)(atomic.LoadPointer(&x.v)) } -func (x *atomicFilePointer) Store(val *File) { atomic.StorePointer(&x.v, unsafe.Pointer(val)) } +func (x *atomicFilePointer) Load() *File { return x.v } +func (x *atomicFilePointer) Store(val *File) { x.v = val } From cd055c9e5a7918e7a94de6871e6713d82ef51652 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 17:22:51 -0700 Subject: [PATCH 57/66] Update hash/maphash --- compiler/natives/src/hash/maphash/maphash.go | 96 ++++++++++++++------ 1 file changed, 66 insertions(+), 30 deletions(-) diff --git a/compiler/natives/src/hash/maphash/maphash.go b/compiler/natives/src/hash/maphash/maphash.go index dceff2c62..5c982404f 100644 --- a/compiler/natives/src/hash/maphash/maphash.go +++ b/compiler/natives/src/hash/maphash/maphash.go @@ -3,31 +3,58 @@ package maphash -// used in hash{32,64}.go to seed the hash function -var hashkey [4]uint32 +import ( + _ "unsafe" // for linkname +) + +// hashkey is similar how it is defined in runtime/alg.go for Go 1.19 +// to be used in hash{32,64}.go to seed the hash function as part of +// runtime_memhash. We're using locally defined memhash so it got moved here. +var hashkey [3]uint32 func init() { for i := range hashkey { - hashkey[i] = uint32(runtime_fastrand64()) + hashkey[i] = runtime_fastrand() | 1 + // The `| 1` is to make sure these numbers are odd } - hashkey[0] |= 1 // make sure these numbers are odd - hashkey[1] |= 1 - hashkey[2] |= 1 - hashkey[3] |= 1 } -func _rthash(b []byte, seed uint64) uint64 { +//go:linkname runtime_fastrand runtime.fastrand +func runtime_fastrand() uint32 + +// Bytes uses less efficient equivalent to avoid using unsafe. +func Bytes(seed Seed, b []byte) uint64 { + var h Hash + h.SetSeed(seed) + _, _ = h.Write(b) + return h.Sum64() +} + +// String uses less efficient equivalent to avoid using unsafe. +func String(seed Seed, s string) uint64 { + var h Hash + h.SetSeed(seed) + _, _ = h.WriteString(s) + return h.Sum64() +} + +// rthash is similar to the Go 1.19.13 version +// with the call to memhash changed to not use unsafe pointers. +func rthash(b []byte, seed uint64) uint64 { if len(b) == 0 { return seed } // The runtime hasher only works on uintptr. Since GopherJS implements a // 32-bit environment, we use two parallel hashers on the lower and upper 32 // bits. - lo := memhash(b, uint32(seed), uint32(len(b))) - hi := memhash(b, uint32(seed>>32), uint32(len(b))) + lo := memhash(b, uint32(seed)) + hi := memhash(b, uint32(seed>>32)) return uint64(hi)<<32 | uint64(lo) } +//gopherjs:purge to remove link using unsafe pointers, use memhash instead. +func runtime_memhash() + // The implementation below is adapted from the upstream runtime/hash32.go // and avoids use of unsafe, which GopherJS doesn't support well and leads to // worse performance. @@ -38,8 +65,9 @@ func _rthash(b []byte, seed uint64) uint64 { // // Hashing algorithm inspired by wyhash: // https://github.com/wangyi-fudan/wyhash/blob/ceb019b530e2c1c14d70b79bfa2bc49de7d95bc1/Modern%20Non-Cryptographic%20Hash%20Function%20and%20Pseudorandom%20Number%20Generator.pdf -func memhash(p []byte, seed uint32, s uint32) uintptr { - a, b := mix32(uint32(seed), uint32(s^hashkey[0])) +func memhash(p []byte, seed uint32) uintptr { + s := len(p) + a, b := mix32(uint32(seed), uint32(s)^hashkey[0]) if s == 0 { return uintptr(a ^ b) } @@ -63,7 +91,7 @@ func memhash(p []byte, seed uint32, s uint32) uintptr { return uintptr(a ^ b) } -func add(p []byte, x uint32) []byte { +func add(p []byte, x int) []byte { return p[x:] } @@ -80,51 +108,59 @@ func mix32(a, b uint32) (uint32, uint32) { /* The following functions were modified in Go 1.17 to improve performance, but at the expense of being unsafe, and thus incompatible with GopherJS. - To compensate, we have reverted these to the unoptimized Go 1.16 versions - for now. + See https://cs.opensource.google/go/go/+/refs/tags/go1.19.13:src/hash/maphash/maphash.go; + To compensate, we use a simplified version of each method from Go 1.19.13, + similar to Go 1.16's versions, with the call to rthash changed to not use unsafe pointers. See upstream issue https://github.com/golang/go/issues/47342 to implement a purego version of this package, which should render this hack (and likely this entire file) obsolete. */ -// Write is borrowed from Go 1.16. +// Write is a simplification from Go 1.19 changed to not use unsafe. func (h *Hash) Write(b []byte) (int, error) { size := len(b) - for h.n+len(b) > len(h.buf) { - k := copy(h.buf[h.n:], b) - h.n = len(h.buf) - b = b[k:] - h.flush() + if h.n+len(b) > bufSize { + h.initSeed() + for h.n+len(b) > bufSize { + k := copy(h.buf[h.n:], b) + h.state.s = rthash(h.buf[:], h.state.s) + b = b[k:] + h.n = 0 + } } h.n += copy(h.buf[h.n:], b) return size, nil } -// WriteString is borrowed from Go 1.16. +// WriteString is a simplification from Go 1.19 changed to not use unsafe. func (h *Hash) WriteString(s string) (int, error) { size := len(s) - for h.n+len(s) > len(h.buf) { - k := copy(h.buf[h.n:], s) - h.n = len(h.buf) - s = s[k:] - h.flush() + if h.n+len(s) > bufSize { + h.initSeed() + for h.n+len(s) > bufSize { + k := copy(h.buf[h.n:], s) + h.state.s = rthash(h.buf[:], h.state.s) + s = s[k:] + h.n = 0 + } } h.n += copy(h.buf[h.n:], s) return size, nil } +// flush is the Go 1.19 version changed to not use unsafe. func (h *Hash) flush() { if h.n != len(h.buf) { panic("maphash: flush of partially full buffer") } h.initSeed() - h.state.s = _rthash(h.buf[:], h.state.s) + h.state.s = rthash(h.buf[:], h.state.s) h.n = 0 } -// Sum64 is borrowed from Go 1.16. +// Sum64 is the Go 1.19 version changed to not use unsafe. func (h *Hash) Sum64() uint64 { h.initSeed() - return _rthash(h.buf[:h.n], h.state.s) + return rthash(h.buf[:h.n], h.state.s) } From 543764212241adf2132f8c7c9523e83b477b70c8 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 17:30:54 -0700 Subject: [PATCH 58/66] Update to reflect and reflectlite --- .../src/internal/reflectlite/all_test.go | 24 +++++ compiler/natives/src/reflect/reflect.go | 91 +++++++++++++++---- compiler/natives/src/reflect/reflect_test.go | 11 ++- 3 files changed, 104 insertions(+), 22 deletions(-) diff --git a/compiler/natives/src/internal/reflectlite/all_test.go b/compiler/natives/src/internal/reflectlite/all_test.go index 977438e4e..4445189a0 100644 --- a/compiler/natives/src/internal/reflectlite/all_test.go +++ b/compiler/natives/src/internal/reflectlite/all_test.go @@ -21,3 +21,27 @@ func TestTypes(t *testing.T) { func TestNameBytesAreAligned(t *testing.T) { t.Skip("TestNameBytesAreAligned") } + +// `A` is used with `B[T any]` and is otherwise not needed. +// +//gopherjs:purge for go1.19 without generics +type ( + A struct{} + B[T any] struct{} +) + +// removing the name tests using `B[T any]` for go1.19 without generics +var nameTests = []nameTest{ + {(*int32)(nil), "int32"}, + {(*D1)(nil), "D1"}, + {(*[]D1)(nil), ""}, + {(*chan D1)(nil), ""}, + {(*func() D1)(nil), ""}, + {(*<-chan D1)(nil), ""}, + {(*chan<- D1)(nil), ""}, + {(*any)(nil), ""}, + {(*interface { + F() + })(nil), ""}, + {(*TheNameOfThisTypeIsExactly255BytesLongSoWhenTheCompilerPrependsTheReflectTestPackageNameAndExtraStarTheLinkerRuntimeAndReflectPackagesWillHaveToCorrectlyDecodeTheSecondLengthByte0123456789_0123456789_0123456789_0123456789_0123456789_012345678)(nil), "TheNameOfThisTypeIsExactly255BytesLongSoWhenTheCompilerPrependsTheReflectTestPackageNameAndExtraStarTheLinkerRuntimeAndReflectPackagesWillHaveToCorrectlyDecodeTheSecondLengthByte0123456789_0123456789_0123456789_0123456789_0123456789_012345678"}, +} diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 5bcfaf66d..47b93662e 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -1179,6 +1179,11 @@ func (v Value) Cap() int { return v.typ.Len() case Chan, Slice: return v.object().Get("$capacity").Int() + case Ptr: + if v.typ.Elem().Kind() == Array { + return v.typ.Elem().Len() + } + panic("reflect: call of reflect.Value.Cap on ptr to non-array Value") } panic(&ValueError{"reflect.Value.Cap", k}) } @@ -1405,6 +1410,11 @@ func (v Value) Len() int { return v.object().Get("$buffer").Get("length").Int() case Map: return v.object().Get("size").Int() + case Ptr: + if v.typ.Elem().Kind() == Array { + return v.typ.Elem().Len() + } + panic("reflect: call of reflect.Value.Len on ptr to non-array Value") default: panic(&ValueError{"reflect.Value.Len", k}) } @@ -1450,6 +1460,29 @@ func (v Value) Set(x Value) { v.ptr = x.ptr } +func (v Value) bytesSlow() []byte { + switch v.kind() { + case Slice: + if v.typ.Elem().Kind() != Uint8 { + panic("reflect.Value.Bytes of non-byte slice") + } + return *(*[]byte)(v.ptr) + case Array: + if v.typ.Elem().Kind() != Uint8 { + panic("reflect.Value.Bytes of non-byte array") + } + if !v.CanAddr() { + panic("reflect.Value.Bytes of unaddressable byte array") + } + // Replace the following with JS to avoid using unsafe pointers. + // p := (*byte)(v.ptr) + // n := int((*arrayType)(unsafe.Pointer(v.typ)).len) + // return unsafe.Slice(p, n) + return js.InternalObject(v.ptr).Interface().([]byte) + } + panic(&ValueError{"reflect.Value.Bytes", v.kind()}) +} + func (v Value) SetBytes(x []byte) { v.mustBeAssignable() v.mustBe(Slice) @@ -1728,29 +1761,47 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { return js.Global.Call("$interfaceIsEqual", js.InternalObject(valueInterface(v1, false)), js.InternalObject(valueInterface(v2, false))).Bool() } -func methodNameSkip() string { - pc, _, _, _ := runtime.Caller(3) - f := runtime.FuncForPC(pc) - if f == nil { - return "unknown method" - } - // Function name extracted from the call stack can be different from vanilla - // Go. Here we try to fix stuff like "Object.$packages.reflect.Q.ptr.SetIterKey" - // into "Value.SetIterKey". - // This workaround may become obsolete after https://github.com/gopherjs/gopherjs/issues/1085 - // is resolved. - name := f.Name() - idx := len(name) - 1 - for idx > 0 { - if name[idx] == '.' { - break +func stringsLastIndex(s string, c byte) int { + for i := len(s) - 1; i >= 0; i-- { + if s[i] == c { + return i } - idx-- } - if idx < 0 { - return name + return -1 +} + +func stringsHasPrefix(s, prefix string) bool { + return len(s) >= len(prefix) && s[:len(prefix)] == prefix +} + +func valueMethodName() string { + var pc [5]uintptr + n := runtime.Callers(1, pc[:]) + frames := runtime.CallersFrames(pc[:n]) + var frame runtime.Frame + for more := true; more; { + frame, more = frames.Next() + name := frame.Function + + // Function name extracted from the call stack can be different from + // vanilla Go, so is not prefixed by "reflect.Value." as needed by the original. + // See https://cs.opensource.google/go/go/+/refs/tags/go1.19.13:src/reflect/value.go;l=173-191 + // Here we try to fix stuff like "Object.$packages.reflect.Q.ptr.SetIterKey" + // into "reflect.Value.SetIterKey". + // This workaround may become obsolete after + // https://github.com/gopherjs/gopherjs/issues/1085 is resolved. + + const prefix = `Object.$packages.reflect.` + if stringsHasPrefix(name, prefix) { + if idx := stringsLastIndex(name, '.'); idx >= 0 { + methodName := name[idx+1:] + if len(methodName) > 0 && 'A' <= methodName[0] && methodName[0] <= 'Z' { + return `reflect.Value.` + methodName + } + } + } } - return "Value" + name[idx:] + return "unknown method" } func verifyNotInHeapPtr(p uintptr) bool { diff --git a/compiler/natives/src/reflect/reflect_test.go b/compiler/natives/src/reflect/reflect_test.go index 112119a4e..79bbe5385 100644 --- a/compiler/natives/src/reflect/reflect_test.go +++ b/compiler/natives/src/reflect/reflect_test.go @@ -285,9 +285,16 @@ func TestMethodCallValueCodePtr(t *testing.T) { t.Skip("methodValueCallCodePtr() is not applicable in GopherJS") } -type B struct{} +//gopherjs:purge for go1.19 without generics +type ( + A struct{} + B[T any] struct{} +) -//gopherjs:prune-original func TestIssue50208(t *testing.T) { t.Skip("This test required generics, which are not yet supported: https://github.com/gopherjs/gopherjs/issues/1013") } + +func TestStructOfTooLarge(t *testing.T) { + t.Skip("This test is dependent on field alignment to determine if a struct size would exceed virtual address space.") +} From 8151b2813eac9a79dc989ea7fa2facb11cd42429 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 17:34:11 -0700 Subject: [PATCH 59/66] Update for fastrand --- compiler/natives/src/net/fastrand.go | 4 ++-- compiler/natives/src/runtime/fastrand.go | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/compiler/natives/src/net/fastrand.go b/compiler/natives/src/net/fastrand.go index 861217a9d..8feafc78f 100644 --- a/compiler/natives/src/net/fastrand.go +++ b/compiler/natives/src/net/fastrand.go @@ -7,5 +7,5 @@ import ( _ "unsafe" // For go:linkname ) -//go:linkname fastrand runtime.fastrand -func fastrand() uint32 +//go:linkname fastrandu runtime.fastrandu +func fastrandu() uint diff --git a/compiler/natives/src/runtime/fastrand.go b/compiler/natives/src/runtime/fastrand.go index 8f6ab6292..a5f2bdbb8 100644 --- a/compiler/natives/src/runtime/fastrand.go +++ b/compiler/natives/src/runtime/fastrand.go @@ -13,3 +13,15 @@ func fastrand() uint32 { // similar distribution. return uint32(js.Global.Get("Math").Call("random").Float() * (1<<32 - 1)) } + +func fastrandn(n uint32) uint32 { + return fastrand() % n +} + +func fastrand64() uint64 { + return uint64(fastrand())<<32 | uint64(fastrand()) +} + +func fastrandu() uint { + return uint(fastrand()) +} From 014360bce172cb58a48916e3981e5896c23953c6 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 17:44:11 -0700 Subject: [PATCH 60/66] Updated net/netip --- compiler/natives/src/net/netip/export_test.go | 2 +- compiler/natives/src/net/netip/fuzz_test.go | 1 + compiler/natives/src/net/netip/netip.go | 3 +-- compiler/natives/src/net/netip/netip_test.go | 10 ++++++++++ 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 compiler/natives/src/net/netip/netip_test.go diff --git a/compiler/natives/src/net/netip/export_test.go b/compiler/natives/src/net/netip/export_test.go index 001f348cc..03b7cbe1b 100644 --- a/compiler/natives/src/net/netip/export_test.go +++ b/compiler/natives/src/net/netip/export_test.go @@ -1,4 +1,5 @@ //go:build js +// +build js package netip @@ -8,7 +9,6 @@ import ( "internal/intern" ) -//gopherjs:prune-original func MkAddr(u Uint128, z any) Addr { switch z := z.(type) { case *intern.Value: diff --git a/compiler/natives/src/net/netip/fuzz_test.go b/compiler/natives/src/net/netip/fuzz_test.go index 574201d2d..f7359c5bb 100644 --- a/compiler/natives/src/net/netip/fuzz_test.go +++ b/compiler/natives/src/net/netip/fuzz_test.go @@ -1,4 +1,5 @@ //go:build js +// +build js package netip_test diff --git a/compiler/natives/src/net/netip/netip.go b/compiler/natives/src/net/netip/netip.go index 92a61900a..9d2b8b2d6 100644 --- a/compiler/natives/src/net/netip/netip.go +++ b/compiler/natives/src/net/netip/netip.go @@ -1,4 +1,5 @@ //go:build js +// +build js package netip @@ -17,7 +18,6 @@ var ( z6noz = "\x00ipv6noz" ) -//gopherjs:prune-original func (ip Addr) Zone() string { if ip.z == z4 || ip.z == z6noz { return "" @@ -25,7 +25,6 @@ func (ip Addr) Zone() string { return ip.z } -//gopherjs:prune-original func (ip Addr) WithZone(zone string) Addr { if !ip.Is6() { return ip diff --git a/compiler/natives/src/net/netip/netip_test.go b/compiler/natives/src/net/netip/netip_test.go new file mode 100644 index 000000000..46b116c00 --- /dev/null +++ b/compiler/natives/src/net/netip/netip_test.go @@ -0,0 +1,10 @@ +//go:build js +// +build js + +package netip_test + +import "testing" + +func TestAddrStringAllocs(t *testing.T) { + t.Skip("testing.AllocsPerRun not supported in GopherJS") +} From db5d7b36f2e14c14380942b7515807da61437435 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 17:47:38 -0700 Subject: [PATCH 61/66] Updating sync/atomic --- compiler/natives/src/sync/atomic/atomic_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/compiler/natives/src/sync/atomic/atomic_test.go b/compiler/natives/src/sync/atomic/atomic_test.go index 27ce36df9..dd1bc9994 100644 --- a/compiler/natives/src/sync/atomic/atomic_test.go +++ b/compiler/natives/src/sync/atomic/atomic_test.go @@ -57,9 +57,21 @@ func TestUnaligned64(t *testing.T) { t.Skip("GopherJS emulates atomics, which makes alignment irrelevant.") } +unc TestAutoAligned64(t *testing.T) { + t.Skip("GopherJS emulates atomics, which makes alignment irrelevant.") +} + func TestNilDeref(t *testing.T) { t.Skip("GopherJS does not support generics yet.") } //gopherjs:purge for go1.19 without generics type List struct{} + +func TestHammer32(t *testing.T) { + t.Skip("use of unsafe") +} + +func TestHammer64(t *testing.T) { + t.Skip("use of unsafe") +} From a6b98dd984c684b466e65a8309481762619a07fa Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 17:58:29 -0700 Subject: [PATCH 62/66] Update syscall/js --- compiler/natives/src/syscall/js/export_test.go | 3 +-- compiler/natives/src/syscall/js/js_test.go | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/natives/src/syscall/js/export_test.go b/compiler/natives/src/syscall/js/export_test.go index 25f6f6833..8f030c4d7 100644 --- a/compiler/natives/src/syscall/js/export_test.go +++ b/compiler/natives/src/syscall/js/export_test.go @@ -4,6 +4,5 @@ package js // Defined to avoid a compile error in the original TestGarbageCollection() -// body. Can't use gopherjs:prune-original on it, since it causes an unused -// import error. +// body. var JSGo Value diff --git a/compiler/natives/src/syscall/js/js_test.go b/compiler/natives/src/syscall/js/js_test.go index c95c2e764..999266da2 100644 --- a/compiler/natives/src/syscall/js/js_test.go +++ b/compiler/natives/src/syscall/js/js_test.go @@ -5,7 +5,6 @@ package js_test import "testing" -//gopherjs:prune-original func TestIntConversion(t *testing.T) { // Same as upstream, but only test cases appropriate for a 32-bit environment. testIntConversion(t, 0) From 967ffcc43e7e9131a908a6848f9c54bba760fcb1 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Jan 2024 18:03:09 -0700 Subject: [PATCH 63/66] Fixed a copy/paste mistake --- compiler/natives/src/sync/atomic/atomic_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/natives/src/sync/atomic/atomic_test.go b/compiler/natives/src/sync/atomic/atomic_test.go index dd1bc9994..e1ec6086c 100644 --- a/compiler/natives/src/sync/atomic/atomic_test.go +++ b/compiler/natives/src/sync/atomic/atomic_test.go @@ -57,7 +57,7 @@ func TestUnaligned64(t *testing.T) { t.Skip("GopherJS emulates atomics, which makes alignment irrelevant.") } -unc TestAutoAligned64(t *testing.T) { +func TestAutoAligned64(t *testing.T) { t.Skip("GopherJS emulates atomics, which makes alignment irrelevant.") } From c08b7bf04917dd4fac1f3265d11ddcd5ce2de087 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 13 Feb 2024 12:12:37 -0700 Subject: [PATCH 64/66] Fixed chocolatey go version --- circle.yml | 2 +- compiler/natives/src/crypto/internal/boring/bbig/big.go | 2 +- compiler/natives/src/crypto/internal/nistec/nistec_test.go | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/circle.yml b/circle.yml index 5c059a6e4..2c3095c8b 100644 --- a/circle.yml +++ b/circle.yml @@ -175,7 +175,7 @@ jobs: - run: name: Install Go command: | - choco install golang --version="<< pipeline.parameters.chocolatey_go_version >>" -my + choco install golang --version="<< pipeline.parameters.chocolatey_go_version >>" -my --force -y go version (Get-Command go).Path [Environment]::SetEnvironmentVariable( diff --git a/compiler/natives/src/crypto/internal/boring/bbig/big.go b/compiler/natives/src/crypto/internal/boring/bbig/big.go index 30ffe1fcd..3a726ba3c 100644 --- a/compiler/natives/src/crypto/internal/boring/bbig/big.go +++ b/compiler/natives/src/crypto/internal/boring/bbig/big.go @@ -33,7 +33,7 @@ func Dec(b boring.BigInt) *big.Int { return new(big.Int) } // Replacing original which uses unsafe: - //x := unsafe.Slice((*big.Word)(&b[0]), len(b)) + // x := unsafe.Slice((*big.Word)(&b[0]), len(b)) x := make([]big.Word, len(b)) for i, w := range b { x[i] = big.Word(w) diff --git a/compiler/natives/src/crypto/internal/nistec/nistec_test.go b/compiler/natives/src/crypto/internal/nistec/nistec_test.go index f5f79398e..d755e7ec3 100644 --- a/compiler/natives/src/crypto/internal/nistec/nistec_test.go +++ b/compiler/natives/src/crypto/internal/nistec/nistec_test.go @@ -4,10 +4,9 @@ package nistec_test import ( - "testing" - "crypto/elliptic" "crypto/internal/nistec" + "testing" ) func TestAllocations(t *testing.T) { From 02aea33b76248f0a35eb7c7044c880fdadf8ee6c Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 13 Feb 2024 13:06:45 -0700 Subject: [PATCH 65/66] Limit augmentation and prune imports to natives that need it --- build/build.go | 42 ++++++++++++++++++++++++++++++++++-------- build/build_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/build/build.go b/build/build.go index d3cd32952..def9cd313 100644 --- a/build/build.go +++ b/build/build.go @@ -180,8 +180,12 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke for _, file := range originalFiles { augmentOriginalImports(pkg.ImportPath, file) - augmentOriginalFile(file, overrides) - pruneImports(file) + } + + if len(overrides) > 0 { + for _, file := range originalFiles { + augmentOriginalFile(file, overrides) + } } return append(overlayFiles, originalFiles...), jsFiles, nil @@ -275,6 +279,7 @@ func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File, // an overlay file AST to collect information such as compiler directives // and perform any initial augmentation needed to the overlay. func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) { + anyChange := false for i, decl := range file.Decls { purgeDecl := astutil.Purge(decl) switch d := decl.(type) { @@ -302,15 +307,20 @@ func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) { } } if purgeSpec { + anyChange = true d.Specs[j] = nil } } } if purgeDecl { + anyChange = true file.Decls[i] = nil } } - finalizeRemovals(file) + if anyChange { + finalizeRemovals(file) + pruneImports(file) + } } // augmentOriginalImports is the part of parseAndAugment that processes @@ -334,10 +344,12 @@ func augmentOriginalImports(importPath string, file *ast.File) { // original file AST to augment the source code using the overrides from // the overlay files. func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { + anyChange := false for i, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: if info, ok := overrides[astutil.FuncKey(d)]; ok { + anyChange = true removeFunc := true if info.keepOriginal { // Allow overridden function calls @@ -358,6 +370,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { } else if recvKey := astutil.FuncReceiverKey(d); len(recvKey) > 0 { // check if the receiver has been purged, if so, remove the method too. if info, ok := overrides[recvKey]; ok && info.purgeMethods { + anyChange = true file.Decls[i] = nil } } @@ -366,6 +379,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { switch s := spec.(type) { case *ast.TypeSpec: if _, ok := overrides[s.Name.Name]; ok { + anyChange = true d.Specs[j] = nil } case *ast.ValueSpec: @@ -378,6 +392,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { // to be run, add the call into the overlay. for k, name := range s.Names { if _, ok := overrides[name.Name]; ok { + anyChange = true s.Names[k] = nil s.Values[k] = nil } @@ -405,6 +420,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { } } if removeSpec { + anyChange = true d.Specs[j] = nil } } @@ -413,7 +429,10 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { } } } - finalizeRemovals(file) + if anyChange { + finalizeRemovals(file) + pruneImports(file) + } } // isOnlyImports determines if this file is empty except for imports. @@ -437,6 +456,14 @@ func isOnlyImports(file *ast.File) bool { // If the removal of code causes an import to be removed, the init's from that // import may not be run anymore. If we still need to run an init for an import // which is no longer used, add it to the overlay as a blank (`_`) import. +// +// This uses the given name or guesses at the name using the import path, +// meaning this doesn't work for packages which have a different package name +// from the path, including those paths which are versioned +// (e.g. `github.com/foo/bar/v2` where the package name is `bar`) +// or if the import is defined using a relative path (e.g. `./..`). +// Those cases don't exist in the native for Go, so we should only run +// this pruning when we have native overlays, but not for unknown packages. func pruneImports(file *ast.File) { if isOnlyImports(file) && !astutil.HasDirectivePrefix(file, `//go:linkname `) { // The file is empty, remove all imports including any `.` or `_` imports. @@ -478,12 +505,11 @@ func pruneImports(file *ast.File) { // since the import is otherwise unused set the name to blank. in.Name = ast.NewIdent(`_`) delete(unused, name) - if len(unused) == 0 { - return - } - break } } + if len(unused) == 0 { + return + } // Remove all unused import specifications isUnusedSpec := map[*ast.ImportSpec]bool{} diff --git a/build/build_test.go b/build/build_test.go index 5f025540b..343e8b933 100644 --- a/build/build_test.go +++ b/build/build_test.go @@ -680,6 +680,39 @@ func TestOriginalAugmentation(t *testing.T) { import _ "unsafe"`, want: `//go:linkname foo bar import _ "unsafe"`, + }, { + desc: `multiple imports for directives`, + info: map[string]overrideInfo{ + `A`: {}, + `C`: {}, + }, + src: `import "unsafe" + import "embed" + + //go:embed hello.txt + var A embed.FS + + //go:embed goodbye.txt + var B string + + var C unsafe.Pointer + + // override Now with hardcoded time for testing + //go:linkname timeNow time.Now + func timeNow() time.Time { + return time.Date(2012, 8, 6, 0, 0, 0, 0, time.UTC) + }`, + want: `import _ "unsafe" + import _ "embed" + + //go:embed goodbye.txt + var B string + + // override Now with hardcoded time for testing + //go:linkname timeNow time.Now + func timeNow() time.Time { + return time.Date(2012, 8, 6, 0, 0, 0, 0, time.UTC) + }`, }, } From 1011858069d07c9dae6799f189033af48d9acd31 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 24 Feb 2024 13:45:51 +0000 Subject: [PATCH 66/66] Update GopherJS version to 1.19.0-beta1 in perparation for release. --- README.md | 2 +- compiler/version_check.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f29bb9084..3804dbf0f 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ version, you can use an [older GopherJS release](https://github.com/gopherjs/gop Install GopherJS with `go install`: ``` -go install github.com/gopherjs/gopherjs@v1.19.0-alpha1 # Or replace 'v1.19.0-alpha1' with another version. +go install github.com/gopherjs/gopherjs@v1.19.0-beta1 # Or replace 'v1.19.0-beta1' with another version. ``` If your local Go distribution as reported by `go version` is newer than Go 1.19, then you need to set the `GOPHERJS_GOROOT` environment variable to a directory that contains a Go 1.19 distribution. For example: diff --git a/compiler/version_check.go b/compiler/version_check.go index 36bd4acd3..d672fa45a 100644 --- a/compiler/version_check.go +++ b/compiler/version_check.go @@ -12,7 +12,7 @@ import ( ) // Version is the GopherJS compiler version string. -const Version = "1.19.0-alpha1+go1.19.13" +const Version = "1.19.0-beta1+go1.19.13" // GoVersion is the current Go 1.x version that GopherJS is compatible with. const GoVersion = 19